KhronosGroup / KhronosGroup/Vulkan-Hpp
Proposal: vk::{CommandBuffer,DescriptorSet,Queue}WithDispatcher (non-owning handles that store the inherited device dispatcher)
- Dominant language
- C++
- Stars
- 3.8k
- Forks
- 374
- Avg merge
- 23h 4m
- Merged PRs (30d)
- 32
Description
# Overview
Related: https://github.com/KhronosGroup/Vulkan-Hpp/issues/929
Using RAII handle is very handy, as it manages the resource lifetime automatically and owns the inherited dispatcher from instance/device.
However, some Vulkan objects are not following the RAII nature: command buffer, descriptor set and queue for instance. Their lifetimes are tied to command pool, descriptor pool and device, respectively. Using the RAII version of them makes the code less performant (calling `vkFreeCommandBuffer()` and `vkFreeDescriptorSet()` for individual command buffer/descriptor set), needs specific creation flag (`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT` for `vk::raii::DescriptorSet`), or completely non-sense (queue).
However, using non-RAII version of them will make losing the benefits of automatic inherited dispatcher. User need to pass the proper dispatcher to each call manually, which is cumbersome and likely to be missed.
```c++
vk::raii::Device device { ... };
vk::raii::CommandPool commandPool { device, ... };
vk::CommandBuffer cb = (*device).allocateCommandBuffers(..., *device.getDispatcher());
cb.begin(..., *device.getDispatcher());
cb.bindPipeline(..., *device.getDispatcher());
cb.draw(..., *device.getDispatcher());
cb.end(*device.getDispatcher());
vk::Queue queue = (*device).getQueue(..., *device.getDispatcher());
queue.submit(..., *device.getDispatcher());
queue.waitIdle(*device.getDispatcher());
```
Using RAII handle can avoid passing the manual dispatcher parameter, but need to call `release()` before destroying the handle, which violates the RAII idiom.
```c++
vk::raii::Device device { ... };
vk::raii::CommandPool commandPool { device, ... };
vk::raii::CommandBuffer cb = device.allocateCommandBuffers(...)[0];
cb.begin(...);
cb.bindPipeline(...);
cb.draw(...);
cb.end();
vk::raii::Queue queue = device.getQueue(...);
queue.submit(...);
queue.waitIdle();
// queue.release(); // optional, as vk::Queue::~Queue() does nothing
cb.release(); // must be called before cb being destroyed, to avoid calling vkFreeCommandBuffer()
```
# Proposal
## 1. Add `vk::{CommandBuffer,DescriptorSet,Queue}WithDispatcher` classes, non-owning handles and store the inherited dispatcher from the device.
```c++
namespace VULKAN_HPP_NAMESPACE {
class CommandBufferWithDispatcher {
public:
using CType = VkCommandBuffer;
using CppType = VULKAN_HPP_NAMESPACE::CommandBuffer;
static VULKAN_HPP_CONST_OR_CONSTEXPR ObjectType objectType = ObjectType::eCommandBuffer;
static VULKAN_HPP_CONST_OR_CONSTEXPR DebugReportObjectTypeEXT debugReportObjectType = DebugReportObjectTypeEXT::eCommandBuffer;
CommandBufferWithDispatcher(CommandBuffer commandBuffer, detail::DeviceDispatcher const & d)
: m_commandBuffer { commandBuffer }
, m_dispatcher { d } { }
// copy/move constructors/assignment operators, swap, ...
//=== VK_VERSION_1_0 ===
// wrapper function for command vkBeginCommandBuffer, see https://registry.khronos.org/vulkan/specs/latest/man/html/vkBeginCommandBuffer.html
typename ResultValueType::type begin( CommandBufferBeginInfo const & beginInfo ) const;
// wrapper function for command vkEndCommandBuffer, see https://registry.khronos.org/vulkan/specs/latest/man/html/vkEndCommandBuffer.html
typename ResultValueType::type end() const;
// wrapper function for command vkResetCommandBuffer, see https://registry.khronos.org/vulkan/specs/latest/man/html/vkResetCommandBuffer.html
typename ResultValueType::type reset( CommandBufferResetFlags flags VULKAN_HPP_DEFAULT_ASSIGNMENT( {} ) ) const;
// ...
private:
CommandBuffer m_commandBuffer;
detail::DeviceDispatcher m_dispatcher;
};
class DescriptorSetWithDispatcher { ... };
class QueueWithDispatcher { ... };
namespace VULKAN_HPP_RAII_NAMESPACE {
class Device {
public:
// ...
VULKAN_HPP_NODISCARD typename ResultValueType>::type allocateCommandBuffersWithDispatcher( CommandBufferAllocateInfo const & allocateInfo ) const {
VULKAN_HPP_NAMESPACE::detail::resultCheck( result, VULKAN_HPP_RAII_NAMESPACE_STRING "::Device::allocateCommandBuffers" );
std::vector commandBuffersWithDispatcher;
if ( result == Result::eSuccess )
{
commandBuffersWithDispatcher.reserve( commandBuffers.size() );
for ( auto & commandBuffer : commandBuffers )
{
commandBuffersWithDispatcher.emplace_back(commandBuffer, getDispatcher());
}
}
return VULKAN_HPP_NAMESPACE::detail::createResultValueType( result, std::move( commandBuffersWithDispatcher ) );
}
VULKAN_HPP_NODISCARD typename ResultValueType>::type allocateDescriptorSetsWithDispatcher( DescriptorSetAllocateInfo const & allocateInfo ) const { ... }
VULKAN_HPP_NODISCARD QueueWithDispatcher getQueueWithDispatcher( uint32_t queueFamilyIndex, uint32_t queueIndex ) const VULKAN_HPP_NOEXCEPT_WHEN_NO_EXCEPTIONS { ... };
VULKAN_HPP_NODISCARD QueueWithDispatcher getQueueWithDispatcher2( DeviceQueueInfo2 const & queueInfo ) const VULKAN_HPP_NOEXCEPT_WHEN_NO_EXCEPTIONS { ... };
private:
// ...
};
}
}
vk::raii::Device device { ... };
vk::raii::CommandPool commandPool { device, ... };
vk::CommandBufferWithDispatcher cb = device.allocateCommandBuffersWithDispatcher(...)[0];
cb.begin();
cb.bindPipeline(...);
cb.draw(...);
cb.end();
vk::QueueWithDispatcher queue = device.getQueueWithDispatcher(...);
queue.submit(...);
queue.waitIdle();
```
These classes are 16-byte (non-RAII handle and inherited device dispatcher), which are leaner than the current RAII handles (32-byte for `vk::raii::CommandBuffer` and `vk::raii::DescriptorSet`). They can be constructed from the original handle and dispatcher instance, or obtained by `vk::raii::Device` methods. In the case, `vk::detail::DeviceDispatcher` will be used for template parameter.
The new class definitions should be in `vulkan_raii.hpp` as they are supposed to be used with RAII handles.
## 2. Mark `vk::raii::Queue` as deprecated in favor of `vk::QueueWithDispatcher`
Basically, the new `vk::QueueWithDispatcher` is equivalent to the current `vk::raii::Queue`. As the current one neither create resource in the constructor nor destroying resource in the destructor, it should be renamed to non-RAII version.
Contributor guide
Research direction
Start in vulkan_raii.hpp and inspect the existing RAII CommandBuffer, DescriptorSet, Queue, and Device implementations. Trace the allocation and queue-getter entry points described in the proposal, then compare their dispatcher handling and ownership behavior. Done means the three non-owning WithDispatcher classes and Device methods are defined, with vk::raii::Queue marked deprecated as proposed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100