[Vulkan] Vulkan Surface
Vulkan은 플랫폼에 구애받지 않는 API이기 때문에 자체적으로 윈도우 시스템과 직접 인터페이스할 수 없다. 화면에 결과를 제시하기 위해 Vulkan과 VK_KHR_surface 확장을 사용해야 한다.
목차
인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다.
😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!
Vulkan Surface
Vulkan Surface란?
Vulkan Surface는 Window를 나타내는 객체다.
Vulkan Surface를 통해 Window와 Window에 연결된 Display에 대한 정보를 얻을 수 있다.


Android에서 Vulkan Surface 생성하기 위한 준비
Vulkan Surface를 생성하기 위해서는 Window System에 맞는 API를 사용해야 한다.
Android는 VK_USE_PLATFORM_ANDROID_KHR 디파인을 정의해야 한다.
# 첫 번째 사용방법:
CMakeList.txt에서 target_compile_definitions을 사용해서 VK_USE_PLATFORM_ANDROID_KHR 을 정의.
이를 통해, Surface 생성에 필요한 API를 사용할 수 있다.
target_compile_definitions(practicevulkan PRIVATE VK_USE_PLATFORM_ANDROID_KHR)
# 두 번째 사용방법:
vulkan 헤더를 인크루드하기 전에 VK_USE_PLATFORM_ANDROID_KHR 디파인 함.
#define VK_USE_PLATFORM_ANDROID_KHR #include <vulkan/vulkan.h>
# 세 번째 사용방법:
VK_USE_PLATFORM_ANDROID_KHR 을 정의하지 않고 dynamic 로딩하는 방법.
Vulkan Surface 생성

Surface를 생성하기 위해서는 ANativeWindow가 필요함.
vkCreateAndroidSurfaceKHR를 호출하여 VkSurfaceKHR를 생성한다.
VkAndroidSurfaceCreateInfoKHR 구조체
typedef struct VkAndroidSurfaceCreateInfoKHR { VkStructureType sType; const void* pNext; VkAndroidSurfaceCreateFlagsKHR flags; struct ANativeWindow* window; } VkAndroidSurfaceCreateInfoKHR;
매개 변수 | 설명 |
sType | 구조체의 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | |
window | Surface와 연결하려는 ANativeWindow |

Vulkan Surface 생성
VkResult vkCreateAndroidSurfaceKHR( VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
매개 변수 | 설명 |
instance | VkInstance |
pCreateInfo | VkAndroidSurfaceCreateInfo 변수의 포인터 |
pAllocator | |
pSurface | VkSurfaceKHR 변수의 포인터 |

Vulkan Physical Device가 Vulkan Surface를 지원하는지 확인
모든 GPU가 Surface 출력을 지원하지 않는다. 그래서 Surface를 생성 후, physical device의 queue-family가 해당 surface를 출력할 수 있는지 확인해야 한다.
- 디스플레이 포트가 없는 GPU도 있다. 이런 GPU들은 Surface를 출력할 수 없다.
- vkGetPhysicalDeviceSurfaceSupportKHR 함수를 호출하여 GPU가 Surface를 호출할 수 있는지 여부를 확인할 수 있다.
VkResult vkGetPhysicalDeviceSurfaceSupportKHR( VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported);
매개 변수 | 설명 |
physicalDevice | VkPhysicalDevice |
queueFamilyIndex | Queue family index |
surface | VkSurfaceKHR |
pSupported | 지원 여부를 얻어오는 변수의 포인터. VK_TRUE이면 지원 / VK_FALSE는 지원하지 않 |

Vulkan Surface 파괴
void vkDestroySurfaceKHR( VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator);
매개 변수 | 설명 |
device | VkDevice |
surface | VkSurface |
pAllocator |

코드
CMakeList.txt
cmake_minimum_required(VERSION 3.22.1) project("practicevulkan") find_package(game-activity REQUIRED CONFIG) find_package(Vulkan REQUIRED) add_library(practicevulkan SHARED VkRenderer.h VkRenderer.cpp VkUtil.h main.cpp AndroidOut.cpp) target_compile_definitions(practicevulkan PRIVATE VK_USE_PLATFORM_ANDROID_KHR) target_link_libraries(practicevulkan game-activity::game-activity android log Vulkan::Vulkan)
- target_compile_definitions 설정함.
VkRender.cpp
#include .. using namespace std; VkRenderer::VkRenderer() { // 1. VkInstance 생성 // 2. VkPhysicalDevice 선택 // 3. VkDevice 생성 // 4. VkSurface 생성 VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{ .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, .window = window }; // surface 생성. VK_CHECK_ERROR(vkCreateAndroidSurfaceKHR(mInstance, &surfaceCreateInfo, nullptr, &mSurface)); VkBool32 supported; // surface 지원 여부 VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mQueueFamilyIndex, mSurface, &supported)); // 지원 여부를 받아옴. assert(supported); } VkRenderer::~VkRenderer() { vkDestroySurfaceKHR(mInstance, mSurface, nullptr); vkDestroyDevice(mDevice, nullptr); // Device 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다. vkDestroyInstance(mInstance, nullptr); }
전체 코드
// MIT License // // Copyright (c) 2024 Daemyung Jang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include <cassert> #include <array> #include <vector> #include "VkRenderer.h" #include "VkUtil.h" #include "AndroidOut.h" using namespace std; VkRenderer::VkRenderer() { // ================================================================================ // 1. VkInstance 생성 // ================================================================================ // VkApplicationInfo 구조체 정의 VkApplicationInfo applicationInfo{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "Practice Vulkan", .applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0), .apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0) }; // 사용할 수 있는 레이어를 얻어온다. uint32_t instanceLayerCount; VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr)); vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount); VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayerProperties.data())); // 활성화할 레이어의 이름을 배열로 만든다. vector<const char*> instanceLayerNames; for (const auto &layerProperty : instanceLayerProperties) { instanceLayerNames.push_back(layerProperty.layerName); } uint32_t instanceExtensionCount; // 사용 가능한 InstanceExtension 개수 VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtensionCount, nullptr)); vector<VkExtensionProperties> instanceExtensionProperties(instanceExtensionCount); VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtensionCount, instanceExtensionProperties.data())); vector<const char *> instanceExtensionNames; // instanceExtensionName을 담는 배열 for (const auto &properties: instanceExtensionProperties) { if (properties.extensionName == string("VK_KHR_surface") || properties.extensionName == string("VK_KHR_android_surface")) { instanceExtensionNames.push_back(properties.extensionName); } } assert(instanceExtensionNames.size() == 2); // 반드시 2개의 이름이 필요하기 때문에 확인 // sType: 구조체의 타입, pApplicationInfo: 어플리케이션의 이름 // enabledLayerCount, ppEnableLayerNames: 사용할 레이어의 정보를 정의 VkInstanceCreateInfo instanceCreateInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &applicationInfo, .enabledLayerCount = static_cast<uint32_t>(instanceLayerNames.size()), .ppEnabledLayerNames = instanceLayerNames.data(), .enabledExtensionCount = static_cast<uint32_t>(instanceExtensionNames.size()), .ppEnabledExtensionNames = instanceExtensionNames.data() }; // vkCreateInstance로 인스턴스 생성. 생성된 인스턴스가 mInstance에 쓰여진다. VK_CHECK_ERROR(vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance)); // ================================================================================ // 2. VkPhysicalDevice 선택 // ================================================================================ uint32_t physicalDeviceCount; VK_CHECK_ERROR(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr)); vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount); VK_CHECK_ERROR(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices.data())); // 간단한 예제를 위해 첫 번째 VkPhysicalDevice를 사용 mPhysicalDevice = physicalDevices[0]; VkPhysicalDeviceProperties physicalDeviceProperties; // 이 구조체 안에 GPU에 필요한 모든 정보가 있다. vkGetPhysicalDeviceProperties(mPhysicalDevice, &physicalDeviceProperties); aout << "Selected Physical Device Information ↓" << endl; aout << setw(16) << left << " - Device Name: " << string_view(physicalDeviceProperties.deviceName) << endl; aout << setw(16) << left << " - Device Type: " << vkToString(physicalDeviceProperties.deviceType) << endl; aout << std::hex; aout << setw(16) << left << " - Device ID: " << physicalDeviceProperties.deviceID << endl; aout << setw(16) << left << " - Vendor ID: " << physicalDeviceProperties.vendorID << endl; aout << std::dec; aout << setw(16) << left << " - API Version: " << VK_API_VERSION_MAJOR(physicalDeviceProperties.apiVersion) << "." << VK_API_VERSION_MINOR(physicalDeviceProperties.apiVersion); aout << setw(16) << left << " - Driver Version: " << VK_API_VERSION_MAJOR(physicalDeviceProperties.driverVersion) << "." << VK_API_VERSION_MINOR(physicalDeviceProperties.driverVersion); // ================================================================================ // 3. VkDevice 생성 // ================================================================================ uint32_t queueFamilyPropertiesCount; //--------------------------------------------------------------------------------- //** queueFamily 속성을 조회 // 사용 가능한 queueFamily의 수(=queueFamilyPropertiesCount)를 얻어온다. vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, nullptr); vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyPropertiesCount); // 해당 queueFamily들의 속성을 배열에 얻어온다. vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, queueFamilyProperties.data()); //--------------------------------------------------------------------------------- // 특정 queueFamilyProperties가 VK_QUEUE_GRAPHICS_BIT를 지원하는지 확인. // 지원하는 queueFamilyProperties를 찾으면 break. queueFamily에 대한 정보는 mQueueFamilyIndex에 저장. for (mQueueFamilyIndex = 0; mQueueFamilyIndex != queueFamilyPropertiesCount; ++mQueueFamilyIndex) { if (queueFamilyProperties[mQueueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { break; } } // 생성할 큐를 정의 const vector<float> queuePriorities{1.0}; VkDeviceQueueCreateInfo deviceQueueCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = mQueueFamilyIndex, // queueFamilyIndex .queueCount = 1, // 생성할 큐의 개수 .pQueuePriorities = queuePriorities.data() // 큐의 우선순위 }; uint32_t deviceExtensionCount; // 사용 가능한 deviceExtension 개수 VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &deviceExtensionCount, nullptr)); vector<VkExtensionProperties> deviceExtensionProperties(deviceExtensionCount); VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &deviceExtensionCount, deviceExtensionProperties.data())); vector<const char *> deviceExtensionNames; for (const auto &properties: deviceExtensionProperties) { if (properties.extensionName == string("VK_KHR_swapchain")) { deviceExtensionNames.push_back(properties.extensionName); } } assert(deviceExtensionNames.size() == 1); // VK_KHR_swapchain이 반드시 필요하기 때문에 확인 // 생성할 Device 정의 VkDeviceCreateInfo deviceCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = 1, // 큐의 개수 .pQueueCreateInfos = &deviceQueueCreateInfo, // 생성할 큐의 정보 .enabledExtensionCount = static_cast<uint32_t>(deviceExtensionNames.size()), .ppEnabledExtensionNames = deviceExtensionNames.data() // 활성화하려는 deviceExtension들을 넘겨줌 }; // vkCreateDevice를 호출하여 Device 생성(= mDevice 생성) VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice)); // 생성된 Device(= mDevice)로부터 큐를 vkGetDeviceQueue를 호출하여 얻어온다. vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue); // ================================================================================ // 4. VkSurface 생성 // ================================================================================ VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{ .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, .window = window }; // surface 생성. VK_CHECK_ERROR(vkCreateAndroidSurfaceKHR(mInstance, &surfaceCreateInfo, nullptr, &mSurface)); VkBool32 supported; // surface 지원 여부 VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mQueueFamilyIndex, mSurface, &supported)); // 지원 여부를 받아옴. assert(supported); } VkRenderer::~VkRenderer() { vkDestroySurfaceKHR(mInstance, mSurface, nullptr); vkDestroyDevice(mDevice, nullptr); // Device 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다. vkDestroyInstance(mInstance, nullptr); }
'⭐ Vulkan & CMake > Vulkan' 카테고리의 다른 글
[Vulkan] Command (0) | 2024.08.05 |
---|---|
[Vulkan] Vulkan Swapchain (0) | 2024.08.05 |
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension (0) | 2024.08.04 |
[Vulkan] Vulkan Device (0) | 2024.08.02 |
[Vulkan] Vulkan Physical device (0) | 2024.08.02 |
댓글
이 글 공유하기
다른 글
-
[Vulkan] Command
[Vulkan] Command
2024.08.05 -
[Vulkan] Vulkan Swapchain
[Vulkan] Vulkan Swapchain
2024.08.05 -
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension
2024.08.04 -
[Vulkan] Vulkan Device
[Vulkan] Vulkan Device
2024.08.02
댓글을 사용할 수 없습니다.