모던 C++와 Vulkan의 만남: Vulkan-HPP로 코드 품질 높이기

이번 글은 Vulkan C API보다 더 모던한 C++ 스타일로 Vulkan을 활용할 수 있는 Vulkan-HPP에 대한 소개 글입니다. 단순히 함수 포인터를 나열하는 수준에 머무르지 않고, Vulkan을 C++ RAII(Resource Acquisition Is Initialization) 패턴, 강타입(Enum class), 예외 처리 등을 활용해 더 안전하고 깔끔하게 사용할 수 있다는 점에 초점을 둡니다. 또한, 최소 예제 코드와 CMake 설정을 통해 실제로 빌드해볼 수 있는 예시를 제시하겠습니다.

Vulkan-HPP란 무엇인가?

Vulkan-HPP는 Vulkan C API를 C++ 스타일로 감싸는 헤더만의 래퍼(wrapper) 라이브러리입니다. Khronos 그룹이 공식적으로 관리하며, vulkan.hpp를 통해 다음과 같은 개선을 제공합니다.

  1. 강타입(Strongly-typed) enum과 핸들: 단순히 VkInstance, VkDevice 같은 struct 포인터 대신, vk::Instance, vk::Device와 같은 C++ 클래스로 래핑해 타입 안정성을 강화합니다.
  2. RAII 패턴: Vulkan 자원(Instance, Device, Buffer, etc.)을 RAII 객체로 관리 가능. 소멸자에서 자원 해제 자동 처리로 메모리 릭이나 리소스 관리 실수를 줄일 수 있습니다.
  3. 함수 호출 시 예외 처리 옵션: Vulkan-HPP는 기본적으로 vk::ResultValue라는 구조를 통해 함수 호출 결과와 값을 묶어 관리하거나, 예외 모드(compile time 옵션)를 통해 함수 실패 시 예외를 던지는 방식을 지원합니다.
  4. 메서드 체이닝 및 메모리 안전성 향상: C++ 메서드 체이닝과 STL 컨테이너를 활용해 더 직관적이고 안전한 코드 작성이 가능합니다.

이러한 특징 덕분에 Vulkan-HPP는 C 스타일 API로 인한 번잡함을 완화하고, 더 현대적인 C++ 코딩 스타일을 Vulkan에 적용하려는 개발자들에게 큰 도움이 됩니다.

Vulkan C API vs Vulkan-HPP 비교

일반적으로 Vulkan C API를 사용할 때는 다음과 같은 패턴이 자주 나옵니다.

VkInstance instance;
VkInstanceCreateInfo createInfo = { ... };
VkResult res = vkCreateInstance(&createInfo, nullptr, &instance);
if (res != VK_SUCCESS) { ... }  

Vulkan-HPP를 사용하면 이를 다음과 같이 바꿀 수 있습니다.

vk::ApplicationInfo appInfo("MyApp", 1, "MyEngine", 1, VK_API_VERSION_1_3);
vk::InstanceCreateInfo createInfo({}, &appInfo);
// vk::createInstance는 ResultValue<Instance> 반환
auto [result, instance] = vk::createInstance(createInfo);
if (result != vk::Result::eSuccess) { /* 에러 처리 */ }

// RAII를 활용하면 vk::UniqueInstance 사용 가능
vk::UniqueInstance uInstance = vk::createInstanceUnique(createInfo);

또는 예외 모드를 활성화하면 vk::createInstance에서 예외가 던져져 에러 처리가 더 간단해질 수 있습니다. (CMake 옵션 또는 define을 통해 활성화)

RAII를 활용해 vk::UniqueInstance, vk::UniqueDevice, vk::UniqueCommandPool 등 "Unique*" 클래스를 사용하면 소멸 시 자동으로 리소스를 해제하므로, 매번 vkDestroy* 함수를 호출할 필요가 줄어듭니다.

간단한 예제 코드 (Hello Vulkan-HPP)

아래는 Vulkan-HPP를 사용해 인스턴스를 생성하고, 물리 디바이스 목록을 출력하는 간단한 예제입니다.

디렉토리 구조 예시

my_vulkan_hpp_project/
 ├─ CMakeLists.txt
 ├─ src/
 │   └─ main.cpp
 └─ build/

main.cpp 예제

#include <iostream>
#include <vulkan/vulkan.hpp>

int main() {
    // ApplicationInfo와 InstanceCreateInfo 설정
    vk::ApplicationInfo appInfo("VulkanHppApp", 1, "NoEngine", 1, VK_API_VERSION_1_3);
    vk::InstanceCreateInfo instanceInfo({}, &appInfo);

    // 예외 비활성화 모드라면 다음처럼 ResultValue 반환
    auto [result, instance] = vk::createInstance(instanceInfo);
    if (result != vk::Result::eSuccess) {
        std::cerr << "Failed to create instance with Vulkan-HPP!\n";
        return 1;
    }
    // RAII를 사용할 경우, vk::UniqueInstance uInstance = vk::createInstanceUnique(instanceInfo); 로 대체 가능

    // 물리 디바이스 열람
    auto [res2, physicalDevices] = instance.enumeratePhysicalDevices();
    if (res2 != vk::Result::eSuccess) {
        std::cerr << "Failed to enumerate physical devices!\n";
        instance.destroy();
        return 1;
    }

    std::cout << "Found " << physicalDevices.size() << " physical devices.\n";
    for (const auto& pd : physicalDevices) {
        vk::PhysicalDeviceProperties props = pd.getProperties();
        std::cout << "Device: " << props.deviceName << "\n";
    }

    instance.destroy();
    return 0;
}

CMakeLists.txt 예제

cmake_minimum_required(VERSION 3.10)
project(vulkan_hpp_example)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Vulkan REQUIRED)

# Vulkan-HPP는 Vulkan SDK에 포함되어 있으므로 별도 라이브러리 링크는 필요 없음
# 단, /usr/include/vulkan/vulkan.hpp 가 Vulkan SDK에 포함되어 있어야 함

add_executable(vulkan_hpp_example src/main.cpp)
target_include_directories(vulkan_hpp_example PRIVATE ${Vulkan_INCLUDE_DIRS})
target_link_libraries(vulkan_hpp_example Vulkan::Vulkan)

빌드 및 실행 방법

mkdir build
cd build
cmake ..
make
./vulkan_hpp_example

정상적으로 물리 디바이스 목록이 출력된다면 Vulkan-HPP와의 연동이 성공한 것입니다.

Vulkan-HPP의 장점 정리

  • 가독성 향상: 함수 호출 결과 std::tie(result, value) = ... 형태나 예외 방식 등 선택 가능
  • 타입 안전성: Enum class, RAII 객체로 실수 예방
  • 코드 유지보수성 증가: 더 현대적 C++ 스타일로 Vulkan 코드를 작성하면 추후 리팩토링이나 확장에 유리

CUDA나 다른 C++ 기반 API와 비교했을 때, Vulkan C API는 매우 로우레벨이고 함수 포인터 기반인데, Vulkan-HPP는 이를 현대적 C++ 코드 컨벤션과 조화시켜 개발 생산성을 높여줍니다.

마무리

Vulkan-HPP는 Vulkan 개발자라면 한 번쯤 고려해볼 만한 옵션입니다. 물론 C API에 직접 익숙해진 뒤 Vulkan-HPP를 쓰면 양자 간의 대응 관계를 이해하기 더 쉽습니다. 하지만 처음부터 Vulkan-HPP를 활용하면 C++ 친화적인 환경에서 Vulkan 코드를 작성할 수 있어, 코딩 경험이 쾌적해지고, 실수를 줄이는데도 도움이 될 것입니다.

앞으로 모던 Vulkan 시리즈에서도 Vulkan-HPP를 기본으로 활용하는 코드를 보여줄 수 있으며, 이를 통해 더 정돈된 예제 코드를 제시할 계획입니다.

반응형