[vk] Drawing triangle - Presentation - Swap chain

2021. 8. 7. 03:09그래픽스/vk

1. Swap chain

- Vulkan 에는 "default framebuffer"라는 개념이 존재하지 않음

- 따라서 시각화하기 전에 렌더링 버퍼를 소유할  infrastructure가 필요하다.

- 이러한 infrastructure를 swap chain이라고 하며 Vulkan에서 명시적으로 생성해야한다.

- swap chain은 기본적으로 화면에 표시되기를 기다리는 이미지 queue이다.

- 응용프로그램은 그런 이미지를 가져와서 그린다음 다시 queue에 반환한다.

- queue가 장확히 작동하는 방식과 queue에서 이미지를 표시하기 위한 조건은 swap chain 설정에 따라다름

- swap chain의 일반적인 목적은 이미지 표시를 화면의 refresh rate를 synchronize하는것이다.

 

 

 

 

2. Checking for swap chain support

체크해야하는 이유

1) 모든 그래픽 카드가 화면에 직접 이미지를 표시할 수 있는것은 아님.

    ex) 서버용으로 설계된 그래픽카드는 디스플레이 출력이 없기 때문.

2) 이미지 표현은 window system 과 window 관련으로 sufaces과 관련 되어있기때문에

   -> 실제로는 Vulkan 코어의 일부가 아님

 

따라서 VK_KHR_swapchain device extension을 요청하고, 활성화 해야한다.

- 이를 위해 저번에 만든 함수 isDeviceSuitable가 이 확장을 지원하는지 확인하는 코드를 추가해야함.

 

VkPhysicalDevice 에의해 확장이 지원되는지 확인해야한다.

- vulkan 헤더파일에는 VK_KHR_SWAPCHAIN_EXTENSION_NAME매크로를 제공해줌.

- 이것은 VK_KHR_swapchain이 정의된것으로, 컴파일러가 맞춤법 오류를 잡아낼 수 있도록해줌

 

- 먼저 필요한 device extensions를 얻어와야한다.

(validation layers를 활성화하는것과 유사)

const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};

 

 

- 다음으로 새로운 함수 checkDeviceExtensionSupport를 생성해야한다.

- 이것은 isDeviceSuitable함수에서 호출된다.

bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    bool extensionsSupported = checkDeviceExtensionSupport(device);

    return indices.isComplete() && extensionsSupported;
}

bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    return true;
}

- 확장리스트를 얻어오고, 모든 확장이 있는지 확인하도록 해야한다.

bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    uint32_t extensionCount;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

    std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

    for (const auto& extension : availableExtensions) {
        requiredExtensions.erase(extension.extensionName);
    }

    return requiredExtensions.empty();
}

- 확인되지 않은 필수 확장을 나타내기 위해 std::string을 사용한것을 볼수있다.

- 이렇게하면, 사용 가능한 확장 시퀀스를 열거하면서 쉽게 체크할 수 있다.

-checkValidationLayerSupport처럼 중첩 루프문을 사용할 수도있다.

  (성능차이는 그렇게 나지 않는다고한다)

- 이제 코드를 실행하여 실제로 생성되는지 확인해봐라.

- presentation queue를 이용가능하다는것은 swapchain extension을 지원할 수 있음을 암시한다는것을 알게될것

- 하지만 항상 활성화할 extension을  명시하는게 좋다. 

 

 

 

3. Enabling device extensions

- VK_KHR_swapchain extension 을  활성화해야 swapchain을 사용할 수 있다.

- 확장을 활성화하려면 logical device creation structure를 약간만 변형하면된다.

 

createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();

 

 

 

 

 

4. Querying details of swap chain support

- 스왑체인이 사용가능한지 확인하는것만으로는 충분하지 않음

- 실제로 window surface 와 호한이 안될 수 있음.

- swapchain 생성에는 더 많은 설정이 포함됨 (instance, device creation 보다)

- 그래서 더 자세한 내용을 쿼리하는게 필요함

 

기본적으로 확인해야하는 세가지 속성

  • 기본 surface capabilities(swap chain 의 최소/최대 이미지 수, 이미지의 최소/최대 너비 및 높이)
  • surface formats (pixel format, color space)
  • 사용 가능한 presentation modes

findQueueFamilies와 유사

- 쿼리가 완료되면 구조체를 사용하여 이러한 세부정보를 전달해야함.

- 위의 세가지 타입 속성은 다음 구조체 및 구조체 목록의 형태로 제공됨.

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};

querySwapChainSupport

- 이 구조체를 채울 새함수를 만들어야함. (populate this struct)

SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;

    return details;
}

- 이 섹션에는 이 정보를 포함하는 구조체를 쿼리하는것을 다룸.

- 이러한 구조체의 의미와 정확히 어떤 데이터가 포함되어 있는지는 다음 섹션에서 설명

 

 

 

 

 

4.1 surface capabilities

- 이 속성은 쿼리하기 간단하고, VkSurfaceCapabilitiesKHR struct 하나만 리턴한다.

vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities)

- 이 함수는  VkPhysicalDevice 그리고 VkSurfaceKHR, 를 매개변수로 받아, 지원하는 기능들을 받아온다.

- 이 두가지 매개변수는 모든 support querying functions에 있는데, 두 요소가 swapchain의 핵심 구성요소여서 그럼.

 

 

 

 

 

4.2 surface format

다음 단계는 지원되는 surface format을 쿼리하는것

- 이전과 마찬가지로 개수를 묻고, 개수대로 데이터를 받아옴.

uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

if (formatCount != 0) {
    details.formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}

 

- 사용 가능한 모든 format을 포함하도록 벡터의 크기를 조정해야한다.

 

 

 

 

4.3 present mode

 

vkGetPhysicalDeviceSurfacePresentModesKHR

- 마지막으로 지원되는 presentation mode를 쿼리해야하며 동일한 방식으로 동작한다.

uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

if (presentModeCount != 0) {
    details.presentModes.resize(presentModeCount);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}

 

 

 

 

 

4.4 isDeviceSuitable

- 이제 모든 세부사항을 구조체에 설정했으므로

- isDevice Suitable 기능을 활용하여 스왑체인 지원이 적절한지 확인해야함 (수정 필요)

- swap chain support는 - 한개의 image format 과  한개의 supported presentation mode만 있으면 충분

  (튜토리얼에선)

bool swapChainAdequate = false;
if (extensionsSupported) {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}

 

- extension을 사용할 수 있는지 확인한 후에만, 스왑 체인 지원을 쿼리하는것이 중요.

- 함수의 마지막 줄은 다음과 같이 변경됨.

return indices.isComplete() && extensionsSupported && swapChainAdequate;
    bool isDeviceSuitable(VkPhysicalDevice device) {
        QueueFamilyIndices indices = findQueueFamilies(device);

        bool extensionsSupported = checkDeviceExtensionSupport(device);

        bool swapChainAdequate = false;
        if (extensionsSupported) {
            SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
            swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
        }

        return indices.isComplete() && extensionsSupported && swapChainAdequate;
    }
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
        SwapChainSupportDetails details;

        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);

        uint32_t formatCount;
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

        if (formatCount != 0) {
            details.formats.resize(formatCount);
            vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
        }

        uint32_t presentModeCount;
        vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

        if (presentModeCount != 0) {
            details.presentModes.resize(presentModeCount);
            vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
        }

        return details;
    }

 

 

5.Choosing the right settings for the swap chain

- swapChainAdequate조건이 다 만족하면 그대로도 충분하다

- 하지만, 여전히 많은 다양한 최적화된 모드들이 있을 수 있음

- 최상의 swap chain에 대한 올바른 설정을 찾기위해 몇가지 함수를 작성해야함.

- 여기서는 다음 세가지 타입을 설정한다

 

  • Surface format (color depth)
  • Presentation mode (conditions for "swapping" images to the screen)
  • Swap extent (resolution of images in swap chain)

- 이러한 각 설정에 대해 사용 가능한 경우,

- 사용할 이상적인 값을 염두에 두고, 그렇지 않은 경우 차선책을 찾기위해 몇가지 logic를 생성할 것임.

 

5.1 Surface format

- 여기서 만들 함수로 나중에SwapChainSupportDetails구조체의 멤버인 formats 를 매개변수로서 넘길것임. 

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {

}

- 각 VkSurfaceFormatKHR항목에는 format, colorSpace 멤버가 포함됨

- format :  the color channels and types

    (ex. VK_FORMAT_B8G8R8A8_SRGB : BGRA 순대로  unsign int 8비트 토탈 32 bit per pixel )

- color space : sRGB color space를 지원하는지, 사용안하는지 나타냄

    VK_COLOR_SPACE_SRGB_NONLINEAR_KHR

    (VK_COLORSPACE_SRGB_NONLINEAR_KHR -> 구버전 사양 )

 

color space 같은 경우 SRGB를 사용할 수 있으면 사용하는게 좋음

- results in more accurate perceived colors

- 또한 텍스처와 같은 이미지의 표준 색상공간이기도 함.

- VK_FORMAT_B8G8R8A8_SRGB 포멧이 가장 일반적임.

 

- 목록을 살펴보고 선호하는 조합이 이용가능한지 확인해야함

for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}

- 만일 이런 조합을 찾을 수 없으면, 가장 좋은 순으로 순위를 매길 수 있음

- 하지만 대부분의 경우 위의 조합이 베스트임.

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

 

 

 

5.2 Presentation Mode

- 화면에 이미지를 표시하기 위한 실제 조건을 나타냄 ( arguably the most important setting)

- 4가지 모드를 사용할 수 있음.

 

  • VK_PRESENT_MODE_IMMEDIATE_KHR: Images submitted by your application are transferred to the screen right away, which may result in tearing.
  • VK_PRESENT_MODE_FIFO_KHR: The swap chain is a queue where the display takes an image from the front of the queue when the display is refreshed and the program inserts rendered images at the back of the queue. If the queue is full then the program has to wait. This is most similar to vertical sync as found in modern games. The moment that the display is refreshed is known as "vertical blank".
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR: This mode only differs from the previous one if the application is late and the queue was empty at the last vertical blank. Instead of waiting for the next vertical blank, the image is transferred right away when it finally arrives. This may result in visible tearing.
  • VK_PRESENT_MODE_MAILBOX_KHR: This is another variation of the second mode. Instead of blocking the application when the queue is full, the images that are already queued are simply replaced with the newer ones. This mode can be used to render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.

 How To Configure Your Vulkan Swapchain

vulkan_best_practice_for_mobile_developer

 

VK_PRESENT_MODE_FIFO_KHR

- 이 모드만 유일하게 보장되는 기능임

- 가능한 최상의 모드를 찾는 함수를 다시 작성해보자.

- energy usage가 좀더 중요한 모바일에서 좋음

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_KHR;
}

VK_PRESENT_MODE_MAILBOX_KHR

- energy usage 가 문제되지 않는다면 좋은 절충안이라고함.

- tearing을 피하게해줌

- low latency 를 유지하게해줌

- 수직공백(vertical blank) 까지 가능한 최신 이미지를 렌더링하게해줌

-  mailbox mode를 사용할 수 있으면 사용하도록 함수를 고칠 수 있음.

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}

 

 

5.3 Swap extent

- 마지막 속성을 추가해야함,

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}

 

- swap extent는 the resolution of the swap chain images 이다.

- 거의 항상 정확하게 window의 해상도와 같다.

- VkSurfaceCapabilitiesKHR 구조체에 가능한 해상도의 범위를 정의되어있음.

- currentExtent의 width, height를 보고 창의 해상도를 매칭하라고 vulkan이 요구함

- 하지만 몇몇 window managers은 currentExtent의 width 와 height 를 uint32_t이 최대값인 특정한수를 세팅함

- 이 경우 minImageExtent and maxImageExtent 범위에서 창과 가장 일치하는 해상도를 선택해야함(올바른 단위여야함)

 

 

- GLFW는 두가지 단위를 사용함 : pixels and screen coordinates

- 예를 들어 앞에서 winodw를 만들때 사용한 {WIDTH, HEIGHT}  는 screen coord 로 측정됨

- Vulkan은 하지만 pixels로 작동함. 따라서 swapchain 또한 pixels로 명시해야함

- 불행하게도, 높은 DPI를 가진 디스플레이 (애플의 레티나 디스플레이) 는 화면좌표가 픽셀과 일치하지 않음

- 이 경우 대신 픽셀밀도가 높아지기 때문에, 픽셀단위창의 해상도는, 화면좌표의 해상도보다 커짐

- 따라서 Vulkan이 swap extent를 수정하지않으면, {WIDTH, HEIGHT} 를 사용할수 없게됨

- 대신에, 최소 및 최대의 이미지 extent 와 매칭하기 전에

- glfwGetFramebufferSize 함수로 창의 pixel 해상도를 쿼리해야함

#include <cstdint> // Necessary for UINT32_MAX
#include <algorithm> // Necessary for std::min/std::max

...

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != UINT32_MAX) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
        actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

        return actualExtent;
    }
}

The clamp function is used here to bound the values of width and height between the allowed minimum and maximum extents that are supported by the implementation.

 

 

6. Creating the swap chain

- 이제 런타임에 선택해야하는 helper functions 을 작성했고, 마침내 swap chain을 생성할 정보를 전부 명시했으니

-  swapchain을 생성할 일만 남음.

 

추가할 createSwapChain함수는 initVulkan에서 logical device creation 이후에 호출된다.

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
}

void createSwapChain() {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
}

- 이러한 속성이외도, swap chain에 포함할 이미지 수를 결정해야함.

- 일단 구현은 작동하는데 필요한 최소의 수로 지정

uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;

if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}

- 그러나 단순히 이 최소값을 설정한다는것은

- 렌더링할 다른 이미지를 얻기 전에

- 드라이버가 내부 작업을 완료할 때까지 기다려야할 수 있음을 의미함. (최소값이 엄청 작을경우, 지연의가능성)

- 따라서 여분으로 한개이상의 이미지를 요청하는것이 좋다.

- 또한 이 작업을 수행하는 동안 최대 이미지 수를초과하지 않도록, 조건문을 걸어준 것을 볼 수 있음

   (또한 최댓값이 0일 수 있으므로, 확인해봐야함)

 

 

 

6.1 VkSwapchainCreateInfoKHR

VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;

- Vulkan의 다른객체와 마찬가지로 createinfo 구조체를 채워야함

createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

- 스왑체인이 연결되어야하는 surface를 지정한 후, swapchain 이미지의 세부사항을 명시해야함.

 

imageArrayLayers

- 각 이미지 구성 layer의 양을 지정

- a stereoscopic 3D application가 아닌이상 항상 1 임.

- imageUsage : swapchain에서 image를 어떤 작업을 실시하는지

- - - - 튜토리얼에선 직접 렌더링할것이므로 color attachment 로 설정

- - - - post-processing 과 같은 작업을 수행하기위해, 먼저 이미지를 별도의 이미지로 렌더링 가능.

- - - - 이 경우 VK_IMAGE_USAGE_TRANSFER_DST_BIT 값으로 설정해야한다.

- - - - 이 설정은 memory operation사용하여 rendered image를 swapchain image로 전송 가능

 

 

swapchain image sharing

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = nullptr; // Optional
}

- 다음으로, 어떻게 swapchain image를 다룰것인지 명시해야함.

- 여기선 graphics queue,  presentation queue가 다른 family인 경우를 다룸

- 먼저 graphics queue에서 swapchain의 이미지를 그린다음, presentation queue로넘김, 

- 여러 queue에서 엑섹스하려는 이미지 처리방법은 두가지이다.

  • VK_SHARING_MODE_EXCLUSIVE: An image is owned by one queue family at a time and ownership must be explicitly transferred before using it in another queue family. This option offers the best performance.
  • VK_SHARING_MODE_CONCURRENT: Images can be used across multiple queue families without explicit ownership transfers.

- 튜토리얼에선, queue family가 다른 경우, concurrent mode 를 사용한다. (ownership에 관한 내용은 다음에)

- Concurrent mode : 사전에 queue families ownership 에게 다음 두 파라미터를 공유할 것임을 명시해야함.

queueFamilyIndexCount 및 pQueueFamilyIndices 

(Concurrent mode requires you to specify in advance between which queue families ownership will be shared using the queueFamilyIndexCount and pQueueFamilyIndices parameters.)

 

- 먼일 graphics queue family 와 presentation queue family 가 같으면 (대부분 하드웨어가 그렇긴함)

- exclusive 모드를 사용해야함 (concurrent 모드는 최소한 구별된 두개의 queue family가 필요)

 

preTransform

createInfo.preTransform = swapChainSupport.capabilities.currentTransform;

- 만일 90도 시계 방향회전, 수평 뒤집기 기능을 지원한다면, swapchain image의 특정 변환을 명시할 수 있음.

   (supportedTransforms in capabilities)

- 이러한 것들이 필요 없다면, 위와 같이 current transformation을 명시하면됨

 

compositeAlpha

createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;

- compositeAlpha 이 필드는 만약 alpha channel을 window system에서 다른 windows과 blending하는데 사용할경우 명시해야함

- 알파 채널을 무시할것이므로 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR를 설정함.

 

presentMode

createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE

- presentMode  

- clipped 멤버가 VK_TRUE : 다른 창이 앞에 있을 경우, 가려진 픽셀의 색상에 대해 신경쓰지 않는다는것

- 이러한 픽셀을 다시 읽고 예측 가능한 결과를 얻을 수 있어야하는 경우가 아니라면 활성화하여 성능을 증가시킬 수 있음.

 

oldSwapChain

createInfo.oldSwapchain = VK_NULL_HANDLE;

- Vulkan을 사용하다 창 크기가 조정되면 swapchain이 무효화되거나, 최적화되지 않을 수 있음

   (invalid or unoptimized)

- 이 경우 swapchain 이 다시 처음부터 만들어야하며, 이전 체인에 대한 참조를 명시한것이다.

- 지금은 하나의 swapchain만 생성한다고 가정하며 이것에 대한것은 a future chapter에서 다룸

 

VkSwapchainKHR객체

VkSwapchainKHR swapChain;

- 객체를 저장할 class member

 

vkCreateSwapchainKHR

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain!");
}

- 스왑 체인을 만드는 것은 위와같이 간단한 호출로 만들 수 있음

- logical device, swap chain creation info, optional custom allocators, a pointer to the variable to store the handle in

 

vkDestroySwapchainKHR

void cleanup() {
    vkDestroySwapchainKHR(device, swapChain, nullptr);
    ...
}

-  device를 제거하기전 먼저 swapchain을 제거해야함.

Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in vkCreateSwapchainKHR or see a message like Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll, then see the FAQ entry about the Steam overlay layer.
Try removing the createInfo.imageExtent = extent; line with validation layers enabled. You'll see that one of the validation layers immediately catches the mistake and a helpful message is printed.

 

7. Retrieving the swap chain images

- swap chain이 생성되었으므로  VkImage 안에 있는 핸들을 가져오는것만 남음.

- 렌더링 작업중에 이들의 핸들을 참조할 것임.

- 핸들을 저장할 클래스 멤버를 추가하자.

std::vector<VkImage> swapChainImages;

- 이미지는 swapchain 구현에 의해 생성되며, swapchain이 파괴되면 자동으로 정리됨.

- createSwapChain 직후에 함수 끝에 핸들을 가져올 코드를 작성해야함. : vkCreateSwapchainKHR

- 이전과 마찬가지로 개수를 묻고, 배열을 리사이즈하고, 가져오는 형식.

- 오직 최소 이미지 수로 설정하였지만, 더 많은 이미지를 가지도록 설정할 수 있음을 명심해야함.

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

- 마지막으로, format 과 extent를 저장해야함. (이미지에 대한 형식과 범위를 멤버변수로, 나중에 쓰임)

VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;

 

We now have a set of images that can be drawn onto and can be presented to the window. The next chapter will begin to cover how we can set up the images as render targets and then we start looking into the actual graphics pipeline and drawing commands!

 

전체 코드 : C++ code

https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain

 

 

 

- createSwapchain 의 구현 

    void createSwapChain() {
        SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

        VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
        VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
        VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);

        uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
        if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
            imageCount = swapChainSupport.capabilities.maxImageCount;
        }

        VkSwapchainCreateInfoKHR createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        createInfo.surface = surface;

        createInfo.minImageCount = imageCount;
        createInfo.imageFormat = surfaceFormat.format;
        createInfo.imageColorSpace = surfaceFormat.colorSpace;
        createInfo.imageExtent = extent;
        createInfo.imageArrayLayers = 1;
        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
        uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

        if (indices.graphicsFamily != indices.presentFamily) {
            createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
            createInfo.queueFamilyIndexCount = 2;
            createInfo.pQueueFamilyIndices = queueFamilyIndices;
        } else {
            createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        }

        createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        createInfo.presentMode = presentMode;
        createInfo.clipped = VK_TRUE;

        createInfo.oldSwapchain = VK_NULL_HANDLE;

        if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
            throw std::runtime_error("failed to create swap chain!");
        }

        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
        swapChainImages.resize(imageCount);
        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

        swapChainImageFormat = surfaceFormat.format;
        swapChainExtent = extent;
    }