[vk] Drawing triangle - setup - Physical devices and queue

2021. 8. 6. 00:05그래픽스/vk

1. Selecting a physical device

- vulkan 라이브러리를 통해 초기화한 후 VkInstance 시스템에서

  필요한 기능을 지원하는 그래픽카드를 찾아 선택해야함.

- 실제로 그래픽 카드를 원하는 수 만큼 선택하여 동시에 사용가능(튜토리얼에선 필요에 맞는 하나 선택)

 

 

2. VKPhysicalDevice

VkPhysicalDevice : 최종적으로 선택하는 그래픽카드를 핸들하는 멤버변수 타입

=> 암시적으로 소멸된다, 따라서 cleanup 함수로 따로 delete 안해줘도 됨.

VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

pickPhysicalDevice

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    pickPhysicalDevice();
}
    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!");
        }
    }

그래픽 카드를 나열하는것은 확장을 나열하는것과 매우 유사하다.

1) 지원하는 장치의 개수를 파악 => 만약 장치가 0개라면 의미가 없음

2) 모든  VkPhysicalDevice 핸들에 할당하기위한 배열의 크기를 할당, 데이터 할당

3) 각 그래픽 카드를 평가

4) 모든 그래픽 카드가 동일하게 생성되지 않기 때문에 수행하려는 작업에 적합한지 확인

   => 위 함수를 보면 맨마지막에 적합한것을 선택하는것을 볼 수 있음

 

isDeviceSuitable => 적합성 판단하는 함수를 따로 만들어야함.

- 이후 장에서 더 많은 기능을 사용하기 시작할 것이므로 더 많은 검사를 포함하도록 이기능을 확장할것임.

 

 

 

 

3. Base device suitability checks

3.1 VkPhysicalDeviceProperties

vkGetPhysicalDeviceProperties

- 장치의 적합성을 평가하기 위해 먼저 몇가지 세부사항을 쿼리할 수 있다.

- 이름, 타입이 지원되는 Vulkan 버전과 같은 기본장치 속성을 사용하여 쿼리할 수 있음.

VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);

 

3.2 VKPhysicalDeviceFeatures

vkGetPhysicalDeviceFeatures

- 텍스처 압축, 64 비트 부동 소수점 및 다중 뷰포트 렌더링 (VR에 유용)과 같은 선택적 기능에 대한 지원을 쿼리

VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

이 두가지 이외도, 장치에 관한 memory 그리고 queue families 를 쿼리할 수 있다. (다음 섹션)

 

 

 

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

- 장치가 적합한지 여부를 확인하고

- 첫번째 장치를 사용하는 대신

- 각 장치에 점수를 부여하고 가장 높은 장치를 선택할 수 있음

- integrated GPU 를 사용할 수 도있음 (기능이 사용가능한게 나눠져있는경우)

That way you could favor a dedicated graphics card by giving it a higher score,

but fall back to an integrated GPU if that's the only available one.

 

구현은 다음과 같다.

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

You don't need to implement all that for this tutorial, but it's to give you an idea of how you could design your device selection process.

 

우리는 오직 Vulkan 지원이되면 끝이다. 그러므로 다음과 같은 함수를 사용해도 튜토리얼은 문제없다.

(아래에서 graphic 을 지원하는 queue를 찾아야하긴 한다.)

bool isDeviceSuitable(VkPhysicalDevice device) {
    return true;
}

 

 

 

 

4. Queue families

- 그리기에서 텍스처 업로드에 이르기 까지, Vulkan의 거의 모든 작업을 수행하려면 , queue에 명령을 제출해야한다.

- 서로 다른 queue families에서 시작하는 다른 타입의 queue들이 있으며, 각 families는 subset of commands만 허용

- ex) (compute commands / memory transfer) 의 처리만 허용하는 queue family

- 우리가사용하기원하는 명령어를 지원하고, 장치가 지원하는 queue families 가 무엇인지 알아야함.

- findQueueFamilies를 사용하여, 모든 queue families를 찾는 새로운 함수를 추가할것

- 지금은 그래픽 명령을 지원하는 queue만 찾을 것.

- 또 다른 queues을 찾을 것이므로, 이에 대비하고 인덱스를 구조체로 묶는것이 좋다.

struct QueueFamilyIndices {
    uint32_t graphicsFamily;
};

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;
    // Logic to find queue family indices to populate struct with
    return indices;
}

 

queue family is not available?

-> 한가지 방법 : throw an exception in findQueueFamilies

-> 하지만 이 함수는 실제로 장치 적합성에 대한 결정을 내리는 데 적합한 위치가 아님.

-> ex) dedicated transfer queue family 를 선호할 수 있지만 필수는 아님,

-> 그러므로 특정 queue family가 발견되었는지 여부를 나타내는 방법이 필요

 

uint32_t

- a queue family 의 존재 가능성에 대해 정확히 판단하지못함

- 인덱스가 0인 queue family가 존재할 수 있기 때문.

#include <optional>

...

std::optional<uint32_t> graphicsFamily;

std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // false

graphicsFamily = 0;

std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true

std::optional

- C++17에는 값이 존재하는지 여부를 구별 하는 데이터 구조가 도입되었음

- 무언가를 할당할 때 까지 값을 포함하지 않는 래퍼임.

- 언제든지 has_value()함수를 호출하여 값이 포함되어있는지 여부를 쿼리할 수 있음

- 로직을 아래와 같이 변경 가능

#include <optional>

...

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
};

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;
    // Assign index to queue families that could be found
    return indices;
}

vkGetPhysicalDeviceQueueFamilyProperties

- 대기열 패밀리 목록을 검색하는 프로세스는 이때까지와 똑같이 먼저 개수를 알아낸 후 데이터를 가져오는것

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

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

 

VkQueueFamilyProperties구조체

- details about the queue family

- including the type of operations

- the number of queues that can be created based on that family

- We need to find at least one queue family that supports VK_QUEUE_GRAPHICS_BIT

    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.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            }
            i++;
        }
        return indices;
    }

 

 

isDeviceSuitable

-> 다음과 같이 수정할 수 있음

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

    return indices.graphicsFamily.has_value();
}

-> 이것을 좀더 보기 편하게 다음과 같이 수정할 수 있음

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;

    bool isComplete() {
        return graphicsFamily.has_value();
    }
};

...

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

    return indices.isComplete();
}

-> findQueueFamilies에서 다음과 같이 코드를 수정할 수 있다.

for (const auto& queueFamily : queueFamilies) {
    ...

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

    i++;
}

Great, that's all we need for now to find the right physical device! The next step is to create a logical device to interface with it.

 

C++ code

 

https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Physical_devices_and_queue_families