Skip to content

feat(dx12, hlsl-out) Support for SHADER_EARLY_DEPTH_TEST on DX12 (only non-MSAA framebuffers) - #10624

Open
DouglasDwyer wants to merge 2 commits into
gfx-rs:trunkfrom
DouglasDwyer:conservative-depth-dx12
Open

DouglasDwyer wants to merge 2 commits into
gfx-rs:trunkfrom
DouglasDwyer:conservative-depth-dx12

Conversation

@DouglasDwyer

@DouglasDwyer DouglasDwyer commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Connections

#3961 - Support for conservative depth
#10625 - feat(metal, msl-out) Support for SHADER_EARLY_DEPTH_TEST on Metal
gpuweb/gpuweb#5349 - [WGSL fragment depth] (less, greater, any) portability

Description

Right now, the early_depth_test intrinsic is supported on Vulkan, but not DX12 or Metal. WGSL code like this fails to compile:

@fragment
@early_depth_test(less_equal)
public fn foo(input: VertexOutput) -> FragmentOutput {
    ...
}

DX12 is capable of supporting conservative depth. In HLSL, instead of writing to SV_Depth, you can write to SV_DepthGreaterEqual or SV_DepthLessEqual. There is no equivalent for @early_depth_test(unchanged).

This PR makes Naga emit SV_DepthGreaterEqual/SV_DepthLessEqual in the appropriate cases. For @early_depth_test(unchanged), Naga makes sure that its output never actually modifies the real SV_Depth - instead, all depth writes are redirected to a private variable.

This PR does not support MSAA render pipelines yet

When conservative depth testing is enabled, DXC requires that the SV_Position fragment input be interpolated as either:

  • Centroid
  • Per-sample

Notably, pixel center is not allowed as an option. This is troublesome because the WebGPU spec always defines @builtin(position) to be at the pixel center, at least according to my reading. So choosing either of these options, for an MSAA framebuffer, would be incorrect.

As such, this PR will only emit conservative depth attributes when compiling for a 1-sample framebuffer. In other cases, it falls back to SV_Depth. But I'm open to reconsidering this approach if centroid/per-sample interpolation turn out to be allowed within the spec.

Testing

I have tested this in my game engine and it worked - there was a measurable improvement in frame times with @early_depth_test(less_equal) in one of my shaders.

Additionally, the following unit tests are added:

  • Added snapshot tests (naga/tests/in/wgsl/early-depth-test-*) that cover every @early_depth_test setting for HLSL:
    • force, greater_equal, less_equal, and unchanged on a bare frag_depth result.
    • A depth output in a struct shared by several entry points, both when they agree and when they don't.
    • unchanged with a color-plus-depth struct, a depth-only struct, and an unrestricted entry point sharing the struct.

Squash or Rebase?

Squash

LLM Use?

Yes - I used Claude to implement and test these changes.

Checklist

  • I self-reviewed and fully understand this PR.
  • CHANGELOG.md entries for the user-facing effects of this change are present.
  • The PR is minimal, and doesn't make sense to land as multiple PRs.
  • Commits are logically scoped and individually reviewable.
  • The PR description has enough context to understand the motivation and solution implemented.
  • (If applicable) WebGPU implementations built with wgpu may be affected behaviorally.
  • (If applicable) Validation and feature gates are in place to confine behavioral changes.
  • (If applicable) Tests demonstrate the validation and altered logic works.

@github-project-automation github-project-automation Bot moved this to To triage in Triage Oct 14, 2026
@DouglasDwyer DouglasDwyer changed the title feat(hlsl-out) Conservative depth dx12 feat(dx12, hlsl-out) Conservative depth dx12 Oct 14, 2026
@DouglasDwyer DouglasDwyer changed the title feat(dx12, hlsl-out) Conservative depth dx12 feat(dx12, hlsl-out) Support for SHADER_EARLY_DEPTH_TEST on DX12 Oct 14, 2026
@DouglasDwyer
DouglasDwyer force-pushed the conservative-depth-dx12 branch 2 times, most recently from b42231f to 57b880e Compare October 15, 2026 00:04
@DouglasDwyer DouglasDwyer changed the title feat(dx12, hlsl-out) Support for SHADER_EARLY_DEPTH_TEST on DX12 feat(dx12, hlsl-out) Support for SHADER_EARLY_DEPTH_TEST on DX12 (only non-MSAA framebuffers) Oct 15, 2026
@DouglasDwyer
DouglasDwyer force-pushed the conservative-depth-dx12 branch 3 times, most recently from 9f81cac to c19cefd Compare October 15, 2026 00:47
Naga's HLSL backend now implements `EarlyDepthTest`:

- `@early_depth_test(force)` emits the `[earlydepthstencil]` attribute on the
  fragment entry point.
- `@early_depth_test(greater_equal/less_equal)` changes the semantic of the
  `frag_depth` output from `SV_Depth` to `SV_DepthGreaterEqual` or
  `SV_DepthLessEqual`, but only for pipelines that are not multisampled. DXC
  only accepts these semantics when the `SV_Position` input is
  `noperspective centroid`, which is equivalent to the default pixel-center
  position only when there is one sample per pixel. Multisampled pipelines use
  an unrestricted `SV_Depth` and leave the position input alone.
- HLSL has no equivalent of `unchanged`. A depth output with that setting would
  be a no-op, so it is discarded instead of being written to `SV_Depth`, which
  would disable early depth tests. Entry points that return only the depth
  return `void`, and a result struct that also has other outputs is returned
  through a wrapper struct without the depth member.

Callers tell the backend whether the pipeline is multisampled with the new
`hlsl::PipelineOptions::multisampled`, which defaults to `false`. wgpu-hal sets
it from the sample count of the render pipeline.

The semantic is part of a struct's definition, so when several fragment entry
points share an output struct, the conservative semantic is only used if they
all agree on the setting. Otherwise `SV_Depth` is used, which is always correct.

The HLSL backend now advertises `Capabilities::EARLY_DEPTH_TEST`, and the DX12
adapter advertises the wgpu feature.
Add HLSL snapshots for `@early_depth_test`, including `force`, `greater_equal`,
`less_equal` and `unchanged`, and depth written through an output struct that is
shared by several entry points (both when they agree and when they do not).
Cover discarding the depth for `unchanged` when the result is only the depth,
a struct of only the depth, and a struct with other outputs.
@DouglasDwyer
DouglasDwyer force-pushed the conservative-depth-dx12 branch from c19cefd to db78b68 Compare October 15, 2026 00:52
@DouglasDwyer
DouglasDwyer marked this pull request as ready for review October 15, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

1 participant