/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net */ // DOM-mode parallel to DescriptorHeapVulkan. WebGPU has no real bindless, // so the "heap" is purely a CPU-side slot allocator with a side-table // mapping slot → JS-side WebGPU handle. UIRenderer looks up the handle by // slot at dispatch time to build (or fetch from cache) the bind group. export module Crafter.Graphics:DescriptorHeapWebGPU; #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :WebGPU; export namespace Crafter { struct DescriptorHeapWebGPU; struct DescriptorRange { std::uint16_t firstElement; std::uint16_t count; }; class BufferSlot { public: DescriptorHeapWebGPU* heap = nullptr; std::uint16_t firstElement = 0xFFFF; BufferSlot() = default; BufferSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {} BufferSlot(const BufferSlot&) = delete; BufferSlot& operator=(const BufferSlot&) = delete; BufferSlot(BufferSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; } BufferSlot& operator=(BufferSlot&& o) noexcept; ~BufferSlot(); explicit operator bool() const noexcept { return firstElement != 0xFFFF; } operator std::uint16_t() const noexcept { return firstElement; } }; class ImageSlot { public: DescriptorHeapWebGPU* heap = nullptr; std::uint16_t firstElement = 0xFFFF; ImageSlot() = default; ImageSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {} ImageSlot(const ImageSlot&) = delete; ImageSlot& operator=(const ImageSlot&) = delete; ImageSlot(ImageSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; } ImageSlot& operator=(ImageSlot&& o) noexcept; ~ImageSlot(); explicit operator bool() const noexcept { return firstElement != 0xFFFF; } operator std::uint16_t() const noexcept { return firstElement; } }; class SamplerSlot { public: DescriptorHeapWebGPU* heap = nullptr; std::uint16_t firstElement = 0xFFFF; SamplerSlot() = default; SamplerSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {} SamplerSlot(const SamplerSlot&) = delete; SamplerSlot& operator=(const SamplerSlot&) = delete; SamplerSlot(SamplerSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; } SamplerSlot& operator=(SamplerSlot&& o) noexcept; ~SamplerSlot(); explicit operator bool() const noexcept { return firstElement != 0xFFFF; } operator std::uint16_t() const noexcept { return firstElement; } }; struct DescriptorHeapWebGPU { std::vector bufferTable; std::vector imageTable; std::vector samplerTable; std::vector bufferFreelist; std::vector imageFreelist; std::vector samplerFreelist; std::uint16_t nextBuffer = 0; std::uint16_t nextImage = 0; std::uint16_t nextSampler = 0; void Initialize(std::uint16_t images, std::uint16_t buffers, std::uint16_t samplers) { imageTable.assign(images, 0); bufferTable.assign(buffers, 0); samplerTable.assign(samplers, 0); imageFreelist.reserve(images); bufferFreelist.reserve(buffers); samplerFreelist.reserve(samplers); } DescriptorRange AllocateBufferSlots(std::uint16_t count) { if (count == 1 && !bufferFreelist.empty()) { auto f = bufferFreelist.back(); bufferFreelist.pop_back(); return { f, 1 }; } if (nextBuffer + count > bufferTable.size()) { std::println("DescriptorHeapWebGPU: buffer slots exhausted"); std::abort(); } DescriptorRange r{ nextBuffer, count }; nextBuffer = static_cast(nextBuffer + count); return r; } DescriptorRange AllocateImageSlots(std::uint16_t count) { if (count == 1 && !imageFreelist.empty()) { auto f = imageFreelist.back(); imageFreelist.pop_back(); return { f, 1 }; } if (nextImage + count > imageTable.size()) { std::println("DescriptorHeapWebGPU: image slots exhausted"); std::abort(); } DescriptorRange r{ nextImage, count }; nextImage = static_cast(nextImage + count); return r; } DescriptorRange AllocateSamplerSlots(std::uint16_t count) { if (count == 1 && !samplerFreelist.empty()) { auto f = samplerFreelist.back(); samplerFreelist.pop_back(); return { f, 1 }; } if (nextSampler + count > samplerTable.size()) { std::println("DescriptorHeapWebGPU: sampler slots exhausted"); std::abort(); } DescriptorRange r{ nextSampler, count }; nextSampler = static_cast(nextSampler + count); return r; } void FreeBufferSlots(std::uint16_t first, std::uint16_t count) noexcept { for (std::uint16_t i = 0; i < count; ++i) { bufferTable[first + i] = 0; bufferFreelist.push_back(first + i); } } void FreeImageSlots(std::uint16_t first, std::uint16_t count) noexcept { for (std::uint16_t i = 0; i < count; ++i) { imageTable[first + i] = 0; imageFreelist.push_back(first + i); } } void FreeSamplerSlots(std::uint16_t first, std::uint16_t count) noexcept { for (std::uint16_t i = 0; i < count; ++i) { samplerTable[first + i] = 0; samplerFreelist.push_back(first + i); } } }; // ─── slot dtors (defined here since they reference DescriptorHeapWebGPU) ─ inline BufferSlot::~BufferSlot() { if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1); } inline BufferSlot& BufferSlot::operator=(BufferSlot&& o) noexcept { if (this != &o) { if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1); heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF; } return *this; } inline ImageSlot::~ImageSlot() { if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1); } inline ImageSlot& ImageSlot::operator=(ImageSlot&& o) noexcept { if (this != &o) { if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1); heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF; } return *this; } inline SamplerSlot::~SamplerSlot() { if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1); } inline SamplerSlot& SamplerSlot::operator=(SamplerSlot&& o) noexcept { if (this != &o) { if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1); heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF; } return *this; } } #endif // CRAFTER_GRAPHICS_WINDOW_DOM