Skip to content

WebGPU: Allow read_write storage buffers and atomic ops in non-compute stages#33626

Open
SebastianBaltes wants to merge 2 commits into
mrdoob:devfrom
SebastianBaltes:patch/fragment-shader-atomics
Open

WebGPU: Allow read_write storage buffers and atomic ops in non-compute stages#33626
SebastianBaltes wants to merge 2 commits into
mrdoob:devfrom
SebastianBaltes:patch/fragment-shader-atomics

Conversation

@SebastianBaltes
Copy link
Copy Markdown

Related issue: #31478

Description

The WebGPU specification explicitly allows GPUBufferBindingType.Storage (read_write) in vertex and fragment shader stages — not only in compute. However, two places in three.js currently restrict storage buffer write access and atomic operations to compute shaders only.

Changes

1. WGSLNodeBuilder.getNodeAccess() — Remove incorrect warning

The method already returns NodeAccess.READ_WRITE for atomic nodes in non-compute stages, which is the correct behavior. But it also logs:

"WebGPURenderer: Atomic operations are only supported in compute shaders."

This warning is misleading — atomic operations on storage buffers are valid in all shader stages per the WebGPU spec (§ Buffer Binding Types). The warning is removed; the logic is unchanged.

2. WebGPUBindingUtils — Use node access instead of stage visibility for buffer binding type

Previously, the GPU binding layout was chosen based on GPUShaderStage.COMPUTE visibility:

  • Compute stage → respect binding.access (Storage vs ReadOnlyStorage)
  • All other stages → always ReadOnlyStorage

This prevented vertex/fragment shaders from getting GPUBufferBindingType.Storage even when READ_WRITE access was explicitly requested. The fix uses binding.access directly regardless of stage — the same logic that was already used for the compute path.

Use case

This enables patterns such as deferred rendering pipelines where fragment shaders use atomicOr() to collect per-pixel bitmasks into storage buffers. Without this fix, any use of atomic operations or read_write storage buffers outside compute shaders is silently broken at the binding level.

Spec reference

…e stages

The WebGPU spec explicitly allows GPUBufferBindingType.Storage (read_write)
in vertex and fragment shader stages. However, two places in three.js
currently restrict storage buffer write access and atomic operations to
compute shaders only:

1. WGSLNodeBuilder.getNodeAccess() logs a warning claiming atomic ops are
   "only supported in compute shaders" — this is incorrect per the spec.
   The code already returns the correct READ_WRITE access; only the
   misleading warning is removed.

2. WebGPUBindingUtils creates the GPU binding layout based on shader stage
   visibility rather than the node's declared access. Non-compute stages
   always received ReadOnlyStorage, even when READ_WRITE was requested.
   The fix uses the node's access property directly, matching the logic
   already used for compute stages.

This enables use cases such as deferred rendering pipelines that use
atomicOr() in fragment shaders to collect per-pixel data into storage
buffers — a pattern that is valid per the WebGPU specification.

Related: mrdoob#31478

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2026

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 365.43
86.79
365.43
86.79
+0 B
+0 B
WebGPU 660.98
182.51
660.96
182.5
-25 B
-12 B
WebGPU Nodes 659.1
182.23
659.08
182.22
-25 B
-11 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 498.3
121.53
498.3
121.53
+0 B
+0 B
WebGPU 732.54
197.36
732.51
197.35
-26 B
-11 B
WebGPU Nodes 681.76
184.75
681.73
184.74
-26 B
-12 B

…e stages

The previous change was too broad — it set GPUBufferBindingType.Storage
for all READ_WRITE storage buffers regardless of shader stage. This caused
GPUValidationError in compute+render examples because the WGSL code uses
var<storage, read> in non-compute stages, which requires ReadOnlyStorage
in the binding layout.

The fix keeps the GPUShaderStage.COMPUTE check for the general case and
adds a targeted condition for atomic buffers: when binding.nodeUniform
is atomic, the fragment shader uses var<storage, read_write> and needs
GPUBufferBindingType.Storage in the layout to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sunag
Copy link
Copy Markdown
Collaborator

sunag commented May 24, 2026

atomic operations on storage buffers are valid in all shader stages per the WebGPU spec (§ Buffer Binding Types)

Did you test it in the vertex shader?
Maybe we should consider the fragment stage, and improve the description to make it warn when used only in vertex?

https://www.w3.org/TR/WGSL/#atomic-builtin-functions

image

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.

2 participants