Skip to content

Commit

Permalink
Native unclipped depth on supported platforms (#16095)
Browse files Browse the repository at this point in the history
# Objective
- Fixes #16078

## Solution

- Rename things to clarify that we _want_ unclipped depth for
directional light shadow views, and need some way of disabling the GPU's
builtin depth clipping
- Use DEPTH_CLIP_CONTROL instead of the fragment shader emulation on
supported platforms
- Pass only the clip position depth instead of the whole clip position
between vertex->fragment shader (no idea if this helps performance or
not, compiler might optimize it anyways)
- Meshlets
- HW raster always uses DEPTH_CLIP_CONTROL since it targets a more
limited set of platforms
- SW raster was not handling DEPTH_CLAMP_ORTHO correctly, it ended up
pretty much doing nothing.
- This PR made me realize that SW raster technically should have depth
clipping for all views that are not directional light shadows, but I
decided not to bother writing it. I'm not sure that it ever matters in
practice. If proven otherwise, I can add it.

## Testing

- Did you test these changes? If so, how?
- Lighting example. Both opaque (no fragment shader) and alpha masked
geometry (fragment shader emulation) are working with
depth_clip_control, and both work when it's turned off. Also tested
meshlet example.
- Are there any parts that need more testing?
  - Performance. I can't figure out a good test scene.
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- Toggle depth_clip_control_supported in prepass/mod.rs line 323 to turn
this PR on or off.
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - Native

---

## Migration Guide
- `MeshPipelineKey::DEPTH_CLAMP_ORTHO` is now
`MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO`
- The `DEPTH_CLAMP_ORTHO` shaderdef has been renamed to
`UNCLIPPED_DEPTH_ORTHO_EMULATION`
- `clip_position_unclamped: vec4<f32>` is now `unclipped_depth: f32`
  • Loading branch information
JMS55 authored Dec 3, 2024
1 parent f375422 commit d221665
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 105 deletions.
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/meshlet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ impl MeshletPlugin {
WgpuFeatures::SHADER_INT64_ATOMIC_MIN_MAX
| WgpuFeatures::SHADER_INT64
| WgpuFeatures::SUBGROUP
| WgpuFeatures::DEPTH_CLIP_CONTROL
| WgpuFeatures::PUSH_CONSTANTS
}
}
Expand Down
48 changes: 7 additions & 41 deletions crates/bevy_pbr/src/meshlet/pipelines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ pub struct MeshletPipelines {
downsample_depth_second_shadow_view: CachedComputePipelineId,
visibility_buffer_software_raster: CachedComputePipelineId,
visibility_buffer_software_raster_depth_only: CachedComputePipelineId,
visibility_buffer_software_raster_depth_only_clamp_ortho: CachedComputePipelineId,
visibility_buffer_hardware_raster: CachedRenderPipelineId,
visibility_buffer_hardware_raster_depth_only: CachedRenderPipelineId,
visibility_buffer_hardware_raster_depth_only_clamp_ortho: CachedRenderPipelineId,
visibility_buffer_hardware_raster_depth_only_unclipped: CachedRenderPipelineId,
resolve_depth: CachedRenderPipelineId,
resolve_depth_shadow_view: CachedRenderPipelineId,
resolve_material_depth: CachedRenderPipelineId,
Expand Down Expand Up @@ -215,29 +214,6 @@ impl FromWorld for MeshletPipelines {
},
),

visibility_buffer_software_raster_depth_only_clamp_ortho: pipeline_cache
.queue_compute_pipeline(ComputePipelineDescriptor {
label: Some(
"meshlet_visibility_buffer_software_raster_depth_only_clamp_ortho_pipeline"
.into(),
),
layout: vec![visibility_buffer_raster_layout.clone()],
push_constant_ranges: vec![],
shader: MESHLET_VISIBILITY_BUFFER_SOFTWARE_RASTER_SHADER_HANDLE,
shader_defs: vec![
"MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(),
"DEPTH_CLAMP_ORTHO".into(),
if remap_1d_to_2d_dispatch_layout.is_some() {
"MESHLET_2D_DISPATCH"
} else {
""
}
.into(),
],
entry_point: "rasterize_cluster".into(),
zero_initialize_workgroup_memory: false,
}),

visibility_buffer_hardware_raster: pipeline_cache.queue_render_pipeline(
RenderPipelineDescriptor {
label: Some("meshlet_visibility_buffer_hardware_raster_pipeline".into()),
Expand Down Expand Up @@ -324,10 +300,10 @@ impl FromWorld for MeshletPipelines {
},
),

visibility_buffer_hardware_raster_depth_only_clamp_ortho: pipeline_cache
visibility_buffer_hardware_raster_depth_only_unclipped: pipeline_cache
.queue_render_pipeline(RenderPipelineDescriptor {
label: Some(
"meshlet_visibility_buffer_hardware_raster_depth_only_clamp_ortho_pipeline"
"meshlet_visibility_buffer_hardware_raster_depth_only_unclipped_pipeline"
.into(),
),
layout: vec![visibility_buffer_raster_layout],
Expand All @@ -337,10 +313,7 @@ impl FromWorld for MeshletPipelines {
}],
vertex: VertexState {
shader: MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE,
shader_defs: vec![
"MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(),
"DEPTH_CLAMP_ORTHO".into(),
],
shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into()],
entry_point: "vertex".into(),
buffers: vec![],
},
Expand All @@ -349,18 +322,15 @@ impl FromWorld for MeshletPipelines {
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
unclipped_depth: true,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: MultisampleState::default(),
fragment: Some(FragmentState {
shader: MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE,
shader_defs: vec![
"MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(),
"DEPTH_CLAMP_ORTHO".into(),
],
shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into()],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::R8Uint,
Expand Down Expand Up @@ -484,7 +454,6 @@ impl MeshletPipelines {
&ComputePipeline,
&ComputePipeline,
&ComputePipeline,
&ComputePipeline,
&RenderPipeline,
&RenderPipeline,
&RenderPipeline,
Expand All @@ -506,14 +475,11 @@ impl MeshletPipelines {
pipeline_cache.get_compute_pipeline(pipeline.visibility_buffer_software_raster)?,
pipeline_cache
.get_compute_pipeline(pipeline.visibility_buffer_software_raster_depth_only)?,
pipeline_cache.get_compute_pipeline(
pipeline.visibility_buffer_software_raster_depth_only_clamp_ortho,
)?,
pipeline_cache.get_render_pipeline(pipeline.visibility_buffer_hardware_raster)?,
pipeline_cache
.get_render_pipeline(pipeline.visibility_buffer_hardware_raster_depth_only)?,
pipeline_cache.get_render_pipeline(
pipeline.visibility_buffer_hardware_raster_depth_only_clamp_ortho,
pipeline.visibility_buffer_hardware_raster_depth_only_unclipped,
)?,
pipeline_cache.get_render_pipeline(pipeline.resolve_depth)?,
pipeline_cache.get_render_pipeline(pipeline.resolve_depth_shadow_view)?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ struct VertexOutput {
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
@location(0) @interpolate(flat) packed_ids: u32,
#endif
#ifdef DEPTH_CLAMP_ORTHO
@location(0) unclamped_clip_depth: f32,
#endif
}

@vertex
Expand All @@ -45,19 +42,12 @@ fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) v
let vertex_position = get_meshlet_vertex_position(&meshlet, vertex_id);
let world_from_local = affine3_to_square(instance_uniform.world_from_local);
let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0));
var clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
#ifdef DEPTH_CLAMP_ORTHO
let unclamped_clip_depth = clip_position.z;
clip_position.z = min(clip_position.z, 1.0);
#endif
let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);

return VertexOutput(
clip_position,
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
(cluster_id << 7u) | triangle_id,
#endif
#ifdef DEPTH_CLAMP_ORTHO
unclamped_clip_depth,
#endif
);
}
Expand All @@ -70,9 +60,6 @@ fn fragment(vertex_output: VertexOutput) {
let depth = bitcast<u32>(vertex_output.position.z);
let visibility = (u64(depth) << 32u) | u64(vertex_output.packed_ids);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility);
#else ifdef DEPTH_CLAMP_ORTHO
let depth = bitcast<u32>(vertex_output.unclamped_clip_depth);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth);
#else
let depth = bitcast<u32>(vertex_output.position.z);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth);
Expand All @@ -84,9 +71,6 @@ fn dummy_vertex() -> VertexOutput {
vec4(divide(0.0, 0.0)), // NaN vertex position
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
0u,
#endif
#ifdef DEPTH_CLAMP_ORTHO
0.0,
#endif
);
}
Expand Down
26 changes: 9 additions & 17 deletions crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ impl Node for MeshletVisibilityBufferRasterPassNode {
downsample_depth_second_shadow_view_pipeline,
visibility_buffer_software_raster_pipeline,
visibility_buffer_software_raster_depth_only_pipeline,
visibility_buffer_software_raster_depth_only_clamp_ortho,
visibility_buffer_hardware_raster_pipeline,
visibility_buffer_hardware_raster_depth_only_pipeline,
visibility_buffer_hardware_raster_depth_only_clamp_ortho,
visibility_buffer_hardware_raster_depth_only_unclipped_pipeline,
resolve_depth_pipeline,
resolve_depth_shadow_view_pipeline,
resolve_material_depth_pipeline,
Expand Down Expand Up @@ -223,19 +222,12 @@ impl Node for MeshletVisibilityBufferRasterPassNode {
continue;
};

let (
shadow_visibility_buffer_software_raster_pipeline,
shadow_visibility_buffer_hardware_raster_pipeline,
) = match light_type {
LightEntity::Directional { .. } => (
visibility_buffer_software_raster_depth_only_clamp_ortho,
visibility_buffer_hardware_raster_depth_only_clamp_ortho,
),
_ => (
visibility_buffer_software_raster_depth_only_pipeline,
visibility_buffer_hardware_raster_depth_only_pipeline,
),
};
let shadow_visibility_buffer_hardware_raster_pipeline =
if let LightEntity::Directional { .. } = light_type {
visibility_buffer_hardware_raster_depth_only_unclipped_pipeline
} else {
visibility_buffer_hardware_raster_depth_only_pipeline
};

render_context.command_encoder().push_debug_group(&format!(
"meshlet_visibility_buffer_raster: {}",
Expand Down Expand Up @@ -270,7 +262,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode {
&meshlet_view_resources.dummy_render_target.default_view,
meshlet_view_bind_groups,
view_offset,
shadow_visibility_buffer_software_raster_pipeline,
visibility_buffer_software_raster_depth_only_pipeline,
shadow_visibility_buffer_hardware_raster_pipeline,
None,
meshlet_view_resources.raster_cluster_rightmost_slot,
Expand Down Expand Up @@ -306,7 +298,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode {
&meshlet_view_resources.dummy_render_target.default_view,
meshlet_view_bind_groups,
view_offset,
shadow_visibility_buffer_software_raster_pipeline,
visibility_buffer_software_raster_depth_only_pipeline,
shadow_visibility_buffer_hardware_raster_pipeline,
None,
meshlet_view_resources.raster_cluster_rightmost_slot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ fn rasterize_cluster(
// Project vertex to viewport space
let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0));
let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
var ndc_position = clip_position.xyz / clip_position.w;
#ifdef DEPTH_CLAMP_ORTHO
ndc_position.z = 1.0 / clip_position.z;
#endif
let ndc_position = clip_position.xyz / clip_position.w;
let viewport_position_xy = ndc_to_uv(ndc_position.xy) * view.viewport.zw;

// Write vertex to workgroup shared memory
Expand Down Expand Up @@ -176,9 +173,6 @@ fn write_visibility_buffer_pixel(x: f32, y: f32, z: f32, packed_ids: u32) {
let depth = bitcast<u32>(z);
let visibility = (u64(depth) << 32u) | u64(packed_ids);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility);
#else ifdef DEPTH_CLAMP_ORTHO
let depth = bitcast<u32>(1.0 / z);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth);
#else
let depth = bitcast<u32>(z);
atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth);
Expand Down
28 changes: 22 additions & 6 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ pub struct PrepassPipeline<M: Material> {
pub deferred_material_vertex_shader: Option<Handle<Shader>>,
pub deferred_material_fragment_shader: Option<Handle<Shader>>,
pub material_pipeline: MaterialPipeline<M>,
pub depth_clip_control_supported: bool,
_marker: PhantomData<M>,
}

Expand Down Expand Up @@ -289,6 +290,10 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {

let mesh_pipeline = world.resource::<MeshPipeline>();

let depth_clip_control_supported = render_device
.features()
.contains(WgpuFeatures::DEPTH_CLIP_CONTROL);

PrepassPipeline {
view_layout_motion_vectors,
view_layout_no_motion_vectors,
Expand All @@ -315,6 +320,7 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
},
material_layout: M::bind_group_layout(render_device),
material_pipeline: world.resource::<MaterialPipeline<M>>().clone(),
depth_clip_control_supported,
_marker: PhantomData,
}
}
Expand Down Expand Up @@ -379,8 +385,14 @@ where
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}

if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) {
shader_defs.push("DEPTH_CLAMP_ORTHO".into());
// For directional light shadow map views, use unclipped depth via either the native GPU feature,
// or emulated by setting depth in the fragment shader for GPUs that don't support it natively.
let emulate_unclipped_depth = key
.mesh_key
.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
&& !self.depth_clip_control_supported;
if emulate_unclipped_depth {
shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into());
// PERF: This line forces the "prepass fragment shader" to always run in
// common scenarios like "directional light calculation". Doing so resolves
// a pretty nasty depth clamping bug, but it also feels a bit excessive.
Expand All @@ -389,6 +401,10 @@ where
// https://github.com/bevyengine/bevy/pull/8877
shader_defs.push("PREPASS_FRAGMENT".into());
}
let unclipped_depth = key
.mesh_key
.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
&& self.depth_clip_control_supported;

if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
Expand Down Expand Up @@ -488,10 +504,10 @@ where
}

// The fragment shader is only used when the normal prepass or motion vectors prepass
// is enabled or the material uses alpha cutoff values and doesn't rely on the standard
// prepass shader or we are clamping the orthographic depth.
// is enabled, the material uses alpha cutoff values and doesn't rely on the standard
// prepass shader, or we are emulating unclipped depth in the fragment shader.
let fragment_required = !targets.is_empty()
|| key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO)
|| emulate_unclipped_depth
|| (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
&& self.prepass_material_fragment_shader.is_some());

Expand Down Expand Up @@ -544,7 +560,7 @@ where
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
unclipped_depth,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
Expand Down
14 changes: 7 additions & 7 deletions crates/bevy_pbr/src/prepass/prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {

out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0));
out.position = position_world_to_clip(out.world_position.xyz);
#ifdef DEPTH_CLAMP_ORTHO
out.clip_position_unclamped = out.position;
out.position.z = min(out.position.z, 1.0);
#endif // DEPTH_CLAMP_ORTHO
#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION
out.unclipped_depth = out.position.z;
out.position.z = min(out.position.z, 1.0); // Clamp depth to avoid clipping
#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION

#ifdef VERTEX_UVS_A
out.uv = vertex.uv;
Expand Down Expand Up @@ -173,9 +173,9 @@ fn fragment(in: VertexOutput) -> FragmentOutput {
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
#endif

#ifdef DEPTH_CLAMP_ORTHO
out.frag_depth = in.clip_position_unclamped.z;
#endif // DEPTH_CLAMP_ORTHO
#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION
out.frag_depth = in.unclipped_depth;
#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION

#ifdef MOTION_VECTOR_PREPASS
let clip_position_t = view.unjittered_clip_from_world * in.world_position;
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_pbr/src/prepass/prepass_io.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ struct VertexOutput {
@location(5) previous_world_position: vec4<f32>,
#endif

#ifdef DEPTH_CLAMP_ORTHO
@location(6) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION
@location(6) unclipped_depth: f32,
#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
@location(7) instance_index: u32,
#endif
Expand All @@ -87,8 +87,8 @@ struct FragmentOutput {
@location(3) deferred_lighting_pass_id: u32,
#endif

#ifdef DEPTH_CLAMP_ORTHO
#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION
@builtin(frag_depth) frag_depth: f32,
#endif // DEPTH_CLAMP_ORTHO
#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION
}
#endif //PREPASS_FRAGMENT
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,7 @@ pub fn queue_shadows<M: Material>(
.expect("Failed to get spot light visible entities"),
};
let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light);
light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);

// NOTE: Lights with shadow mapping disabled will have no visible entities
// so no meshes will be queued
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,9 @@ bitflags::bitflags! {
// See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test
const ENVIRONMENT_MAP = 1 << 8;
const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9;
const DEPTH_CLAMP_ORTHO = 1 << 10;
const UNCLIPPED_DEPTH_ORTHO = 1 << 10; // Disables depth clipping for use with directional light shadow views
// Emulated via fragment shader depth on hardware that doesn't support it natively
// See: https://www.w3.org/TR/webgpu/#depth-clipping and https://therealmjp.github.io/posts/shadow-maps/#disabling-z-clipping
const TEMPORAL_JITTER = 1 << 11;
const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12;
const LIGHTMAPPED = 1 << 13;
Expand Down
Loading

0 comments on commit d221665

Please sign in to comment.