|
| 1 | +# Vulkan Physical Device |
| 2 | + |
| 3 | +[Physical Device](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-physical-device-enumeration) 代表一個完整的 Vulkan 實作; 就我們的用途而言,可以把它視為一張 GPU(它也可能是像 Mesa/lavapipe 這樣的軟體算繪器)。 有些機器上可能同時存在多個 Physical Device,例如配備雙顯卡的筆電。 我們需要在下列限制條件下,挑選要使用的是哪一個: |
| 4 | + |
| 5 | +1. 必須支援 Vulkan 1.3 |
| 6 | +2. 必須支援 Vulkan Swapchain |
| 7 | +3. 必須有支援 Graphics 與 Transfer 作業的 Vulkan Queue |
| 8 | +4. 必須能呈現(present)先前建立的 Vulkan Surface |
| 9 | +5. (Optional)優先選擇獨立顯示卡(discrete GPU) |
| 10 | + |
| 11 | +我們會把實際的 Physical Device 以及其他幾個實用物件包裝成 `struct Gpu`。 由於它會搭配一個相當龐大的工具函式,我們會把它放到獨立的 hpp/cpp 檔案中,並把 `vk_version_v` 這個常數移到新的標頭檔裡: |
| 12 | + |
| 13 | +```cpp |
| 14 | +constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0); |
| 15 | + |
| 16 | +struct Gpu { |
| 17 | + vk::PhysicalDevice device{}; |
| 18 | + vk::PhysicalDeviceProperties properties{}; |
| 19 | + vk::PhysicalDeviceFeatures features{}; |
| 20 | + std::uint32_t queue_family{}; |
| 21 | +}; |
| 22 | + |
| 23 | +[[nodiscard]] auto get_suitable_gpu(vk::Instance instance, |
| 24 | + vk::SurfaceKHR surface) -> Gpu; |
| 25 | +``` |
| 26 | +
|
| 27 | +函式定義: |
| 28 | +
|
| 29 | +```cpp |
| 30 | +auto lvk::get_suitable_gpu(vk::Instance const instance, |
| 31 | + vk::SurfaceKHR const surface) -> Gpu { |
| 32 | + auto const supports_swapchain = [](Gpu const& gpu) { |
| 33 | + static constexpr std::string_view name_v = |
| 34 | + VK_KHR_SWAPCHAIN_EXTENSION_NAME; |
| 35 | + static constexpr auto is_swapchain = |
| 36 | + [](vk::ExtensionProperties const& properties) { |
| 37 | + return properties.extensionName.data() == name_v; |
| 38 | + }; |
| 39 | + auto const properties = gpu.device.enumerateDeviceExtensionProperties(); |
| 40 | + auto const it = std::ranges::find_if(properties, is_swapchain); |
| 41 | + return it != properties.end(); |
| 42 | + }; |
| 43 | +
|
| 44 | + auto const set_queue_family = [](Gpu& out_gpu) { |
| 45 | + static constexpr auto queue_flags_v = |
| 46 | + vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer; |
| 47 | + for (auto const [index, family] : |
| 48 | + std::views::enumerate(out_gpu.device.getQueueFamilyProperties())) { |
| 49 | + if ((family.queueFlags & queue_flags_v) == queue_flags_v) { |
| 50 | + out_gpu.queue_family = static_cast<std::uint32_t>(index); |
| 51 | + return true; |
| 52 | + } |
| 53 | + } |
| 54 | + return false; |
| 55 | + }; |
| 56 | +
|
| 57 | + auto const can_present = [surface](Gpu const& gpu) { |
| 58 | + return gpu.device.getSurfaceSupportKHR(gpu.queue_family, surface) == |
| 59 | + vk::True; |
| 60 | + }; |
| 61 | +
|
| 62 | + auto fallback = Gpu{}; |
| 63 | + for (auto const& device : instance.enumeratePhysicalDevices()) { |
| 64 | + auto gpu = Gpu{.device = device, .properties = device.getProperties()}; |
| 65 | + if (gpu.properties.apiVersion < vk_version_v) { continue; } |
| 66 | + if (!supports_swapchain(gpu)) { continue; } |
| 67 | + if (!set_queue_family(gpu)) { continue; } |
| 68 | + if (!can_present(gpu)) { continue; } |
| 69 | + gpu.features = gpu.device.getFeatures(); |
| 70 | + if (gpu.properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { |
| 71 | + return gpu; |
| 72 | + } |
| 73 | + // keep iterating in case we find a Discrete Gpu later. |
| 74 | + fallback = gpu; |
| 75 | + } |
| 76 | + if (fallback.device) { return fallback; } |
| 77 | +
|
| 78 | + throw std::runtime_error{"No suitable Vulkan Physical Devices"}; |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +最後,在 `App` 中加入 `Gpu` 成員,並在 `create_surface()` 之後初始化它: |
| 83 | + |
| 84 | +```cpp |
| 85 | +create_surface(); |
| 86 | +select_gpu(); |
| 87 | + |
| 88 | +// ... |
| 89 | +void App::select_gpu() { |
| 90 | + m_gpu = get_suitable_gpu(*m_instance, *m_surface); |
| 91 | + std::println("Using GPU: {}", |
| 92 | + std::string_view{m_gpu.properties.deviceName}); |
| 93 | +} |
| 94 | +``` |
0 commit comments