From 4b7bd9df3d32d17942a332780bb4965332dcc935 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sun, 20 Aug 2023 22:06:46 -0700 Subject: [PATCH] Add Vulkan Descriptor-Update batching Allows multiple descriptor operations to be batched up and dispatched in one API call rather than scattered through out the code base. --- CMakeLists.txt | 2 + .../vk_descriptor_update_batch.hpp | 62 ++++++++++++ .../vk_descriptor_update_batch.cpp | 98 +++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 include/renderer_vk/vk_descriptor_update_batch.hpp create mode 100644 src/core/renderer_vk/vk_descriptor_update_batch.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a12cf337..1218b97b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,12 +241,14 @@ if(ENABLE_VULKAN) set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp include/renderer_vk/vk_descriptor_heap.hpp + include/renderer_vk/vk_descriptor_update_batch.hpp include/renderer_vk/vk_memory.hpp include/renderer_vk/vk_pica.hpp ) set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp src/core/renderer_vk/vk_api.cpp src/core/renderer_vk/vk_debug.cpp src/core/renderer_vk/vk_descriptor_heap.cpp + src/core/renderer_vk/vk_descriptor_update_batch.cpp src/core/renderer_vk/vk_memory.cpp src/core/renderer_vk/vk_pica.cpp ) diff --git a/include/renderer_vk/vk_descriptor_update_batch.hpp b/include/renderer_vk/vk_descriptor_update_batch.hpp new file mode 100644 index 00000000..1a10214d --- /dev/null +++ b/include/renderer_vk/vk_descriptor_update_batch.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + // Implements a re-usable structure for batching up descriptor writes with a + // finite amount of space for both convenience and to reduce the overall amount + // of API calls to `vkUpdateDescriptorSets` + class DescriptorUpdateBatch { + private: + const vk::Device device; + + const usize descriptorWriteMax; + const usize descriptorCopyMax; + + using DescriptorInfoUnion = std::variant; + + // Todo: Maybe some kind of hash so that these structures can be re-used + // among descriptor writes. + std::unique_ptr descriptorInfos; + std::unique_ptr descriptorWrites; + std::unique_ptr descriptorCopies; + + usize descriptorWriteEnd = 0; + usize descriptorCopyEnd = 0; + + DescriptorUpdateBatch(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax) + : device(device), descriptorWriteMax(descriptorWriteMax), descriptorCopyMax(descriptorCopyMax) {} + + public: + ~DescriptorUpdateBatch() = default; + + DescriptorUpdateBatch(DescriptorUpdateBatch&&) = default; + + void flush(); + + void addImage( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral + ); + void addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler); + + void addImageSampler( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, + vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal + ); + void addBuffer( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size = VK_WHOLE_SIZE + ); + + void copyBinding( + vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement = 0, + u8 targetArrayElement = 0, u8 descriptorCount = 1 + ); + + static std::optional create(vk::Device device, usize descriptorWriteMax = 256, usize descriptorCopyMax = 256); + }; +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vk_descriptor_update_batch.cpp b/src/core/renderer_vk/vk_descriptor_update_batch.cpp new file mode 100644 index 00000000..a414ca2d --- /dev/null +++ b/src/core/renderer_vk/vk_descriptor_update_batch.cpp @@ -0,0 +1,98 @@ +#include "renderer_vk/vk_descriptor_update_batch.hpp" + +#include +#include + +namespace Vulkan { + + void DescriptorUpdateBatch::flush() { + device.updateDescriptorSets({std::span(descriptorWrites.get(), descriptorWriteEnd)}, {std::span(descriptorCopies.get(), descriptorCopyEnd)}); + + descriptorWriteEnd = 0; + descriptorCopyEnd = 0; + } + + void DescriptorUpdateBatch::addImage(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(vk::Sampler(), imageView, imageLayout); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampledImage, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(sampler, vk::ImageView(), vk::ImageLayout()); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampler, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addImageSampler( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, vk::ImageLayout imageLayout + ) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(sampler, imageView, imageLayout); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addBuffer( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size + ) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& bufferInfo = descriptorInfos[descriptorWriteEnd].emplace(buffer, offset, size); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eStorageImage, nullptr, &bufferInfo, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::copyBinding( + vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement, + u8 targetArrayElement, u8 descriptorCount + ) { + if (descriptorCopyEnd >= descriptorCopyMax) { + flush(); + } + + descriptorCopies[descriptorCopyEnd] = vk::CopyDescriptorSet( + sourceDescriptor, sourceBinding, sourceArrayElement, targetDescriptor, targetBinding, targetArrayElement, descriptorCount + ); + + ++descriptorCopyEnd; + } + + std::optional DescriptorUpdateBatch::create(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax) + + { + DescriptorUpdateBatch newDescriptorUpdateBatch(device, descriptorWriteMax, descriptorCopyMax); + + newDescriptorUpdateBatch.descriptorInfos = std::make_unique(descriptorWriteMax); + newDescriptorUpdateBatch.descriptorWrites = std::make_unique(descriptorWriteMax); + newDescriptorUpdateBatch.descriptorCopies = std::make_unique(descriptorCopyMax); + + return {std::move(newDescriptorUpdateBatch)}; + } + +} // namespace Vulkan \ No newline at end of file