1use 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
39pub trait DepthRenderableMaterial: Material {
44 const ALPHA_CUTOUT: Option<AlphaCutoutSpec>;
46}
47
48pub struct AlphaCutoutSpec {
50 pub index: u32,
53 pub cutoff_offset: u32,
56 pub uv_transform_offset: Option<u32>,
59}
60
61#[derive(Debug, Copy, Clone)]
62#[repr(C)]
63struct AlphaDataAbi {
64 stride: u32,
66 texture_offset: u32,
68 cutoff_offset: u32,
70 uv_transform_offset: u32,
73}
74
75unsafe impl bytemuck::Pod for AlphaDataAbi {}
76unsafe impl bytemuck::Zeroable for AlphaDataAbi {}
77
78pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum DepthPassType {
303 Shadow,
306 Prepass,
308}
309
310pub 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}