|
| 1 | +# Vulkan Device |
| 2 | + |
| 3 | +A [Vulkan Device](https://registry.khronos.org/vulkan/specs/latest/man/html/VkDevice.html) is a logical instance of a Physical Device, and will the primary interface for everything Vulkan now onwards. [Vulkan Queues](https://registry.khronos.org/vulkan/specs/latest/man/html/VkQueue.html) are owned by the Device, we will need one from the queue family stored in the `Gpu`, to submit recorded command buffers. We also need to explicitly declare all features we want to use, eg [Dynamic Rendering](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_dynamic_rendering.html) and [Synchronization2](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html). |
| 4 | + |
| 5 | +Setup a `vk::QueueCreateInfo` object: |
| 6 | + |
| 7 | +```cpp |
| 8 | + auto queue_ci = vk::DeviceQueueCreateInfo{}; |
| 9 | + // since we use only one queue, it has the entire priority range, ie, 1.0 |
| 10 | + static constexpr auto queue_priorities_v = std::array{1.0f}; |
| 11 | + queue_ci.setQueueFamilyIndex(m_gpu.queue_family) |
| 12 | + .setQueueCount(1) |
| 13 | + .setQueuePriorities(queue_priorities_v); |
| 14 | +``` |
| 15 | +
|
| 16 | +Setup the core device features: |
| 17 | +
|
| 18 | +```cpp |
| 19 | + auto enabled_features = vk::PhysicalDeviceFeatures{}; |
| 20 | + enabled_features.fillModeNonSolid = m_gpu.features.fillModeNonSolid; |
| 21 | + enabled_features.wideLines = m_gpu.features.wideLines; |
| 22 | + enabled_features.samplerAnisotropy = m_gpu.features.samplerAnisotropy; |
| 23 | + enabled_features.sampleRateShading = m_gpu.features.sampleRateShading; |
| 24 | +``` |
| 25 | + |
| 26 | +Setup the additional features, using `setPNext()` to chain them: |
| 27 | + |
| 28 | +```cpp |
| 29 | + auto sync_feature = vk::PhysicalDeviceSynchronization2Features{vk::True}; |
| 30 | + auto dynamic_rendering_feature = |
| 31 | + vk::PhysicalDeviceDynamicRenderingFeatures{vk::True}; |
| 32 | + sync_feature.setPNext(&dynamic_rendering_feature); |
| 33 | +``` |
| 34 | +
|
| 35 | +Setup a `vk::DeviceCreateInfo` object: |
| 36 | +
|
| 37 | +```cpp |
| 38 | + auto device_ci = vk::DeviceCreateInfo{}; |
| 39 | + static constexpr auto extensions_v = |
| 40 | + std::array{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; |
| 41 | + device_ci.setPEnabledExtensionNames(extensions_v) |
| 42 | + .setQueueCreateInfos(queue_ci) |
| 43 | + .setPEnabledFeatures(&enabled_features) |
| 44 | + .setPNext(&sync_feature); |
| 45 | +``` |
| 46 | + |
| 47 | +Declare a `vk::UniqueDevice` member after `m_gpu`, create it, and initialize the dispatcher against it: |
| 48 | + |
| 49 | +```cpp |
| 50 | + m_device = m_gpu.device.createDeviceUnique(device_ci); |
| 51 | + VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_device); |
| 52 | +``` |
| 53 | + |
| 54 | +Declare a `vk::Queue` member (order doesn't matter since it's just a handle, the actual Queue is owned by the Device) and initialize it: |
| 55 | + |
| 56 | +```cpp |
| 57 | + static constexpr std::uint32_t queue_index_v{0}; |
| 58 | + m_queue = m_device->getQueue(m_gpu.queue_family, queue_index_v); |
| 59 | +``` |
| 60 | +
|
| 61 | +## ScopedWaiter |
| 62 | +
|
| 63 | +A useful abstraction to have is an object that in its destructor waits/blocks until the Device is idle. Being able to do arbitary things on scope exit is useful in general too, but it requires some custom class template like `UniqueResource<Type, Deleter>`. We shall "abuse" `std::unique_ptr<Type, Deleter>` instead: it will not manage the pointer (`Type*`) at all, but instead `Deleter` will call a member function on it (if it isn't null). |
| 64 | +
|
| 65 | +Adding this to a new header `scoped_waiter.hpp`: |
| 66 | +
|
| 67 | +```cpp |
| 68 | +class ScopedWaiter { |
| 69 | + public: |
| 70 | + ScopedWaiter() = default; |
| 71 | +
|
| 72 | + explicit ScopedWaiter(vk::Device const* device) : m_device(device) {} |
| 73 | +
|
| 74 | + private: |
| 75 | + struct Deleter { |
| 76 | + void operator()(vk::Device const* device) const noexcept { |
| 77 | + if (device == nullptr) { return; } |
| 78 | + device->waitIdle(); |
| 79 | + } |
| 80 | + }; |
| 81 | + std::unique_ptr<vk::Device const, Deleter> m_device{}; |
| 82 | +}; |
| 83 | +``` |
| 84 | + |
| 85 | +This requires the passed `vk::Device*` to outlive itself, so to be defensive we make `App` be non-moveable and non-copiable, and create a member factory function for waiters: |
| 86 | + |
| 87 | +```cpp |
| 88 | + auto operator=(App&&) = delete; |
| 89 | +// ... |
| 90 | + |
| 91 | + [[nodiscard]] auto create_waiter() const -> ScopedWaiter { |
| 92 | + return ScopedWaiter{&*m_device}; |
| 93 | + } |
| 94 | +``` |
0 commit comments