Crafter.Graphics/interfaces/Crafter.Graphics-DescriptorHeapWebGPU.cppm
2026-05-19 00:27:09 +02:00

195 lines
8 KiB
C++

/*
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<WebGPUBufferRef> bufferTable;
std::vector<WebGPUTextureRef> imageTable;
std::vector<WebGPUSamplerRef> samplerTable;
std::vector<std::uint16_t> bufferFreelist;
std::vector<std::uint16_t> imageFreelist;
std::vector<std::uint16_t> 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<std::uint16_t>(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<std::uint16_t>(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<std::uint16_t>(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;
}
// Convenience: create the "standard" linear-filter clamp-to-edge sampler,
// allocate a slot for it, and return the slot. The wgpu* bridge call is
// intentionally kept inside the library — example code shouldn't need to
// reach into Crafter::WebGPU directly.
inline SamplerSlot AllocateLinearClampSampler(DescriptorHeapWebGPU& heap) {
DescriptorRange r = heap.AllocateSamplerSlots(1);
heap.samplerTable[r.firstElement] = WebGPU::wgpuCreateLinearClampSampler();
return SamplerSlot(&heap, r.firstElement);
}
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM