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

@ -168,14 +168,17 @@ export namespace Crafter {
void DispatchText(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
// Generic dispatch — user-authored shaders. Vulkan-only in v1; on DOM
// the WebGPU side has no bindless and would need per-shader bind-group
// declaration. See plan section 3b for the design path.
void Dispatch(GraphicsCommandBuffer cmd, const ComputeShader& shader,
// Generic dispatch for user-authored shaders. On Vulkan, `shader` is
// a SPIR-V compute pipeline (bindless via VK_EXT_descriptor_heap, so
// any resource indices baked into push data resolve through the
// global heap). On DOM, `shader` carries a UICustomBinding list
// declared at Load time; the renderer reads the listed slot uints
// out of `push`, resolves them against heap.bufferTable /
// imageTable / samplerTable, and builds the bind groups before
// dispatching.
void Dispatch(GraphicsCommandBuffer cmd, const GraphicsComputeShader& shader,
const void* push, std::uint32_t pushBytes,
std::uint32_t gx, std::uint32_t gy = 1, std::uint32_t gz = 1);
#endif
// Allocates a heap slot for the buffer and registers the GPU handle.
// Returns a move-only BufferSlot RAII handle.

View file

@ -71,5 +71,15 @@ namespace Crafter::WebGPU {
extern "C" void wgpuDispatchText(std::uint32_t itemsHandle, const void* headerPtr,
std::int32_t gx, std::int32_t gy,
std::uint32_t atlasHandle, std::uint32_t sampHandle);
// ─── custom user-authored compute shaders ───────────────────────────
__attribute__((import_module("env"), import_name("wgpuLoadCustomShader")))
extern "C" std::uint32_t wgpuLoadCustomShader(const void* wgslPtr, std::int32_t wgslLen,
const void* bindingsPtr, std::int32_t bindingsCount);
__attribute__((import_module("env"), import_name("wgpuDispatchCustom")))
extern "C" void wgpuDispatchCustom(std::uint32_t pipelineHandle,
const void* pushPtr, std::int32_t pushBytes,
const void* handlesPtr, std::int32_t handlesCount,
std::int32_t gx, std::int32_t gy, std::int32_t gz);
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM

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