Skip to content

Commit 2b7b116

Browse files
committed
Select GPU
1 parent 5d22baa commit 2b7b116

File tree

6 files changed

+177
-5
lines changed

6 files changed

+177
-5
lines changed

guide/src/SUMMARY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
- [GLFW Window](initialization/glfw_window.md)
1313
- [Vulkan Instance](initialization/instance.md)
1414
- [Vulkan Surface](initialization/surface.md)
15-
15+
- [Vulkan Physical Device](initialization/gpu.md)

guide/src/initialization/gpu.md

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
```

src/app.cpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
55

66
namespace lvk {
7-
namespace {
8-
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
9-
} // namespace
10-
117
void App::run() {
128
create_window();
139
create_instance();
1410
create_surface();
11+
select_gpu();
1512

1613
main_loop();
1714
}
@@ -43,6 +40,12 @@ void App::create_surface() {
4340
m_surface = glfw::create_surface(m_window.get(), *m_instance);
4441
}
4542

43+
void App::select_gpu() {
44+
m_gpu = get_suitable_gpu(*m_instance, *m_surface);
45+
std::println("[lvk] Using GPU: {}",
46+
std::string_view{m_gpu.properties.deviceName});
47+
}
48+
4649
void App::main_loop() {
4750
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
4851
glfwPollEvents();

src/app.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include <gpu.hpp>
23
#include <vulkan/vulkan.hpp>
34
#include <window.hpp>
45

@@ -11,11 +12,13 @@ class App {
1112
void create_window();
1213
void create_instance();
1314
void create_surface();
15+
void select_gpu();
1416

1517
void main_loop();
1618

1719
glfw::Window m_window{};
1820
vk::UniqueInstance m_instance{};
1921
vk::UniqueSurfaceKHR m_surface{};
22+
Gpu m_gpu{};
2023
};
2124
} // namespace lvk

src/gpu.cpp

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <gpu.hpp>
2+
#include <algorithm>
3+
#include <ranges>
4+
#include <stdexcept>
5+
6+
auto lvk::get_suitable_gpu(vk::Instance const instance,
7+
vk::SurfaceKHR const surface) -> Gpu {
8+
auto const supports_swapchain = [](Gpu const& gpu) {
9+
static constexpr std::string_view name_v =
10+
VK_KHR_SWAPCHAIN_EXTENSION_NAME;
11+
static constexpr auto is_swapchain =
12+
[](vk::ExtensionProperties const& properties) {
13+
return properties.extensionName.data() == name_v;
14+
};
15+
auto const properties = gpu.device.enumerateDeviceExtensionProperties();
16+
auto const it = std::ranges::find_if(properties, is_swapchain);
17+
return it != properties.end();
18+
};
19+
20+
auto const set_queue_family = [](Gpu& out_gpu) {
21+
static constexpr auto queue_flags_v =
22+
vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer;
23+
for (auto const [index, family] :
24+
std::views::enumerate(out_gpu.device.getQueueFamilyProperties())) {
25+
if ((family.queueFlags & queue_flags_v) == queue_flags_v) {
26+
out_gpu.queue_family = static_cast<std::uint32_t>(index);
27+
return true;
28+
}
29+
}
30+
return false;
31+
};
32+
33+
auto const can_present = [surface](Gpu const& gpu) {
34+
return gpu.device.getSurfaceSupportKHR(gpu.queue_family, surface) ==
35+
vk::True;
36+
};
37+
38+
auto fallback = Gpu{};
39+
for (auto const& device : instance.enumeratePhysicalDevices()) {
40+
auto gpu = Gpu{.device = device, .properties = device.getProperties()};
41+
if (gpu.properties.apiVersion < vk_version_v) { continue; }
42+
if (!supports_swapchain(gpu)) { continue; }
43+
if (!set_queue_family(gpu)) { continue; }
44+
if (!can_present(gpu)) { continue; }
45+
gpu.features = gpu.device.getFeatures();
46+
if (gpu.properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
47+
return gpu;
48+
}
49+
// keep iterating in case we find a Discrete Gpu later.
50+
fallback = gpu;
51+
}
52+
if (fallback.device) { return fallback; }
53+
54+
throw std::runtime_error{"No suitable Vulkan Physical Devices"};
55+
}

src/gpu.hpp

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
#include <vulkan/vulkan.hpp>
3+
4+
namespace lvk {
5+
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
6+
7+
struct Gpu {
8+
vk::PhysicalDevice device{};
9+
vk::PhysicalDeviceProperties properties{};
10+
vk::PhysicalDeviceFeatures features{};
11+
std::uint32_t queue_family{};
12+
};
13+
14+
[[nodiscard]] auto get_suitable_gpu(vk::Instance instance,
15+
vk::SurfaceKHR surface) -> Gpu;
16+
} // namespace lvk

0 commit comments

Comments
 (0)