Overview
Related: #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.
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.
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.
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<void>::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<void>::type end() const;
// wrapper function for command vkResetCommandBuffer, see https://registry.khronos.org/vulkan/specs/latest/man/html/vkResetCommandBuffer.html
typename ResultValueType<void>::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<std::vector<CommandBufferWithDispatcher>>::type allocateCommandBuffersWithDispatcher( CommandBufferAllocateInfo const & allocateInfo ) const {
VULKAN_HPP_NAMESPACE::detail::resultCheck( result, VULKAN_HPP_RAII_NAMESPACE_STRING "::Device::allocateCommandBuffers" );
std::vector<CommandBufferWithDispatcher> 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<std::vector<DescriptorSetWithDispatcher>>::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.
Overview
Related: #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()andvkFreeDescriptorSet()for individual command buffer/descriptor set), needs specific creation flag (VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BITforvk::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.
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.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}WithDispatcherclasses, non-owning handles and store the inherited dispatcher from the device.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::CommandBufferandvk::raii::DescriptorSet). They can be constructed from the original handle and dispatcher instance, or obtained byvk::raii::Devicemethods. In the case,vk::detail::DeviceDispatcherwill be used for template parameter.The new class definitions should be in
vulkan_raii.hppas they are supposed to be used with RAII handles.2. Mark
vk::raii::Queueas deprecated in favor ofvk::QueueWithDispatcherBasically, the new
vk::QueueWithDispatcheris equivalent to the currentvk::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.