Skip to content

Commit 4495770

Browse files
dzamkovWumpf
andauthored
Vulkan support for SHADER_EARLY_DEPTH_TEST and fix to conservative depth optimizations (#7676)
Co-authored-by: Andreas Reich <r_andreas2@web.de>
1 parent ff29165 commit 4495770

18 files changed

+256
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Naga now infers the correct binding layout when a resource appears only in an as
9191

9292
- Mark `readonly_and_readwrite_storage_textures` & `packed_4x8_integer_dot_product` language extensions as implemented. By @teoxoy in [#7543](https://github.com/gfx-rs/wgpu/pull/7543)
9393
- `naga::back::hlsl::Writer::new` has a new `pipeline_options` argument. `hlsl::PipelineOptions::default()` can be passed as a default. The `shader_stage` and `entry_point` members of `pipeline_options` can be used to write only a single entry point when using the HLSL and MSL backends (GLSL and SPIR-V already had this functionality). The Metal and DX12 HALs now write only a single entry point when loading shaders. By @andyleiserson in [#7626](https://github.com/gfx-rs/wgpu/pull/7626).
94+
- Implemented `early_depth_test` for SPIR-V backend, enabling `SHADER_EARLY_DEPTH_TEST` for Vulkan. Additionally, fixed conservative depth optimizations when using `early_depth_test`. The syntax for forcing early depth tests is now `@early_depth_test(force)` instead of `@early_depth_test`. By @dzamkov in [#7676](https://github.com/gfx-rs/wgpu/pull/7676).
9495

9596
#### D3D12
9697

naga/src/back/glsl/features.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,16 @@ impl<W> Writer<'_, W> {
300300
pub(super) fn collect_required_features(&mut self) -> BackendResult {
301301
let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
302302

303-
if let Some(depth_test) = self.entry_point.early_depth_test {
304-
// If IMAGE_LOAD_STORE is supported for this version of GLSL
305-
if self.options.version.supports_early_depth_test() {
306-
self.features.request(Features::IMAGE_LOAD_STORE);
307-
}
308-
309-
if depth_test.conservative.is_some() {
310-
self.features.request(Features::CONSERVATIVE_DEPTH);
303+
if let Some(early_depth_test) = self.entry_point.early_depth_test {
304+
match early_depth_test {
305+
crate::EarlyDepthTest::Force => {
306+
if self.options.version.supports_early_depth_test() {
307+
self.features.request(Features::IMAGE_LOAD_STORE);
308+
}
309+
}
310+
crate::EarlyDepthTest::Allow { .. } => {
311+
self.features.request(Features::CONSERVATIVE_DEPTH);
312+
}
311313
}
312314
}
313315

naga/src/back/glsl/mod.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -750,22 +750,23 @@ impl<'a, W: Write> Writer<'a, W> {
750750
}
751751

752752
// Enable early depth tests if needed
753-
if let Some(depth_test) = self.entry_point.early_depth_test {
753+
if let Some(early_depth_test) = self.entry_point.early_depth_test {
754754
// If early depth test is supported for this version of GLSL
755755
if self.options.version.supports_early_depth_test() {
756-
writeln!(self.out, "layout(early_fragment_tests) in;")?;
757-
758-
if let Some(conservative) = depth_test.conservative {
759-
use crate::ConservativeDepth as Cd;
760-
761-
let depth = match conservative {
762-
Cd::GreaterEqual => "greater",
763-
Cd::LessEqual => "less",
764-
Cd::Unchanged => "unchanged",
765-
};
766-
writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?;
756+
match early_depth_test {
757+
crate::EarlyDepthTest::Force => {
758+
writeln!(self.out, "layout(early_fragment_tests) in;")?;
759+
}
760+
crate::EarlyDepthTest::Allow { conservative, .. } => {
761+
use crate::ConservativeDepth as Cd;
762+
let depth = match conservative {
763+
Cd::GreaterEqual => "greater",
764+
Cd::LessEqual => "less",
765+
Cd::Unchanged => "unchanged",
766+
};
767+
writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?;
768+
}
767769
}
768-
writeln!(self.out)?;
769770
} else {
770771
log::warn!(
771772
"Early depth testing is not supported for this version of GLSL: {}",

naga/src/back/spv/writer.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,35 @@ impl Writer {
11331133
crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex,
11341134
crate::ShaderStage::Fragment => {
11351135
self.write_execution_mode(function_id, spirv::ExecutionMode::OriginUpperLeft)?;
1136+
match entry_point.early_depth_test {
1137+
Some(crate::EarlyDepthTest::Force) => {
1138+
self.write_execution_mode(
1139+
function_id,
1140+
spirv::ExecutionMode::EarlyFragmentTests,
1141+
)?;
1142+
}
1143+
Some(crate::EarlyDepthTest::Allow { conservative }) => {
1144+
// TODO: Consider emitting EarlyAndLateFragmentTestsAMD here, if available.
1145+
// https://github.khronos.org/SPIRV-Registry/extensions/AMD/SPV_AMD_shader_early_and_late_fragment_tests.html
1146+
// This permits early depth tests even if the shader writes to a storage
1147+
// binding
1148+
match conservative {
1149+
crate::ConservativeDepth::GreaterEqual => self.write_execution_mode(
1150+
function_id,
1151+
spirv::ExecutionMode::DepthGreater,
1152+
)?,
1153+
crate::ConservativeDepth::LessEqual => self.write_execution_mode(
1154+
function_id,
1155+
spirv::ExecutionMode::DepthLess,
1156+
)?,
1157+
crate::ConservativeDepth::Unchanged => self.write_execution_mode(
1158+
function_id,
1159+
spirv::ExecutionMode::DepthUnchanged,
1160+
)?,
1161+
}
1162+
}
1163+
None => {}
1164+
}
11361165
if let Some(ref result) = entry_point.function.result {
11371166
if contains_builtin(
11381167
result.binding.as_ref(),

naga/src/front/glsl/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,7 @@ impl Frontend {
13701370
ctx.module.entry_points.push(EntryPoint {
13711371
name: "main".to_string(),
13721372
stage: self.meta.stage,
1373-
early_depth_test: Some(crate::EarlyDepthTest { conservative: None })
1373+
early_depth_test: Some(crate::EarlyDepthTest::Force)
13741374
.filter(|_| self.meta.early_fragment_tests),
13751375
workgroup_size: self.meta.workgroup_size,
13761376
workgroup_size_overrides: None,

naga/src/front/spv/mod.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4825,24 +4825,49 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
48254825

48264826
match mode {
48274827
ExecutionMode::EarlyFragmentTests => {
4828-
if ep.early_depth_test.is_none() {
4829-
ep.early_depth_test = Some(crate::EarlyDepthTest { conservative: None });
4830-
}
4828+
ep.early_depth_test = Some(crate::EarlyDepthTest::Force);
48314829
}
48324830
ExecutionMode::DepthUnchanged => {
4833-
ep.early_depth_test = Some(crate::EarlyDepthTest {
4834-
conservative: Some(crate::ConservativeDepth::Unchanged),
4835-
});
4831+
if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test {
4832+
if let &mut crate::EarlyDepthTest::Allow {
4833+
ref mut conservative,
4834+
} = early_depth_test
4835+
{
4836+
*conservative = crate::ConservativeDepth::Unchanged;
4837+
}
4838+
} else {
4839+
ep.early_depth_test = Some(crate::EarlyDepthTest::Allow {
4840+
conservative: crate::ConservativeDepth::Unchanged,
4841+
});
4842+
}
48364843
}
48374844
ExecutionMode::DepthGreater => {
4838-
ep.early_depth_test = Some(crate::EarlyDepthTest {
4839-
conservative: Some(crate::ConservativeDepth::GreaterEqual),
4840-
});
4845+
if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test {
4846+
if let &mut crate::EarlyDepthTest::Allow {
4847+
ref mut conservative,
4848+
} = early_depth_test
4849+
{
4850+
*conservative = crate::ConservativeDepth::GreaterEqual;
4851+
}
4852+
} else {
4853+
ep.early_depth_test = Some(crate::EarlyDepthTest::Allow {
4854+
conservative: crate::ConservativeDepth::GreaterEqual,
4855+
});
4856+
}
48414857
}
48424858
ExecutionMode::DepthLess => {
4843-
ep.early_depth_test = Some(crate::EarlyDepthTest {
4844-
conservative: Some(crate::ConservativeDepth::LessEqual),
4845-
});
4859+
if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test {
4860+
if let &mut crate::EarlyDepthTest::Allow {
4861+
ref mut conservative,
4862+
} = early_depth_test
4863+
{
4864+
*conservative = crate::ConservativeDepth::LessEqual;
4865+
}
4866+
} else {
4867+
ep.early_depth_test = Some(crate::EarlyDepthTest::Allow {
4868+
conservative: crate::ConservativeDepth::LessEqual,
4869+
});
4870+
}
48464871
}
48474872
ExecutionMode::DepthReplacing => {
48484873
// Ignored because it can be deduced from the IR.

naga/src/front/wgsl/parse/mod.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,15 +2838,17 @@ impl Parser {
28382838
workgroup_size.set(new_workgroup_size, name_span)?;
28392839
}
28402840
"early_depth_test" => {
2841-
let conservative = if lexer.skip(Token::Paren('(')) {
2842-
let (ident, ident_span) = lexer.next_ident_with_span()?;
2843-
let value = conv::map_conservative_depth(ident, ident_span)?;
2844-
lexer.expect(Token::Paren(')'))?;
2845-
Some(value)
2841+
lexer.expect(Token::Paren('('))?;
2842+
let (ident, ident_span) = lexer.next_ident_with_span()?;
2843+
let value = if ident == "force" {
2844+
crate::EarlyDepthTest::Force
28462845
} else {
2847-
None
2846+
crate::EarlyDepthTest::Allow {
2847+
conservative: conv::map_conservative_depth(ident, ident_span)?,
2848+
}
28482849
};
2849-
early_depth_test.set(crate::EarlyDepthTest { conservative }, name_span)?;
2850+
lexer.expect(Token::Paren(')'))?;
2851+
early_depth_test.set(value, name_span)?;
28502852
}
28512853
"must_use" => {
28522854
must_use.set(name_span, name_span)?;

naga/src/ir/mod.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,19 +237,27 @@ use crate::{FastIndexMap, NamedExpressions};
237237

238238
pub use block::Block;
239239

240-
/// Early fragment tests.
240+
/// Explicitly allows early depth/stencil tests.
241241
///
242-
/// In a standard situation, if a driver determines that it is possible to switch on early depth test, it will.
242+
/// Normally, depth/stencil tests are performed after fragment shading. However, as an optimization,
243+
/// most drivers will move the depth/stencil tests before fragment shading if this does not
244+
/// have any observable consequences. This optimization is disabled under the following
245+
/// circumstances:
246+
/// - `discard` is called in the fragment shader.
247+
/// - The fragment shader writes to the depth buffer.
248+
/// - The fragment shader writes to any storage bindings.
243249
///
244-
/// Typical situations when early depth test is switched off:
245-
/// - Calling `discard` in a shader.
246-
/// - Writing to the depth buffer, unless ConservativeDepth is enabled.
250+
/// When `EarlyDepthTest` is set, it is allowed to perform an early depth/stencil test even if the
251+
/// above conditions are not met. When [`EarlyDepthTest::Force`] is used, depth/stencil tests
252+
/// **must** be performed before fragment shading.
247253
///
248-
/// To use in a shader:
254+
/// To force early depth/stencil tests in a shader:
249255
/// - GLSL: `layout(early_fragment_tests) in;`
250256
/// - HLSL: `Attribute earlydepthstencil`
251257
/// - SPIR-V: `ExecutionMode EarlyFragmentTests`
252-
/// - WGSL: `@early_depth_test`
258+
/// - WGSL: `@early_depth_test(force)`
259+
///
260+
/// This may also be enabled in a shader by specifying a [`ConservativeDepth`].
253261
///
254262
/// For more, see:
255263
/// - <https://www.khronos.org/opengl/wiki/Early_Fragment_Test#Explicit_specification>
@@ -259,8 +267,24 @@ pub use block::Block;
259267
#[cfg_attr(feature = "serialize", derive(Serialize))]
260268
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
261269
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
262-
pub struct EarlyDepthTest {
263-
pub conservative: Option<ConservativeDepth>,
270+
pub enum EarlyDepthTest {
271+
/// Requires depth/stencil tests to be performed before fragment shading.
272+
///
273+
/// This will disable depth/stencil tests after fragment shading, so discarding the fragment
274+
/// or overwriting the fragment depth will have no effect.
275+
Force,
276+
277+
/// Allows an additional depth/stencil test to be performed before fragment shading.
278+
///
279+
/// It is up to the driver to decide whether early tests are performed. Unlike `Force`, this
280+
/// does not disable depth/stencil tests after fragment shading.
281+
Allow {
282+
/// Specifies restrictions on how the depth value can be modified within the fragment
283+
/// shader.
284+
///
285+
/// This may be taken into account when deciding whether to perform early tests.
286+
conservative: ConservativeDepth,
287+
},
264288
}
265289

266290
/// Enables adjusting depth without disabling early Z.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
god_mode = true
2+
targets = "SPIRV | GLSL"
3+
4+
[glsl]
5+
version.Desktop = 420
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@fragment
2+
@early_depth_test(less_equal)
3+
fn main(@builtin(position) pos: vec4<f32>) -> @builtin(frag_depth) f32 {
4+
return pos.z - 0.1;
5+
}

0 commit comments

Comments
 (0)