[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