Skip to content

Allow sampling multisampled render targets on WebGPU - #9167

Open
SashaRX wants to merge 20 commits into
playcanvas:mainfrom
SashaRX:allow-sampling-msaa-rt-webgpu
Open

Allow sampling multisampled render targets on WebGPU#9167
SashaRX wants to merge 20 commits into
playcanvas:mainfrom
SashaRX:allow-sampling-msaa-rt-webgpu

Conversation

@SashaRX

@SashaRX SashaRX commented Aug 13, 2026

Copy link
Copy Markdown

Allows a multisampled render target to be sampled in a user shader on
WebGPU (textureLoad on texture_multisampled_2d /
texture_depth_multisampled_2d), so applications can implement custom
MSAA resolves the hardware box resolve cannot express.

const renderTarget = new RenderTarget({
    colorBuffer,
    samples: 4,
    bindMultisampled: true
});

device.scope.resolve('msColor').setValue(renderTarget.getMultisampledColorBuffer());
var msColor: texture_multisampled_2d<f32>;
let sample = textureLoad(msColor, pixel, sampleIndex);

Fixes #9059

Changes:

  • Multisampled color attachments can opt into texture-binding usage and
    be bound through BindGroupFormat.
  • MSAA color is stored (not discarded) when bindMultisampled is set,
    so a later pass can textureLoad the samples.
  • WGSL reflection understands texture_depth_multisampled_2d and emits
    the correct multisampled type.

API Changes:

  • RenderTarget option bindMultisampled (boolean, default false).
    WebGPU, samples > 1. Ignored if transientColor actually applies.

  • RenderTarget#bindMultisampled — resolved flag.

  • RenderTarget#getMultisampledColorBuffer(index = 0) — bind this
    Texture, not colorBuffer. Owned by the render target.

    The public bindable texture is the MSAA color attachment.
    Auto-allocated MS depth already has TEXTURE_BINDING, and
    texture_depth_multisampled_2d reflects; a public
    getMultisampledDepthBuffer() is a follow-up.

  • BindTextureFormat 7th parameter multisampled (default false).
    Forces no sampler; SAMPLETYPE_FLOAT becomes unfilterable-float.

No example added.

Checklist

  • I have read the contributing guidelines
  • My code follows the project's coding standards
  • This PR focuses on a single change

@SashaRX
SashaRX marked this pull request as draft August 13, 2026 14:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds WebGPU support for shader access to multisampled render-target color attachments for custom MSAA resolves.

Changes:

  • Exposes bindable multisampled color textures from render targets.
  • Adds multisampled texture bind-group and WGSL reflection support.
  • Preserves MSAA samples and adds unit coverage.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/platform/graphics/bind-group-format.js Adds multisampled texture formats.
src/platform/graphics/null/null-render-target.js Stubs multisampled color textures.
src/platform/graphics/render-pass.js Stores bindable MSAA samples.
src/platform/graphics/render-target.js Adds the render-target API.
src/platform/graphics/texture.js Tracks texture sample counts.
src/platform/graphics/webgpu/webgpu-bind-group-format.js Emits multisampled layouts.
src/platform/graphics/webgpu/webgpu-render-target.js Creates bindable MSAA attachments.
src/platform/graphics/webgpu/webgpu-shader-processor-wgsl.js Reflects multisampled WGSL textures.
src/platform/graphics/webgpu/webgpu-texture.js Wraps render-target-owned textures.
test/platform/graphics/bind-group-format.test.mjs Tests format behavior.
test/platform/graphics/render-pass.test.mjs Tests MSAA storage.
test/platform/graphics/render-target.test.mjs Tests the public API.
test/platform/graphics/webgpu/webgpu-bind-group-format.test.mjs Tests WebGPU layouts.
test/platform/graphics/webgpu/webgpu-render-target.test.mjs Tests texture usage flags.
test/platform/graphics/webgpu/webgpu-shader-processor-wgsl-compute.test.mjs Tests WGSL reflection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +185 to +188
if (multisampled) {
Debug.assert(textureDimension === TEXTUREDIMENSION_2D, `Multisampled texture binding '${name}' requires TEXTUREDIMENSION_2D.`);
Debug.assert(!hasSampler, `Multisampled texture binding '${name}' cannot have a sampler.`);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed — this assert contradicted the documented API.

hasSampler defaults to true, so the natural call new BindTextureFormat(name, visibility, dim, type, undefined, undefined, true) (or just the 7th argument) hit Debug.assert(!hasSampler) in debug builds even though we already coerce this.hasSampler = false.

Removed the hasSampler assert. Coercion stays (multisampled still forces hasSampler = false and samplerName = null). The TEXTUREDIMENSION_2D assert stays — that one is a real WebGPU invariant, not a default-parameter clash.

Shader reflection already strips a following sampler (hasSampler && !resource.multisampled) and asserts at the WGSL source if someone declares sampler after a multisampled texture.

Comment on lines 322 to +324
this._storage = options.storage ?? false;
this._samples = options.samples ?? 1;
this._importedGpuTexture = options._importedGpuTexture ?? null;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed — Texture.samples is not a public way to allocate an MSAA texture, and we should not pretend it is.

WebgpuTexture.create still hardcodes sampleCount: 1 for a normal allocation. The sample count is only meaningful on the wrapper around the render target’s existing MSAA GPUTexture.

What we did:

  • Dropped options.samples from the public Texture constructor JSDoc and from API Changes.
  • Stopped reading options.samples entirely. new Texture(device, { samples: 4 }) now stays at sample count 1, matching the GPU texture that is actually created.
  • The wrapper uses an internal _samples option together with _importedGpuTexture (both @ignore). Texture#samples stays @ignore so getMultisampledColorBuffer() can still describe the imported GPU texture to WebGPU (sampleCount on the adopted descriptor).

We are not adding public multisampled Texture allocation in this PR.

Comment thread src/platform/graphics/render-target.js
@willeastcott willeastcott added enhancement Request for a new feature area: graphics Graphics related issue labels Aug 13, 2026
@SashaRX
SashaRX marked this pull request as ready for review August 13, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: graphics Graphics related issue enhancement Request for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebGPU: allow sampling multisampled render targets (custom MSAA resolves for HDR color + depth)

3 participants