|
| 1 | +# Vulkan Physical Device |
| 2 | + |
| 3 | +A [Physical Device](https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDevice.html) represents a single complete implementation of Vulkan, for out intents and purposes a single GPU. (It could also be eg a software renderer like Mesa/lavapipe.) Some machines may have multiple Physical Devices available, like laptops with dual-GPUs. We need to select the one we want to use, given our constraints: |
| 4 | + |
| 5 | +1. Vulkan 1.3 must be supported |
| 6 | +1. Vulkan Swapchains must be supported |
| 7 | +1. A Vulkan Queue that supports Graphics and Transfer operations must be available |
| 8 | +1. It must be able to present to the previously created Vulkan Surface |
| 9 | +1. (Optional) Prefer discrete GPUs |
| 10 | + |
| 11 | +We wrap the actual Physical Device and a few other useful objects into `struct Gpu`. Since it will be accompanied by a hefty utility function, we put it in its own hpp/cpp files, and move the `vk_version_v` constant to this new header: |
| 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 | +The implementation: |
| 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 | +Finally, add a `Gpu` member in `App` and initialize it after `create_surface()`: |
| 83 | + |
| 84 | +```cpp |
| 85 | + create_surface(); |
| 86 | + select_gpu(); |
| 87 | + // ... |
| 88 | + |
| 89 | + |
| 90 | +void App::select_gpu() { |
| 91 | + m_gpu = get_suitable_gpu(*m_instance, *m_surface); |
| 92 | + std::println("Using GPU: {}", |
| 93 | + std::string_view{m_gpu.properties.deviceName}); |
| 94 | +} |
| 95 | +``` |
0 commit comments