Scientia Conditorium

[Vulkan005][번역] Vulkan Tutorial - 삼각형 그리기 인스턴스(Drawing a triangle - Instance) 본문

프로그래밍/컴퓨터 그래픽스

[Vulkan005][번역] Vulkan Tutorial - 삼각형 그리기 인스턴스(Drawing a triangle - Instance)

크썸 2023. 11. 19. 20:19

원문 : https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Instance

[Vulkan005][번역] Vulkan Tutorial - 삼각형 그리기 인스턴스(Drawing a triangle - Instance)

 

Instance - Vulkan Tutorial

Creating an instance The very first thing you need to do is initialize the Vulkan library by creating an instance. The instance is the connection between your application and the Vulkan library and creating it involves specifying some details about your ap

vulkan-tutorial.com


인스턴스 만들기(Creating an instance)

가장 먼저 해야할 일은 인스턴스를 생성하여 Vulkan 라이브러리를 초기화하는 것입니다. 인스턴스는 응용 프로그램과 Vulkan 라이브러리간의 연결이며, 인스턴스를 생성하려면 드라이버에 응용 프로그램에 대한 몇 가지 세부 정보를 지정해야 합니다.

 

먼저  CreateInstance  함수를 추가하고  initVulkan  함수에서 호출합니다.

void initVulkan() {
    createInstance();
}

인스턴스에 핸들을 보유할 데이터 멤버를 추가합니다.

private:
VkInstance instance;

이제 인스턴스를 생성하려면 먼저 응용 프로그램에 대한 몇 가지 정보로 구조체를 채워야 합니다. 이 데이터는 기술적으로 선택 사항이지만 특정 애플리케이션을 최적화하기 위해 드라이버에 유용한 정보를 제공할 수 있습니다. 예를 들어 특정 특수 동작이 있는 잘 알려진 그래픽 엔진을 사용하기 위해서). 이 구조체를  VkApplicationInfo  라고 합니다.

void createInstance() {
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Hello Triangle";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;
}

앞서 언근했듯이, Vulkan의 많은 구조체에서는  sType  멤버 유형을 명시적으로 지정해야 합니다. 이 구조체도 나중에 확장 정보를 가리킬 수 있는  pNext  멤버가 있는 많은 구조체 중 하나입니다. 여기서는 초기값을  nullptr  로 남겨두겠습니다.(구조체 생성시 {} 해주면 포인터의 경우 nullptr로 초기화됩니다)

 

Vulkan의 많은 정보는 함수 매개변수 대신 구조체를 통해 전달되며 인스턴스 생성을 위한 충분한 정보를 제공하기 위해 구조체를 하나 더 채워야 합니다. 이 다음 구조체는 선택 사항이 아니며 Vulkan 드라이버에 사용할 전역 확장 및 유효성 검사 레이어를 알려줍니다. 여기서 전역이란 특정 장치가 아닌 프로그램 전체에 적용된다는 의미이며, 이는 다음 몇 챕터에서 명확해질 것입니다. 

VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;

처음 두 매개변수는 간단합니다. 다음 두 레이어는 원하는 전역 확장을 지정합니다. 개요에서 언급했듯이 Vulkan은 플랫폼에 구애받지 않는 API이므로 창 시스템과 인터페이스하기 위해서는 확장이 필요합니다. GLFW에는 구조체에 전달할 수 있는 작업을 수행하는 데 필요한 확장자를 반환하는 편리한 내장 함수가 있습니다.

uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;

구조체의 마지막 두 멤버는 활성화할 전역 유효성 검사 계층을 결정합니다. 이에 대해서는 다음 장에서 더 자세히 설명할 예정이므로 지금은 비워두시면 되겠습니다. 

createInfo.enabledLayerCount = 0;

이제 Vulkan 이 인스턴스를 생성하는 데 필요한 모든 것을 지정했으며, 마지막으로  vkCreateInstance  호출을 실행할 수 있습니다.

VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);

보시다시피 Vulkan의 개체 생성 함수 파라미터가 따르는 일반적인 패턴은 다음과 같습니다.

 

  • 생성 정보가 있는 구조체에 대한 포인터
  • 사용자 정의 할당자 콜백에 대한 포인터(이 튜토리얼에서는 항상 nullptr)
  • 새 개체에 대한 핸들을 저장하는 변수에 대한 포인터

모든 것이 정상적으로 진행되었다면 인스턴스에 대한 핸들이 VkInstance 클래스 멤버에 저장됩니다. 거의 모든 Vulkan 함수는 VK_SUCCESS 또는 오류 코드인 VkResult 타입의 값을 반환합니다. 인스턴스가 성공적으로 생성되었는지 확인하기 위해 결과를 저장할 필요가 없으며 대신 성공 값에 대한 검사를 사용하면 됩니다.

if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}

이제 프로그램을 실행하여 인스턴스가 성공적으로 생성되었는지 확인합니다.

 

VK_ERROR_INCOMPATIBLE_DRIVER가 발생했을 때

최신 MoltenVK sdk와 함께 MacOS를 사용하는 경우,  vkCreateInstance  에서  VK_ERROR_INCOMPATIBLE_DRIVER 가 반환될 수 있습니다. lunarg 홈페이지의 macOS에서 Vulkan SDK 시작하기 페이지를 참고하면, 1.3.216 Vulkan SDK부터  VK_KHR_PORTABILITY_subset  확장은 필수입니다. 

 

이 오류를 해결하려면 먼저  VkInstanceCreateInfo  구조체의 플래그에  VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR  비트를 추가한 다음 인스턴스 활성화 확장자 목록에  VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME  을 추가합니다.

 

일반적인 코드는 다음과 같습니다.

...

std::vector<const char*> requiredExtensions;

for(uint32_t i = 0; i < glfwExtensionCount; i++) {
    requiredExtensions.emplace_back(glfwExtensions[i]);
}

requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);

createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;

createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size();
createInfo.ppEnabledExtensionNames = requiredExtensions.data();

if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}

 

 

확장판 지원 확인(Checking for extension support)

 vkCreateInstance  문서를 보면 가능한 오류 코드 중 하나가  VK_ERROR_EXTENSION_NOT_PRESENT  라는 것을 알 수 있습니다. 필요한 확장을 지정하고 해당 오류 코드가 다시 발생했을 때 종료하면 됩니다. 이 방법은 창 시스템 인터페이스와 같은 필수 확장 기능에는 적합하지만, 선택적 기능을 확인하려면 어떻게 해야 할까요?

 

인스턴스를 생성하기 전에 지원되는 확장 목록을 검색하려면  vkEnumerateInstanceExtensionProperties  함수가 있습니다. 이 함수는 확장 기능의 수를 저장하는 변수에 대한 포인터와 확장 기능의 세부 정보를 저정하는  VkExtensionProperties  배열을 받습니다. 또한 특정 유효성 검사 계층을 기준으로 확장을 필터링할 수 있는 선택적 첫 번째 매개변수가 필요하지만 지금은 무시하겠습니다.

 

확장자 세부 정보를 저장할 배열을 할당하려면 먼저 확장자 수가 몇 개인지 알아야 합니다. 후자의 매개변수를 비워두면 확장자 수만 요청할 수 있습니다.

uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

이제 확장자 세부 정보를 담을 배열을 할당합니다.(#include <vector>)

std::vector<VkExtensionProperties> extensions(extensionCount);

마지막으로 확장자 세부 정보를 쿼리할 수 있습니다.(쿼리한다라는 말이 자주 나오는데, 쉽게 생각해서 어떤 데이터베이스에 또는 데이터 저장소에 정보를 요청하여 받아온다라고 생각하시면 됩니다.) 

vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

각  VkExtensionProperties  구조체에는 확장의 이름과 버전이 포함되어 있습니다. 간단한 for문(\t는 들여쓰기를 위한 탭)로 나열할 수 있습니다.

std::cout << "available extensions:\n";

for (const auto& extension : extensions) {
    std::cout << '\t' << extension.extensionName << '\n';
}

Vulkan 지원에 대한 세부 정보를 제공하려는 경우 이 코드를  createInstance  함수에 추가할 수 있습니다. 도전 과제로  glfwGetRequiredInstanceExtensions  가 반환한 모든 확장이 지원되는 확장 목록에 포함되는지 확인하는 함수를 만들어 보세요. 

 

정리(Cleaning up)

프로그램이 종료되기 직전에만  VkInstance  를 삭제해야 합니다.  vkDestoryInstance  함수를 사용하여  cleanup  에서 삭제할 수 있습니다. 

void cleanup() {
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}

 vkDestroyInstance  함수의 매개변수는 간단합니다. 이전 장에서 언급했듯이 Vulkan의 할당 및 해제 함수에는 선택적 할당자 콜백이 있는데, 이 콜백에 nullptr을 전달하면 무시할 수 있습니다. 다음 장에서 생성할 다른 모든 Vulkan 리소스는 인스턴스가 소멸되기 전에 정리해야 합니다.

 

인스턴스 생성 후 더 복잡한 단계를 계속하기 전에 유효성 검사 계층을 확인하여 디버깅 옵션을 평가할 차례입니다.

이번 장의 전체 코드는 아래 페이지에서 확인할 수 있습니다.

C++ code


주인장 코드

작성 중