[Vulkan] Vulkan Device
목차
인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다.
😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!
Vulkan Device
Vulkan Device란?
렌더링이나 컴퓨팅을 하기 위해서는 Vulkan Device가 반드시 필요함.
Vulkan Device는 Logical한 자원임.
Vulkan Physical Device를 Vulkan Device를 통해 추상화함으로써 하드웨어 의존성을 벗어날 수 있음.
- NVIDIA GPU와 AMD GPU가 Logical하게 같다면 두 Vulkan Device를 동일하다고 생각할 수 있다.
- Logical하게 같은 두 Vulkan Device를 하나로 묶어서 mGPU로 사용할 수 있습니다.
Vulkan Device 생성
Vulkan Queue
Vulkan Queue는 생성되는 자원이 아니라 Vulkan Device의 요소입니다.
RDNA 아키텍처를 살펴보면 다양한 Queue가 있는 것을 확인할 수 있다. Queue는 아래에 적힌 Graphics Command Processor, AC, DMA 중 한 개에 포함된다. 이러한 분류를 Queue Family라고 한다.
- Graphics Command Processor
- ACE
- DMA
개발자는 다양한 Queue 중에서 어플리케이션에 적합한 Queue를 선택하여 Vulkan Device를 생성한다.
Vulkan Queue family properties 얻기
void vkGetPhysicalDeviceQueueFamilyProperties(
VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties* pQueueFamilyProperties);
매개 변수 | 설명 |
physicalDevice | VkPhysicalDevice |
pQueueFamilyPropertyCount | 사용할 수 있는 Queue family의 개수를 얻는데 사용되거나, 얻으려는 Queue family의 개수를 정의한 변수의 포인터입니다. |
pQueueFamilyProperties | 사용할 수 있는 Queue family의 개수를 얻기 위해서는 NULL이어야 하고, Queue family의 정보를 얻기 위해선 VkQueueFamilyProperties 배열의 포인터이어야 합니다. |
VkQueueFamilyProperties 구조체
typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags;
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
매개 변수 | 설명 |
queueFlags | VkQueue의 특징 - Graphics Command Processor, AC, DMA에 대한 정보가 VkQueueFlags에 담겨 있다. |
queueCount | Queue family에 해당하는 VkQueue의 개수 |
timestampValidBits | |
minImageTransferGranularity |
VkQueueFlagBits 열거형
typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001, // 랜더링 작업을 수행하기 위해서는 켜져 있어야 함.
VK_QUEUE_COMPUTE_BIT = 0x00000002,
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
VK_QUEUE_PROTECTED_BIT = 0x00000010,
} VkQueueFlagBits;
Vulkan Queue가 어떤 기능을 제공하는지 VkQueueFlagsBits 열거형을 사용하여 알 수 있다.
Vulkan을 사용해서 렌더링을 할 것이기 때문에 VK_QUEUE_GRAPHICS_BIT이 활성화되어 있어야 한다.
Vulkan을 통해 랜더링 작업을 수행하기 위해서는 VK_QUEUE_GRAPHICS_BIT이 활성화된 queue-family를 선택해야 한다. 만약 이 플래그가 활성화되지 않으면 해당 queue에서 렌더링 작업을 할 수 없다.
Graphics가 지원되는 Vulkan Queue family 찾기
mQueueFamilyIndex는 VKGapPhysicalDeviceQueueFamilyProperty 함수를 통해 얻어온 배열의 인덱스를 의미한다.
VkDeviceQueueCreateInfo 구조체
typedef struct VkDeviceQueueCreateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceQueueCreateFlags flags;
uint32_t queueFamilyIndex;
uint32_t queueCount;
const float* pQueuePriorities;
} VkDeviceQueueCreateInfo;
매개 변수 | 설명 |
sType | 구조체의 타입을 정의 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | Queue family에 해당하는 VkQueue의 개수 |
queueFamilyIndex | Queue family index Queue family index는 VkQueueFamilyProperties 배열의 Index |
queueCount | VkQueue의 개수 |
pQueuePriorities | VkQueue간의 우선순위를 정의한 배열의 포인터 0.0 ~ 1.0 사이의 값 |
VkDeviceCreateInfo 구조체
만약 queue를 2개 생성한다면 const vector<float> queuePriorities 배열의 크기도 2가 되어야 한다.
VkDeviceCreateInfo 구조체
typedef struct VkDeviceCreateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceCreateFlags flags;
uint32_t queueCreateInfoCount;
const VkDeviceQueueCreateInfo* pQueueCreateInfos;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
const VkPhysicalDeviceFeatures* pEnabledFeatures;
} VkDeviceCreateInfo;
매개 변수 | 설명 |
sType | 구조체의 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 지금은 사용되지 않는다. |
queueCreateInfoCount | VkQueueCreateInfo 배열의 크기 |
pQueueCreateInfos | VkQueueCreateInfo 배열의 포인터 |
enabledLayerCount | 더 이상 사용되지 않는다. 하위 호환성을 위해 존재한다. |
ppEnabledLayerNames | 더 이상 사용되지 않는다. 하위 호환성을 위해 존재한다. |
enabledExtensionCount | 활성화할 Extension의 개수 |
ppEnabledExtensionNames | 활성화할 Extension 이름 배열의 포인터 |
pEnabledFeatures |
Vulkan Device 정의
Vulkan Device 생성
VkResult vkCreateDevice(
VkPhysicalDevice physicalDevice,
const VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDevice* pDevice);
매개 변수 | 설명 |
physicalDevice | VkPhysicalDevice |
pCreateInfo | VkDeviceCreateInfo 변수의 포인터 |
pAllocator | |
pDevice | VkDevice 변수의 포인터 |
Vulkan Queue 얻기
void vkGetDeviceQueue(
VkDevice device,
uint32_t queueFamilyIndex,
uint32_t queueIndex,
VkQueue* pQueue);
매개 변수 | 설명 |
device | VkDevice |
queueFamilyIndex | Queue family index |
queueIndex | Queue family에 해당하는 VkQueue의 Index |
pQueue | VkQueue 변수의 포인터 |
Vulkan Device 파괴
void vkDestroyDevice(
VkDevice device,
const VkAllocationCallbacks* pAllocator);
매개 변수 | 설명 |
device | VkDevice |
pAllocator | Queue family index |
코드
#include ...
using namespace std;
VkRenderer::VkRenderer() {
// 1. VkInstance 생성
// 2. VkPhysicalDevice 선택
// ================================================================================
// 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() // 큐의 우선순위
};
// 생성할 Device 정의
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1, // 큐의 개수
.pQueueCreateInfos = &deviceQueueCreateInfo // 생성할 큐의 정보
};
// vkCreateDevice를 호출하여 Device 생성(= mDevice 생성)
VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
// 생성된 Device(= mDevice)로부터 큐를 vkGetDeviceQueue를 호출하여 얻어온다.
vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
}
VkRenderer::~VkRenderer() {
vkDestroyDevice(mDevice, nullptr); // Device 파괴
vkDestroyInstance(mInstance, nullptr);
// queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
}
전체 코드
#include <cassert>
#include <array>
#include <vector>
#include <iomanip>
#include "VkRenderer.h"
#include "VkUtil.h"
#include "AndroidOut.h"
using namespace std;
VkRenderer::VkRenderer() {
// ================================================================================
// 1. VkInstance 생성
// ================================================================================
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 &properties: instanceLayerProperties) {
instanceLayerNames.push_back(properties.layerName);
}
VkInstanceCreateInfo instanceCreateInfo{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &applicationInfo,
.enabledLayerCount = static_cast<uint32_t>(instanceLayerNames.size()),
.ppEnabledLayerNames = instanceLayerNames.data()
};
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()));
mPhysicalDevice = physicalDevices[0];
VkPhysicalDeviceProperties physicalDeviceProperties;
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;
vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, nullptr);
vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyPropertiesCount);
vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount,
queueFamilyProperties.data());
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,
.queueCount = 1,
.pQueuePriorities = queuePriorities.data()
};
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &deviceQueueCreateInfo
};
VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
}
VkRenderer::~VkRenderer() {
vkDestroyDevice(mDevice, nullptr);
vkDestroyInstance(mInstance, nullptr);
}
'⭐ Vulkan & CMake > Vulkan' 카테고리의 다른 글
[Vulkan] Vulkan Surface (0) | 2024.08.04 |
---|---|
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension (0) | 2024.08.04 |
[Vulkan] Vulkan Physical device (0) | 2024.08.02 |
[Vulkan] Vulkan Instance (0) | 2024.08.02 |
[Vulkan] Vulkan Application/ Loader/ Layer/ Driver (0) | 2024.08.01 |
댓글
이 글 공유하기
다른 글
-
[Vulkan] Vulkan Surface
[Vulkan] Vulkan Surface
2024.08.04 -
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension
[Vulkan] 화면 출력을 위해 필요한 Vulkan Extension
2024.08.04 -
[Vulkan] Vulkan Physical device
[Vulkan] Vulkan Physical device
2024.08.02 -
[Vulkan] Vulkan Instance
[Vulkan] Vulkan Instance
2024.08.02