2021. 8. 6. 13:18ㆍ그래픽스/vk
1. Window surface
- Vulkan은 platform agnostic API 이다.
- 직접적으로 윈도우 시스템과 인터페이스할 수 없다.
- 연결설정한 후, 결과를 화면에 표시하려면
- WSI(Window System Integration) extensions을 사용하는것이 필요하다.
VK_KHR_surface.
- VkSurfaceKHR 객체를 expose함.
- 렌더링할 이미지를 surface에 나타나게해주는 추상타입.
- 프로그램의 surface 는 glfw로 이미 생성한 window에 띄워질것임.
- 이 확장은 instance lavel extension이다.
- 따라서 이미 활성화되어있다.
- glfwGetRequiredInstanceExtensions 에 포함되있기 때문
- 이 리스트는 또한 다른 WSI extensions을 포함하는데, 다음챕터에서 다룸.
Window surface
- instance 를 생성한 후 바로 만들어야한다.
- 왜냐하면, 확실히 physical device selection에 영향을 주기 때문.
- 이것은 render targets에 대한 부분이므로, 더 큰 주제임.
- 선택적인 구성이므로 당연히 off-screen rendering을 할 수 있다.
- hacks 없이 invisible window를 생성할 수 도 있다. (OpenGL은 hacks)
2. Window surface creation
- surface 변수 를 생성해야한다.
VkSurfaceKHR surface;
- 비록 VkSurfaceKHR 객체 와 그것의 사용이 platform에 agnostic하지만
- 이것의 생성이 agnostic하다는것이 아니다.
- 왜냐하면 window system details 에 의존적이기때문.
- windows는 HWND, HMODULE handles 를 다루는것이 필요하다.
- 그러므로 Windows에서는 VK_KHR_win32_surface 확장자가 필요하다.
- (플랫폼마다 추가해야할 확장자가 달라짐 platform-specific addition to the extension)
- 이런것들은 자동적으로 glfwGetRequiredInstanceExtensions 에 포함됨.
- 이러한 플랫폼별 확장을 사용하여 surface를 생성하는것을 여기선 보여주지만
- 튜토리얼 전체에선 사용하지 않음
- GLFW 와 같은 라이브러리를 사용하면 플랫폼별 코드를 사용하는것은 의미없음.
- 실제로 glfwCreateWindowSurface 함수를 통해 플랫폼 차이를 처리할 수 있음
2.1 VkWin32SurfaceCreateInfoKHR
- native 하게 플랫폼함수들에 접근하려면 다음과 같이 헤더 부분을 추가해야함
#define VK_USE_PLATFORM_WIN32_KHR
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
- window surface는 Vulkan object 이므로, VkWin32SurfaceCreateInfoKHR라는
- createInfo 구조체를 작성해야함
- 이 구조체에선 hwnd and hinstance 파라미터가 중요하다.
- 이것들은 window와 process에 대한 핸들임.
VkWin32SurfaceCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window);
createInfo.hinstance = GetModuleHandle(nullptr);
- glfwGetWin32Window 함수는 raw HWND를 GLFWwindow 객체에서 가져온다.
- GetModuleHandle함수는 현재 process 의 handle인 HINSTANCE 를 반환해줌
2.2 vkCreateWin32SurfaceKHR
- vkCreateWin32SurfaceKHR 함수 호출을 통해,
- 파라미터안에 instance 와 creation info 와 , costom allocator
- 그리고 초기화할 핸들인 surface를 넣어줘야한다.
- 엄밀히 말하면 기술적으로 이것은 WSI extension 함수이다.
- 그러나 너무 일반적으로 사용되므로, 표준 Vulkan loader에 포함됨.
- 다른 extension과 다르게 주소를 사용한 명시적인 로드는 불필요
if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface!");
}
- 이러한 프로세스는 windows 뿐만이 아니라 Linux 같은 다른 플랫폼들 또한 유사
- 리눅스인 경우 vkCreateXcbSurfaceKHR로 XCB connection 과 X11의 생성 세부정보로 생성
- glfwCreateWindowSurface함수를 사용하면, 플랫폼마다 다른 이러한 확장 기능 작업을 정확히 수행함
- 이제 프로그램에 통합할 것임.
2.3 glfwCreateWindowSurface
- 인스턴스 생성과 디버그 메신저를 생성한 직후 createSurface함수를 추가하여 실제 구현해보자.
void initVulkan() {
createInstance();
setupDebugMessenger();
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!");
}
}
- 파라미터 1 : VkInstance
- 파라미터 2 : GLFWwindow 포인터
- 파라미터 3 : costom allocators
- 파라미터 4 : 생성할 surface 핸들 변수
-> 생성이 제대로 됬는지 확인하는 절차도 잊지 말아야한다!
- GLFW은 이 객체를 Destroying 할 특별한 함수를 제공하지 않음
- 그러나 다음과 같이 original API를 통해 가능
void cleanup() {
...
vkDestroySurfaceKHR(instance, surface, nullptr);
vkDestroyInstance(instance, nullptr);
...
}
- 확실히 instance 를 제거하기전에 surface를 제거해야함.
3. Querying for presentation support - 지원하는 큐 찾기
- 비록 Vulkan에서 WSI(window system integration) 을 지원하도록 구현을 할 수 있지만
- 모든 device가 이 시스템을 지원한다는것은 아님
- 따라서 적합한지 검사해야함
- device가 이미지를 표시할 수 있게 보장하도록 isDeviceSuitable함수를 만들어야한다.
- 이러한 presentation 은 queue-specific feature 임.
- 때문에, 문제는 만든 surface를 지원하는 queue family를 찾아야한다는것.
- drawing commands를 지원하는 queue families이 presentation을 지원안할 수 있음
- 따라서 이전에 작성했던 QueueFamilyIndices구조체를 수정하여, 따로 family를 찾을 수 있게해야함
struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
- 다음으로 findQueueFamilies 또한 수정해야함
- presenting 기능을 가지는 queue family를 찾도록해야함.
- vkGetPhysicalDeviceSurfaceSupportKHR 함수를 통해 확인
- 이 함수는 물리적 장치, queue family 인덱스, surface,를 매개변수로 받음
- VK_QUEUE_GRAPHICS_BIT를 찾는 루프에 추가하면됨.
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
- 인덱스를 저장해주는것도 잊지 말아야함.
if (presentSupport) {
indices.presentFamily = i;
}
- 결국 동일한 queue가 선택될 가능성이 높지만
- 프로그램 전체에서 균일한 접근방식을 위해, 별도의 queue인것처럼 처리해야함
- 그럼에도 불구하고 성능향상을 위해, 똑같은 queue인것을 명시할 수 있긴하다.
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.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
i++;
}
return indices;
}
4. Creating the presentation queue
- 이제 logical device 생성 절차를 수정하여 presentation queue인 VkQueue를 생성해야한다.
- 이 핸들에 대한 멤버 변수를 다음과 같이 추가해야함.
VkQueue presentQueue;
- 두 family에서 큐를 생성하려면 다수의 VkDeviceQueueCreateInfo 구조체를 생성해야한다
- 하지만 이런걸 피할 수 있는 우아한 방법은
- 필요한 queue에 필요한 모든 고유한 queue families 의 set을 만드는것이다.
(An elegant way to do that is to create a set of all unique queue families that are necessary for the required queues)
#include <set>
...
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
float queuePriority = 1.0f;
for (uint32_t 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를 수정하여 vector를 가리키게해야함.
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
- 만일 queue families가 같다면, 해당 인덱스를 한번만 전달해주면된다
- 마지막으로 queue handle을 검색하는 호출을 추가해주면된다,
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
- queue families가 동일한 경우, 두개의 핸들이 이제 동일한 값을 가질 가능성이 높아졌다.
void createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
float queuePriority = 1.0f;
for (uint32_t 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.value(), 0, &graphicsQueue);
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
}
- 다음 장에선, swapchains에대해 살펴보며,
- 어떻게 이들이 이미지를 표시하는 기능을 제공하는지 살펴봄.
https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Window_surface
'그래픽스 > vk' 카테고리의 다른 글
[vk] Drawing triangle - Presentation - Image views (0) | 2021.08.09 |
---|---|
[vk] Drawing triangle - Presentation - Swap chain (0) | 2021.08.07 |
[vk] Drawing triangle - setup - Logical device and queue (0) | 2021.08.06 |
[vk] Drawing triangle - setup - Physical devices and queue (0) | 2021.08.06 |
[vk] Drawing triangle - setup - Validation layers (0) | 2021.08.05 |