[모던 Vulkan (C++ 버전)] #1: Vulkan-HPP와 Modern C++ 개요

왜 Vulkan-HPP인가?

이전 입문 시리즈에서는 주로 C 스타일 Vulkan API를 사용했습니다. C 스타일 API는 다음과 같은 특징을 갖습니다.

  • 함수 포인터 기반 호출: vkCreateInstance, vkEnumeratePhysicalDevices 등
  • 리소스 관리의 수동성: vkDestroy* 함수를 일일이 호출해야 함
  • 타입 안전성 부족: 단순 enum, void* 기반 핸들, 에러 처리 시 VK_SUCCESS 등을 if문으로 체크

이런 패턴은 어느 정도 익숙해지면 사용할 수 있지만, 실수하기 쉽고 코드량이 많아집니다. Modern C++(C++17 이상)에서는 RAII, 스마트 포인터, enum class, std::optional, std::variant 등 다양한 언어 기능을 통해 코드 품질을 높이고 실수를 방지하는 것이 일반적입니다.

Vulkan-HPP는 이러한 Modern C++ 패턴을 Vulkan API에 적용한 헤더 라이브러리입니다. Khronos 그룹에서 공식 관리하며, #include <vulkan/vulkan.hpp>로 C++ 래퍼 API를 사용할 수 있습니다.

Vulkan-HPP의 장점

  1. RAII(Resource Acquisition Is Initialization):
    vk::UniqueInstance, vk::UniqueDevice, vk::UniqueBuffer 등 "Unique" 클래스들을 통해 Vulkan 자원을 객체 수명에 묶어 자동 해제합니다. 이제 자원 정리를 깜빡하는 실수를 줄일 수 있습니다.
  2. 타입 안전성 강화:
    VkInstance → vk::Instance (C++ 객체)
    VkResult → vk::Result (enum class)
    이런 식으로 Vulkan-HPP는 강타입 enum class, 클래스 래핑 등을 통해 실수 가능성을 낮춥니다.
  3. 예외 처리 옵션 및 ResultValue 패턴:
    함수 호출 시 (result, value) 튜플로 반환하거나, 예외 모드를 활성화해 실패 시 예외를 던지는 등 에러 처리를 더 직관적으로 할 수 있습니다.
  4. 메서드 체이닝, std::vector와의 자연스러운 연계:
    C++ STL 컨테이너와 자연스럽게 호환되고, 메서드 호출이 C++ 메서드처럼 되어 있어 가독성이 좋아집니다.

기존 C 스타일 예제와의 비교

예를 들어, C 스타일로 인스턴스를 생성할 때:

VkInstanceCreateInfo createInfo = { ... };
VkInstance instance;
VkResult res = vkCreateInstance(&createInfo, NULL, &instance);
if (res != VK_SUCCESS) {
    // 에러 처리
}

이제 Vulkan-HPP를 사용하면:

vk::ApplicationInfo appInfo("MyApp", 1, "MyEngine", 1, VK_API_VERSION_1_3);
vk::InstanceCreateInfo createInfo({}, &appInfo);

// 예외 비활성화 모드일 때
auto [result, instance] = vk::createInstance(createInfo);
if (result != vk::Result::eSuccess) {
    // 에러 처리
}

// RAII 사용 시
vk::UniqueInstance uInstance = vk::createInstanceUnique(createInfo);

이렇게 RAII 객체를 사용하면 uInstance가 범위를 벗어날 때 자동으로 인스턴스가 파괴되어, vkDestroyInstance 호출 필요가 없어집니다.

에러 처리 전략

Vulkan-HPP는 함수 호출 결과를 vk::ResultValue라는 템플릿 구조로 감싸 반환하거나, VULKAN_HPP_NO_EXCEPTIONS를 정의하지 않으면 예외를 던지는 모드를 사용할 수 있습니다. 예외 모드에서는 VK_SUCCESS가 아니면 std::runtime_error와 유사한 예외를 던져 더 단순한 에러 처리가 가능합니다.

CUDA나 다른 C++ 라이브러리를 쓸 때 예외 처리에 익숙하다면, Vulkan-HPP의 예외 모드가 자연스러울 수 있습니다. 반면 예외를 선호하지 않는다면 ResultValue 패턴을 활용해 명시적으로 결과를 체크할 수 있습니다.

시리즈 진행 방향

이 시리즈에서는 이전 입문 시리즈에서 다룬 다음과 같은 주제를 다시 한 번 Vulkan-HPP와 Modern C++ 스타일로 구현해볼 것입니다.

  • 인스턴스 생성, Validation Layer 설정 (C++ RAII)
  • 물리 디바이스 열람 및 로지컬 디바이스, 큐 확보 (Modern C++)
  • 커맨드 풀, 커맨드 버퍼, 큐 제출 흐름 재작성 (RAII 기반)
  • 메모리 관리, 버퍼 생성, 호스트 메모리 매핑 (에러 처리 단순화)
  • Compute 파이프라인 및 디스크립터 셋 구성 (C++ 스타일)
  • 벡터 덧셈 예제를 C++ RAII 기반으로 재구현
  • 디버깅, 프로파일링, Validation Layer 활용 시 C++ 코드로 얻는 이점

이 과정을 거치며, 이전에 이미 익힌 Vulkan 개념을 Modern C++와 Vulkan-HPP로 재정립하여 코드 가독성과 안정성, 유지보수성을 크게 향상할 수 있을 것입니다.

다음 글 예고

다음 글(#2)에서는 인스턴스 생성 과정을 Vulkan-HPP를 활용해 C++ 스타일로 재작성하는 실전 예제를 다룹니다. RAII 객체와 예외 처리 모드 등을 적용하여, 이전보다 훨씬 깔끔한 인스턴스 생성 코드를 살펴볼 예정입니다.

반응형