본문 바로가기

Vulkan

Presentation - Window surface

이전 글 : Logical device and queue
다음 글 : Swap chain



Window surface


Vulkan은 플랫폼에 독립적인 API 이기 때문에 자체적으로 윈도우 시스템과 직접 인터페이스 할 수 없습니다. Vulkan 과 윈도우 시스템 간의 연결을 설정하여 결과를 화면에 표시 하려면 WSI (Window System Integration) 확장을 사용해야 합니다. 이 장에서는 VK_KHR_surface 의 첫 번째 항목에 대해 설명 합겠습니다.

렌더링 된 이미지를 윈도우 화면에 나타내는 VkSurfaceKHR 객체에 대하여 살펴 보게 될 것 입니다.. 우리 프로그램의 윈도우 화면은 이미 GLFW로 만들어져 있습니다.


VK_KHR_surface 확장 기능은 인스턴스 수준의 확장 기능이며 glfwGetRequiredInstanceExtensions 에 의해 반환 된 목록에 포함되어 있으므로 실제로 이미 활성화 되어 있습니다. 이 목록에는 다음 몇 장에서 사용할 WSI 확장도 포함되어 있습니다.


창 표면은 실제로 물리적 장치 선택에 영향을 줄 수 있기 때문에 인스턴스 생성 직후에 만들어야 합니다. 우리가 이를 미룬 이유는 창 표면이 렌더링 대상과 프레젠테이션의 더 큰 주제의 일부이며 이것에 대한  설명이 기본 설정을 너무 복잡하게 만들수 있기 때문입니다. 또한 오프스크린 렌더링만 하면 Vulkan 에서 창 표면이 완전히 선택적인 구성 요소임을 참고 해야 합니다. 오프스크린 렌더링은 벌컨 (Vulkan)은 보이지 않는 창 (OpenGL에서 필요함)을 만드는 것과 같은 그러한 해킹 없이 할 수있게 해줍니다.


Window surface creation


디버그 콜백 바로 아래에 surface 클래스 멤버를 추가하여 시작하십시오.

VkSurfaceKHR surface;


VkSurfaceKHR 객체와 그 사용법은 플랫폼에 좌우되지 않지만 생성하는 것 자체가 윈도우 시스템 세부 사항에 의존하기 때문에 그렇지는 않습니다. 예를 들어 Windows에서 HWND 및 HMODULE 핸들이 필요합니다. 따라서 Windows에서 VK_KHR_win32_surface 라고 하는 플랫폼 관련 추가 기능이 있으며 이 함수는 glfwGetRequiredInstanceExtensions의 목록에도  포함되어 있습니다.


Vulkan 플랫폼 고유의 확장 기능을 사용하여 Windows에서 서페이스를 만드는 방법을 보여 드릴 수는 있지만 실제로 이 강좌 에서는 사용하지 않을 것입니다. GLFW와 같은 라이브러리를 사용하면서 Vulkan 플랫폼 자체 코드를 계속 사용하는 것은 의미가 없습니다. GLFW는 실제로 우리를 위해 다양한 플랫폼의 차이를 처리하는 glfwCreateWindowSurface 가 있습니다.


창 표면은 Vulkan 객체이기 때문에 기술해야 하는 VkWin32SurfaceCreateInfoKHR 구조체가 제공됩니다. 여기에는 두 가지 중요한 매개 변수 인 hwnd와 hinstance가 있습니다. 이것들은 창과 프로세스의 핸들입니다.


VkWin32SurfaceCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window);
createInfo.hinstance = GetModuleHandle(nullptr);

glfwGetWin32Window 함수는 GLFW 창 개체 에서 원시 HWND를 가져 오는 데 사용됩니다. GetModuleHandle 호출은 현재 프로세스의 HINSTANCE 핸들을 리턴합니다.


그 후 vkCreateWin32SurfaceKHR로 표면을 생성 할 수 있습니다.이 표면은 다시 명시 적으로 로드해야 합니다. 그 외에도 호출은 간단하며 인스턴스에 대한 매개 변수, 표면 작성 세부 사항, 사용자 정의 할당 자 및 표면 핸들에 대한 변수가 저장됩니다.


auto CreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR");

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


이 프로세스는 Linux와 같은 다른 플랫폼에서도 비슷 합니다. vkCreateXcbSurfaceKHR 은 XCB 연결 및 창을 X11과 함께 생성 세부 정보로 사용합니다.


glfwCreateWindowSurface 함수는 각 플랫폼마다 다른 구현으로이 작업을 정확하게 수행합니다.


이제 이들을 프로그램에 넣을할 것입니다. 인스턴스 생성 직후 initVulkan에서 호출 할 함수 createSurface와 setupDebugCallback을 추가하십시오.


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

void createSurface() {

}


GLFW 호출은 함수의 구현을 매우 직관적으로 만드는 구조체 대신 간단한 매개 변수를 사용합니다.


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


매개 변수는 VkInstance, GLFW 창 포인터, 사용자 지정 할당 자 및 VkSurfaceKHR 변수에 대한 포인터입니다. 이는 VkResult 를 리턴할 것 입니다. GLFW는 서피스 할당 해재 하기 위한 기능을 제공하지 않지만 원래 API를 통해 쉽게 수행 할 수 있습니다.


void cleanup() {
       ...
       vkDestroySurfaceKHR(instance, surface, nullptr);
       vkDestroyInstance(instance, nullptr);
       ...
   }


서피스 인스턴스가 프로그램 인스턴스보다 먼저 파괴되는지 확인 하십시오.




Querying for presentation support

프레젠테이션 지원 확인하기


Vulkan 구현은 윈도우 시스템 통합을 지원할 수도 있지만 시스템의 모든 장치가 이를 지원한다는 의미는 아닙니다. 따라서 장치가 우리가 만든 표면에 이미지를 표시 할 수 있는지를 알아 보기 위하여  isDeviceSuitable 함수를 작성 해야 합니다. 프레젠테이션은 큐 특정 기능이기 때문에 문제는 실제로 우리가 만든 표면에 프리젠테이션을 지원하는 큐 패밀리를 찾는 것입니다.


실제로는 그리기 명령을 지원하는 대기열 패밀리와 표현을 지원하는 대기열 패밀리가 겹치지 않을 수도 있습니다. 따라서 우리는 QueueFamilyIndices 구조체를 수정하여 다른 프리젠 테이션 큐가 있을 수 있다는 점을 고려 할수 있도록 해야 합니다.


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

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



다음으로 findQueueFamilies 함수를 수정하여 윈도우 화면에 표시 할 수 있는 대기열 패밀리를 찾습니다. 이를 확인하는 함수는 vkGetPhysicalDeviceSurfaceSupportKHR 입니다.이 함수는 물리적 장치, 대기열 인덱스 및 표면을 매개 변수를 전달해야 합니다. VK_QUEUE_GRAPHICS_BIT와 같은 루프에서 호출을 추가 하십시오.

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


그런 다음 리턴되는 값을 확인하고 프리젠테이션 패밀리 큐 인덱스를 저장합니다.

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


결국 이들이 동일한 대기열 패밀리가 될 가능성이 높지만, 프로그램 전반에 걸쳐 서로 동일한 방식의 호출을 위해 별도의 대기열 인 것처럼 처리 할 것입니다. 그럼에도 당신은 성능 향상을 위해 같은 큐에서 그리기와 프리젠테이션을 동시에 지원하는 물리적 장치를 선호 할 것 입니다.




Creating the presentation queue

프리젠테이션 큐 생성

이제 남아있는 한 가지는 프리젠테이션 큐를 생성하고 VkQueue 핸들을 검색하기 위해 논리적 디바이스 생성 프로 시저를 수정하는 것입니다. 핸들에 대한 멤버 변수를 추가하십시오.


VkQueue presentQueue;


다음으로, 우리는 두 패밀리로부터 큐를 생성하기 위해 여러 개의 VkDeviceQueueCreateInfo 구조체를 가질 필요가 있습니다. 이를 수행하는 우아한 방법은 필수 대기열에 필요한 모든 고유 대기열 집합을 작성하는 것입니다.


#include <set>

...

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


벡터를 가리 키도록 VkDeviceCreateInfo 를 수정합니다.


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


대기열 패밀리가 동일하면 인덱스를 한 번만 전달 하면 됩니다. 마지막으로, 호출을 추가하여 큐 핸들을 검색합니다.


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


대기열 패밀리가 동일한 경우 두 핸들은 현재 동일한 값을 가질 가능성이 큽니다. 다음 장에서는 스왑 체인을 살펴보고 이미지를 표면에 표시하는 방법을 설명합니다.



C++ 코드 입니다.


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

#include <iostream>
#include <stdexcept>
#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"
};

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

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;

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

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

   void cleanup() {
       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 = 0;

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

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

       return indices.isComplete();
   }

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



이전 글 : Logical device and queue
다음 글 : Swap chain


'Vulkan' 카테고리의 다른 글

Presentation - Image views  (0) 2018.01.23
Presentation - Swap chain  (1) 2018.01.23
Setup - Logical device and queue  (0) 2018.01.23
Setup - Physical devices and queue families  (0) 2018.01.21
Setup - Validation layers  (0) 2018.01.21