[Vulkan Tutorial] 09-Create Logical Device

목차: 01-Overview (Link)
이전 글: 08-Search and Select Queue Families (Link)
다음 글: 10-Create Swap Chain (Link)


Create Logical Device

Physical Device를 선택하고, Queue Family를 선택하였으면 다음은 Logical Device를 생성할 차례이다. Logical Device는 Physical Device와 인터페이스(Interface, 통신의 의미로 사용하면 될 것 같음)를 하기 위해서 사용한다. Logical Device 생성 과정은 Instance 생성 과정과 비슷하다. 하나의 Physical Device를 사용해서 여러 개의 Logical Device를 생성할 수 있다. 여러 개의 Logical Device는 동일한 Physical Device를 사용해서 Feature, Extension을 달리할 수 있다.

아래 코드 1은 Logical Device를 생성하는 코드를 추가한 전체 코드이다. 

코드 1: Logical Device 생성 코드

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

#define APP_SHORT_NAME "mkVulkanExample"

#define MK_GET_INSTANCE_PROC_ADDR(inst, entrypoint)                               			\
{                                                                          					\
	fp##entrypoint = (PFN_vk##entrypoint)vkGetInstanceProcAddr(instance, "vk" #entrypoint); \
	if (fp##entrypoint == NULL) {                                     						\
		std::cout << "vkGetDeviceProcAddr failed to find vk" #entrypoint;  					\
		exit(-1);                                                          					\
	}                                                                      					\
}

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

	private:
		void initVulkan(){
			createInstance();
			createSurface();
			pickPhysicalDevice();
			searchQueue();
			//MK: (코드 1-2) Create Logical Device 함수 호출
			createLogicalDevice();
		}

		void mainLoop(){
		}

		void cleanup(){
			//MK: (코드 1- 9) Logical Deivce Destory 함수 호출
			vkDestroyDevice(device, nullptr);
			vkDestroySurfaceKHR(instance, surface, NULL);
			vkDestroyInstance(instance, NULL);
		}

		void createInstance(){

			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;

			uint32_t extensionCount = 0;
			vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
			std::vector<VkExtensionProperties> extensions(extensionCount);
			vkEnumerateInstanceExtensionProperties(NULL, &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);
			}

			uint32_t layerCount = 0;
			vkEnumerateInstanceLayerProperties(&layerCount, NULL);
			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);
			}

			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;

			VkResult result = vkCreateInstance(&createInfo, NULL, &instance);
			
			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Instance (If not, should not see this print)");

		}

		void createSurface(){
			
			LOGI("MK: createSurface Function");
			VkAndroidSurfaceCreateInfoKHR createInfo = {};
			createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
			createInfo.pNext = NULL;
			createInfo.flags = 0;
			createInfo.window = AndroidGetApplicationWindow();

			MK_GET_INSTANCE_PROC_ADDR(instance, CreateAndroidSurfaceKHR);
			
			VkResult result = fpCreateAndroidSurfaceKHR(instance, &createInfo, NULL, &surface);

			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Surface (If not, should not see this print)");
		}

		void pickPhysicalDevice(){
			LOGI("MK: pickPhysicalDevice Function");

			uint32_t deviceCount = 0;
			vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
			assert(deviceCount != 0);

			LOGI("MK: Physical Device Count - %d", deviceCount);

			std::vector<VkPhysicalDevice> devices(deviceCount);
			vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

			for(const auto &device : devices){
				printPhysicalDeviceInfo(device);
			}

			physicalDevice = devices[0];

			assert(physicalDevice != VK_NULL_HANDLE);
			LOGI("MK: Successfully Select Physical Device (If not, should not see this print)");
		}

		void printPhysicalDeviceInfo(VkPhysicalDevice device){
			VkPhysicalDeviceProperties deviceProperties;
			VkPhysicalDeviceFeatures deviceFeatures;
			vkGetPhysicalDeviceProperties(device, &deviceProperties);
			vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

			LOGI("MK: Physical Device Name - %s", deviceProperties.deviceName);
			LOGI("MK: Physical Device - geometryShader (%d)", deviceFeatures.geometryShader);
			LOGI("MK: Physical Device - shaderInt64 (%d)", deviceFeatures.shaderInt64);
		}

		void searchQueue(){
			LOGI("MK: searchQueue Function");

			uint32_t queueCount = 0;
			vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, nullptr);
			assert(queueCount > 0);
			std::vector<VkQueueFamilyProperties> queueFamilies(queueCount);
			vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueFamilies.data());

			LOGI("MK: Queue Family Count - %d", queueCount);

			bool graphicsFound = false;
			bool presentFound = false;
			for(int i = 0; i < queueCount; i++){
				LOGI("MK: %d QueueFamily has a total of %d", i, queueFamilies[i].queueCount);
				if(queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT){
					graphicQueueFamilyIndex = i;
					graphicsFound = true;
				}

				VkBool32 presentSupport = false;
				vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &presentSupport);

				if(presentSupport){
					presentQueueFamilyIndex = i;
					presentFound = true;
				}

				if(presentFound && graphicsFound){
					break;
				}
			}
			assert(graphicsFound);
			assert(presentFound);

			LOGI("MK: Found Graphic Queue Family = %d", graphicQueueFamilyIndex);
			LOGI("MK: Found Queue Family with Present Support = %d", presentQueueFamilyIndex);

			LOGI("MK: Found Graphics Queue with Present Support (If not, should not see this print)");
		}

		//MK: (코드 1-1) Logical Device를 생성하기 위한 코드
		void createLogicalDevice(){
			
			//MK: (코드 1-4) Unique Queue 번호를 찾음
			std::set<uint32_t> uniqueQueueFamilies = {graphicQueueFamilyIndex, presentQueueFamilyIndex};

			std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;

			//MK: (코드 1-5) queueCreateInfo Struct 값 입력. Qeuue Family가 여러개의 경우 여러개의 queueCreateInfo를 생성함
			float queuePriority = 1.0f;
			for(uint32_t queueFamily : uniqueQueueFamilies){
				VkDeviceQueueCreateInfo queueCreateInfo = {};
				queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
				queueCreateInfo.pNext = NULL;
				queueCreateInfo.flags = 0;
				queueCreateInfo.queueFamilyIndex = queueFamily;
				queueCreateInfo.queueCount = 1;
				queueCreateInfo.pQueuePriorities = &queuePriority;
				queueCreateInfos.push_back(queueCreateInfo);
			}

			//MK: (코드 1-6) 필요한 Device Feature, Extension을 추가함
			VkPhysicalDeviceFeatures deviceFeatures = {};
			std::vector<const char *> deviceExtensions;
			deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);

			//MK: (코드 1-7) VkDeviceCreateInfo Struct에 필요한 값을 입력함
			VkDeviceCreateInfo createInfo = {};
			createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
			createInfo.pNext = NULL;
			createInfo.flags = 0;
			createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
			createInfo.pQueueCreateInfos = queueCreateInfos.data();
			//createInfo.enabledLayerCount
			//createInfo.ppEnabledLayerNames
			createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
			createInfo.ppEnabledExtensionNames = deviceExtensions.data();
			createInfo.pEnabledFeatures = &deviceFeatures;

			//MK: (코드 1-8) Logical Device를 생성함
			VkResult result = vkCreateDevice(physicalDevice, &createInfo, NULL, &device);

			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Logical Device (If not, should not see this print)");

			//MK: (코드 1-11) 생성된 Queue를 저장하기 위한 코드
			vkGetDeviceQueue(device, graphicQueueFamilyIndex, 0, &graphicsQueue);
			vkGetDeviceQueue(device, presentQueueFamilyIndex, 0, &presentQueue);
		}

		VkInstance instance;

		VkSurfaceKHR surface;
		PFN_vkCreateAndroidSurfaceKHR fpCreateAndroidSurfaceKHR;

		VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

		uint32_t graphicQueueFamilyIndex = 0;
		uint32_t presentQueueFamilyIndex = 0;

		//MK: (코드 1-3) Logical Device 변수 추가
		VkDevice device; 

		//MK: (코드 1-10) Queue를 저장하기 위한 변수 추가
		VkQueue graphicsQueue;
		VkQueue presentQueue;
};

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;
}

먼저 코드 1-1과 같이 createLogicalDevice(…) 함수를 하나 생성한다. 그리고 해당 함수는 코드 1-2 initVulkan(…) 함수에서 호출한다. 추가로 Logical Device 변수를 코드 1-3과 같이 추가한다.

Logical Device 생성하기 위해서 가장 먼저 VkDeviceQueueCreateInfo 에 정보 값을 입력한다. VkDeviceQueueCreateInfo는 우리가 몇 개의 Queue를 사용할 것인지를 정의하기 위해서 사용한다. VkDeviceQueueCreateInfo에 필요한 정보 값을 입력하기 이전에 코드 1-4와 같이 총 필요한 Queue Family를 찾는다. Standard (STD) 라이브러리의 Set 함수를 사용해서 Unique 한 Queue Family Index를 찾는다. 이렇게 필요한 총 Queue Family 개수를 찾으면 그 개수 만큼 VkDeviceQueueCreateInfo를 생성한다. 코드 1-5는 여러 개의 VkDeviceQueueCreateInfo Struct 을 생성하는 코드이다. 아래는 VkDeviceQueueCreateInfo Struct가 포함하고 있는 변수에 대한 설명이다.

VkDeviceQueueCreateInfo Struct (출처 2)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type을 나타냄
  • pNext (const void*): Extension Struct Pointer
  • flags (VkDeviceQueueCreateFlags): 미래에 사용하기 위해서 미리 만들어 두었다고 함
  • queueFamilyIndex (uint32_t): Physical Device에서의 Queue Family Index (실제 사용할 Queue Family Index)
  • queueCount (uint32_t): Queue Family에서 사용할 Queue 개수
  • pQueuePriorities (const float*): Queue의 Priority (Floating Number 사용, Specifying priorities of work that will be submitted to each created queue. See Queue Priority for more information)

다음은 Device Feature와 Extension을 선택해야 한다. 코드 1-6은 Device Feature와 Extension을 추가하는 코드이다. 현시점에 필요한 Feature는 없는 관계로 비워두고 Extension에 VK_KHR_swapchain (VK_KHR_SWAPCHAIN_EXTENSION_NAME 와 동일함)을 하나 추가하였다. 해당 Extension은 GPU에서 Rendering 한 이미지를 Window에 보여주기 위해서 사용한다.

이제 Logical Device를 생성하기 위해서 VkDeviceCreateInfo Struct을 생성해야 한다. 코드 1-7은 VkDeviceCreateInfo Struct에 필요한 값을 작성하는 코드이다. VkDeviceCreateInfo Struct은 아래와 같이 총 10개의 변수를 가진다. 

VkDeviceCreateInfo Struct (출처 3)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type을 나타냄
  • pNext (const void*): Extension Struct Pointer
  • flags (VkDeviceCreateFlags): 미래에 사용하기 위해서 미리 만들어 두었다고 함
  • queueCreateInfoCount (uint32_t): 아래 pQueueCreateInfos에 사용할 Array 개수
  • pQueueCreateInfos (const VkDeviceQueueCreateInfo*): VkDeviceQueueCreateInfo Array (Queue Creation 에서 생성한 Queue Family 개수를 의미함)
  • enabledLayerCount (uint32_t): Deprecated (더 이상 사용하지 않음)
  • ppEnabledLayerNames (const char* const*): Deprecated (더 이상 사용하지 않음)
  • enabledExtensionCount (uint32_t): Enable할 Device Extension 개수
  • ppEnabledExtensionNames (const char* const*): Enable할 Device Extension Array (String으로 저장해야 함)
  • pEnabledFeatures (const VkPhysicalDeviceFeatures*): 사용(필요한)할 Feature을 Enable할 VkPhysicalDeviceFeatures Struct 포인터 (필요 없는 경우 NULL 값을 사용)

VkDeviceCreateInfo Struct에 필요한 변수값을 저장하면 vkCreateDevice(…) 함수를 사용해서 Logical Device를 생성한다. 코드 1-8은 vkCreateDevice(…) 함수를 사용해서 Logical Device를 생성하는 코드이다. vkCreateDevice (…) 함수는 아래와 같이 4개의 Parameter(인자)를 가진다. 

vkCreateDevice(…) (출처 4)

  • physicalDevice (VkPhysicalDevice): vkEnumeratePhysicalDevices(…)함수를 사용해서 전달받은 Physical Device (physicalDevice) Handler
  • pCreateInfo (const VkDeviceCreateInfo*): VkDeviceCreateInfo Struct 변수
  • pAllocator (const VkAllocationCallbacks*):  Pointer to custom allocator callbacks
  • pDevice (VkDevice*): Logical Device Pointer (생성된 Logical Device 저장할 공간을 의미함)

생성한 Logical Device는 제거하는 코드도 추가해야 한다. 코드 1-9는 생성된 Logical Device를 제거하는 코드이다. 여기까지 코드를 작성하고 컴파일을 하면 Logical Device 생성이 완료된다. Logical Device가 생성되고 나면 Queue가 같이 생성된다. 해당 Queue와 Interface 하기 위한 코드를 추가해야 한다.


Retrieving Queue Handles

위 과정까지 코드를 작성하면 Logical Device 생성에 필요한 모든 코드 작성이 완료되었다. Logical Device 생성을 하면 Queue가 자동으로 생성된다고 한다. Queue가 생성은 되지만 Interface 하기 위한 변수를 기록해두어야 한다.

코드 1-10은 Queue Handle을 위한 변수를 추가한 부분이다. 본 예제에서는 Present와 Graphics Queue 2가지 변수를 생성한다. 코드 1-11은 vkGetDeviceQueue(…) 함수를 사용해서 생성된 Queue를 찾기 위한 코드이다. vkGetDeviceQueue(…) 함수는 아래와 같이 4개의 Parameter(인자)를 가진다.

vkGetDeviceQueue(…) (출처 5)

  • device (VkDevice): Logical Device 변수 (앞에서 생성한 Logical Device)
  • queueFamilyIndex (uint32_t): Queue Family Index 
  • queueIndex (uint32_t): Queue Family 내부의 Queue Index 
  • pQueue (VkQueue*): 요청한 Queue handle의 위한 VkQueue Object 변수
그림 1: Logical Device 생성 코드 실행 결과

코드 1을 빌드해서 실행하면 여전히 검은색 화면만 출력된다. 그림 1은 해당 빌드를 실행하면 출력되는 Logcat 결과이다. 크게 달라진 부분이 없으며 Logical Deivce가 성공적으로 생성되었다는 로그만 출력된다. 코드 1은 출처 6에서 확인할 수 있다.


 출처

  1. https://vulkan-tutorial.com/
  2. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkDeviceQueueCreateInfo.html
  3. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkDeviceCreateInfo.html
  4. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateDevice.html
  5. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetDeviceQueue.html
  6. https://github.com/mkblog-cokr/androidVulkanTutorial

Leave a Comment