195 lines
8 KiB
C++
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
|