본문 바로가기

Vulkan

Presentation - Swap chain

이전 글 : Window surface
다음 글 : Image views


Swap chain

스왑 체인

Vulkan에는 "기본 프레임 버퍼" 개념이 없으므로 화면에서 시각화 하기 전에 렌더링 할 버퍼를 소유하는 인프라가 필요합니다. 이 인프라를 스왑 체인 이라고 하며 Vulkan에 명시 적으로 만들어야 합니다. 스왑 체인은 본질적으로 화면에 표시되기를 기다리는 이미지 대기열입니다. 우리의 응용 프로그램은 그와 같은 이미지를 가져 와서 그 이미지를 큐에 반환합니다. 대기열의 작동 방식과 대기열에서 이미지를 표시하기 위한 조건은 스왑 체인 설정 방법에 따라 다르지만 스왑 체인의 일반적인 목적은 이미지의 프레젠테이션을 화면 새로 고침 빈도와 동기화하는 것입니다.



Checking for swap chain support

스왑 체인 기능 지원 검사


모든 그래픽 카드가 여러 가지 이유로 화면에 직접 이미지를 표시 할 수 있는 것은 아닙니다. 예를 들어, 서버 용 장치는 서버용으로 설계 되어 디스플레이 출력이 없기 때문입니다.

둘째, 이미지 프레젠테이션은 윈도우 시스템과 윈도우와 관련된 표면에 많이 묶여 있기 때문에 사실 Vulkan 코어의 일부가 아닙니다. 해당 지원을 쿼리 한 후 VK_KHR_swapchain 장치 확장을 사용하도록 설정해야 합니다.


이를 위해 isDeviceSuitable 함수를 먼저 작성 하여 이 확장이 지원되는지 확인 합니다. 우리는 이전에 VkPhysicalDevice에서 지원하는 확장 기능을 나열하는 방법을 보았습니다. 그렇게하면 매우 간단합니다. Vulkan 헤더 파일은 VK_KHR_swapchain으로 정의 된 멋진 매크로 VK_KHR_SWAPCHAIN_EXTENSION_NAME을 제공합니다. 이 매크로를 사용할 때의 이점은 컴파일러가 맞춤법 오류를 발생 시키지 않는다는 것 입니다.


먼저 활성화 할 유효성 검사 레이어 목록과 비슷한 필수 장치 확장명 목록을 선언합니다.


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


그런 다음 isDeviceSuitable 함수에  추가 적ㅇ로 검사하기 위하여 호출되는 checkDeviceExtensionSupport 라는 새 함수를 만듭니다.


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();
}


확인되지 않은 필수 확장을 나타 내기 위해 여기서 문자열 세트를 사용하기로 했습니다. 그렇게하면 사용 가능한 확장자의 순서를 나열하면서 쉽게 알아볼 수 있습니다. 물론 checkValidationLayerSupport와 같은 중첩 루프를 사용할 수도 있습니다. 성능 차이는 별로 없습니다. 이제 코드를 실행하여 그래픽 카드가 실제로 스왑 체인을 만들 수 있는지 확인하십시오. 이전 장에서 확인한 것처럼 프리젠테이션 큐의 가용성은 스왑 체인 확장이 지원되어야 함을 의미합니다. 그러나 이런 모든 것들은 직접 명시하는 것이 여전히 좋으며 확장기능은 명시적으로 활성화 되어야 합니다.


확장 기능을 사용하려면 논리 장치 생성 구조를 약간 변경해야 합니다.


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




Querying details of swap chain support

스왑 체인 지원에 대한 세부 정보 쿼리


스왑 체인이 사용 가능한지 여부를 확인하는 것만으로는 충분하지 않습니다. 실제로 스왑 체인이 우리 윈도우 표면과 호환되지 않을 수 있기 때문입니다. 스왑 체인을 생성하면 인스턴스 및 장치 생성보다 많은 설정이 포함되므로 진행하기 전에 더 자세한 내용을 쿼리해야 합니다.

기본적으로 확인해야 할 3 가지 속성이 있습니다.


  • 기본 표면 기능 (스왑 체인의 최소 / 최대 이미지 수, 최소 / 최대 너비 및 이미지 높이)

  • 표면 포맷 (픽셀 포맷, 컬러 스페이스)

  • 사용 가능한 프리젠테이션 모드


findQueueFamilies와 마찬가지로 구조체를 사용하여 쿼리 한 후에 이러한 세부 정보를 전달합니다. 앞서 언급 한 세 가지 유형의 속성은 다음 구조체와 구조체 목록 형식으로 제공됩니다.


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


이제 이 구조체를 채울 querySwapChainSupport라는 새로운 함수를 생성 할 것입니다.


SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
   SwapChainSupportDetails details;

   return details;
}


이 섹션 에서는 이 정보가 포함 된 구조체를 쿼리하는 방법을 설명합니다. 이 구조체의 의미와 정확히 포함된 데이터는 다음 섹션에서 설명 하겠습니다.


기본 서피스 기능부터 살펴 보겠습니다. 이러한 속성은 쿼리하기 쉽고 단일 VkSurfaceCapabilitiesKHR 구조체에 반환 됩니다.


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


이 함수는 지원되는 기능을 결정할 때 지정된 VkPhysicalDevice 및 VkSurfaceKHR 윈도우 표면을 고려 합니다. 모든 지원 쿼리 함수는 스왑 체인의 핵심 구성 요소이기 때문에 이 두 매개 변수를 첫 번째 매개 변수로 사용합니다.


다음 단계는 지원되는 표면 형식을 쿼리하는 것입니다. 이것은 구조체 목록이기 때문에 우리에게 익숙한 다음 2 개의 함수 호출을 따릅니다.


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

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



사용 가능한 모든 형식을 포함하도록 벡터 크기가 조정 되었는지 확인 하십시오. 마지막으로 지원되는 프리젠테이션 모드를 쿼리하는 것은 vkGetPhysicalDeviceSurfacePresentModesKHR 을 사용하여 동일한 방식으로 작동합니다.


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

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


모든 세부 사항은 이제 구조체에 있으므로, isDeviceSuitable 함수를 한 번 더 확장 수정 하여 이 함수를 활용하여 스왑 체인 지원이 적절한 지 확인 하십시오. 지원되는 이미지 형식이 적어도 하나 있고 지원되는 프리젠테이션 모드가 하나라도 있는 경우 이 강좌에서의 스왑 체인 지원으로 충분합니다.


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


확장을 사용할 수 있는지 확인한 후 스왑 체인 지원을 쿼리하기만 하면됩니다. 함수의 마지막 줄은 다음과 같이 변경됩니다.

return indices.isComplete() && extensionsSupported && swapChainAdequate;




Choosing the right settings for the swap chain

스왑 체인에 적합한 설정 선택


swapChainAdequate 조건이 충족되면 지원은 확실히 충분하지만 다양한 최적화 모드가 여전히 다양하게 있을 수 있습니다. 가능한 스왑 체인을 위한 올바른 설정을 찾기 위해 몇 가지 함수를 작성합니다. 여기에는 결정할 세 가지 유형의 설정이 있습니다.


  • 표면 포맷 (색상 깊이)

  • 프리젠 테이션 모드 (이미지를 화면에 "스왑" 하기위한 조건)

  • 스왑 범위 (스왑 체인의 이미지 해상도)


이러한 각각의 설정에 대해 사용 가능한 경우 우리는 이를 따라갈 것 이라는 점을 염두에 두고 가장 좋은 방법이라고 생각할 것 입니다. 그렇지 않으면 우리는 차선책을 찾기 위해 약간의 다른 방법을 만들 것입니다.



Surface format (표면 포맷)


이 설정 기능은 다음과 같이 시작됩니다. 나중에 SwapChainSupportDetails 구조체의 formats 멤버를 인수로 전달합니다.


VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {

}


각 VkSurfaceFormatKHR 항목은 형식과 colorSpace 멤버를 포함 합니다. 형식 멤버는 색상 채널과 유형을 지정합니다. 예를 들어 VK_FORMAT_B8G8R8A8_UNORM은 픽셀 당 총 32 비트의 8 비트 부호없는 정수로 B, G, R 및 알파 채널을 이 순서로 저장 한다는 의미입니다. colorSpace 멤버는 VGB_COLOR_SPACE_SRGB_NONLINEAR_KHR 플래그를 사용하여 SRGB 색상 공간이 지원되는지 여부를 나타냅니다. 이전 버전의 스펙에서는 이 플래그를 VK_COLORSPACE_SRGB_NONLINEAR_KHR 이라고 했습니다.


우리가 SRGB를 사용할 수 있는는 경우 색 공간에서  더 정확한 색으로 인식하기 때문에 사용할 것입니다. SRGB 색상을 직접 사용하는 것은 약간의 어려움이 있으므로 가장 일반적인 색상 형식 인 표준 RGB를 VK_FORMAT_B8G8R8A8_UNORM으로 사용합니다.


가장 좋은 경우는 Vulkan이 포맷 멤버가 VK_FORMAT_UNDEFINED로 설정된 VkSurfaceFormatKHR 엔트리 하나만 반환함으로써 표면에 선호 포맷이 없다는 것입니다.


if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
   return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}


형식을 자유롭게 선택할 수 없는 경우 목록을 검토하여 원하는 조합을 사용할 수 있는지 확인합니다.


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


또한 실패 할 경우 "좋은" 형식을 기반으로 사용 가능한 형식의 순위를 매길 수도 있지만 대부분의 경우 지정된 첫 번째 형식으로 해결하는 것이 좋습니다.


VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
   if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
       return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
   }

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

   return availableFormats[0];
}






Presentation mode


프레젠테이션 모드는 화면에 이미지를 표시하기 위한 실제 조건을 나타내므로 스왑 체인의 가장 중요한 설정입니다. Vulkan에는 네 가지 가능한 모드가 있습니다.


  • VK_PRESENT_MODE_IMMEDIATE_KHR : 애플리케이션에서 제출 한 이미지가 즉시 화면으로 전송되어 끊어 질 수 있습니다.

  • VK_PRESENT_MODE_FIFO_KHR : 스왑 체인은 디스플레이가 새로 고쳐지고 프로그램이 렌더링 된 이미지를 큐의 뒤쪽에 삽입 할 때 디스플레이가 큐의 앞에서 이미지를 가져 오는 큐입니다. 대기열이 가득 차면 프로그램이 대기해야 합니다. 이는 현대 게임에서 볼수있는 수직 동기화와 가장 유사합니다. 디스플레이가 새로 고쳐지는 순간을  "수직 공백" 이라고 합니다.

  • VK_PRESENT_MODE_FIFO_RELAXED_KHR :이 모드는 응용 프로그램이 늦고 마지막 수직 공백에서 큐가 비어있는 경우에만 이전 모드와 다릅니다. 다음 수직 여백을 기다리지 않고 이미지가 도착하면 곧바로 전송됩니다. 이로 인해 눈에는  눈물이 생길 수 있습니다.

  • VK_PRESENT_MODE_MAILBOX_KHR : 두 번째 모드의 또 다른 변형입니다. 대기열이 가득 차면 응용 프로그램을 차단하는 대신 이미 대기열에 있는 이미지를 새로운 대기열로 간단하게 대체 할 수 있습니다. 이 모드는 트리플 버퍼링을 구현하는 데 사용할 수 있습니다. 이중 버퍼링을 사용하는 표준 수직 동기화보다 훨씬 적은 대기 시간 문제로 찢어짐을 방지 할 수 있습니다.



VK_PRESENT_MODE_FIFO_KHR 모드 만 사용할 수 있도록 보장 되므로 사용 가능한 최상의 모드를 찾는 함수를 다시 작성해야 합니다.


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


나는 개인적으로 트리플 버퍼링이 매우 좋은 트레이드 오프라고 생각합니다. 이를 통해 수직 여백까지 가능한 한 최신의 새로운 이미지를 렌더링함으로써 상당히 낮은 대기 시간을 유지하면서 찢어지지 않도록 할 수 있습니다. 목록을 살펴보고 사용 가능한지 확인해 봅시다.


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;
}


아쉽게도 일부 드라이버는 현재 VK_PRESENT_MODE_FIFO_KHR을 제대로 지원하지 않으므로 VK_PRESENT_MODE_MAILBOX_KHR을 사용할 수 없는 경우 VK_PRESENT_MODE_IMMEDIATE_KHR을 사용하는 것을 추천합니다.


VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> availablePresentModes) {
   VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;

   for (const auto& availablePresentMode : availablePresentModes) {
       if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
           return availablePresentMode;
       } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
           bestMode = availablePresentMode;
       }
   }

   return bestMode;
}




스왑 범위

Swap extent


앞에서 우리는 중요한 속성 하나를 남겨 두었습니다. 마지막 기능 하나를 추가합니다.


VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}


스왑 범위는 스왑 체인 이미지의 해상도 이며 거의 항상 우리가 그리는 창의 해상도와 정확히 같습니다. 가능한 해상도의 범위는 VkSurfaceCapabilitiesKHR 구조에 정의됩니다. Vulkan은 currentExtent 멤버에서 너비와 높이를 설정하여 윈도우의 해상도를 일치 시키라고 알려줍니다. 그러나 일부 창 관리자는 여기에서 우리가 다른 것을 허용합니다. 이것은 currentExtent의 너비와 높이를 특별한 값으로 설정하여 나타냅니다 : uint32_t의 최대 값. 이 경우 minImageExtent 및 maxImageExtent 범위 내에서 창과 가장 일치하는 해상도를 선택합니다.


VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
   if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
       return capabilities.currentExtent;
   } else {
       VkExtent2D actualExtent = {WIDTH, HEIGHT};

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

       return actualExtent;
   }
}


max 및 min 함수는 WIDTH 및 HEIGHT 값을 구현에서 지원되는 허용 된 최소 범위와 최대 범위 사이에 고정하는 데 사용됩니다. 그것들을 사용하려면 <algorithm> 헤더 파일을 포함 시켜야 합니다.



Creating the swap chain

스왑체인의 생성


이제 우리는 런타임에 선택할 수 있는 헬퍼 함수를 모두 지원 했으므로 마침내 작동하는 스왑 체인을 만드는 데 필요한 모든 정보를 갖게 되었습니다.


createSwapChain 함수를 작성하고 논리 장치 생성 후 initVulkan 에서 호출 해야 합니다.


void initVulkan() {
   createInstance();
   setupDebugCallback();
   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);
}


실제로 결정해야 할 작은 것이 하나 더 있습니다. 그러나 너무 간단해서 별개의 기능을 만들 가치가 없습니다. 첫 번째는 스왑 체인의 이미지 수입니다. 이는 기본적으로 큐 길이입니다. 구현은 제대로 작동하는 이미지의 최소량을 지정합니다. 우리는 3 중 버퍼링을 올바르게 구현하기 위해 하나 이상의 이미지를 갖도록 할 것입니다.


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


maxImageCount의 값이 0이면 메모리 요구 사항 외에 다른 제한은 없다는 것을 의미합니다. 그 이유는 이를 확인해야하기 때문입니다.


Vulkan 개체의 전통처럼 스왑 체인 개체를 만들려면 큰 구조를 채워야 합니다. 그것은 다음과 같이 시작하며 이는 우리에게 이제 매우 친숙합니다.


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;


imageArrayLayers는 각 이미지로 구성되는 레이어의 양을 지정합니다. 입체 3D 응용 프로그램을 개발하지 않는 한 항상 1입니다.

imageUsage 비트 필드는 스왑 체인의 이미지를 사용할 작업의 종류를 지정합니다. 이 자습서에서는 직접 렌더링하는 방법을 설명합니다. 즉, 색상 첨부 파일로 사용됩니다. 사후 처리와 같은 작업을 수행하기 위해 먼저 이미지를 별도의 이미지로 렌더링 할 수도 있습니다. 이 경우 VK_IMAGE_USAGE_TRANSFER_DST_BIT와 같은 값을 대신 사용하고 메모리 연산을 사용하여 렌더링 된 이미지를 스왑 체인 이미지로 전송할 수 있습니다.


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

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
}


다음으로 우리는 여러 대기열 패밀리에서 사용되는 스왑 체인 이미지를 처리하는 방법을 지정해야 합니다. 이 응용 프로그램의 경우에 그래픽 대기열 패밀리가 프리젠테이션 대기열과 다른 경우 입니다. 스왑 체인의 이미지를 그래픽 대기열에서 그린 다음 프레젠테이션 대기열에 제출합니다. 여러 대기열에서 액세스하는 이미지를 처리하는 방법에는 두 가지가 있습니다.


  • VK_SHARING_MODE_EXCLUSIVE : 이미지는 한 번에 하나의 대기열 패밀리가 소유하며 다른 대기열 패밀리에서 사용하기 전에 소유권을 명시 적으로 전송해야 합니다. 이 옵션은 최상의 성능을 제공합니다.

  • VK_SHARING_MODE_CONCURRENT : 이미지는 명시적 소유권 이전 없이 여러 대기열 패밀리에서 사용할 수 있습니다.


대기열 패밀리가 다른 경우 이 자습서의 동시 모드를 사용하여 소유권 이전을 수행하지 않아도 됩니다. 이는 소유권 이전을 나중에 설명하는 몇 가지 개념이 포함되어 있기 때문입니다. 동시 모드에서는 queueFamilyIndexCount 및 pQueueFamilyIndices 매개 변수를 사용하여 소유권을 공유 할 대기열 패밀리를 미리 지정해야 합니다. 그래픽 대기열 패밀리와 프리젠테이션 대기열 패밀리가 동일하다면, 대부분의 하드웨어에서 그렇습니다. 동시 모드는 적어도 두 개의 별개의 대기열 패밀리를 지정해야 하기 때문에 배타적 모드에 유의해야 합니다.


createInfo.preTransform = swapChainSupport.capabilities.currentTransform;


스왑 체인이 지원되는 경우 스왑 체인의 이미지에 특정 변형을 적용하라고 지정할 수 있습니다 (예 : 역전 기능 지원). 90도 시계 방향 회전 또는 수평 뒤집기와 같은 기능입니다. 변환을 원하지 않는다고 지정하려면 현재 변환을 지정하면 됩니다.


createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;


compositeAlpha 필드는 윈도우 시스템의 다른 윈도우와 혼합하기 위해 알파 채널을 사용해야하는지 여부를 지정합니다. 거의 항상 알파 채널을 무시하기 때문에 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR입니다.


createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;


presentMode 값은 앞에서 지정한 값 입니다.. 클리핑 된 멤버가 VK_TRUE로 설정되면, 예를 들어 다른 윈도우가 앞에 있기 때문에 가려진 픽셀의 색상을 신경 쓰지 않는다는 의미입니다. 이러한 픽셀을 다시 읽고 예측 가능한 결과를 얻을 수 있어야 하는 경우가 아니면 클리핑을 사용하여 최상의 성능을 얻을 수 있습니다.


createInfo.oldSwapchain = VK_NULL_HANDLE;


마지막 필드 인 oldSwapChain 을 남겨 둡니다. Vulkan을 사용하면 응용 프로그램이 실행되는 동안 스왑 체인이 유효하지 않거나 최적화되지 않을 가능성이 있습니다 (예 : 창 크기가 조정 되었기 때문). 이 경우 실제로 스왑 체인을 다시 작성해야 하며 이전 필드에 대한 참조를 이 필드에 지정해야 합니다. 이것은 앞으로의 장에서 더 자세히 배우게 될 복잡한 주제입니다. 지금은 하나의 스왑 체인 만 만든다고 가정합니다.


이제 클래스 멤버를 추가하여 VkSwapchainKHR 객체를 저장합니다.


VkSwapchainKHR swapChain;


스왑 체인을 생성하는 것은 vkCreateSwapchainKHR을 호출하는 것처럼 간단합니다.


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


매개 변수는 논리적 장치, 스왑 체인 생성 정보, 선택적 사용자 정의 할당 자 및 핸들을 저장하는 변수에 대한 포인터입니다. 별다른것은 없습니다. 이것은 최후에  vkDestroySwapchainKHR을 사용하여 제거 해야합니다 :


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


이제 응용 프로그램을 실행하여 스왑 체인이 성공적으로 만들어 졌는지 확인하십시오!


createInfo.imageExtent = extent를 제거해보십시오. 유효성 검사 레이어가 활성화 된 라인. 유효성 검사 레이어 중 하나에서 즉시 실수를 발견하고 유용한 메시지가 인쇄 된 것을 볼 수 있습니다.






Retrieving the swap chain images

스왑 체인 이미지 가져오기

스왑 체인이 생성 되었으므로 남은 것은 스왑 체인의 VkImages 핸들을 가져 오는 것입니다. 우리는 이후 장에서 렌더링 작업 중에 이들을 참조 할 것입니다. 핸들을 저장할 클래스 멤버를 추가합니다.


std::vector<VkImage> swapChainImages;


이미지는 스왑 체인에 대한 구현으로 만들어졌으며 스왑 체인이 파괴되면 자동으로 정리되므로 나중에 제거할 코드를 추가 할 필요가 없습니다.


vkCreateSwapchainKHR 호출 바로 다음에 createSwapChain 함수 끝에 핸들을 가져 오는 코드를 추가 합니다. 그들을 검색하는 것은 Vulkan에서 객체의 배열을 검색 한 다른 시간과 매우 유사합니다. 먼저 vkGetSwapchainImagesKHR을 호출하여 스왑 체인의 이미지 수를 쿼리 한 다음 컨테이너의 크기를 조정하고 마지막으로 다시 호출하여 핸들을 검색합니다.


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


스왑 체인을 만들 때 원하는 이미지의 수를 minImageCount라는 필드에 전달했습니다. 구현시 더 많은 이미지를 만들 수 있도록 하기 위해서는 명시 적으로 그 수를 다시 쿼리해야합니다.


마지막으로 스왑 체인 이미지에 대해 선택한 형식과 범위를 멤버 변수에 저장하십시오. 우리는 앞으로의 설명에서 그들을 필요로 할 것이다.


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

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;



우리는 이제 그려 질 수 있고 창에 표시 될 수있는 일련의 이미지를 가지고 있습니다. 다음 장에서는 이미지를 렌더 타겟으로 설정하는 방법에 대해 다루기 시작하고 실제 그래픽 파이프 라인과 드로잉 명령을 살펴보기 시작할 것 입니다.


C++ 코드

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <vector>
#include <cstring>
#include <set>

const int WIDTH = 800;
const int HEIGHT = 600;

const std::vector<const char*> validationLayers = {
   "VK_LAYER_LUNARG_standard_validation"
};

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

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
   auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
   if (func != nullptr) {
       return func(instance, pCreateInfo, pAllocator, pCallback);
   } else {
       return VK_ERROR_EXTENSION_NOT_PRESENT;
   }
}

void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
   auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
   if (func != nullptr) {
       func(instance, callback, pAllocator);
   }
}

struct QueueFamilyIndices {
   int graphicsFamily = -1;
   int presentFamily = -1;

   bool isComplete() {
       return graphicsFamily >= 0 && presentFamily >= 0;
   }
};

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

class HelloTriangleApplication {
public:
   void run() {
       initWindow();
       initVulkan();
       mainLoop();
       cleanup();
   }

private:
   GLFWwindow* window;

   VkInstance instance;
   VkDebugReportCallbackEXT callback;
   VkSurfaceKHR surface;

   VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
   VkDevice device;

   VkQueue graphicsQueue;
   VkQueue presentQueue;

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

   void initWindow() {
       glfwInit();

       glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
       glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

       window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
   }

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

   void mainLoop() {
       while (!glfwWindowShouldClose(window)) {
           glfwPollEvents();
       }
   }

   void cleanup() {
       vkDestroySwapchainKHR(device, swapChain, nullptr);
       vkDestroyDevice(device, nullptr);
       DestroyDebugReportCallbackEXT(instance, callback, nullptr);
       vkDestroySurfaceKHR(instance, surface, nullptr);
       vkDestroyInstance(instance, nullptr);

       glfwDestroyWindow(window);

       glfwTerminate();
   }

   void createInstance() {
       if (enableValidationLayers && !checkValidationLayerSupport()) {
           throw std::runtime_error("validation layers requested, but not available!");
       }

       VkApplicationInfo appInfo = {};
       appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
       appInfo.pApplicationName = "Hello Triangle";
       appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
       appInfo.pEngineName = "No Engine";
       appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
       appInfo.apiVersion = VK_API_VERSION_1_0;

       VkInstanceCreateInfo createInfo = {};
       createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
       createInfo.pApplicationInfo = &appInfo;

       auto extensions = getRequiredExtensions();
       createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
       createInfo.ppEnabledExtensionNames = extensions.data();

       if (enableValidationLayers) {
           createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
           createInfo.ppEnabledLayerNames = validationLayers.data();
       } else {
           createInfo.enabledLayerCount = 0;
       }

       if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
           throw std::runtime_error("failed to create instance!");
       }
   }

   void setupDebugCallback() {
       if (!enableValidationLayers) return;

       VkDebugReportCallbackCreateInfoEXT createInfo = {};
       createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
       createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
       createInfo.pfnCallback = debugCallback;

       if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
           throw std::runtime_error("failed to set up debug callback!");
       }
   }

   void createSurface() {
       if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
           throw std::runtime_error("failed to create window surface!");
       }
   }

   void pickPhysicalDevice() {
       uint32_t deviceCount = 0;
       vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

       if (deviceCount == 0) {
           throw std::runtime_error("failed to find GPUs with Vulkan support!");
       }

       std::vector<VkPhysicalDevice> devices(deviceCount);
       vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

       for (const auto& device : devices) {
           if (isDeviceSuitable(device)) {
               physicalDevice = device;
               break;
           }
       }

       if (physicalDevice == VK_NULL_HANDLE) {
           throw std::runtime_error("failed to find a suitable GPU!");
       }
   }

   void createLogicalDevice() {
       QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

       std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
       std::set<int> uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily};

       float queuePriority = 1.0f;
       for (int queueFamily : uniqueQueueFamilies) {
           VkDeviceQueueCreateInfo queueCreateInfo = {};
           queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
           queueCreateInfo.queueFamilyIndex = queueFamily;
           queueCreateInfo.queueCount = 1;
           queueCreateInfo.pQueuePriorities = &queuePriority;
           queueCreateInfos.push_back(queueCreateInfo);
       }

       VkPhysicalDeviceFeatures deviceFeatures = {};

       VkDeviceCreateInfo createInfo = {};
       createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

       createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
       createInfo.pQueueCreateInfos = queueCreateInfos.data();

       createInfo.pEnabledFeatures = &deviceFeatures;

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

       if (enableValidationLayers) {
           createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
           createInfo.ppEnabledLayerNames = validationLayers.data();
       } else {
           createInfo.enabledLayerCount = 0;
       }

       if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
           throw std::runtime_error("failed to create logical device!");
       }

       vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);
       vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue);
   }

   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[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily};

       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;
   }

   VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
       if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
           return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
       }

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

       return availableFormats[0];
   }

   VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> availablePresentModes) {
       VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;

       for (const auto& availablePresentMode : availablePresentModes) {
           if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
               return availablePresentMode;
           } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
               bestMode = availablePresentMode;
           }
       }

       return bestMode;
   }

   VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
       if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
           return capabilities.currentExtent;
       } else {
           VkExtent2D actualExtent = {WIDTH, HEIGHT};

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

           return actualExtent;
       }
   }

   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;
   }

   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;
   }

   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();
   }

   QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
       QueueFamilyIndices indices;

       uint32_t queueFamilyCount = 0;
       vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

       std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
       vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

       int i = 0;
       for (const auto& queueFamily : queueFamilies) {
           if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
               indices.graphicsFamily = i;
           }

           VkBool32 presentSupport = false;
           vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);

           if (queueFamily.queueCount > 0 && presentSupport) {
               indices.presentFamily = i;
           }

           if (indices.isComplete()) {
               break;
           }

           i++;
       }

       return indices;
   }

   std::vector<const char*> getRequiredExtensions() {
       uint32_t glfwExtensionCount = 0;
       const char** glfwExtensions;
       glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

       std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

       if (enableValidationLayers) {
           extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
       }

       return extensions;
   }

   bool checkValidationLayerSupport() {
       uint32_t layerCount;
       vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

       std::vector<VkLayerProperties> availableLayers(layerCount);
       vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

       for (const char* layerName : validationLayers) {
           bool layerFound = false;

           for (const auto& layerProperties : availableLayers) {
               if (strcmp(layerName, layerProperties.layerName) == 0) {
                   layerFound = true;
                   break;
               }
           }

           if (!layerFound) {
               return false;
           }
       }

       return true;
   }

   static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) {
       std::cerr << "validation layer: " << msg << std::endl;

       return VK_FALSE;
   }
};

int main() {
   HelloTriangleApplication app;

   try {
       app.run();
   } catch (const std::runtime_error& e) {
       std::cerr << e.what() << std::endl;
       return EXIT_FAILURE;
   }

   return EXIT_SUCCESS;
}

이전 글 : Window surface
다음 글 : Image views



'Vulkan' 카테고리의 다른 글

Graphics Pipeline basics - introduction  (0) 2018.01.23
Presentation - Image views  (0) 2018.01.23
Presentation - Window surface  (0) 2018.01.23
Setup - Logical device and queue  (0) 2018.01.23
Setup - Physical devices and queue families  (0) 2018.01.21