custom shader webgpu

This commit is contained in:
Jorijn van der Graaf 2026-05-18 05:39:17 +02:00
commit 64116cd980
12 changed files with 445 additions and 36 deletions

View file

@ -4,22 +4,74 @@ Copyright (C) 2026 Catcrafts®
catcrafts.net
*/
// Placeholder ComputeShader for DOM mode. The four standard UI pipelines
// are compiled JS-side at startup (see additional/dom-webgpu.js); the C++
// side never sees a WGSL handle. This type exists so UIRenderer can
// declare `WebGPUComputeShader drawQuads;` members for symmetry with the
// Vulkan side, but `Load()` and `Dispatch()` are intentionally absent —
// the DispatchQuads / DispatchCircles / etc convenience methods on
// UIRenderer route directly to the JS bridge.
// 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<rgba8unorm, write>), binding 1
// is the sampled `prev` (texture_2d<f32>). 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 {
struct WebGPUComputeShader {
// Marker only; pipelines live JS-side per dispatchStandard in
// dom-webgpu.js. No state required.
enum class UICustomBindingKind : std::uint8_t {
Buffer = 0, // read-only-storage SSBO, handle is a slot into heap.bufferTable
SampledTexture = 1, // sampled texture_2d<f32>, handle is a slot into heap.imageTable
Sampler = 2, // filtering sampler, handle is a slot into heap.samplerTable
};
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 _pad;
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;
std::vector<UICustomBinding> customBindings;
WebGPUComputeShader() = default;
WebGPUComputeShader(const WebGPUComputeShader&) = delete;
WebGPUComputeShader& operator=(const WebGPUComputeShader&) = delete;
WebGPUComputeShader(WebGPUComputeShader&& o) noexcept
: pipelineHandle(o.pipelineHandle),
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 — the user's
// shader must declare @group(0)/@group(1) bindings matching the
// contract above. `bindings` lists every additional resource
// (groups 2+) that the renderer should bind at dispatch time.
void Load(std::string_view wgsl,
std::span<const UICustomBinding> bindings = {});
// 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<const UICustomBinding> bindings = {});
};
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM