//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® /* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net */ // User-authored compute shader for DOM mode. // // Contract: // - WGSL source authored by the user. // - Group 0 binding 0 is reserved for the UIDispatchHeader uniform // (with dynamic offset). The library writes it from the first 48 // bytes of the push data each dispatch. // - Group 1 is reserved for the ping-pong textures: binding 0 is the // storage `out` (texture_storage_2d), binding 1 // is the sampled `prev` (texture_2d). The library auto-binds // the right textures depending on the current ping-pong state. // - Groups 2+ are user-defined. The user declares each binding via a // UICustomBinding descriptor at Load time, naming: // - the @group(N) and @binding(N) numbers, // - the resource KIND (buffer / sampled texture / sampler), // - the BYTE OFFSET in the per-dispatch push data where a // uint32 heap slot index lives. // At Dispatch time the renderer reads each declared slot out of // push data, looks the GPU handle up in the heap (bufferTable / // imageTable / samplerTable), and assembles the bind group. export module Crafter.Graphics:WebGPUComputeShader; #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :WebGPU; export namespace Crafter { // Texel format for runtime-allocated textures and write-only storage- // texture bindings. Values are the wire contract with dom-webgpu.js's // texelFormatStr(); keep them in sync. rgba8unorm (0) is the default so // an unset `format` byte preserves prior behaviour. enum class WebGPUTexelFormat : std::uint8_t { RGBA8Unorm = 0, RGBA16Float = 1, // HDR post-process targets — filterable per spec R32Float = 2, }; enum class UICustomBindingKind : std::uint8_t { Buffer = 0, // read-only-storage SSBO, handle is a slot into heap.bufferTable SampledTexture = 1, // sampled texture_2d, handle is a slot into heap.imageTable Sampler = 2, // filtering sampler, handle is a slot into heap.samplerTable SampledTextureArray = 3, // sampled texture_2d_array, handle is a slot into heap.imageTable // read-write storage SSBO (var in WGSL). Use // for buffers shaders need to MUTATE — e.g. physics shaders that // integrate node momentum, write brace stress, or output TLAS // instance transforms. BufferReadWrite = 4, // write-only storage texture (texture_storage_2d in // WGSL). The `format` field carries the WebGPUTexelFormat. Lets a // compute pass write a user-owned texture (e.g. an rgba16float bloom // mip) at an arbitrary group/binding — the missing primitive for an // HDR mip pyramid. Bind the same texture as SampledTexture to read // it back in a later pass. StorageTexture = 5, }; struct UICustomBinding { std::uint8_t group; // @group(N), must be >= 2 (0 and 1 are reserved) std::uint8_t binding; // @binding(N) UICustomBindingKind kind; std::uint8_t format; // WebGPUTexelFormat for StorageTexture; 0 otherwise std::uint32_t pushOffset; // offset in push data where the slot uint32 lives }; static_assert(sizeof(UICustomBinding) == 8); class WebGPUComputeShader { public: std::uint32_t pipelineHandle = 0; bool rayQueryCapable = false; std::vector customBindings; WebGPUComputeShader() = default; WebGPUComputeShader(const WebGPUComputeShader&) = delete; WebGPUComputeShader& operator=(const WebGPUComputeShader&) = delete; WebGPUComputeShader(WebGPUComputeShader&& o) noexcept : pipelineHandle(o.pipelineHandle), rayQueryCapable(o.rayQueryCapable), customBindings(std::move(o.customBindings)) { o.pipelineHandle = 0; } // Compile + link a custom compute shader. `wgsl` is the source // string; the library does NOT add anything to it (except when // `rayQuery` is true — then a RT prelude exposing the rayQuery* // API is prepended). The user's shader must declare // @group(0)/@group(1) bindings matching the contract above // (rayQuery shaders MUST NOT redeclare group(1)). // `bindings` lists every additional resource (groups 2+) that the // renderer should bind at dispatch time. void Load(std::string_view wgsl, std::span bindings = {}, bool rayQuery = false); // Path-based overload for symmetry with the Vulkan ComputeShader. // Reads the file from disk (browser VFS) and forwards to Load(wgsl). void Load(const std::filesystem::path& wgslPath, std::span bindings = {}, bool rayQuery = false); }; } #endif // CRAFTER_GRAPHICS_WINDOW_DOM