feat(webgpu): automatic bind-group reflection for compute shaders#8821
Merged
Conversation
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).
Contributor
There was a problem hiding this comment.
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.
This was referenced Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
computeBindGroupFormatis no longer required.Changes:
declarations (no
@group/@binding) are reflected automatically: looseuniforms(collapsed into a single generated uniform buffer), textures + samplers, storage
buffers, and storage textures.
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 arebyte-for-byte unaffected.
read_write(previously onlymatched
read/write); this is shared with the vertex/fragment reflector.API Changes:
Shaderdefinitions:computeBindGroupFormatandcomputeUniformBufferFormatsare now optional. Non-breaking — supplying them behavesexactly as before.
Examples:
edge-detectandparticlesto the simplified syntax (nocomputeBindGroupFormat).Fixes #7689