From 72c77e41b4d577799f3655946fcfaa69b049970f Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sun, 20 Aug 2023 21:29:37 -0700 Subject: [PATCH] Draft Vulkan DescriptorHeap A utility class from a personal project for managing a heap of descriptors of a particular layout. Allows the display graphics pipeline to be successfully created, satisfying its descriptor layout issues. --- CMakeLists.txt | 10 +- include/renderer_vk/renderer_vk.hpp | 2 + include/renderer_vk/vk_descriptor_heap.hpp | 49 ++++++++ src/core/renderer_vk/renderer_vk.cpp | 14 ++- src/core/renderer_vk/vk_descriptor_heap.cpp | 119 ++++++++++++++++++++ 5 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 include/renderer_vk/vk_descriptor_heap.hpp create mode 100644 src/core/renderer_vk/vk_descriptor_heap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 81f3a1eb..a12cf337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,13 +239,15 @@ 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_memory.hpp - include/renderer_vk/vk_pica.hpp + include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp + include/renderer_vk/vk_descriptor_heap.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_memory.cpp - src/core/renderer_vk/vk_pica.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_memory.cpp src/core/renderer_vk/vk_pica.cpp ) set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES}) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 5f216b7d..e93a97b1 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -4,6 +4,7 @@ #include "math_util.hpp" #include "renderer.hpp" #include "vk_api.hpp" +#include "vk_descriptor_heap.hpp" class GPU; @@ -88,6 +89,7 @@ class RendererVK final : public Renderer { vk::RenderPass getRenderPass(vk::Format colorFormat, std::optional depthFormat); vk::RenderPass getRenderPass(PICA::ColorFmt colorFormat, std::optional depthFormat); + std::unique_ptr displayDescriptorHeap; vk::UniquePipeline displayPipeline; vk::UniquePipelineLayout displayPipelineLayout; diff --git a/include/renderer_vk/vk_descriptor_heap.hpp b/include/renderer_vk/vk_descriptor_heap.hpp new file mode 100644 index 00000000..8a9630e3 --- /dev/null +++ b/include/renderer_vk/vk_descriptor_heap.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + // Implements a basic heap of descriptor sets given a layout of particular + // bindings. Create a descriptor set by providing a list of bindings and it will + // automatically create both the pool, layout, and maintail a heap of descriptor + // sets. Descriptor sets will be reused and recycled. Assume that newly + // allocated descriptor sets are in an undefined state. + class DescriptorHeap { + private: + const vk::Device device; + + vk::UniqueDescriptorPool descriptorPool; + vk::UniqueDescriptorSetLayout descriptorSetLayout; + std::vector descriptorSets; + + std::vector bindings; + + std::vector allocationMap; + + explicit DescriptorHeap(vk::Device device); + + public: + ~DescriptorHeap() = default; + + DescriptorHeap(DescriptorHeap&&) = default; + + const vk::DescriptorPool& getDescriptorPool() const { return descriptorPool.get(); }; + + const vk::DescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout.get(); }; + + const std::span getDescriptorSets() const { return descriptorSets; }; + + std::span getBindings() const { return bindings; }; + + std::optional allocateDescriptorSet(); + bool freeDescriptorSet(vk::DescriptorSet set); + + static std::optional create( + vk::Device device, std::span bindings, u16 descriptorHeapCount = 1024 + ); + }; +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 3e1dd64a..49543bc5 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1093,6 +1093,17 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } } + static vk::DescriptorSetLayoutBinding displayShaderLayout[] = { + {// Just a singular texture slot + 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, + }; + + if (auto createResult = Vulkan::DescriptorHeap::create(device.get(), displayShaderLayout); createResult.has_value()) { + displayDescriptorHeap = std::make_unique(std::move(createResult.value())); + } else { + Helpers::panic("Error creating descriptor heap\n"); + } + auto vk_resources = cmrc::RendererVK::get_filesystem(); auto displayVertexShader = vk_resources.open("vulkan_display.vert.spv"); auto displayFragmentShader = vk_resources.open("vulkan_display.frag.spv"); @@ -1103,7 +1114,8 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { vk::RenderPass screenTextureRenderPass = getRenderPass(screenTextureInfo.format, {}); std::tie(displayPipeline, displayPipelineLayout) = createGraphicsPipeline( - device.get(), {}, {}, displayVertexShaderModule.get(), displayFragmentShaderModule.get(), {}, {}, screenTextureRenderPass + device.get(), {}, {{displayDescriptorHeap.get()->getDescriptorSetLayout()}}, displayVertexShaderModule.get(), + displayFragmentShaderModule.get(), {}, {}, screenTextureRenderPass ); } diff --git a/src/core/renderer_vk/vk_descriptor_heap.cpp b/src/core/renderer_vk/vk_descriptor_heap.cpp new file mode 100644 index 00000000..ecf71d92 --- /dev/null +++ b/src/core/renderer_vk/vk_descriptor_heap.cpp @@ -0,0 +1,119 @@ +#include "renderer_vk/vk_descriptor_heap.hpp" + +#include +#include +#include + +namespace Vulkan { + + DescriptorHeap::DescriptorHeap(vk::Device device) : device(device) {} + + std::optional DescriptorHeap::allocateDescriptorSet() { + // Find a free slot + const auto freeSlot = std::find(allocationMap.begin(), allocationMap.end(), false); + + // If there is no free slot, return + if (freeSlot == allocationMap.end()) { + return std::nullopt; + } + + // Mark the slot as allocated + *freeSlot = true; + + const u16 index = static_cast(std::distance(allocationMap.begin(), freeSlot)); + + vk::UniqueDescriptorSet& newDescriptorSet = descriptorSets[index]; + + if (!newDescriptorSet) { + // Descriptor set doesn't exist yet. Allocate a new one + vk::DescriptorSetAllocateInfo allocateInfo = {}; + + allocateInfo.descriptorPool = descriptorPool.get(); + allocateInfo.pSetLayouts = &descriptorSetLayout.get(); + allocateInfo.descriptorSetCount = 1; + + if (auto AllocateResult = device.allocateDescriptorSetsUnique(allocateInfo); AllocateResult.result == vk::Result::eSuccess) { + newDescriptorSet = std::move(AllocateResult.value[0]); + } else { + // Error allocating descriptor set + return std::nullopt; + } + } + + return newDescriptorSet.get(); + } + + bool DescriptorHeap::freeDescriptorSet(vk::DescriptorSet Set) { + // Find the descriptor set + const auto found = + std::find_if(descriptorSets.begin(), descriptorSets.end(), [&Set](const auto& CurSet) -> bool { return CurSet.get() == Set; }); + + // If the descriptor set is not found, return + if (found == descriptorSets.end()) { + return false; + } + + // Mark the slot as free + const u16 index = static_cast(std::distance(descriptorSets.begin(), found)); + + allocationMap[index] = false; + + return true; + } + + std::optional DescriptorHeap::create( + vk::Device device, std::span bindings, u16 descriptorHeapCount + ) { + DescriptorHeap newDescriptorHeap(device); + + // Create a histogram of each of the descriptor types and how many of each + // the pool should have + // Todo: maybe keep this around as a hash table to do more dynamic + // allocations of descriptor sets rather than allocating them all up-front + std::vector poolSizes; + { + std::unordered_map descriptorTypeCounts; + + for (const auto& binding : bindings) { + descriptorTypeCounts[binding.descriptorType] += binding.descriptorCount; + } + for (const auto& descriptorTypeCount : descriptorTypeCounts) { + poolSizes.push_back(vk::DescriptorPoolSize(descriptorTypeCount.first, descriptorTypeCount.second * descriptorHeapCount)); + } + } + + // Create descriptor pool + { + vk::DescriptorPoolCreateInfo poolInfo; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = descriptorHeapCount; + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.poolSizeCount = poolSizes.size(); + if (auto createResult = device.createDescriptorPoolUnique(poolInfo); createResult.result == vk::Result::eSuccess) { + newDescriptorHeap.descriptorPool = std::move(createResult.value); + } else { + return std::nullopt; + } + } + + // Create descriptor set layout + { + vk::DescriptorSetLayoutCreateInfo layoutInfo; + layoutInfo.pBindings = bindings.data(); + layoutInfo.bindingCount = bindings.size(); + + if (auto createResult = device.createDescriptorSetLayoutUnique(layoutInfo); createResult.result == vk::Result::eSuccess) { + newDescriptorHeap.descriptorSetLayout = std::move(createResult.value); + } else { + return std::nullopt; + } + } + + newDescriptorHeap.descriptorSets.resize(descriptorHeapCount); + newDescriptorHeap.allocationMap.resize(descriptorHeapCount); + + newDescriptorHeap.bindings.assign(bindings.begin(), bindings.end()); + + return {std::move(newDescriptorHeap)}; + } +} // namespace Vulkan \ No newline at end of file