모던 Vulkan (C++ 버전) 시리즈의 세 번째 글입니다. 지난 글(#2)에서는 Vulkan-HPP와 Modern C++ 스타일을 적용해 인스턴스를 생성하고 Validation Layer와 익스텐션 설정 과정을 간소화했습니다. 이제 인스턴스가 준비되었으니, 시스템에 장착된 GPU(물리 디바이스)를 탐색하고, Compute 또는 Graphics에 적합한 큐 패밀리를 선택한 뒤 로지컬 디바이스를 생성하고 큐를 확보하는 과정을 C++ RAII 패턴을 활용해 다시 구현해보겠습니다.
입문 시리즈에서 C 스타일 코드로 했던 과정을 이제 Vulkan-HPP와 RAII(Unique*), 예외 처리 등을 통해 더 깔끔하고 유지보수하기 쉬운 형태로 재작성합니다.
목표
- 인스턴스가 준비되었다고 가정하고, 물리 디바이스(Physical Device) 리스트를 얻어 GPU를 하나 선택
- computeQueueFamilyIndex나 graphicsQueueFamilyIndex 등 원하는 큐 패밀리를 탐색
- vk::DeviceCreateInfo로 로지컬 디바이스 생성
- vk::UniqueDevice로 디바이스를 RAII 관리
- vk::Device::getQueue로 큐 핸들 얻기
- 예외 처리 모드 활용 시 디바이스 생성 실패 시 예외 처리
기본 개념 복습
이전에는 다음과 같은 흐름으로 진행했습니다.
vkEnumeratePhysicalDevices
로 GPU 리스트 얻기- 반복문 돌며 조건(예: Compute 기능 지원)을 만족하는 디바이스 선택
- 큐 패밀리 열람 후 적절한 인덱스 찾기
vkCreateDevice
로 로지컬 디바이스 생성,vkGetDeviceQueue
로 큐 획득
이제 Vulkan-HPP에서는 instance.enumeratePhysicalDevices()
, physicalDevice.getQueueFamilyProperties()
등을 활용해 std::vector와 C++ 스타일로 처리합니다.
코드 예제
디렉토리 구조
my_vulkan_hpp_device/
├─ CMakeLists.txt
├─ src/
│ └─ main.cpp
└─ build/
main.cpp 코드
#include <iostream>
#include <vector>
#include <vulkan/vulkan.hpp>
int main() {
// 인스턴스 생성 (이전 글과 동일, 예외 모드 가정)
vk::ApplicationInfo appInfo("MyVulkanApp", 1, "MyEngine", 1, VK_API_VERSION_1_3);
std::vector<const char*> layers = { "VK_LAYER_KHRONOS_validation" };
std::vector<const char*> extensions = { "VK_EXT_debug_utils" };
vk::InstanceCreateInfo instanceInfo({}, &appInfo, (uint32_t)layers.size(), layers.data(), (uint32_t)extensions.size(), extensions.data());
vk::UniqueInstance instance = vk::createInstanceUnique(instanceInfo);
// 물리 디바이스 열람
auto physicalDevices = instance->enumeratePhysicalDevices();
if (physicalDevices.empty()) {
std::cerr << "No Vulkan-supported GPU found!\n";
return 1;
}
// Compute 가능한 큐 패밀리를 지원하는 디바이스를 하나 선택
vk::PhysicalDevice chosenDevice = VK_NULL_HANDLE;
uint32_t computeQueueFamilyIndex = UINT32_MAX;
for (auto& pd : physicalDevices) {
auto queueFamilies = pd.getQueueFamilyProperties();
for (uint32_t i = 0; i < queueFamilies.size(); i++) {
if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eCompute) {
chosenDevice = pd;
computeQueueFamilyIndex = i;
break;
}
}
if (chosenDevice) break;
}
if (!chosenDevice) {
std::cerr << "No suitable GPU with compute capability found!\n";
return 1;
}
float queuePriority = 1.0f;
vk::DeviceQueueCreateInfo queueCreateInfo({}, computeQueueFamilyIndex, 1, &queuePriority);
vk::DeviceCreateInfo deviceCreateInfo({}, 1, &queueCreateInfo);
// 확장이나 Layer를 추가하고 싶다면 deviceCreateInfo에 추가 가능
try {
vk::UniqueDevice device = chosenDevice.createDeviceUnique(deviceCreateInfo);
// 큐 획득
vk::Queue computeQueue = device->getQueue(computeQueueFamilyIndex, 0);
std::cout << "Device and compute queue successfully created with Vulkan-HPP!\n";
// device, instance가 범위를 벗어나면 자동으로 vkDestroyDevice, vkDestroyInstance 호출
} catch (const vk::SystemError& err) {
std::cerr << "Failed to create device: " << err.what() << "\n";
return 1;
}
return 0;
}
CMakeLists.txt 예제
cmake_minimum_required(VERSION 3.10)
project(vulkan_hpp_device)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Vulkan REQUIRED)
add_executable(vulkan_hpp_device src/main.cpp)
target_include_directories(vulkan_hpp_device PRIVATE ${Vulkan_INCLUDE_DIRS})
target_link_libraries(vulkan_hpp_device Vulkan::Vulkan)
빌드 및 실행
mkdir build
cd build
cmake ..
make
./vulkan_hpp_device
정상적으로 GPU와 큐를 선택했다면 “Device and compute queue successfully created with Vulkan-HPP!”가 출력됩니다.
주요 포인트
- std::vector와 Vulkan-HPP 메서드(
.enumeratePhysicalDevices()
,.getQueueFamilyProperties()
) 조합으로 자원 탐색 코드 간결화 - vk::UniqueDevice로 디바이스 RAII 관리 → 디바이스 파괴 코드 필요 없음
- 예외 던지는 모드에서 디바이스 생성 실패 시 try/catch로 에러 처리 단순화
- C 스타일 대비 코드 라인 수와 복잡성 감소
정리 및 다음 글 예고
이번 글에서는 물리 디바이스 선택, 로지컬 디바이스 생성, 큐 획득 과정을 C++ RAII, 예외 처리, Vulkan-HPP를 활용해 재작성했습니다. 코드가 한결 깔끔해지고, 메모리 관리 및 에러 처리가 단순화되는 것을 확인할 수 있습니다.
다음 글(#4)에서는 커맨드 풀, 커맨드 버퍼, 큐 제출 과정을 다시 Modern C++ 스타일로 재구현해보겠습니다. RAII를 통해 커맨드 버퍼와 커맨드 풀을 자동 정리하고, 에러 처리 역시 예외로 단순화하는 패턴을 살펴볼 예정입니다.
'개발 이야기 > Vulkan' 카테고리의 다른 글
[모던 Vulkan (C++ 버전)] #5: 메모리 관리 & 버퍼 생성 (RAII 기반 Modern C++) (0) | 2024.12.19 |
---|---|
[모던 Vulkan (C++ 버전)] #4: 커맨드 버퍼, 커맨드 풀, 큐 제출 (RAII 기반) (0) | 2024.12.19 |
[모던 Vulkan (C++ 버전)] #2: 인스턴스 생성 (RAII와 예외 처리 활용) (0) | 2024.12.19 |
[모던 Vulkan (C++ 버전)] #1: Vulkan-HPP와 Modern C++ 개요 (0) | 2024.12.19 |
모던 C++와 Vulkan의 만남: Vulkan-HPP로 코드 품질 높이기 (0) | 2024.12.19 |