왜 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의 장점
- RAII(Resource Acquisition Is Initialization):
vk::UniqueInstance, vk::UniqueDevice, vk::UniqueBuffer 등 "Unique" 클래스들을 통해 Vulkan 자원을 객체 수명에 묶어 자동 해제합니다. 이제 자원 정리를 깜빡하는 실수를 줄일 수 있습니다. - 타입 안전성 강화:
VkInstance → vk::Instance (C++ 객체)
VkResult → vk::Result (enum class)
이런 식으로 Vulkan-HPP는 강타입 enum class, 클래스 래핑 등을 통해 실수 가능성을 낮춥니다. - 예외 처리 옵션 및 ResultValue 패턴:
함수 호출 시 (result, value) 튜플로 반환하거나, 예외 모드를 활성화해 실패 시 예외를 던지는 등 에러 처리를 더 직관적으로 할 수 있습니다. - 메서드 체이닝, 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 객체와 예외 처리 모드 등을 적용하여, 이전보다 훨씬 깔끔한 인스턴스 생성 코드를 살펴볼 예정입니다.
'개발 이야기 > Vulkan' 카테고리의 다른 글
[모던 Vulkan (C++ 버전)] #3: 물리 디바이스 선택 및 로지컬 디바이스, 큐 확보 (RAII 적용) (0) | 2024.12.19 |
---|---|
[모던 Vulkan (C++ 버전)] #2: 인스턴스 생성 (RAII와 예외 처리 활용) (0) | 2024.12.19 |
모던 C++와 Vulkan의 만남: Vulkan-HPP로 코드 품질 높이기 (0) | 2024.12.19 |
[Vulkan으로 GPGPU 시작하기] #10: 마무리와 다음 단계로의 길잡이 (1) | 2024.12.19 |
[Vulkan으로 GPGPU 시작하기] #9: 중간 정리와 다음 단계로의 길잡이 (0) | 2024.12.19 |