rend3_routine/
depth.rs

1//! Material agnostic depth rendering. Shadows or a prepass.
2//!
3//! Any material that implements [`DepthRenderableMaterial`] will be able to use
4//! this routine to render their shadows or a depth prepass.
5
6use std::{marker::PhantomData, mem, num::NonZeroU64};
7
8use arrayvec::ArrayVec;
9use rend3::{
10    format_sso,
11    graph::{
12        DataHandle, DepthHandle, RenderGraph, RenderPassDepthTarget, RenderPassTarget, RenderPassTargets,
13        RenderTargetHandle,
14    },
15    types::{Handedness, Material, SampleCount},
16    util::{
17        bind_merge::{BindGroupBuilder, BindGroupLayoutBuilder},
18        math::round_up_pot,
19    },
20    ProfileData, Renderer, RendererDataCore, RendererProfile,
21};
22use wgpu::{
23    util::{BufferInitDescriptor, DeviceExt},
24    BindGroup, BindGroupLayout, BufferUsages, Color, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
25    DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineLayoutDescriptor, PolygonMode,
26    PrimitiveState, PrimitiveTopology, RenderPipeline, RenderPipelineDescriptor, ShaderStages, StencilState,
27    TextureFormat, VertexState,
28};
29
30use crate::{
31    common::{
32        profile_safe_shader, PerMaterialArchetypeInterface, WholeFrameInterfaces, CPU_VERTEX_BUFFERS,
33        GPU_VERTEX_BUFFERS,
34    },
35    culling::{self, PerMaterialArchetypeData},
36    pbr::PbrMaterial,
37};
38
39/// Trait for all materials that can use the built-in shadow/prepass rendering.
40///
41/// When implementing this trait it is recommended to write a test using
42/// [`bytemuck::offset_of`] to validate your offsets are correct.
43pub trait DepthRenderableMaterial: Material {
44    /// If Some it is possible to do alpha cutouts
45    const ALPHA_CUTOUT: Option<AlphaCutoutSpec>;
46}
47
48/// How the material should be read for alpha cutouting.
49pub struct AlphaCutoutSpec {
50    /// Index into the texture array to read the alpha from. Currently _must_ be
51    /// 0. This will be lifted.
52    pub index: u32,
53    /// Byte index into the data array that represents a single f32 to use as
54    /// the alpha cutout value.
55    pub cutoff_offset: u32,
56    /// Byte index into the data array that represents a single mat3 to use as
57    /// uv transform.
58    pub uv_transform_offset: Option<u32>,
59}
60
61#[derive(Debug, Copy, Clone)]
62#[repr(C)]
63struct AlphaDataAbi {
64    // Stride in offset into a float array (i.e. byte index / 4). Unused when CpuDriven.
65    stride: u32,
66    // Must be zero when GpuDriven. When CpuDriven, it's the index into the material data with the texture enable bitflag.
67    texture_offset: u32,
68    // Stride in offset into a float array  (i.e. byte index / 4)
69    cutoff_offset: u32,
70    // Stride in offset into a float array pointing to a mat3 with the uv transform (i.e. byte index / 4). 0xFFFFFFFF
71    // represents "no transform"
72    uv_transform_offset: u32,
73}
74
75unsafe impl bytemuck::Pod for AlphaDataAbi {}
76unsafe impl bytemuck::Zeroable for AlphaDataAbi {}
77
78/// Depth rendering routine.
79///
80/// To use this with your material, call [`DepthRoutine::new`] for every
81/// Material.
82///
83/// Call the appropriate add-to-graph function for each archetype with the
84/// proper settings set for that archetype.
85pub struct DepthRoutine<M> {
86    pipelines: DepthPipelines,
87    bg: Option<BindGroup>,
88    _phantom: PhantomData<M>,
89}
90
91impl<M: DepthRenderableMaterial> DepthRoutine<M> {
92    pub fn new(
93        renderer: &Renderer,
94        data_core: &RendererDataCore,
95        interfaces: &WholeFrameInterfaces,
96        per_material: &PerMaterialArchetypeInterface<M>,
97        unclipped_depth_supported: bool,
98    ) -> Self {
99        let abi_bgl;
100        let bg;
101        if let Some(alpha) = M::ALPHA_CUTOUT {
102            let abi = if renderer.profile == RendererProfile::GpuDriven {
103                let data_base_offset = round_up_pot(M::TEXTURE_COUNT * 4, 16);
104                let stride = data_base_offset + round_up_pot(M::DATA_SIZE, 16);
105
106                AlphaDataAbi {
107                    stride: stride / 4,
108                    texture_offset: 0,
109                    cutoff_offset: (data_base_offset + alpha.cutoff_offset) / 4,
110                    uv_transform_offset: alpha
111                        .uv_transform_offset
112                        .map(|o| (data_base_offset + o) / 4)
113                        .unwrap_or(0xFF_FF_FF_FF),
114                }
115            } else {
116                let texture_enable_offset = round_up_pot(M::DATA_SIZE, 16);
117                AlphaDataAbi {
118                    stride: 0,
119                    texture_offset: texture_enable_offset / 4,
120                    cutoff_offset: alpha.cutoff_offset / 4,
121                    uv_transform_offset: alpha.uv_transform_offset.map(|o| o / 4).unwrap_or(0xFF_FF_FF_FF),
122                }
123            };
124
125            let buffer = renderer.device.create_buffer_init(&BufferInitDescriptor {
126                label: None,
127                contents: bytemuck::bytes_of(&abi),
128                usage: BufferUsages::UNIFORM,
129            });
130            abi_bgl = Some(
131                BindGroupLayoutBuilder::new()
132                    .append(
133                        ShaderStages::FRAGMENT,
134                        wgpu::BindingType::Buffer {
135                            ty: wgpu::BufferBindingType::Uniform,
136                            has_dynamic_offset: false,
137                            min_binding_size: NonZeroU64::new(mem::size_of::<AlphaDataAbi>() as _),
138                        },
139                        None,
140                    )
141                    .build(&renderer.device, Some("AlphaDataAbi BGL")),
142            );
143            bg = Some(BindGroupBuilder::new().append_buffer(&buffer).build(
144                &renderer.device,
145                Some("AlphaDataAbi BG"),
146                abi_bgl.as_ref().unwrap(),
147            ))
148        } else {
149            bg = None;
150            abi_bgl = None;
151        };
152
153        let pipelines = DepthPipelines::new(
154            renderer,
155            data_core,
156            interfaces,
157            &per_material.bgl,
158            abi_bgl.as_ref(),
159            unclipped_depth_supported,
160        );
161
162        Self {
163            pipelines,
164            bg,
165            _phantom: PhantomData,
166        }
167    }
168
169    #[allow(clippy::too_many_arguments)]
170    pub fn add_prepass_to_graph<'node>(
171        &'node self,
172        graph: &mut RenderGraph<'node>,
173        forward_uniform_bg: DataHandle<BindGroup>,
174        culled: DataHandle<PerMaterialArchetypeData>,
175        samples: SampleCount,
176        cutout: bool,
177        color: RenderTargetHandle,
178        resolve: Option<RenderTargetHandle>,
179        depth: RenderTargetHandle,
180    ) {
181        let mut builder = graph.add_node(if cutout { "Prepass Cutout" } else { "Prepass Opaque" });
182
183        let hdr_color_handle = builder.add_render_target_output(color);
184        let hdr_resolve = builder.add_optional_render_target_output(resolve);
185        let hdr_depth_handle = builder.add_render_target_output(depth);
186
187        let forward_uniform_handle = builder.add_data_input(forward_uniform_bg);
188        let cull_handle = builder.add_data_input(culled);
189
190        let rpass_handle = builder.add_renderpass(RenderPassTargets {
191            targets: vec![RenderPassTarget {
192                color: hdr_color_handle,
193                clear: Color::BLACK,
194                resolve: hdr_resolve,
195            }],
196            depth_stencil: Some(RenderPassDepthTarget {
197                target: DepthHandle::RenderTarget(hdr_depth_handle),
198                depth_clear: Some(0.0),
199                stencil_clear: None,
200            }),
201        });
202
203        let pt_handle = builder.passthrough_ref(self);
204
205        builder.build(move |pt, _renderer, encoder_or_pass, temps, ready, graph_data| {
206            let this = pt.get(pt_handle);
207            let rpass = encoder_or_pass.get_rpass(rpass_handle);
208            let forward_uniform_bg = graph_data.get_data(temps, forward_uniform_handle).unwrap();
209            let culled = graph_data.get_data(temps, cull_handle).unwrap();
210
211            let pipeline = match (cutout, samples) {
212                (false, SampleCount::One) => &this.pipelines.prepass_opaque_s1,
213                (false, SampleCount::Four) => &this.pipelines.prepass_opaque_s4,
214                (true, SampleCount::One) => this.pipelines.prepass_cutout_s1.as_ref().unwrap(),
215                (true, SampleCount::Four) => this.pipelines.prepass_cutout_s4.as_ref().unwrap(),
216            };
217
218            graph_data.mesh_manager.buffers().bind(rpass);
219
220            rpass.set_pipeline(pipeline);
221            rpass.set_bind_group(0, forward_uniform_bg, &[]);
222            rpass.set_bind_group(1, &culled.per_material, &[]);
223            if let Some(ref bg) = this.bg {
224                rpass.set_bind_group(2, bg, &[]);
225            }
226
227            match culled.inner.calls {
228                ProfileData::Cpu(ref draws) => {
229                    culling::draw_cpu_powered::<M>(rpass, draws, graph_data.material_manager, 3)
230                }
231                ProfileData::Gpu(ref data) => {
232                    rpass.set_bind_group(3, ready.d2_texture.bg.as_gpu(), &[]);
233                    culling::draw_gpu_powered(rpass, data);
234                }
235            }
236        });
237    }
238
239    pub fn add_shadow_rendering_to_graph<'node>(
240        &'node self,
241        graph: &mut RenderGraph<'node>,
242        cutout: bool,
243        shadow_index: usize,
244        shadow_uniform_bg: DataHandle<BindGroup>,
245        culled: DataHandle<PerMaterialArchetypeData>,
246    ) {
247        let mut builder = graph.add_node(&*if cutout {
248            format_sso!("Shadow Cutout S{}", shadow_index)
249        } else {
250            format_sso!("Shadow Opaque S{}", shadow_index)
251        });
252
253        let shadow_uniform_handle = builder.add_data_input(shadow_uniform_bg);
254        let culled_handle = builder.add_data_input(culled);
255        let shadow_output_handle = builder.add_shadow_output(shadow_index);
256
257        let rpass_handle = builder.add_renderpass(RenderPassTargets {
258            targets: vec![],
259            depth_stencil: Some(RenderPassDepthTarget {
260                target: DepthHandle::Shadow(shadow_output_handle),
261                depth_clear: Some(0.0),
262                stencil_clear: None,
263            }),
264        });
265
266        let pt_handle = builder.passthrough_ref(self);
267
268        builder.build(move |pt, _renderer, encoder_or_pass, temps, ready, graph_data| {
269            let this = pt.get(pt_handle);
270            let rpass = encoder_or_pass.get_rpass(rpass_handle);
271            let shadow_uniform = graph_data.get_data(temps, shadow_uniform_handle).unwrap();
272            let culled = graph_data.get_data(temps, culled_handle).unwrap();
273
274            let pipeline = match cutout {
275                false => &this.pipelines.shadow_opaque_s1,
276                true => this.pipelines.shadow_cutout_s1.as_ref().unwrap(),
277            };
278
279            graph_data.mesh_manager.buffers().bind(rpass);
280            rpass.set_pipeline(pipeline);
281            rpass.set_bind_group(0, shadow_uniform, &[]);
282            rpass.set_bind_group(1, &culled.per_material, &[]);
283            if let Some(ref bg) = this.bg {
284                rpass.set_bind_group(2, bg, &[]);
285            }
286
287            match culled.inner.calls {
288                ProfileData::Cpu(ref draws) => {
289                    culling::draw_cpu_powered::<M>(rpass, draws, graph_data.material_manager, 3)
290                }
291                ProfileData::Gpu(ref data) => {
292                    rpass.set_bind_group(3, ready.d2_texture.bg.as_gpu(), &[]);
293                    culling::draw_gpu_powered(rpass, data);
294                }
295            }
296        });
297    }
298}
299
300/// Type of depth pass being done. Determines pipeline settings
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum DepthPassType {
303    /// Should have shadow acne compensation, front-face culling, and no color
304    /// state.
305    Shadow,
306    /// Should have unused color state and back-face culling.
307    Prepass,
308}
309
310/// Set of all possible needed pipelines for a material.
311pub struct DepthPipelines {
312    pub shadow_opaque_s1: RenderPipeline,
313    pub shadow_cutout_s1: Option<RenderPipeline>,
314    pub prepass_opaque_s1: RenderPipeline,
315    pub prepass_cutout_s1: Option<RenderPipeline>,
316    pub prepass_opaque_s4: RenderPipeline,
317    pub prepass_cutout_s4: Option<RenderPipeline>,
318}
319impl DepthPipelines {
320    pub fn new(
321        renderer: &Renderer,
322        data_core: &RendererDataCore,
323        interfaces: &WholeFrameInterfaces,
324        per_material_bgl: &BindGroupLayout,
325        abi_bgl: Option<&BindGroupLayout>,
326        unclipped_depth_supported: bool,
327    ) -> DepthPipelines {
328        profiling::scope!("build depth pass pipelines");
329        let depth_vert = unsafe {
330            profile_safe_shader(
331                &renderer.device,
332                renderer.profile,
333                "depth pass vert",
334                "depth.vert.cpu.wgsl",
335                "depth.vert.gpu.spv",
336            )
337        };
338
339        let depth_opaque_frag = unsafe {
340            profile_safe_shader(
341                &renderer.device,
342                renderer.profile,
343                "depth pass opaque frag",
344                "depth-opaque.frag.cpu.wgsl",
345                "depth-opaque.frag.gpu.spv",
346            )
347        };
348
349        let depth_cutout_frag = unsafe {
350            profile_safe_shader(
351                &renderer.device,
352                renderer.profile,
353                "depth pass cutout frag",
354                "depth-cutout.frag.cpu.wgsl",
355                "depth-cutout.frag.gpu.spv",
356            )
357        };
358
359        let mut bgls: ArrayVec<&BindGroupLayout, 4> = ArrayVec::new();
360        bgls.push(&interfaces.depth_uniform_bgl);
361        bgls.push(per_material_bgl);
362        if let Some(abi_bgl) = abi_bgl {
363            bgls.push(abi_bgl);
364        }
365        if renderer.profile == RendererProfile::GpuDriven {
366            bgls.push(data_core.d2_texture_manager.gpu_bgl())
367        } else {
368            bgls.push(data_core.material_manager.get_bind_group_layout_cpu::<PbrMaterial>());
369        }
370
371        let shadow_pll = renderer.device.create_pipeline_layout(&PipelineLayoutDescriptor {
372            label: Some("shadow pll"),
373            bind_group_layouts: &bgls,
374            push_constant_ranges: &[],
375        });
376
377        bgls[0] = &interfaces.forward_uniform_bgl;
378        let prepass_pll = renderer.device.create_pipeline_layout(&PipelineLayoutDescriptor {
379            label: Some("prepass pll"),
380            bind_group_layouts: &bgls,
381            push_constant_ranges: &[],
382        });
383
384        let inner = |name, ty, pll, frag, samples| {
385            create_depth_inner(
386                renderer,
387                samples,
388                ty,
389                unclipped_depth_supported,
390                pll,
391                &depth_vert,
392                frag,
393                name,
394            )
395        };
396
397        DepthPipelines {
398            shadow_opaque_s1: inner(
399                "Shadow Opaque 1x",
400                DepthPassType::Shadow,
401                &shadow_pll,
402                &depth_opaque_frag,
403                SampleCount::One,
404            ),
405            shadow_cutout_s1: Some(inner(
406                "Shadow Cutout 1x",
407                DepthPassType::Shadow,
408                &shadow_pll,
409                &depth_cutout_frag,
410                SampleCount::One,
411            )),
412            prepass_opaque_s1: inner(
413                "Prepass Opaque 1x",
414                DepthPassType::Prepass,
415                &prepass_pll,
416                &depth_opaque_frag,
417                SampleCount::One,
418            ),
419            prepass_cutout_s1: Some(inner(
420                "Prepass Cutout 1x",
421                DepthPassType::Prepass,
422                &prepass_pll,
423                &depth_cutout_frag,
424                SampleCount::One,
425            )),
426            prepass_opaque_s4: inner(
427                "Prepass Opaque 4x",
428                DepthPassType::Prepass,
429                &prepass_pll,
430                &depth_opaque_frag,
431                SampleCount::Four,
432            ),
433            prepass_cutout_s4: Some(inner(
434                "Prepass Cutout 4x",
435                DepthPassType::Prepass,
436                &prepass_pll,
437                &depth_cutout_frag,
438                SampleCount::Four,
439            )),
440        }
441    }
442}
443
444#[allow(clippy::too_many_arguments)]
445fn create_depth_inner(
446    renderer: &Renderer,
447    samples: SampleCount,
448    ty: DepthPassType,
449    unclipped_depth_supported: bool,
450    pll: &wgpu::PipelineLayout,
451    vert: &wgpu::ShaderModule,
452    frag: &wgpu::ShaderModule,
453    name: &str,
454) -> RenderPipeline {
455    profiling::scope!("build depth pipeline", name);
456    let color_state = [ColorTargetState {
457        format: TextureFormat::Rgba16Float,
458        blend: None,
459        write_mask: ColorWrites::empty(),
460    }];
461    renderer.device.create_render_pipeline(&RenderPipelineDescriptor {
462        label: Some(name),
463        layout: Some(pll),
464        vertex: VertexState {
465            module: vert,
466            entry_point: "main",
467            buffers: match renderer.profile {
468                RendererProfile::CpuDriven => &CPU_VERTEX_BUFFERS,
469                RendererProfile::GpuDriven => &GPU_VERTEX_BUFFERS,
470            },
471        },
472        primitive: PrimitiveState {
473            topology: PrimitiveTopology::TriangleList,
474            strip_index_format: None,
475            front_face: match renderer.handedness {
476                Handedness::Left => FrontFace::Cw,
477                Handedness::Right => FrontFace::Ccw,
478            },
479            cull_mode: Some(match ty {
480                DepthPassType::Shadow => Face::Front,
481                DepthPassType::Prepass => Face::Back,
482            }),
483            unclipped_depth: matches!(ty, DepthPassType::Shadow) && unclipped_depth_supported,
484            polygon_mode: PolygonMode::Fill,
485            conservative: false,
486        },
487        depth_stencil: Some(DepthStencilState {
488            format: TextureFormat::Depth32Float,
489            depth_write_enabled: true,
490            depth_compare: CompareFunction::GreaterEqual,
491            stencil: StencilState::default(),
492            bias: match ty {
493                DepthPassType::Prepass => DepthBiasState::default(),
494                DepthPassType::Shadow => DepthBiasState {
495                    constant: -2,
496                    slope_scale: -2.0,
497                    clamp: 0.0,
498                },
499            },
500        }),
501        multisample: MultisampleState {
502            count: samples as u32,
503            ..Default::default()
504        },
505        fragment: Some(FragmentState {
506            module: frag,
507            entry_point: "main",
508            targets: match ty {
509                DepthPassType::Prepass => &color_state,
510                DepthPassType::Shadow => &[],
511            },
512        }),
513        multiview: None,
514    })
515}