Program Listing for File DescriptorSetGroup.ixx#
↰ Return to documentation for file (Src/GraphicsEngineVulkan/vulkan_base/DescriptorSetGroup.ixx)
module;
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <vector>
#include <vulkan/vulkan.hpp>
export module kataglyphis.vulkan.descriptor_set_group;
import kataglyphis.vulkan.device;
export namespace Kataglyphis {
// Derives descriptor pool sizes from a set of layout bindings: one entry per
// distinct descriptor type, accumulated as descriptorCount * set_count
// across all bindings that share the type, in first-seen order. Pure and
// device-free so it can be unit-tested without a VulkanDevice.
std::vector<vk::DescriptorPoolSize> deriveDescriptorPoolSizes(
std::span<const vk::DescriptorSetLayoutBinding> bindings, uint32_t set_count);
// True when a write of writeCount descriptors matches what the binding
// declared. Named (rather than an inline comparison) so beginWrite's
// single-descriptor writers and writeImageArray's array writer cannot drift.
bool descriptorWriteCountMatchesBinding(const vk::DescriptorSetLayoutBinding &binding, uint32_t writeCount);
// Returns the first binding number that appears more than once in
// `bindings`, or std::nullopt if every binding number is unique. Pure and
// device-free, matching deriveDescriptorPoolSizes, so create() can reject a
// duplicate binding before it produces an invalid layout.
std::optional<uint32_t> firstDuplicateBinding(std::span<const vk::DescriptorSetLayoutBinding> bindings);
// Owns ONE descriptor set layout + descriptor pool + N descriptor sets
// (typically one per swapchain image). Replaces the four structurally
// identical layout/pool/set triads that used to live in VulkanRenderer.
//
// Usage: declare the bindings with addBinding(), then create(device, N).
// Pool sizes are derived from the bindings (count * set_count per type)
// via deriveDescriptorPoolSizes(). The write helpers look up descriptor
// type and array count from the declared binding, so call sites stay
// declarative.
//
// cleanUp() is idempotent and also clears the recorded bindings, so a
// swapchain recreation can rebuild the group from a fresh declarative
// binding list. Move-only with a destructor safety net, matching the
// established leaf-RAII pattern (VulkanBuffer et al.).
class DescriptorSetGroup
{
public:
DescriptorSetGroup();
DescriptorSetGroup(const DescriptorSetGroup &) = delete;
DescriptorSetGroup &operator=(const DescriptorSetGroup &) = delete;
DescriptorSetGroup(DescriptorSetGroup &&other) noexcept;
DescriptorSetGroup &operator=(DescriptorSetGroup &&other) noexcept;
DescriptorSetGroup &addBinding(uint32_t binding,
vk::DescriptorType type,
uint32_t descriptor_count,
vk::ShaderStageFlags stages);
// Creates layout + pool and allocates set_count sets. Releases any
// layout/pool from a previous create() first, so calling create() again
// on an already-created instance replaces rather than leaks it. Logs and
// returns false if called without bindings, with zero sets, or with a
// duplicate binding number, before anything is created; every later
// failure goes through ASSERT_VULKAN, which logs critical and aborts, so
// there is no partial-cleanup path.
[[nodiscard]] bool create(std::shared_ptr<VulkanDevice> vulkan_device, uint32_t set_count); // DEVICE_SINK_OK: moved into member
// -- write helpers (thin wrappers around vkUpdateDescriptorSets; the
// descriptor type / array count come from the declared binding)
void writeBuffer(uint32_t set_index,
uint32_t binding,
vk::Buffer buffer,
vk::DeviceSize range,
vk::DeviceSize offset = 0);
// sampler may be null (sampled images, storage images, input attachments)
void writeImage(uint32_t set_index,
uint32_t binding,
vk::ImageView image_view,
vk::ImageLayout image_layout,
vk::Sampler sampler = nullptr);
// Writes the binding's whole descriptor array; infos.size() must match
// the declared descriptor count.
void writeImageArray(uint32_t set_index, uint32_t binding, std::span<const vk::DescriptorImageInfo> infos);
void writeAccelerationStructure(uint32_t set_index, uint32_t binding, const vk::AccelerationStructureKHR &tlas);
[[nodiscard]] vk::DescriptorSetLayout getLayout() const { return layout; }
[[nodiscard]] const std::vector<vk::DescriptorSet> &sets() const { return descriptor_sets; }
// Destroys pool + layout, frees the sets and forgets the declared
// bindings. Safe to call multiple times.
void cleanUp();
~DescriptorSetGroup();
private:
// Returns nullptr (and logs) when the binding was never declared.
const vk::DescriptorSetLayoutBinding *findBinding(uint32_t binding) const;
// Validates set_index/binding and looks up the declared binding, filling
// the common fields shared by every write (dstSet, dstBinding,
// dstArrayElement = 0, descriptorType, descriptorCount = 1). Returns the
// found binding, or nullptr (having already logged) on failure; callers
// must return early in that case.
const vk::DescriptorSetLayoutBinding *beginWrite(uint32_t set_index, uint32_t binding,
vk::WriteDescriptorSet &out) const;
// Destroys pool + layout (guarded on device) and clears descriptor_sets,
// leaving bindings and device alone. The GPU-resource half of cleanUp();
// called as create()'s first statement so a second create() releases
// instead of leaking, and by cleanUp() itself so the two cannot drift.
void releaseGpuResources();
std::shared_ptr<VulkanDevice> device{ nullptr };
// Value-initialize ({}) so the vectors are empty regardless of which
// translation unit constructs this class. Without the braces, a C++23
// module ABI mismatch can leave the vectors with garbage internal state
// (the module interface unit's default init and the implementation
// unit's constructor definition disagree on whether to zero-init).
std::vector<vk::DescriptorSetLayoutBinding> bindings{};
vk::DescriptorSetLayout layout{};
vk::DescriptorPool pool{};
std::vector<vk::DescriptorSet> descriptor_sets{};
};
}// namespace Kataglyphis