본문 바로가기

Vulkan

Setup - Physical devices and queue families

이전 글 : Validation layers
다음 글 : Logical device and queue

Physical devices and queue families

물리적 장치 및 대기열 패밀리

Selecting a hysical device

물리적 장치 선택


VkInstance 를 통해 Vulkan 라이브러리를 초기화 한 후에는 시스템에서 필요한 기능을 지원하는 그래픽 카드를 찾아 선택해야 합니다. 실제로 여러 개의 그래픽 카드를 선택하여 동시에 사용할 수 있지만 이 튜토리얼에서는 우리가 필요로하는 첫 번째 그래픽 카드를 사용 할 것입니다.


pickPhysicalDevice 함수를 추가하고 initVulkan 함수에서 이 함수에 대한 호출을 추가 합니다.


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

void pickPhysicalDevice() {

}


선택된 그래픽 카드는 새로운 클래스 멤버로 추가 된 VkPhysicalDevice 핸들에 저장됩니다. 이 개체는 VkInstance 가 제거 될 때 암묵적으로 삭제 되므로 정리 기능에서 새로운 작업을 수행 할 필요가 없습니다.


VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;


그래픽 카드를 나열하는 것은 확장 기능을 나열하는 것과 매우 유사하며 단지 번호만 쿼리하는 것으로 시작됩니다.


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


Vulkan을 지원하는 장치가 0 개 이면 지원하는 장치가 없다는 것 입니다.


if (deviceCount == 0) {
   throw std::runtime_error("failed to find GPUs with Vulkan support!");
}

그렇지 않으면 모든 VkPhysicalDevice 핸들을 보유 할 배열을 할당 할 수 있습니다.


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


이제 우리는 각각의 그래픽 카드가 모두 똑같은 조건과 성능으로 만들어 지는 것이 아니기 때문에, 각각의 그래픽 카드를 평가하고 우리가 수행하고자 하는 작업에 적합한지 확인해야 합니다. 이를 위해 우리는 새로운 기능을 소개 할 것입니다 :


bool isDeviceSuitable(VkPhysicalDevice device) {
   return true;
}


그리고 물리적 장치 중 어떤 것이 요구되는 기능 구현 사항을 충족시키는 지 확인합니다.

for (const auto& device : devices) {
   if (isDeviceSuitable(device)) {
       physicalDevice = device;
       break;
   }
}

if (physicalDevice == VK_NULL_HANDLE) {
   throw std::runtime_error("failed to find a suitable GPU!");
}


다음 섹션에서는 isDeviceSuitable 함수에서 확인할 첫 번째 요구 사항을 소개합니다. 이후 장에서 더 많은 Vulkan 기능을 사용하기 시작할 것이므로 더 많은 검사를 포함 하도록 이 기능을 확장 할 것입니다.




Base device suitability checks

기본 장치 적합성 검사


장치의 적합성을 평가하기 위해 몇 가지 세부 정보를 쿼리하여 시작할 수 있습니다. 이름, 유형 및 지원되는 Vulkan 버전과 같은 기본 장치 속성은 vkGetPhysicalDeviceProperties 를 사용하여 쿼리 할 수 있습니다.


VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);


vkGetPhysicalDeviceFeatures 를 사용하여 텍스처 압축, 64 비트 부동 소수점 및 다중 뷰포트 렌더링 (VR에 유용)과 같은 선택적 기능에 대한 지원을 쿼리 할 수 있습니다.

VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);



디바이스 메모리와 큐 (queue) 패밀리에 관해 나중에 논의 할 디바이스들로부터 질의 할 수있는 더 많은 세부 사항들이 있습니다 (다음 섹션 참조).


예를 들어 지오메트리 셰이더를 지원하는 전용 그래픽 카드에서만 응용 프로그램을 사용할 수 있다고 가정 해 봅시다.  isDeviceSuitable 함수는 다음과 같습니다.

bool isDeviceSuitable(VkPhysicalDevice device) {
   VkPhysicalDeviceProperties deviceProperties;
   VkPhysicalDeviceFeatures deviceFeatures;
   vkGetPhysicalDeviceProperties(device, &deviceProperties);
   vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

   return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
          deviceFeatures.geometryShader;
}


장치가 적합한 지 여부를 확인하는 대신 첫 번째 장치와 함께 사용하면 각 장치에 점수를 주고 가장 높은 것을 선택할 수 있습니다. 그렇게 하면 더 높은 점수를 부여하여 전용 그래픽 카드를 선호 할 수 있지만 사용 가능한 유일한 경우 통합 GPU로 폴백 합니다. 다음과 같이 구현할 수 있습니다.


#include <map>

...

void pickPhysicalDevice() {
   ...

   // Use an ordered map to automatically sort candidates by increasing score
   std::multimap<int, VkPhysicalDevice> candidates;

   for (const auto& device : devices) {
       int score = rateDeviceSuitability(device);
       candidates.insert(std::make_pair(score, device));
   }

   // Check if the best candidate is suitable at all
   if (candidates.rbegin()->first > 0) {
       physicalDevice = candidates.rbegin()->second;
   } else {
       throw std::runtime_error("failed to find a suitable GPU!");
   }
}

int rateDeviceSuitability(VkPhysicalDevice device) {
   ...

   int score = 0;

   // Discrete GPUs have a significant performance advantage
   if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
       score += 1000;
   }

   // Maximum possible size of textures affects graphics quality
   score += deviceProperties.limits.maxImageDimension2D;

   // Application can't function without geometry shaders
   if (!deviceFeatures.geometryShader) {
       return 0;
   }

   return score;
}


이 자습서 에서는 이 모든 것을 구현할 필요는 없지만 장치 선택 프로세스를 어떻게 설계 할 수 있는지에 대한 아이디어를 제공합니다. 물론 선택 항목의 이름을 표시하고 사용자가 선택하도록 허용 할 수도 있습니다.


우리가 막 시작했기 때문에 Vulkan 지원이 우리가 필요로 하는 유일한 것이므로 아마도 지금의 모든 GPU에 만족해야 할 것입니다.


bool isDeviceSuitable(VkPhysicalDevice device) {
   return true;
}


다음 섹션에서는 확인할 수 있는 첫 번째 필수 기능에 대해 설명합니다.





Queue families

대기열 패밀리(대기열 그룹으로 해석하는것도 좋습니다)


Vulkan의 거의 모든 작업들,즉 드로잉에서 텍스처 업로드에 이르기 까지의 명령이 대기열에 제출 되어야 한다는 사실은 앞서 간략히 설명 하였습니다. 다른 대기열 패밀리에서 비롯된 대기열에는 여러 유형이 있으며 각 대기열에는 명령의 하위 집합 만 허용됩니다. 예를 들어 계산 명령만 처리 할 수 있는 대기열 패밀리 또는 메모리 전송 관련 명령만 허용하는 대기열 패밀리가 있을 수 있습니다.


어떤 대기열 패밀리가 장치에 의해 지원되는지, 그리고 어떤 대기열 패밀리가 우리가 사용하고자 하는 명령을 지원하는지 확인해야 합니다. 그 목적을 위해 필요한 모든 대기열 패밀리를 찾는 새로운 함수 findQueueFamilies 를 추가 할 것입니다. 지금은 그래픽 명령을 지원하는 대기열 만 살펴 보겠습니다. 그러나 이 기능을 확장하여 나중에 더 많은 것을 찾을 수 있습니다.


이 함수는 원하는 특정 속성을 만족하는 대기열 패밀리의 색인을 반환합니다. 이를 수행하는 가장 좋은 방법은 구조체를 사용하는 것입니다. 여기서 인덱스 -1은 "찾을 수 없음"을 나타냅니다.

struct QueueFamilyIndices {
   int graphicsFamily = -1;

   bool isComplete() {
       return graphicsFamily >= 0;
   }
};


이제 findQueueFamilies 함수를 구현할 수 있습니다.

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
   QueueFamilyIndices indices;

   ...

   return indices;
}


대기열 패밀리 목록을 검색하는 과정은 예상대로이며 vkGetPhysicalDeviceQueueFamilyProperties 를 사용합니다.


uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());


VkQueueFamilyProperties 구조체에는 지원되는 작업 유형과 해당 패밀리를 기반으로 생성 될 수 있는 대기열 수를 포함하여 대기열 패밀리에 대한 몇 가지 세부 정보가 들어 있습니다. VK_QUEUE_GRAPHICS_BIT 를 지원하는 대기열 패밀리를 하나 이상 찾아야 합니다.


int i = 0;
for (const auto& queueFamily : queueFamilies) {
   if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
       indices.graphicsFamily = i;
   }

   if (indices.isComplete()) {
       break;
   }

   i++;
}


이 멋진 대기열 패밀리 찾기 기능이 있으므로 isDeviceSuitable 함수의 체크기능으로 사용하여 디바이스가 사용하려는 명령을 처리 할 수 있도록 합니다.

bool isDeviceSuitable(VkPhysicalDevice device) {
   QueueFamilyIndices indices = findQueueFamilies(device);

   return indices.isComplete();
}


잘하셨습니다, 그게 바로 우리가 올바른 물리적 장치를 찾는 데 필요한 전부입니다!


다음 단계는 인터페이스와 연결할 논리적 장치를 만드는 것입니다.


작성된 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);
   }
}

struct QueueFamilyIndices {
   int graphicsFamily = -1;

   bool isComplete() {
       return graphicsFamily >= 0;
   }
};

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

private:
   GLFWwindow* window;

   VkInstance instance;
   VkDebugReportCallbackEXT callback;

   VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

   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();
       pickPhysicalDevice();
   }

   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!");
       }
   }

   void pickPhysicalDevice() {
       uint32_t deviceCount = 0;
       vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

       if (deviceCount == 0) {
           throw std::runtime_error("failed to find GPUs with Vulkan support!");
       }

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

       for (const auto& device : devices) {
           if (isDeviceSuitable(device)) {
               physicalDevice = device;
               break;
           }
       }

       if (physicalDevice == VK_NULL_HANDLE) {
           throw std::runtime_error("failed to find a suitable GPU!");
       }
   }

   bool isDeviceSuitable(VkPhysicalDevice device) {
       QueueFamilyIndices indices = findQueueFamilies(device);

       return indices.isComplete();
   }

   QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
       QueueFamilyIndices indices;

       uint32_t queueFamilyCount = 0;
       vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

       std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
       vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

       int i = 0;
       for (const auto& queueFamily : queueFamilies) {
           if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
               indices.graphicsFamily = i;
           }

           if (indices.isComplete()) {
               break;
           }

           i++;
       }

       return indices;
   }

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


이전 글 : Validation layers
다음 글 : Logical device and queue



'Vulkan' 카테고리의 다른 글

Presentation - Window surface  (0) 2018.01.23
Setup - Logical device and queue  (0) 2018.01.23
Setup - Validation layers  (0) 2018.01.21
Setup - Creating an instance  (0) 2018.01.20
Setup - Base code  (0) 2018.01.20