Skip to content

feat(webgpu): automatic bind-group reflection for compute shaders#8821

Merged
mvaligursky merged 3 commits into
mainfrom
mv-compute-wgsl-reflection
Jun 2, 2026
Merged

feat(webgpu): automatic bind-group reflection for compute shaders#8821
mvaligursky merged 3 commits into
mainfrom
mv-compute-wgsl-reflection

Conversation

@mvaligursky
Copy link
Copy Markdown
Contributor

WebGPU compute shaders can now use the same simplified WGSL syntax as vertex/fragment
shaders. The engine reflects resources directly from the shader source and builds the
bind group automatically, so a hand-written computeBindGroupFormat is no longer required.

Changes:

  • Compute shader source is now run through the WGSL reflector. Simplified-syntax
    declarations (no @group/@binding) are reflected automatically: loose uniforms
    (collapsed into a single generated uniform buffer), textures + samplers, storage
    buffers, and storage textures.
  • Reflected resources are placed in their own bind group: group 1 when a caller-provided
    format is supplied (which stays at group 0), or group 0 when none is. Explicitly-bound
    (@group/@binding) resources are left untouched, so existing compute shaders are
    byte-for-byte unaffected.
  • Generalized the compute pipeline / bind-group execution to support multiple bind groups.
  • Fixed the storage-buffer reflection regex to accept read_write (previously only
    matched read/write); this is shared with the vertex/fragment reflector.
  • Added unit tests covering the compute reflection paths.

API Changes:

  • WGSL compute Shader definitions: computeBindGroupFormat and
    computeUniformBufferFormats are now optional. Non-breaking — supplying them behaves
    exactly as before.

Examples:

  • Converted edge-detect and particles to the simplified syntax (no
    computeBindGroupFormat).

Fixes #7689

WebGPU compute shaders can now use the same simplified WGSL syntax as
vertex/fragment shaders. The engine reflects resources from the shader source
and builds the bind group automatically, so a hand-written computeBindGroupFormat
is no longer required.

- Reflect loose uniforms (into a generated uniform buffer), textures + samplers,
  storage buffers, and storage textures declared with simplified syntax.
- computeBindGroupFormat / computeUniformBufferFormats are now optional. Reflected
  resources go in their own bind group (group 1 when a caller format is supplied,
  otherwise group 0); explicitly-bound resources are left untouched.
- Generalize compute pipeline / bind-group execution to support multiple bind groups.
- Fix storage-buffer reflection regex to accept read_write.
- Convert edge-detect and particles examples to the simplified syntax.
- Add unit tests for the compute reflection paths.

Fixes #7689
The @returns {GPUComputePipeline} JSDoc emitted an explicit return type into
the .d.ts, which fails the standalone type check (no WebGPU lib types in scope).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the existing WGSL shader reflection pipeline to compute shaders, allowing simplified-syntax compute resources (loose uniforms, textures/samplers, storage buffers, storage textures) to be reflected automatically and placed into an engine-managed bind group. This reduces the need for callers to hand-author computeBindGroupFormat / computeUniformBufferFormats, while keeping explicitly-bound (@group/@binding) declarations unchanged.

Changes:

  • Added compute-shader reflection path that generates a dedicated bind group (and collapses loose uniforms into a generated uniform buffer).
  • Updated compute execution + pipeline caching to support up to two bind groups (caller group + reflected group).
  • Updated examples to use simplified syntax and added unit tests covering compute reflection cases.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/platform/graphics/webgpu/webgpu-shader-processor-wgsl-compute.test.mjs Adds unit tests for compute reflection (storage buffers/textures, textures+samplers, uniform collapsing, group selection).
src/platform/graphics/webgpu/webgpu-shader.js Routes compute WGSL through the reflector and stores reflected formats/group index on the shader impl.
src/platform/graphics/webgpu/webgpu-shader-processor-wgsl.js Implements runCompute, adds storage texture support in resource reflection/codegen, and generalizes resource format building.
src/platform/graphics/webgpu/webgpu-compute.js Creates/binds multiple bind groups (caller + reflected) and passes formats array into compute pipeline creation.
src/platform/graphics/webgpu/webgpu-compute-pipeline.js Extends pipeline cache keying + layout creation to include up to 2 bind group formats.
examples/src/examples/compute/particles.shader-simulation.wgsl Converts compute shader to simplified-syntax resources and loose uniforms.
examples/src/examples/compute/particles.example.mjs Removes manual compute bind-group/uniform formats to rely on reflection.
examples/src/examples/compute/edge-detect.example.mjs Removes manual computeBindGroupFormat and relies on reflection.
examples/src/examples/compute/edge-detect.compute-shader.wgsl Converts compute shader resource declarations to simplified syntax.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +177 to +187
// reverse of gpuTextureFormats: WGSL/GPU storage format string -> PIXELFORMAT. Built once, used to
// reflect storage texture declarations. Several PIXELFORMATs can map to the same string (e.g. RGB8
// and RGBA8 both -> 'rgba8unorm'); first-wins is fine as the chosen PIXELFORMAT only needs to
// round-trip back to the same string via gpuTextureFormats[...], which is what the bind group
// layout (and WebGPU validation) uses.
const gpuFormatToPixelFormat = new Map();
gpuTextureFormats.forEach((str, pixelFormat) => {
if (str && !gpuFormatToPixelFormat.has(str)) {
gpuFormatToPixelFormat.set(str, pixelFormat);
}
});
Comment on lines +34 to +39
const {
computeBindGroupFormat, computeUniformBufferFormats,
computeReflectedBindGroupFormat, computeReflectedUniformBufferFormat,
computeReflectedGroupIndex
} = shader.impl;

Comment on lines +462 to +465
* Process a compute shader: reflect its simplified-syntax declarations (loose `uniform`s,
* textures/samplers, storage buffers) into a single bind group at `reflectedGroupIndex`, leaving
* any explicitly-bound (`@group/@binding`) declarations untouched. The loose uniforms are
* collapsed into one generated uniform buffer (`ub_compute`) placed inside that same group.
Comment on lines +701 to +712
/**
* Converts parsed resource lines (textures, samplers, storage buffers) into an array of bind
* formats. Shared by the vertex/fragment path ({@link processResources}) and the compute path
* ({@link runCompute}); only the shader-stage visibility differs.
*
* @param {Array<ResourceLine>} resources - The parsed resource lines.
* @param {number} visibility - Shader stage visibility bit-flags for the created formats.
* @param {Shader} shader - The shader (for error reporting).
* @returns {Array<BindTextureFormat|BindStorageBufferFormat>} - The bind formats, in declaration
* order (a texture with a sampler consumes the following sampler line).
* @private
*/
Comment on lines 739 to +742
const readOnly = resource.accessMode !== 'read_write';
const bufferFormat = new BindStorageBufferFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, readOnly);
const bufferFormat = new BindStorageBufferFormat(resource.name, visibility, readOnly);
bufferFormat.format = resource.type;
textureFormats.push(bufferFormat);
formats.push(bufferFormat);
- gpuFormatToPixelFormat: use last-wins so the canonical PIXELFORMAT (e.g. RGBA8
  for 'rgba8unorm') is chosen, avoiding a needless bind group layout / pipeline
  cache split (the key uses the numeric PIXELFORMAT).
- Assert computeUniformBufferFormats requires a computeBindGroupFormat, instead of
  silently dropping the uniform buffers.
- STORAGE_BUFFER_REGEX: drop the invalid 'write' token (storage buffers only support
  read / read_write in WGSL).
- Sync runCompute and buildResourceFormats JSDoc to mention storage textures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WGSL compute shader - automatic bind slot assignments

2 participants