[Vulkan Tutorial] 07-Select Physical Device

목차: 01-Overview (Link)
이전 글: 06-Create Window Surface (Link)
다음 글: 08-Search and Select Queue Families (Link)


이번 장에서는 Physical GPU Device(간단히 GPU라고 할 예정)를 선택하는 코드를 작성할 차례이다. Desktop과 같이 여러 개의 GPU를 가질 수 있는 기기에서 GPU를 선택하는 과정은 다소 복잡한 과정이 필요하다. 예를 들어서 GPU가 여러 개가 있는 경우 특정 Extension을 지원하는 GPU를 선택할 수도 있고, 성능이 더 우수하다고 판단되는 GPU를 선택 할 수도 있다. 이런 경우 GPU의 Extension 및 이름 등을 파악해서 GPU를 선택해야 한다. 또한, 여러 개의 GPU를 동시에 사용해도 된다. 하지만, Android(안드로이드) 단말의 경우 1개의 GPU만 내장되어 있고 만약 해당 GPU가 Vulkan API를 지원하지 않는 경우 Instance조차 생성이 안 되도록 코드를 작성하였다. 그래서 사실상 GPU 선택하는 코드만 간단히 작성하면 된다.

MK: 개인적으로 GPU가 2개 이상 있는 Android 단말을 아직(2020년 2월 기준) 본 적이 없어서 위와 같이 설명을 하였다. 혹시 여러 개의 GPU가 탑재된 Android 단말의 경우 앞에서 설명한 것처럼 GPU를 선택하는 코드를 추가로 작성해야 한다.

아래 코드 1은 GPU를 선택하는 코드이다. 

코드 1: Physical GPU 선택 코드

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

#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();
			//MK: Physical Device를 선택하는 함수 호출
			pickPhysicalDevice();
		}

		void mainLoop(){
		}

		void cleanup(){
			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)");
		}

		//MK: (코드 1-2) Physical Device를 선택하는 함수
		void pickPhysicalDevice(){
			LOGI("MK: pickPhysicalDevice Function");

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

			LOGI("MK: Physical Device Count - %d", deviceCount);
	
			//MK: 안드로이드 단말의 경우 보통(?) 1개의 GPU만 가지고 있음
			//MK: (코드 1-3) Physical Device 정보를 가져오는 코드
			std::vector<VkPhysicalDevice> devices(deviceCount);
			vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

			//MK: (코드 1-4) Query 한 단말 정보를 출력하기 위한 코드
			for(const auto &device : devices){
				printPhysicalDeviceInfo(device);
			}

			//MK: 단말에 GPU가 1개 밖에 없다는 가정으로 제일 처음 GPU를 선택함
			physicalDevice = devices[0];

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

		//MK: (코드 1-5) GPU 정보를 간단하게 출력하는 함수
		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);
		}

		VkInstance instance;

		VkSurfaceKHR surface;
   		PFN_vkCreateAndroidSurfaceKHR fpCreateAndroidSurfaceKHR;

   		//MK: (코드 1-1) Physical Device 변수 추가
   		VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
};

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처럼 GPU를 선택하기 위한 변수를 한 개 추가한다. 다음은 코드 1-2와 같이 GPU를 선택하는 함수를 하나 추가한다. 해당 함수는 initVulkan(…) 함수에서 createSurface(…) 함수 다음에 호출하면 된다.

코드 1-3은 GPU를 선택하기 위한 코드이다. 사실 크게 설명할 코드가 없다. 먼저 GPU가 몇 개인지 확인한다. GPU가 1개 밖에 없다는 사실을 알고 있지만, 그냥 확인해보았다. 코드 1-4, 코드 1-5는 GPU의 이름 및 지원하는 몇 가지 기능을 간단히 출력해주는 코드이다. 만약 GPU가 여러 개 있는 경우 코드 1-5 함수를 수정해서 원하는 GPU를 선택하는 부분을 추가하면 된다. 아래 정보는 GPU Device를 선택하는 vkEnumeratePhysicalDevices (…) 함수 설명이다.

vkEnumeratePhysicalDevices (…) (출처 2)

  • instance (VkInstance): Instance 변수
  • pPhysicalDeviceCount (uint32_t *): 지원하는 GPU 개수를 저장하는 변수
  • pPhysicalDevices (VkPhysicalDevice *): VkPhysicalDevice Array 변수 (VkPhysicalDevice는 GPU 정보를 저장하는 변수로 파악됨 (출처 3 참조))

아래 그림 1은 코드 1을 빌드해서 실행하면 출력되는 Logcat 결과이다. 여전히 빌드한 App를 실행하면 검은색 화면만 출력된다. 그림 1의 빨간색 부분이 실험을 진행하고 있는 단말의 GPU 정보를 보여준다. 위 코드는 아래 출처 4에서 확인 할 수 있다.

그림 1: Logcat 결과

MK: 출처 1에서 Physical GPU Device를 선택하는 설명에서 QueueFamily에 대한 설명을 같이한다. 저의 경우 QueueFamily 부분 설명을 다음에 따로 진행할 예정이다.


출처

  1. https://vulkan-tutorial.com/
  2. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkEnumeratePhysicalDevices.html
  3. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDevice.html
  4. https://github.com/mkblog-cokr/androidVulkanTutorial

Leave a Comment