본문 바로가기

Vulkan

Setup - Validation layers

Validation layers


이전 글 : Drawing Triangle - Creating an instance

다음 글 : Physical devices and queue families


What are validation layers?

유효성 검사 계층이란 무엇입니까?


Vulkan API는 드라이버 오버 헤드를 최소화 하자는 목표를 바탕으로 설계 되었으며 이 목표 중 하나는 API에서 기본적으로 오류 검사를 하지 않는다는 것 입니다.


열거형(enum) 을 잘못된 값으로 설정 하거나 필요한 매개 변수에 null 포인터를 전달하는 것처럼 간단한 실수는  명시적으로 처리되지 않습니다. 또한 이러한 이유에 의하여 단순한 충돌 또는 정의되지 않은 동작을 발생 하게 합니다.


Vulkan은 여러분이 하고 있는 모든 것에 대해 아주 명료하게 표현해야 하기 때문에 새로운 GPU 기능을 사용 하는데 있어서 논리적 장치 생성시 요청하는 것을 잊어 버리는 등의 작은 실수를 자주 하게 됩니다.


그러나 이것이 이러한 검사를 API에 추가 할 수 없다는 것을 의미 하지는 않습니다. 벌컨 (Vulkan)은 유효성 검사 레이어 라고하는 우하한 멋진 시스템을 도입 했습니다. 유효성 검사 계층은  선택적 구성 요소이며 이는 Vulkan 함수를 후킹하여 추가적인 동작을 수행하도록 합니다.


유효성 검사 레이어의 일반적인 작성 방법은 다음과 같습니다.


  • 잘못을 감지하기 위한 스펙에서 원하는 값과 입력된 매개 변수 값 비교

  • 객체의 생성과 소멸을 추적하여 누출 된 리소스를 찾아냅니다.

  • 호출된 스레드를 추적하여 스레드의 안전성을 확인 합니다.

  • 모든 호출과 매개 변수를 표준 출력에 기록합니다

  • Vulkan 추적 프로파일링 및 재생


다음은 진단을 위한 유효성 검사 레이어에서 함수 구현이 어떻게 생겼는지에 대한 예입니다.


VkResult vkCreateInstance(
   const VkInstanceCreateInfo* pCreateInfo,
   const VkAllocationCallbacks* pAllocator,
   VkInstance* instance) {

   if (pCreateInfo == nullptr || instance == nullptr) {
       log("Null pointer passed to required parameter!");
       return VK_ERROR_INITIALIZATION_FAILED;
   }

   return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}

이러한 유효성 검사 레이어는 원하는 모든 디버깅 기능을 포함 할수 있도록 하도록 하기 위하여 원하는 만큼 중첩 할 수 있습니다.


디버그 빌드에서는 유효성 검사 레이어를 사용하도록 설정하고 릴리스 빌드에 대해서는 유효성 검사 레이어를 완전히 비활성화 하면 두 가지 장점 모두를 얻을 수 있습니다.


Vulkan에는 유효성 검사 레이어가 내장되어 있지 않지만 LunarG Vulkan SDK는 일반적인 오류를 검사하는 멋진 레이어 세트를 제공 합니다. 그것들은 또한 완전한 오픈소스 이기 때문에 어떤 종류의 실수가 있는지 확인하고 다른 사람들을 위해 기여할 수 있습니다.


유효성 검사 레이어를 사용하는 것은 정의되지 않은 동작의 실수에 의하여 드라이버를 사용하는 어떤 다른 응용 프로그램이 중단되는 것을 방지하는 가장 좋은 방법 입니다.


유효성 검사 계층은 시스템에 설치된 경우에만 사용할 수 있습니다. 다시 말해 LunarG 유효성 검사 레이어는 Vulkan SDK가 설치된 PC에서만 사용할 수 있습니다.


Vulkan에는 이전에 두 가지 유형의 유효성 검사 레이어가 있었습니다 :인스턴스 계층 및 장치 특정 계층


인스턴스 계층은 인스턴스와 같은  Vulkan 전역 개체와 관련된 호출 만 확인하고 장치 특정 계층은 특정 GPU와 관련된 호출 만 확인합니다.


장치 특정 계층은 이제 더 이상 사용되지 않습니다. 즉, 인스턴스 유효성 검사 계층이 모든 Vulkan 호출에 적용됩니다. 스펙 문서에서는 일부 구현에서 요구되는 호환성을 위해 장치 수준에서 유효성 검사 레이어를 사용하도록 권장하고 있습니다.


논리적 장치 레벨에서 인스턴스와 동일한 레이어를 지정하기만 하면 됩니다. 나중에 설명 하겠습니다.



Using validation layers

유효성 검사 레이어 사용하기


이번 섹션에서는 Vulkan SDK에서 제공하는 표준 진단 레이어를 활성화 하는 방법을 설명 합니다. 확장 기능의 사용과 마찬 가지로 유효성 검사 레이어는 이름을 지정하여 활성화 해야합니다. 모든 유용한 레이어를 명시적으로 지정하는 대신 SDK를 사용하면 유용한 진단 레이어 전체를 암시적으로 활성화하는 VK_LAYER_LUNARG_standard_validation 레이어를 요청할 수 있습니다.


우선 두 개의 구성 변수를 프로그램에 추가하여 활성화 할 레이어와 활성화 여부를 지정 하십시오. 프로그램이 디버그 모드로 컴파일되는지 아닌지에 따라 그 값을 결정했습니다. NDEBUG 매크로는 C ++ 표준의 일부이며 "디버그하지 않음"을 의미합니다.


const int WIDTH = 800;
const int HEIGHT = 600;

const std::vector<const char*> validationLayers = {
   "VK_LAYER_LUNARG_standard_validation"
};

#ifdef NDEBUG
   const bool enableValidationLayers = false;
#else
   const bool enableValidationLayers = true;
#endif


요청 된 모든 레이어가 사용 가능한지 확인하는 checkValidationLayerSupport 라는 새 함수를 추가 합니다. 먼저 vkEnumerateInstanceLayerProperties 함수를 사용하여 사용 가능한 모든 확장을 나열 하십시오. 그 사용법은 인스턴스의 생성에 대한 글에서 논의 된 vkEnumerateInstanceExtensionProperties의 사용법과 동일 합니다.


bool checkValidationLayerSupport() {
   uint32_t layerCount;
   vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

   std::vector<VkLayerProperties> availableLayers(layerCount);
   vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

   return false;
}


다음으로, validationLayers의 모든 레이어가 availableLayers 목록에 있는지 확인합니다. strcmp 함수를 사용하기 위해  <cstring> 헤더파일을 포함해야 할 수도 있습니다.


for (const char* layerName : validationLayers) {
   bool layerFound = false;

   for (const auto& layerProperties : availableLayers) {
       if (strcmp(layerName, layerProperties.layerName) == 0) {
           layerFound = true;
           break;
       }
   }

   if (!layerFound) {
       return false;
   }
}

return true;


이제 이 함수를 createInstance 함수에서 사용할 수 있습니다.

void createInstance() {
   if (enableValidationLayers && !checkValidationLayerSupport()) {
       throw std::runtime_error("validation layers requested, but not available!");
   }

   ...
}


이제 디버그 모드에서 프로그램을 실행하고 오류가 발생하지 않도록 하십시오. 만약 오류가 발생 했다면 Vulkan SDK를 올바르게 설치 했는지 확인 하십시오.

마냑 보고된 레이어가 없거나 있어도 아주 적은 내용일 경우 이 문제를 여기에 제출하여 해결할 수 있습니다 (LunarG 계정이 필요합니다). 문제를 수정하는 데 대한 도움을 받으려면 해당 페이지를 참조하십시오.


마지막으로 사용 가능한 유효성 레이어 이름을 포함시키기 위하여 VkInstanceCreateInfo 구조체 인스턴스를 수정 하십시오.


if (enableValidationLayers) {
   createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
   createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
   createInfo.enabledLayerCount = 0;
}


검사가 성공하면 vkCreateInstance는 VK_ERROR_LAYER_NOT_PRESENT 오류를 반환하지 않아야 하며  프로그램을 실행하여 이를 확인 해야합니다.



Message callback

메시지 콜백


불행하게도 레이어를 활성화 하는 것만으로는 디버그 메시지를 프로그램에 전달할 방법이 없기 때문에 충분한 도움이 되지 않습니다.

이러한 메시지를 받으려면 콜벡을 설정해야 합니다. 여기에 필요한 것이 VK_EXT_debug_report 확장 기능 입니다.


먼저 다음과 같이 유효성 검사 레이어의 사용 가능 여부에 따라 필요한 확장 프로그램 목록을 반환하는 getRequiredExtensions 함수를 만듭니다.

std::vector<const char*> getRequiredExtensions() {
   uint32_t glfwExtensionCount = 0;
   const char** glfwExtensions;
   glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

   std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

   if (enableValidationLayers) {
       extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
   }

   return extensions;
}


GLFW로 지정된 확장기능은 항상 필요 하지만 디버그 보고서 확장기능은 조건부로 추가됩니다.


여기서 리터럴 문자열 "VK_EXT_debug_report"와 동일한 VK_EXT_DEBUG_REPORT_EXTENSION_NAME 매크로를 사용했습니다.

이 매크로를 사용하면 오타를 피할 수 있습니다.


이제 createInstance 에서 이 함수를 사용할 수 있습니다.


auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();


프로그램을 실행하여 VK_ERROR_EXTENSION_NOT_PRESENT 오류가 발생하지 않도록 하십시오.  이 확장 기능의 존재 여부를 확인할 필요는 없습니다. 이는 유효성 검사 레이어를 사용 한다는 것 자체에 내포되어 있기 때문입니다


이제 콜백 함수가 어떻게 보이는지 살펴 보겠습니다.

debugCallback 이라는 정적 멤버 함수를 PFN_vkDebugReportCallbackEXT 프로토 타입과 함께 추가 합니다. VKAPI_ATTR 및 VKAPI_CALL은 Vulkan이 이러한 함수를 호출 하는데 있어서 올바른 서명을 갖고 있는지를 보증합니다.


static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
   VkDebugReportFlagsEXT flags,
   VkDebugReportObjectTypeEXT objType,
   uint64_t obj,
   size_t location,
   int32_t code,
   const char* layerPrefix,
   const char* msg,
   void* userData) {

   std::cerr << "validation layer: " << msg << std::endl;

   return VK_FALSE;
}



첫 번째 매개 변수는 다음 비트 플래그 를 조합하여 확인하기 위한 메시지 유형을 지정합니다.


  • VK_DEBUG_REPORT_INFORMATION_BIT_EXT

  • VK_DEBUG_REPORT_WARNING_BIT_EXT

  • VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT

  • VK_DEBUG_REPORT_ERROR_BIT_EXT

  • VK_DEBUG_REPORT_DEBUG_BIT_EXT


objType 매개 변수는 메시지의 제목인 객체 유형을 지정합니다. 예를 들어 obj가 VkPhysicalDevice 이면 objType은 VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT가 됩니다. 이것은 내부적으로 모든 Vulkan 핸들이 uint64_t 형식으로 typedef 되었기 때문에 가능 합니다.

msg 매개 변수는 메시지 자체에 대한 포인터를 포함합니다. 마지막으로 콜백에 자신의 데이터를 전달하는 userData 매개 변수가 있습니다.


콜백은 유효성 검사 레이어 메시지를 트리거 한 Vulkan 호출이 중단되어야 하는지를 나타내는 bool 값을 반환 합니다. 콜백이 true를 반환하면 VK_ERROR_VALIDATION_FAILED_EXT 오류로 호출이 중단됩니다. 이것은 일반적으로 유효성 검사 레이어 자체를 테스트하는 데만 사용 되므로 항상 VK_FALSE를 반환 해야합니다.


이제는 Vulkan에게 콜백 함수에 대해 알려주는 일이 남았습니다. 다소 놀랍게도 Vulkan의 디버그 콜백 조차도 명시 적으로 만들고 파괴 해야하는 핸들을 사용하여 관리해야 합니다.

인스턴스 바로 아래에 이 핸들에 대한 클래스 멤버를 추가합니다.


VkDebugReportCallbackEXT callback;


이제 createInstance 바로 다음에 initVulkan에서 호출 할 setupDebugCallback 함수를 추가 하십시오.


void initVulkan() {
   createInstance();
   setupDebugCallback();
}

void setupDebugCallback() {
   if (!enableValidationLayers) return;

}


콜백에 대한 세부 정보가있는 구조체를 채워야 합니다.


VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;


플래그 필드를 사용하여 수신 할 메시지 유형을 필터링 할 수 있습니다. pfnCallback 필드는 콜백 함수에 대한 포인터를 지정합니다. 선택적으로 userData 매개 변수를 통해 콜백 함수에 전달 될 pUserData 필드에 대한 포인터를 전달할 수 있습니다. 예를 들어, 이 기능을 사용하여 HelloTriangleApplication 클래스에 대한 포인터를 전달할 수 있습니다.


이 구조체는 vkDebugReportCallbackEXT 개체를 생성하기 위해 vkCreateDebugReportCallbackEXT 함수에 전달 되어야 합니다. 불행하게도 이 함수는 확장 함수이기 때문에 자동으로 로드되지 않습니다. vkGetInstanceProcAddr을 사용하여 직접 주소를 찾아야 합니다. 우리는 백그라운드에서 이것을 처리하는 우리 자신의 프록시 함수를 생성 할 것입니다. HelloTriangleApplication 클래스 정의 바로 위에 추가 했습니다.

VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
   auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
   if (func != nullptr) {
       return func(instance, pCreateInfo, pAllocator, pCallback);
   } else {
       return VK_ERROR_EXTENSION_NOT_PRESENT;
   }
}


함수를 로드 할 수 없는 경우 vkGetInstanceProcAddr 함수는 nullptr을 반환합니다. 사용 가능한 경우 확장 객체를 만들기 위해 이 함수를 호출 할 수 있습니다.


if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
   throw std::runtime_error("failed to set up debug callback!");
}


두 번째 매개 변수는 매개 변수가 매우 간단 하다는 것 외에 nullptr로 설정 한 선택적 할당 자 콜백입니다.

디버그 콜백은 Vulkan 인스턴스 및 해당 레이어에만 해당 되므로 첫 번째 인수로 명시적으로 지정해야 합니다. 이 패턴은 나중에 다른 하위 오브젝트와 함께 표시 됩니다. 그것이 작동하는지 봅시다. ... 프로그램을 실행하고 빈 창을 쳐다 보면서 싫증이 나면 창을 닫으세요.


명령 프롬프트에 다음 메시지가 인쇄 될 것 입니다.



죄송합니다. 프로그램에서 버그를 발견 했습니다! VkDebugReportCallbackEXT 개체는 vkDestroyDebugReportCallbackEXT를 호출하여 정리해야 합니다. vkCreateDebugReportCallbackEXT와 마찬가지로 함수를 명시적으로 로드 해야합니다. CreateDebugReportCallbackEXT 바로 아래에 다른 프록시 함수를 만듭니다.


void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
   auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
   if (func != nullptr) {
       func(instance, callback, pAllocator);
   }
}


이 함수가 정적 클래스 함수 이거나 클래스 외부의 함수인지 확인 하십시오. 그런 다음 리소스 정리 함수에서 호출 할 수 있습니다.


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

   glfwDestroyWindow(window);

   glfwTerminate();
}


프로그램을 다시 실행하면 오류 메시지가 사라진 것을 알 수 있습니다. 어떤 호출이 메시지를 트리거했는지 보려면 메시지 콜백에 중단 점을 추가하고 스택 추적을 살펴보십시오.




Configuration


유효성 검사 레이어의 동작에 대한 설정은 VkDebugReportCallbackCreateInfoEXT 구조체에 지정된 플래그보다 훨씬 많습니다. Vulkan SDK를 찾아 Config 디렉토리로 이동하십시오. 여기서 레이어를 구성하는 방법을 설명하는 vk_layer_settings.txt 파일을 찾을 수 있습니다.


자신의 응용 프로그램에 대한 계층 설정을 구성하려면 파일을 프로젝트의 Debug 및 Release 디렉토리에 복사하고 지침에 따라 원하는 동작을 설정 합니다. 이 튜토리얼의 나머지 부분에서는 기본 설정을 사용하고 있다고 가정합니다.


이 튜토리얼에서 나는 유효성 검사 레이어가 그 (것)들을 붙잡는데 얼마나 도움이 되는지를 보여주고 Vulkan으로 무엇을 하고 있는지 정확히 아는 것이 얼마나 중요한지 가르쳐 주기 위해 몇 가지 의도적 인 실수를 할 것입니다. 이제 시스템에서 Vulkan 장치를 살펴 보겠습니다.


작성된 C++ 코드 입니다.

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>

const int WIDTH = 800;
const int HEIGHT = 600;

const std::vector<const char*> validationLayers = {
   "VK_LAYER_LUNARG_standard_validation"
};

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
   auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
   if (func != nullptr) {
       return func(instance, pCreateInfo, pAllocator, pCallback);
   } else {
       return VK_ERROR_EXTENSION_NOT_PRESENT;
   }
}

void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
   auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
   if (func != nullptr) {
       func(instance, callback, pAllocator);
   }
}

class HelloTriangleApplication {
public:
   void run() {
       initWindow();
       initVulkan();
       mainLoop();
       cleanup();
   }

private:
   GLFWwindow* window;

   VkInstance instance;
   VkDebugReportCallbackEXT callback;

   void initWindow() {
       glfwInit();

       glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
       glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

       window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
   }

   void initVulkan() {
       createInstance();
       setupDebugCallback();
   }

   void mainLoop() {
       while (!glfwWindowShouldClose(window)) {
           glfwPollEvents();
       }
   }

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

       glfwDestroyWindow(window);

       glfwTerminate();
   }

   void createInstance() {
       if (enableValidationLayers && !checkValidationLayerSupport()) {
           throw std::runtime_error("validation layers requested, but not available!");
       }

       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;

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

       auto extensions = getRequiredExtensions();
       createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
       createInfo.ppEnabledExtensionNames = extensions.data();

       if (enableValidationLayers) {
           createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
           createInfo.ppEnabledLayerNames = validationLayers.data();
       } else {
           createInfo.enabledLayerCount = 0;
       }

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

   void setupDebugCallback() {
       if (!enableValidationLayers) return;

       VkDebugReportCallbackCreateInfoEXT createInfo = {};
       createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
       createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
       createInfo.pfnCallback = debugCallback;

       if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
           throw std::runtime_error("failed to set up debug callback!");
       }
   }

   std::vector<const char*> getRequiredExtensions() {
       uint32_t glfwExtensionCount = 0;
       const char** glfwExtensions;
       glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

       std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

       if (enableValidationLayers) {
           extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
       }

       return extensions;
   }

   bool checkValidationLayerSupport() {
       uint32_t layerCount;
       vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

       std::vector<VkLayerProperties> availableLayers(layerCount);
       vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

       for (const char* layerName : validationLayers) {
           bool layerFound = false;

           for (const auto& layerProperties : availableLayers) {
               if (strcmp(layerName, layerProperties.layerName) == 0) {
                   layerFound = true;
                   break;
               }
           }

           if (!layerFound) {
               return false;
           }
       }

       return true;
   }

   static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) {
       std::cerr << "validation layer: " << msg << std::endl;

       return VK_FALSE;
   }
};

int main() {
   HelloTriangleApplication app;

   try {
       app.run();
   } catch (const std::runtime_error& e) {
       std::cerr << e.what() << std::endl;
       return EXIT_FAILURE;
   }

   return EXIT_SUCCESS;
}



이전 글 : Drawing Triangle - Creating an instance

다음 글 : Physical devices and queue families



'Vulkan' 카테고리의 다른 글

Setup - Logical device and queue  (0) 2018.01.23
Setup - Physical devices and queue families  (0) 2018.01.21
Setup - Creating an instance  (0) 2018.01.20
Setup - Base code  (0) 2018.01.20
Development environment  (0) 2018.01.19