Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native unclipped depth on supported platforms #16095

Merged
merged 11 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -140,6 +140,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