[Vulkan Tutorial] 05-Create an Instance

목차: 01-Overview (Link)
이전 글: 04-Create Sub-Module and Base Code (Link)
다음 글: 06-Create Window Surface (Link)


Vulkan API를 사용해서 프로그램 작성을 시작하면 가장 먼저 Instance를 생성해야 한다. 아래 코드 1은 Instance를 생성하기 위한 코드이다.

코드 1: Instance 생성 코드

#include <iostream>
#include <stdexcept>
#include <functional>
#include <cstdlib>
#include <util_init.hpp>

#define APP_SHORT_NAME "mkVulkanExample"

class mkTriangle{
	public:
		void run(){
			LOGI("MK: mkTriangle-->run()");
			initVulkan();
			mainLoop();
			cleanup();
		}

	private:
		void initVulkan(){
			createInstance();
		}

		void mainLoop(){
		}

		void cleanup(){
			//MK: (코드 1-6) Instance Destory 코드                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
			vkDestroyInstance(instance, nullptr);
		}

		void createInstance(){

			//MK: (코드 1-1) Application 정보를 입력함 (Optional)
			LOGI("MK: Create VkApplicationInfo");
			VkApplicationInfo appInfo = {};
			appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
			appInfo.pNext = NULL;
			appInfo.pApplicationName = APP_SHORT_NAME;
			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;

			//MK: (코드 1-3) 지원하는 Extension을 모두 Enable 함
			uint32_t extensionCount = 0;
			vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
			std::vector<VkExtensionProperties> extensions(extensionCount);
			vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
			LOGI("MK: Instance Extensions Count - %d", extensionCount);
			std::vector<const char *> instanceExtensions;
			for (const auto& extension : extensions){
				LOGI("MK: Instance Extension - %s", extension.extensionName);
				//MK: 필요 Extnesion은 String으로 보내야 함
				instanceExtensions.push_back(extension.extensionName);
			}

			//MK: (코드 1- 4) 지원하는 Layer을 모두 Enable 함
			uint32_t layerCount = 0;
			vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
			std::vector<VkLayerProperties> layers(layerCount);
			vkEnumerateInstanceLayerProperties(&layerCount, layers.data());

			LOGI("MK: Instance Layer Count - %d", layerCount);
			std::vector<const char *> instanceLayers;
			for(const auto& layer : layers){
				LOGI("MK: Instance Layer - %s", layer.layerName);
				//MK: 필요 layer은 String으로 보내야함
				instanceLayers.push_back(layer.layerName);
			}

			//MK: (코드 1-2) Instance 정보 입력
			LOGI("MK: Create VkInstanceCreateInfo");
			VkInstanceCreateInfo createInfo = {};
			createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
			createInfo.pNext = NULL;
			createInfo.flags = 0;
			createInfo.pApplicationInfo = &appInfo;
			createInfo.enabledLayerCount = layerCount;
			createInfo.ppEnabledLayerNames = layerCount ? instanceLayers.data() : NULL;
			createInfo.enabledExtensionCount = extensionCount;
			createInfo.ppEnabledExtensionNames = extensionCount ? instanceExtensions.data() : NULL;

			//MK: (코드 1-5) Instance 생성
			VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
			
			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Instance (If not, should be able to see this print)");

		}

		VkInstance instance;
};

int sample_main(int argc, char *argv[]) {
	
	if(!InitVulkan()){
		LOGE("MK: Failed to Initialize Vulkan APIs");
		return EXIT_FAILURE;
	}
		
	mkTriangle mkApp;

	try{
		mkApp.run();
	} catch (const std::exception &e){
		std::cerr << e.what() << std::endl;
		LOGE("MK: Failed to Run Application");
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

Instance는 Vulkan Library를 로딩해서 Application과 연결하는 역할을 수행한다. Instance를 생성하기 위해서는 Application에 대한 정보를 작성해서 Instance 생성에 사용한다. Application 정보를 추가하는 과정은 꼭 필요한 과정이 아니다. 선택적으로 정보를 추가하지 않아도 전혀 문제 되지 않는다. 하지만 Application 정보를 제공함으로 드라이버가 특정 프로그램에 대한 Optimization 작업을 수행할 수 있다고 한다.

MK: 여기서 말하는 Optimization이 정확히 어떤 의미인지 모르겠다. Vulkan은 기존 Graphic API와 달리 드라이버가 하던 일을 프로그래머가 하도록 많은 권한을 프로그래머에게 넘긴 걸로 알고 있다. 그런데 갑자기 Optimization을 할 수 있다고 하니 정확히 어떤 의미인지 잘 모르겠다.

코드 1-1은 VkApplicationInfo에 Application 정보를 설정하는 코드이다. Vulkan API의 경우 Struct 구조를 많이 사용한다. C Struct 구조를 사용함으로 Function을 사용할 때 넘겨주는 인자(Parameter)의 개수를 줄였다. VkApplicationInfo는 아래와 같이 여러 가지 정보를 추가해줘야 한다.

VkApplicationInfo (출처 2)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type을 나타냄 (해당 Struct가 어떤 Type인지를 보여주기 위해서 사용함)
  • pNext (const void*): Extension Struct Pointer (해당 Tutorial에서는 대부분 NULL 값을 사용함)
  • pApplicationName (const char*): Application 이름
  • applicationVersion (uint32_t): Application 버전
  • pEngineName (const char*): Engine 이름 
  • engineVersion (uint32_t): Engine 버전 
  • apiVersion (uint32_t): Vulkan API 버전

아마 대부분은 이름으로 어떤 의미인지를 알 수 있다. 하지만 pNext는 정확히 어떤 역할을 하는지 잘 모르겠다. 출처 3의 Example 코드에서는 pNext 값이 NULL이 아닌 경우가 없다. 다음에 의미와 사용 방법을 알게 되면 추가로 글을 작성할 예정이다.

Instance 생성을 위해서 하나의 Struct을 더 만들어야 한다. 코드 1-2는 VkInstanceCreateInfo Struct을 생성하는 코드이다. VkInstanceCreateInfo의 경우 꼭 필요한 Struct 이다. 해당 Struct은 Global Extension을 사용할 것인지, 사용하면 어떤 Extension을 Enable 할 건지, 어떤 Validation Layer를 사용할 것인지 등의 정보를 Vulkan Driver에게 알려주기 위해서 작성한다. 여기서 Global의 의미는 해당 프로그램(Application) 전체를 의미한다. Extension, Layer를 찾는 방법은 아래 추가로 설명한다. VkInstanceCreateInfo Struct에 아래와 같이 여러 가지 정보를 추가해야 한다.

VkInstanceCreateInfo (출처 4)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type 작성
  • pNext (const void*): Extension Struct Pointer
  • flags (VkInstanceCreateFlags): 미래에 사용을 위해서 미리 만들어 두었다고 함 (Reserved for Future Use)
  • pApplicationInfo (const VkApplicationInfo*): Null 또는 앞에서 작성한 VkApplicationInfo 변수
  • enabledLayerCount (uint32_t): Enable하는 Layer 개
  • ppEnabledLayerNames (const char* const*): Enable하는 Layer 이름(String)을 Array로 작성
  • enabledExtensionCount (uint32_t): Enable하는 Global Extension 개수
  • ppEnabledExtensionNames (const char* const*): Enable하는 Global Extension 이름(String)을 Array로 작성

Vulkan의 경우 Agnostic API이다. 다양한 OS에서 사용 가능하다는 의미이다. 그래서 Window System 등과 통신을 하기 위해서는 보통 Extension을 사용한다. 정확히 필요한 Extension이 뭔지 몰라서 저의 경우 지원하는 모든 Extension을 Enable 하였다. 코드 1-3은 현재 기기에서 지원하는 모든 Extension을 추가하는 코드이다.

Layer이란 개념은 조금은 이해하기 어려운 것 같다. 출처 5에 따르면 Layer는 Vulkan 함수(Function)를 Intercept 하거나, Evaluate 하거나, Function을 수정하는 데 사용한다고 한다. Vulkan Layer를 사용하는 이유는 아래와 같다.

  • Vulkan API를 제대로 사용하고 있는지 확인하기 위해 사용 (Validation Layer를 의미하는 것 같음)
  • Vulkan API를 Tracing 하거나, Debugging 하기 위해서 사용
  • Application Surface에 특정 Overlay Content를 추가하기 위해서 사용

Validation Layer를 많이 사용한다. Validation Layer의 경우 Application 개발과정에서 Vulkan API를 제대로 사용하는지 확인하기 위해서 사용한다. Application 개발이 완료되면 Vulkan Validation Layer를 제거한 후에 Application을 Release 하면 된다. Validation Layer에 따른 Overhead가 제거됨으로 Application 성능 향상에 도움이 된다. 저의 경우 Extension과 동일하게 지원하는 모든 Layer를 포함하였다. 코드 1-4는 모든 Layer를 포함하는 코드이다.

위의 2개의 Struct을 모두 생성하고 나면 드디어 Instance를 생성할 차례이다. 코드 1-5는 Instance를 생성하는 코드이다. vkCreateInstace는 총 3개의 Parameter를 가진다. 

VkResult vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) (출처 6)

  • VkInstanceCreateInfo: 앞에서 생성한 VkInstanceCreateInfo Struct 포인터 변수
  • VkAllocationCallbacks: Pointer to custom allocator callbacks
  • VkInstance: Instance Object를 저장하기 위한 변수 포인터

“Custom Allocator Callbacks”의 정확한 의미와 사용 방법은 모르겠다. 역시나 출처 3의 Example 코드에서는 NULL 값을 사용하였다. 코드 1-5 코드를 실행하고 정상적으로 Instance 생성되면 “VK_SUCCESS” 값을 Return 한다. Instance는 프로그램이 종료하기 전에 Destory 해야 한다. 코드 1-6은 생성한 Instance를 제거하는 코드이다.

void vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator) (출처 7)

  • VkInstance: Destroy할 Instance Object 변수
  • VkAllocationCallbacks: Pointer to custom allocator callbacks
그림 1: Instance 실행 결과 (Logcat 결과)

코드를 실행해서 빌드하면 역시나 검은색 화면만 나온다. 그림 1은 Logcat에서 출력되는 값을 보여준다. 해당 단말에서 지원하는 Extension은 총 10개이면 지원하는 Extension을 String으로 출력한다. 현재 지원하는 Layer는 없다. 마지막으로 Instance는 정상적으로 생성되었다는 정보가 출력된다. 출처 8에서 Instance 생성 코드를 확인 할 수 있다.


출처

  1. https://vulkan-tutorial.com/
  2. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkApplicationInfo.html
  3. https://developer.android.com/ndk/guides/graphics/getting-started
  4. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkInstanceCreateInfo.html
  5. https://vulkan.lunarg.com/doc/sdk/1.0.61.1/windows/LoaderAndLayerInterface.html
  6. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateInstance.html
  7. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkDestroyInstance.html
  8. https://github.com/mkblog-cokr/androidVulkanTutorial

Leave a Comment