Skip to content

Commit 4465f25

Browse files
authored
Add MAY_DISCARD shader def, enabling early depth tests for most cases (#6697)
# Objective - Right now we can't really benefit from [early depth testing](https://www.khronos.org/opengl/wiki/Early_Fragment_Test) in our PBR shader because it includes codepaths with `discard`, even for situations where they are not necessary. ## Solution - This PR introduces a new `MeshPipelineKey` and shader def, `MAY_DISCARD`; - All possible material/mesh options that that may result in `discard`s being needed must set `MAY_DISCARD` ahead of time: - Right now, this is only `AlphaMode::Mask(f32)`, but in the future might include other options/effects; (e.g. one effect I'm personally interested in is bayer dither pseudo-transparency for LOD transitions of opaque meshes) - Shader codepaths that can `discard` are guarded by an `#ifdef MAY_DISCARD` preprocessor directive: - Right now, this is just one branch in `alpha_discard()`; - If `MAY_DISCARD` is _not_ set, the `@early_depth_test` attribute is added to the PBR fragment shader. This is a not yet documented, possibly non-standard WGSL extension I found browsing Naga's source code. [I opened a PR to document it there](gfx-rs/naga#2132). My understanding is that for backends where this attribute is supported, it will force an explicit opt-in to early depth test. (e.g. via `layout(early_fragment_tests) in;` in GLSL) ## Caveats - I included `@early_depth_test` for the sake of us being explicit, and avoiding the need for the driver to be “smart” about enabling this feature. That way, if we make a mistake and include a `discard` unguarded by `MAY_DISCARD`, it will either produce errors or noticeable visual artifacts so that we'll catch early, instead of causing a performance regression. - I'm not sure explicit early depth test is supported on the naga Metal backend, which is what I'm currently using, so I can't really test the explicit early depth test enable, I would like others with Vulkan/GL hardware to test it if possible; - I would like some guidance on how to measure/verify the performance benefits of this; - If I understand it correctly, this, or _something like this_ is needed to fully reap the performance gains enabled by #6284; - This will _most definitely_ conflict with #6284 and #6644. I can fix the conflicts as needed, depending on whether/the order they end up being merging in. --- ## Changelog ### Changed - Early depth tests are now enabled whenever possible for meshes using `StandardMaterial`, reducing the number of fragments evaluated for scenes with lots of occlusions.
1 parent 1ff4b98 commit 4465f25

File tree

6 files changed

+39
-41
lines changed

6 files changed

+39
-41
lines changed

crates/bevy_pbr/src/material.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ pub fn queue_material_meshes<M: Material>(
472472
AlphaMode::Multiply => {
473473
mesh_key |= MeshPipelineKey::BLEND_MULTIPLY;
474474
}
475+
AlphaMode::Mask(_) => {
476+
mesh_key |= MeshPipelineKey::MAY_DISCARD;
477+
}
475478
_ => (),
476479
}
477480

crates/bevy_pbr/src/prepass/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,8 @@ where
364364
shader_defs.push("DEPTH_PREPASS".into());
365365
}
366366

367-
if key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK) {
368-
shader_defs.push("ALPHA_MASK".into());
367+
if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) {
368+
shader_defs.push("MAY_DISCARD".into());
369369
}
370370

371371
let blend_key = key
@@ -467,9 +467,7 @@ where
467467
// is enabled or the material uses alpha cutoff values and doesn't rely on the standard
468468
// prepass shader
469469
let fragment_required = !targets.is_empty()
470-
|| ((key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK)
471-
|| blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA
472-
|| blend_key == MeshPipelineKey::BLEND_ALPHA)
470+
|| (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
473471
&& self.material_fragment_shader.is_some());
474472

475473
let fragment = fragment_required.then(|| {
@@ -967,7 +965,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
967965
let alpha_mode = material.properties.alpha_mode;
968966
match alpha_mode {
969967
AlphaMode::Opaque => {}
970-
AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::ALPHA_MASK,
968+
AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::MAY_DISCARD,
971969
AlphaMode::Blend
972970
| AlphaMode::Premultiplied
973971
| AlphaMode::Add

crates/bevy_pbr/src/render/light.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,11 +1608,11 @@ pub fn queue_shadows<M: Material>(
16081608
}
16091609
let alpha_mode = material.properties.alpha_mode;
16101610
match alpha_mode {
1611-
AlphaMode::Mask(_) => {
1612-
mesh_key |= MeshPipelineKey::ALPHA_MASK;
1613-
}
1614-
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add => {
1615-
mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA;
1611+
AlphaMode::Mask(_)
1612+
| AlphaMode::Blend
1613+
| AlphaMode::Premultiplied
1614+
| AlphaMode::Add => {
1615+
mesh_key |= MeshPipelineKey::MAY_DISCARD;
16161616
}
16171617
_ => {}
16181618
}

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,8 @@ bitflags::bitflags! {
582582
const DEPTH_PREPASS = (1 << 3);
583583
const NORMAL_PREPASS = (1 << 4);
584584
const MOTION_VECTOR_PREPASS = (1 << 5);
585-
const ALPHA_MASK = (1 << 6);
585+
const MAY_DISCARD = (1 << 6); // Guards shader codepaths that may discard, allowing early depth tests in most cases
586+
// See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test
586587
const ENVIRONMENT_MAP = (1 << 7);
587588
const DEPTH_CLAMP_ORTHO = (1 << 8);
588589
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
@@ -795,6 +796,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
795796
}
796797
}
797798

799+
if key.contains(MeshPipelineKey::MAY_DISCARD) {
800+
shader_defs.push("MAY_DISCARD".into());
801+
}
802+
798803
if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
799804
shader_defs.push("ENVIRONMENT_MAP".into());
800805
}

crates/bevy_pbr/src/render/pbr_functions.wgsl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@ fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f3
1414
if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
1515
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
1616
color.a = 1.0;
17-
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
17+
}
18+
19+
#ifdef MAY_DISCARD
20+
else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
1821
if color.a >= material.alpha_cutoff {
1922
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
2023
color.a = 1.0;
2124
} else {
22-
// NOTE: output_color.a < in.material.alpha_cutoff should not is not rendered
23-
// NOTE: This and any other discards mean that early-z testing cannot be done!
25+
// NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
2426
discard;
2527
}
2628
}
29+
#endif
30+
2731
return color;
2832
}
2933

crates/bevy_pbr/src/render/pbr_prepass.wgsl

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,7 @@ const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
3030
// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff
3131
fn prepass_alpha_discard(in: FragmentInput) {
3232

33-
// This is a workaround since the preprocessor does not support
34-
// #if defined(ALPHA_MASK) || defined(BLEND_PREMULTIPLIED_ALPHA)
35-
#ifndef ALPHA_MASK
36-
#ifndef BLEND_PREMULTIPLIED_ALPHA
37-
#ifndef BLEND_ALPHA
38-
39-
#define EMPTY_PREPASS_ALPHA_DISCARD
40-
41-
#endif // BLEND_ALPHA
42-
#endif // BLEND_PREMULTIPLIED_ALPHA not defined
43-
#endif // ALPHA_MASK not defined
44-
45-
#ifndef EMPTY_PREPASS_ALPHA_DISCARD
33+
#ifdef MAY_DISCARD
4634
var output_color: vec4<f32> = material.base_color;
4735

4836
#ifdef VERTEX_UVS
@@ -51,22 +39,22 @@ fn prepass_alpha_discard(in: FragmentInput) {
5139
}
5240
#endif // VERTEX_UVS
5341

54-
#ifdef ALPHA_MASK
55-
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) && output_color.a < material.alpha_cutoff {
56-
discard;
57-
}
58-
#else // BLEND_PREMULTIPLIED_ALPHA || BLEND_ALPHA
5942
let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
60-
if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD)
61-
&& output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
62-
discard;
63-
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED
64-
&& all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
65-
discard;
43+
if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
44+
if output_color.a < material.alpha_cutoff {
45+
discard;
46+
}
47+
} else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) {
48+
if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
49+
discard;
50+
}
51+
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED {
52+
if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
53+
discard;
54+
}
6655
}
67-
#endif // !ALPHA_MASK
6856

69-
#endif // EMPTY_PREPASS_ALPHA_DISCARD not defined
57+
#endif // MAY_DISCARD
7058
}
7159

7260
#ifdef PREPASS_FRAGMENT

0 commit comments

Comments
 (0)