/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net */ // WebGPU buffer wrapper — DOM-mode parallel to VulkanBuffer. // Holds a JS-side GPUBuffer handle + (when Mapped) a wasm-memory staging // array. `.value` points to the staging memory; the user writes into it // directly, and `.Flush(cmd)` copies to the GPU via queue.writeBuffer. export module Crafter.Graphics:WebGPUBuffer; #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :WebGPU; export namespace Crafter { class WebGPUBufferBase { public: WebGPUBufferRef handle = 0; std::uint32_t size = 0; // bytes }; template class WebGPUBufferMapped { public: T* value = nullptr; }; class WebGPUBufferMappedEmpty {}; template using WebGPUBufferMappedConditional = std::conditional_t, WebGPUBufferMappedEmpty>; template class WebGPUBuffer : public WebGPUBufferBase, public WebGPUBufferMappedConditional { public: WebGPUBuffer() = default; WebGPUBuffer(const WebGPUBuffer&) = delete; WebGPUBuffer& operator=(const WebGPUBuffer&) = delete; WebGPUBuffer(WebGPUBuffer&& other) noexcept { handle = other.handle; size = other.size; other.handle = 0; if constexpr (Mapped) { this->value = other.value; other.value = nullptr; } } void Create(std::uint32_t count) { size = static_cast(count * sizeof(T)); handle = WebGPU::wgpuCreateBuffer(static_cast(size)); if constexpr (Mapped) { this->value = new T[count](); } } void Clear() { if (handle != 0) { WebGPU::wgpuDestroyBuffer(handle); handle = 0; } if constexpr (Mapped) { if (this->value) { delete[] this->value; this->value = nullptr; } } } void Resize(std::uint32_t count) { if (handle != 0) Clear(); Create(count); } void Flush(WebGPUCommandEncoderRef /*cmd*/) requires(Mapped) { WebGPU::wgpuWriteBuffer(handle, this->value, static_cast(size)); } void FlushDevice() requires(Mapped) { WebGPU::wgpuWriteBuffer(handle, this->value, static_cast(size)); } // Partial upload — write the bytes [srcByteOffset, srcByteOffset+byteCount) // of the host mirror to GPU offset `dstByteOffset`. BuildTLAS uses // this to leave the GPU-owned transform field of an RTInstance // intact (the physics-tlas-transform compute shader is its sole // writer) while still pushing the CPU-side metadata fields. void FlushDeviceRange(std::uint32_t dstByteOffset, std::uint32_t srcByteOffset, std::uint32_t byteCount) requires(Mapped) { const auto* base = reinterpret_cast(this->value); WebGPU::wgpuWriteBufferRange(handle, dstByteOffset, base + srcByteOffset, static_cast(byteCount)); } // Push one element's worth of bytes from the host mirror to GPU. // Use when a single SoA slot was mutated (body construction, // per-instance flag flip) and a full FlushDevice would clobber // the GPU-side updates the sim has applied to neighboring slots. void FlushDeviceSlot(std::uint32_t idx) requires(Mapped) { constexpr std::uint32_t kStride = sizeof(T); const std::uint32_t off = idx * kStride; FlushDeviceRange(off, off, kStride); } // Schedule a GPU→CPU readback of this buffer's entire contents. // Asynchronous; data isn't ready until a later PollReadback // returns true. Successive Enqueues without a Poll are dropped // — they're a no-op while the previous map is in flight. // // `resetBytes` ≥ 0 — if non-zero, the first `resetBytes` bytes // of THIS buffer are clearBuffer-cleared on the GPU command // encoder immediately after the copy, so the readback captures // the pre-clear bytes and the next frame's writers see zeros. // The reset is tied to a successful enqueue (skipped enqueue = // skipped reset), preserving accumulated state across missed // drains. void EnqueueReadback(std::uint32_t resetBytes = 0) { WebGPU::wgpuReadbackEnqueue(handle, static_cast(size), static_cast(resetBytes)); } // Try to copy the readback bytes into this->value. Returns true // if the previous EnqueueReadback resolved and the data is now // mirrored into .value; false if the map is still pending. bool PollReadback() requires(Mapped) { return WebGPU::wgpuReadbackPoll(handle, this->value, static_cast(size)) != 0; } // Non-consuming readiness probe. Returns true if a subsequent // PollReadback would succeed without changing state otherwise. // Use to verify a sibling buffer is also ready before consuming. bool IsReadbackReady() const { return WebGPU::wgpuReadbackReady(handle) != 0; } ~WebGPUBuffer() { Clear(); } }; } #endif // CRAFTER_GRAPHICS_WINDOW_DOM