목차: 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에서 확인 할 수 있다.

MK: 출처 1에서 Physical GPU Device를 선택하는 설명에서 QueueFamily에 대한 설명을 같이한다. 저의 경우 QueueFamily 부분 설명을 다음에 따로 진행할 예정이다.
출처
- https://vulkan-tutorial.com/
- https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkEnumeratePhysicalDevices.html
- https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDevice.html
- https://github.com/mkblog-cokr/androidVulkanTutorial