Skip to content

Add RenderTarget depthResolveMode controlling multisampled depth resolve - #9226

Merged
mvaligursky merged 1 commit into
mainfrom
mv-depth-resolve-mode
Aug 25, 2026
Merged

Add RenderTarget depthResolveMode controlling multisampled depth resolve#9226
mvaligursky merged 1 commit into
mainfrom
mv-depth-resolve-mode

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

Adds control over how the samples of a multisampled depth buffer are resolved into a single depth value by the WebGPU shader-based depth resolve - used by the depth grab pass (sceneDepthMap), a depth RenderTarget#copy, and the automatic resolve into a provided depthBuffer.

const rt = new RenderTarget({ colorBuffer, depth: true, samples: 4, depthResolveMode: DEPTHRESOLVE_MAX });
rt.depthResolveMode = DEPTHRESOLVE_SAMPLE0;  // mutable at any time, used at resolve time

Behavior change: the default resolve changes from sample 0 to DEPTHRESOLVE_MIN. Sample 0 is an arbitrary sample position - at geometry silhouettes it lands on the foreground or background essentially at random per pixel, causing edge noise in depth-consuming effects such as soft particles. MIN (the nearest surface, with a standard depth buffer) is deterministic and conservative, and matches what depth consumers such as Hi-Z occlusion expect. DEPTHRESOLVE_SAMPLE0 remains available to restore the previous behavior.

Changes:

  • New constants DEPTHRESOLVE_MIN (default), DEPTHRESOLVE_MAX and DEPTHRESOLVE_SAMPLE0, and a mutable RenderTarget#depthResolveMode property with a matching constructor option. WebGPU only - on WebGL2 the depth resolve sample selection is defined by the implementation and the property is ignored.
  • WebgpuResolver supports all three modes, with shaders and pipelines lazily cached per mode and format. Its WGSL source is extracted into a shader chunk (shader-chunks/frag/webgpu-depth-resolve.js), with the mode selected by a DEPTH_RESOLVE_*** define handled by the shader preprocessor - the first of the internal device-level renderers to move its shader to the platform chunks.
  • Fixed a crash in copyRenderTarget when copying depth from a render target with a user-provided depthBuffer and samples > 1 - the multisampled depth of such a target is stored separately from the internally-allocated case, and the copy read a null texture.

Verified on WebGPU (Metal): a known-depth scene rendered into an MSAA target and resolved with each mode - at all 100 mixed-sample edge pixels, MIN returned the near surface depth, MAX the far clear value, and SAMPLE0 a value within that range; mode switching at runtime works. The ground-fog and particles-snow examples exercise the new default through the MSAA depth grab.

Checklist

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

@github-actions

Copy link
Copy Markdown

Public API report

This PR changes the public API surface (+6 / −1), per the docs' rules (@ignore / @Private / undocumented are excluded).

Show API diff
-RenderTarget.constructor(options?: { autoResolve: boolean; colorBuffer: Texture; colorBuffers: Texture[]; depth: boolean; depthBuffer: Texture; face: number; mipLevel: number; name: string; origin: string; resolveBuffer: Texture | null; resolveBuffers: Texture | null[]; samples: number; stencil: boolean; transientColor: boolean; transientDepth: boolean })
+RenderTarget.constructor(options?: { autoResolve: boolean; colorBuffer: Texture; colorBuffers: Texture[]; depth: boolean; depthBuffer: Texture; depthResolveMode: string; face: number; mipLevel: number; name: string; origin: string; resolveBuffer: Texture | null; resolveBuffers: Texture | null[]; samples: number; stencil: boolean; transientColor: boolean; transientDepth: boolean })
+RenderTarget.get depthResolveMode(): string
+RenderTarget.set depthResolveMode(value: string)
+const DEPTHRESOLVE_MAX: "max"
+const DEPTHRESOLVE_MIN: "min"
+const DEPTHRESOLVE_SAMPLE0: "sample0"

Informational only — this never fails the build.

@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2385.9 KB (+0.6 KB, +0.03%) 613.2 KB (+0.4 KB, +0.06%) 475.7 KB (+0.4 KB, +0.09%)
playcanvas.min.mjs 2383.3 KB (+0.6 KB, +0.03%) 611.9 KB (+0.4 KB, +0.07%) 475.1 KB (+0.4 KB, +0.08%)

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Automated PR review by Codex (GPT-5) at exact head 3ef58df615f34b9d79f8f40d6ffb3adffb043364.

No actionable issues found. I reviewed the public constants and RenderTarget API, the intentional default behavior change, all shader-based resolve call paths (scene depth grab, depth copy, and automatic resolve), user-provided versus internally allocated MSAA depth attachments, WGSL reduction logic, direction of min/max depth semantics, runtime mode switching, shader/pipeline cache keys and cleanup, destination formats, performance, generated types, tests, CI, and current discussion.

Verification performed:

  • Full unit suite: 2,429 passing, 2 pending
  • Focused render-target/WebGPU resolver tests: 40 passing
  • Changed-file ESLint: clean
  • Type generation and type tests: passing
  • Deployed ground-fog MSAA depth-grab path: rendered successfully on WebGPU with no relevant shader, pipeline, or validation errors
  • All required CI and deployment checks: green

Residual test risk: the automated resolver tests verify the preprocessed WGSL for all three modes, but do not numerically render and read back mixed-sample pixels on a real GPU. The PR description reports that numerical WebGPU coverage was performed manually; the deployed default MIN path also compiled and ran successfully during this review.

@mvaligursky
mvaligursky merged commit 363a2be into main Aug 25, 2026
10 checks passed
@mvaligursky
mvaligursky deleted the mv-depth-resolve-mode branch August 25, 2026 15:46
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.

1 participant