rend3_routine/
forward.rs

1//! Material agnostic routine for forward rendering.
2//!
3//! Will default to the PBR shader code if custom code is not specified.
4
5use std::marker::PhantomData;
6
7use arrayvec::ArrayVec;
8use rend3::{
9    graph::{
10        DataHandle, DepthHandle, RenderGraph, RenderPassDepthTarget, RenderPassTarget, RenderPassTargets,
11        RenderTargetHandle,
12    },
13    types::{Handedness, Material, SampleCount},
14    ProfileData, Renderer, RendererDataCore, RendererProfile,
15};
16use wgpu::{
17    BindGroup, BindGroupLayout, BlendState, Color, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
18    DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineLayoutDescriptor, PolygonMode,
19    PrimitiveState, PrimitiveTopology, RenderPipeline, RenderPipelineDescriptor, ShaderModule, StencilState,
20    TextureFormat, VertexState,
21};
22
23use crate::{
24    common::{
25        profile_safe_shader, PerMaterialArchetypeInterface, WholeFrameInterfaces, CPU_VERTEX_BUFFERS,
26        GPU_VERTEX_BUFFERS,
27    },
28    culling,
29    pbr::PbrMaterial,
30};
31
32/// A set of pipelines for rendering a specific combination of a material.
33pub struct ForwardRoutine<M: Material> {
34    pub pipeline_s1: RenderPipeline,
35    pub pipeline_s4: RenderPipeline,
36    pub _phantom: PhantomData<M>,
37}
38impl<M: Material> ForwardRoutine<M> {
39    /// Create a new forward routine with optional customizations.
40    ///
41    /// Specifying vertex or fragment shaders will override the default ones.
42    ///
43    /// The order of BGLs passed to the shader is:  
44    /// 0: Forward uniforms  
45    /// 1: Per material data  
46    /// 2: Texture Array (GpuDriven) / Material (CpuDriven)  
47    /// 3+: Contents of extra_bgls  
48    ///
49    /// Blend state is passed through to the pipeline.
50    ///
51    /// If use_prepass is true, depth tests/writes are set such that it is
52    /// assumed a full depth-prepass has happened before.
53    #[allow(clippy::too_many_arguments)]
54    pub fn new(
55        renderer: &Renderer,
56        data_core: &mut RendererDataCore,
57        interfaces: &WholeFrameInterfaces,
58        per_material: &PerMaterialArchetypeInterface<M>,
59
60        vertex: Option<(&str, &ShaderModule)>,
61        fragment: Option<(&str, &ShaderModule)>,
62        extra_bgls: &[BindGroupLayout],
63
64        blend: Option<BlendState>,
65        use_prepass: bool,
66        label: &str,
67    ) -> Self {
68        profiling::scope!("PrimaryPasses::new");
69
70        let _forward_pass_vert_owned;
71        let forward_pass_vert;
72        let vert_entry_point;
73        match vertex {
74            Some((inner_name, inner)) => {
75                _forward_pass_vert_owned = None;
76                forward_pass_vert = inner;
77                vert_entry_point = inner_name;
78            }
79            None => {
80                _forward_pass_vert_owned = Some(unsafe {
81                    profile_safe_shader(
82                        &renderer.device,
83                        renderer.profile,
84                        "forward pass vert",
85                        "opaque.vert.cpu.wgsl",
86                        "opaque.vert.gpu.spv",
87                    )
88                });
89                vert_entry_point = "main";
90                forward_pass_vert = _forward_pass_vert_owned.as_ref().unwrap();
91            }
92        };
93
94        let _forward_pass_frag_owned;
95        let forward_pass_frag;
96        let frag_entry_point;
97        match fragment {
98            Some((inner_name, inner)) => {
99                _forward_pass_frag_owned = None;
100                forward_pass_frag = inner;
101                frag_entry_point = inner_name;
102            }
103            None => {
104                _forward_pass_frag_owned = Some(unsafe {
105                    profile_safe_shader(
106                        &renderer.device,
107                        renderer.profile,
108                        "forward pass frag",
109                        "opaque.frag.cpu.wgsl",
110                        "opaque.frag.gpu.spv",
111                    )
112                });
113                frag_entry_point = "main";
114                forward_pass_frag = _forward_pass_frag_owned.as_ref().unwrap()
115            }
116        };
117
118        let mut bgls: ArrayVec<&BindGroupLayout, 8> = ArrayVec::new();
119        bgls.push(&interfaces.forward_uniform_bgl);
120        bgls.push(&per_material.bgl);
121        if renderer.profile == RendererProfile::GpuDriven {
122            bgls.push(data_core.d2_texture_manager.gpu_bgl())
123        } else {
124            bgls.push(data_core.material_manager.get_bind_group_layout_cpu::<PbrMaterial>());
125        }
126        bgls.extend(extra_bgls);
127
128        let pll = renderer.device.create_pipeline_layout(&PipelineLayoutDescriptor {
129            label: Some("opaque pass"),
130            bind_group_layouts: &bgls,
131            push_constant_ranges: &[],
132        });
133
134        let inner = |samples| {
135            build_forward_pipeline_inner(
136                renderer,
137                &pll,
138                vert_entry_point,
139                forward_pass_vert,
140                frag_entry_point,
141                forward_pass_frag,
142                blend,
143                use_prepass,
144                label,
145                samples,
146            )
147        };
148
149        Self {
150            pipeline_s1: inner(SampleCount::One),
151            pipeline_s4: inner(SampleCount::Four),
152            _phantom: PhantomData,
153        }
154    }
155
156    /// Add the given routine to the graph with the given settings.
157    ///
158    /// I understand the requirement that extra_bgs is explicitly a Vec is
159    /// weird, but due to my lifetime passthrough logic I can't pass through a
160    /// slice.
161    #[allow(clippy::too_many_arguments)]
162    pub fn add_forward_to_graph<'node>(
163        &'node self,
164        graph: &mut RenderGraph<'node>,
165        forward_uniform_bg: DataHandle<BindGroup>,
166        culled: DataHandle<culling::PerMaterialArchetypeData>,
167        extra_bgs: Option<&'node Vec<BindGroup>>,
168        label: &str,
169        samples: SampleCount,
170        color: RenderTargetHandle,
171        resolve: Option<RenderTargetHandle>,
172        depth: RenderTargetHandle,
173    ) {
174        let mut builder = graph.add_node(label);
175
176        let hdr_color_handle = builder.add_render_target_output(color);
177        let hdr_resolve = builder.add_optional_render_target_output(resolve);
178        let hdr_depth_handle = builder.add_render_target_output(depth);
179
180        let rpass_handle = builder.add_renderpass(RenderPassTargets {
181            targets: vec![RenderPassTarget {
182                color: hdr_color_handle,
183                clear: Color::BLACK,
184                resolve: hdr_resolve,
185            }],
186            depth_stencil: Some(RenderPassDepthTarget {
187                target: DepthHandle::RenderTarget(hdr_depth_handle),
188                depth_clear: Some(0.0),
189                stencil_clear: None,
190            }),
191        });
192
193        let _ = builder.add_shadow_array_input();
194
195        let forward_uniform_handle = builder.add_data_input(forward_uniform_bg);
196        let cull_handle = builder.add_data_input(culled);
197
198        let this_pt_handle = builder.passthrough_ref(self);
199        let extra_bg_pt_handle = extra_bgs.map(|v| builder.passthrough_ref(v));
200
201        builder.build(move |pt, _renderer, encoder_or_pass, temps, ready, graph_data| {
202            let this = pt.get(this_pt_handle);
203            let extra_bgs = extra_bg_pt_handle.map(|h| pt.get(h));
204            let rpass = encoder_or_pass.get_rpass(rpass_handle);
205            let forward_uniform_bg = graph_data.get_data(temps, forward_uniform_handle).unwrap();
206            let culled = graph_data.get_data(temps, cull_handle).unwrap();
207
208            let pipeline = match samples {
209                SampleCount::One => &this.pipeline_s1,
210                SampleCount::Four => &this.pipeline_s4,
211            };
212
213            graph_data.mesh_manager.buffers().bind(rpass);
214
215            rpass.set_pipeline(pipeline);
216            rpass.set_bind_group(0, forward_uniform_bg, &[]);
217            rpass.set_bind_group(1, &culled.per_material, &[]);
218            if let Some(v) = extra_bgs {
219                for (idx, bg) in v.iter().enumerate() {
220                    rpass.set_bind_group((idx + 3) as _, bg, &[])
221                }
222            }
223
224            match culled.inner.calls {
225                ProfileData::Cpu(ref draws) => {
226                    culling::draw_cpu_powered::<PbrMaterial>(rpass, draws, graph_data.material_manager, 2)
227                }
228                ProfileData::Gpu(ref data) => {
229                    rpass.set_bind_group(2, ready.d2_texture.bg.as_gpu(), &[]);
230                    culling::draw_gpu_powered(rpass, data);
231                }
232            }
233        });
234    }
235}
236
237#[allow(clippy::too_many_arguments)]
238fn build_forward_pipeline_inner(
239    renderer: &Renderer,
240    pll: &wgpu::PipelineLayout,
241    vert_entry_point: &str,
242    forward_pass_vert: &wgpu::ShaderModule,
243    frag_entry_point: &str,
244    forward_pass_frag: &wgpu::ShaderModule,
245    blend: Option<BlendState>,
246    use_prepass: bool,
247    label: &str,
248    samples: SampleCount,
249) -> RenderPipeline {
250    renderer.device.create_render_pipeline(&RenderPipelineDescriptor {
251        label: Some(label),
252        layout: Some(pll),
253        vertex: VertexState {
254            module: forward_pass_vert,
255            entry_point: vert_entry_point,
256            buffers: match renderer.profile {
257                RendererProfile::CpuDriven => &CPU_VERTEX_BUFFERS,
258                RendererProfile::GpuDriven => &GPU_VERTEX_BUFFERS,
259            },
260        },
261        primitive: PrimitiveState {
262            topology: PrimitiveTopology::TriangleList,
263            strip_index_format: None,
264            front_face: match renderer.handedness {
265                Handedness::Left => FrontFace::Cw,
266                Handedness::Right => FrontFace::Ccw,
267            },
268            cull_mode: Some(Face::Back),
269            unclipped_depth: false,
270            polygon_mode: PolygonMode::Fill,
271            conservative: false,
272        },
273        depth_stencil: Some(DepthStencilState {
274            format: TextureFormat::Depth32Float,
275            depth_write_enabled: blend.is_none() && use_prepass,
276            depth_compare: match use_prepass {
277                true => CompareFunction::Equal,
278                false => CompareFunction::GreaterEqual,
279            },
280            stencil: StencilState::default(),
281            bias: DepthBiasState::default(),
282        }),
283        multisample: MultisampleState {
284            count: samples as u32,
285            ..Default::default()
286        },
287        fragment: Some(FragmentState {
288            module: forward_pass_frag,
289            entry_point: frag_entry_point,
290            targets: &[ColorTargetState {
291                format: TextureFormat::Rgba16Float,
292                blend,
293                write_mask: ColorWrites::all(),
294            }],
295        }),
296        multiview: None,
297    })
298}