wde_renderer/passes/
render_graph.rs

1use crate::prelude::*;
2use wde_logger::prelude::*;
3use wde_wgpu::passes::{
4    CommandBuffer, RenderPassBuilder, RenderPassColorAttachment, RenderPassDepth
5};
6
7use bevy::ecs::system::{ReadOnlySystemParam, SystemParamItem, SystemState};
8use bevy::prelude::*;
9use std::collections::HashMap;
10use std::ops::Range;
11use wde_wgpu::pipelines::BindGroup;
12
13use crate::{
14    assets::{Mesh, Texture},
15    core::SwapchainFrame
16};
17
18pub use wde_wgpu::command_buffer::{LoadOp, Operations, StoreOp};
19pub use wde_wgpu::render_pass::RenderPassInstance;
20
21/// Color attachment description for a render pass.
22/// If `texture` is None, the pass will render to the swapchain texture.
23pub struct RenderPassDescColorAttachment {
24    /// The texture to render to.
25    pub texture: Option<AssetId<Texture>>,
26    /// How to load the texture at the start of the pass (default: Load).
27    pub load: LoadOp<crate::prelude::Color>,
28    /// How to store the texture at the end of the pass (default: Store).
29    pub store: StoreOp,
30    /// Optional resolve target for multisampled textures. If set, the multisampled texture will be resolved into this texture at the end of the pass.
31    pub resolve_target: Option<AssetId<Texture>>
32}
33impl Default for RenderPassDescColorAttachment {
34    fn default() -> Self {
35        Self {
36            texture: None,
37            load: LoadOp::Load,
38            store: StoreOp::Store,
39            resolve_target: None
40        }
41    }
42}
43/// Depth attachment description for a render pass.
44pub struct RenderPassDescDepthAttachment {
45    /// The texture to use as depth buffer.
46    pub texture: Option<AssetId<Texture>>,
47    /// How to load the texture at the start of the pass (default: Load).
48    pub load: LoadOp<f32>,
49    /// How to store the texture at the end of the pass (default: Store).
50    pub store: StoreOp
51}
52impl Default for RenderPassDescDepthAttachment {
53    fn default() -> Self {
54        Self {
55            texture: None,
56            load: LoadOp::Load,
57            store: StoreOp::Store
58        }
59    }
60}
61/// Descibes a render pass and its execution logic.
62#[derive(Default)]
63pub struct RenderPassDesc {
64    /// Optional depth attachment. If None, no depth buffer is used.
65    pub attachments_depth: Option<RenderPassDescDepthAttachment>,
66    /// Optional color attachment. If None, the pass renders to the swapchain texture.
67    pub attachments_colors: Option<Vec<RenderPassDescColorAttachment>>
68}
69
70/// A batch of draw commands that can be issued together with the same pipeline and bind groups.
71pub struct DrawCommandsBatch {
72    pub bind_group: Option<(u32, BindGroup)>, // index, bind group
73    pub index_range: Range<u32>,
74    pub instance_range: Range<u32>
75}
76impl Default for DrawCommandsBatch {
77    fn default() -> Self {
78        Self {
79            bind_group: None,
80            index_range: 0..0,
81            instance_range: 0..1
82        }
83    }
84}
85/// Commands to execute in a render pass, in order. For example: set pipeline, set bind groups, draw calls, etc.
86pub enum SubPassCommand {
87    /// Set the pipeline for subsequent draw calls.
88    Pipeline(Option<CachedPipelineIndex>),
89    /// Set a bind group at the given index for subsequent draw calls.
90    BindGroup(u32, Option<BindGroup>),
91    /// Set the vertex and index buffers for subsequent draw calls.
92    Mesh(Option<AssetId<Mesh>>),
93    /// Issue draw calls with the given index and instance ranges, using the currently set pipeline and bind groups.
94    DrawBatches(Vec<DrawCommandsBatch>),
95    /// Execute a custom function that has access to the render world and the render pass instance. This can be used for custom rendering logic that doesn't fit into the other command types. This function should be written as `fn _name_(world: &World, render_pass: &mut RenderPassInstance) { ... }`.
96    Custom(for<'pass> fn(&'pass World, &mut RenderPassInstance<'pass>))
97}
98/// A sub-pass is a sequence of commands executed within a render pass.
99/// For example, a GBuffer pass might have one sub-pass for rendering opaque objects and another for transparent objects.
100#[derive(Default)]
101pub struct RenderSubPassDesc(pub Vec<SubPassCommand>);
102
103/// Type alias for render pass IDs. These are numeric identifiers that control the execution order of passes in the render graph; lower IDs execute first.
104pub type RenderPassId = i32;
105/// Describes a render pass in the render graph, with its attachments, load ops, etc.
106/// This is returned by the `describe` method of the `RenderPass` trait, which is called in the render world before rendering to get the pass description and attachments.
107/// See [crate::passes] for more details and examples.
108pub trait RenderPass {
109    type Params: ReadOnlySystemParam;
110
111    /// Describe the render pass (attachments, load ops, etc).
112    fn describe(params: &SystemParamItem<Self::Params>) -> RenderPassDesc;
113    /// Return the unique numeric ID of this render pass. Passes execute in order of their IDs (lower IDs execute first).
114    fn id() -> RenderPassId;
115    /// Return the (non-unique) label of the render pass, used for logging and debugging.
116    fn label() -> &'static str;
117
118    /// Custom rendering logic for this pass.
119    /// This can be used for passes that require custom rendering logic that doesn't fit into the default render graph execution flow.
120    ///
121    /// Notes:
122    /// - Returns None by default, meaning the pass will be rendered with the default render graph execution flow.
123    /// - If it returns Some(true), it means the pass executed custom rendering logic and should be considered successfully rendered for this frame.
124    /// - The provided command buffer is already begun and will be submitted at the end of the render graph execution, so the custom rendering logic can simply record commands to it.
125    fn custom_render(_world: &mut World, _command_buffer: &mut CommandBuffer) -> Option<bool> {
126        None
127    }
128}
129// Utility traits for the render graph implementation. Not meant to be used by users of the render graph.
130trait RenderPassNode: Send + Sync + 'static {
131    fn describe(&mut self, world: &mut World) -> RenderPassDesc;
132    fn label(&mut self) -> &'static str;
133    fn custom_render(
134        &mut self,
135        world: &mut World,
136        command_buffer: &mut CommandBuffer
137    ) -> Option<bool>;
138}
139struct RenderPassHolder<P: RenderPass> {
140    _phantom: std::marker::PhantomData<P>
141}
142impl<P> RenderPassNode for RenderPassHolder<P>
143where
144    P: RenderPass + Send + Sync + 'static
145{
146    fn describe(&mut self, world: &mut World) -> RenderPassDesc {
147        let mut state: SystemState<P::Params> = SystemState::new(world);
148        let params = state.get(world);
149        P::describe(&params)
150    }
151    fn label(&mut self) -> &'static str {
152        P::label()
153    }
154    fn custom_render(
155        &mut self,
156        world: &mut World,
157        command_buffer: &mut CommandBuffer
158    ) -> Option<bool> {
159        P::custom_render(world, command_buffer)
160    }
161}
162
163/// Core trait for all render sub-passes in the render graph. A sub-pass is a sequence of commands executed within a render pass.
164/// For example, a GBuffer pass might have one sub-pass for rendering opaque objects and another for transparent objects.
165/// The `describe` method defines the commands to execute in this sub-pass, which can be setting pipelines, bind groups, vertex buffers, draw calls, or even custom rendering logic.
166/// See [crate::passes] for more details and examples.
167pub trait RenderSubPass {
168    type Params: ReadOnlySystemParam;
169
170    /// Describe the sub-pass commands to execute within the render pass.
171    fn describe(params: &SystemParamItem<Self::Params>) -> RenderSubPassDesc;
172    /// Return the (non-unique) label of this sub-pass, used for logging and debugging.
173    fn label() -> &'static str;
174}
175// Utility traits for the render graph implementation. Not meant to be used by users of the render graph.
176trait RenderSubPassNode: Send + Sync + 'static {
177    fn describe(&mut self, world: &mut World) -> RenderSubPassDesc;
178    fn label(&mut self) -> &'static str;
179}
180struct RenderSubPassHolder<SP: RenderSubPass> {
181    _phantom: std::marker::PhantomData<SP>
182}
183impl<SP> RenderSubPassNode for RenderSubPassHolder<SP>
184where
185    SP: RenderSubPass + Send + Sync + 'static
186{
187    fn describe(&mut self, world: &mut World) -> RenderSubPassDesc {
188        let mut state: SystemState<SP::Params> = SystemState::new(world);
189        let params = state.get(world);
190        SP::describe(&params)
191    }
192    fn label(&mut self) -> &'static str {
193        SP::label()
194    }
195}
196
197/// The render graph resource, which stores all render passes and sub-passes, their descriptions, and their execution order.
198/// This is the core of the render graph system, and is responsible for executing the render passes in the correct order with the correct attachments and commands.
199///
200/// To register a new render pass, use the `add_pass` method with a type that implements the `RenderPass` trait. To register a sub-pass to a render pass, use the `add_sub_pass` method with a type that implements the `RenderSubPass` trait and the parent render pass type.
201#[derive(Resource, Default)]
202pub struct RenderGraph {
203    // Raw storage of passes and subpasses
204    passes: Vec<Box<dyn RenderPassNode>>,
205    sub_passes: Vec<Box<dyn RenderSubPassNode>>,
206
207    // Ordering logic for the passes
208    sorted_ids: Vec<RenderPassId>, // pass indices in `passes`, sorted by their numeric IDs
209
210    // Indexing for execution
211    render_passes_by_id: HashMap<RenderPassId, usize>, // pass id -> pass index in `passes`
212    sub_passes_by_renderpass: HashMap<usize, Vec<usize>>  // render pass index -> sub-pass indices
213}
214impl RenderGraph {
215    /// Add a render pass to the graph. Passes execute in order of their IDs (lower IDs execute first).
216    /// The pass type must implement the `RenderPass` trait, which defines the pass's description and execution logic.
217    pub fn add_pass<P: RenderPass + Send + Sync + 'static>(&mut self) -> &mut Self {
218        let id = P::id();
219
220        // Assert that the pass ID is unique
221        debug_assert!(
222            !self.render_passes_by_id.contains_key(&id),
223            "A render pass with ID {} already exists in the render graph.",
224            id
225        );
226
227        // Insert the new pass
228        self.passes.push(Box::new(RenderPassHolder {
229            _phantom: std::marker::PhantomData::<P>
230        }));
231        self.render_passes_by_id.insert(id, self.passes.len() - 1);
232        self.sorted_ids.push(id);
233
234        // Sort the passes by ID
235        self.sorted_ids.sort();
236        self
237    }
238
239    /// Add a sub-pass to a render pass in the graph. The sub-pass will be executed in the order it was added within its parent render pass.
240    /// The sub-pass type must implement the `RenderSubPass` trait, which defines the sub-pass's commands and the render pass it belongs to.
241    pub fn add_sub_pass<SP: RenderSubPass + Send + Sync + 'static, P: RenderPass>(
242        &mut self
243    ) -> &mut Self {
244        self.sub_passes.push(Box::new(RenderSubPassHolder {
245            _phantom: std::marker::PhantomData::<SP>
246        }));
247        let sub_pass_index = self.render_passes_by_id.get(&P::id()).copied();
248        debug_assert!(
249            sub_pass_index.is_some(),
250            "Cannot add sub-pass '{}' to render pass '{}': no render pass with ID {} found in the render graph.",
251            SP::label(),
252            P::label(),
253            P::id()
254        );
255        self.sub_passes_by_renderpass
256            .entry(sub_pass_index.unwrap())
257            .or_default()
258            .push(self.sub_passes.len() - 1);
259        self
260    }
261
262    pub(crate) fn render(world: &mut World) {
263        world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
264            render(&mut graph, world);
265        });
266    }
267}
268
269fn render(graph: &mut RenderGraph, world: &mut World) {
270    trace!(
271        "Starting render graph execution with {} passes.",
272        graph.passes.len()
273    );
274
275    // Create command buffer
276    let mut command_buffer = {
277        let render_instance = world.get_resource::<RenderInstance>().unwrap();
278        CommandBuffer::new(&render_instance.0.read().unwrap(), "render-graph")
279    };
280
281    // Execute passes in order of their IDs
282    for pass_id in &graph.sorted_ids {
283        // Get pass description
284        let pass_index = *graph.render_passes_by_id.get(pass_id).unwrap();
285        let pass = &mut graph.passes[pass_index];
286        let pass_label = pass.label();
287        let pass_desc = pass.describe(world);
288
289        // Try custom rendering logic for this pass
290        if let Some(custom_render_result) = pass.custom_render(world, &mut command_buffer) {
291            // If it was Some(true or false), it means the pass used custom rendering logic.
292            if !custom_render_result {
293                debug!(
294                    "Custom rendering for pass '{}' could not be completed. Skipping this pass for this frame.",
295                    pass_label
296                );
297            }
298            continue;
299        }
300
301        // Precompute sub-pass descriptions before creating the render pass to avoid borrow conflicts with the world
302        let mut sub_passes = Vec::new();
303        if let Some(sub_pass_indices) = graph.sub_passes_by_renderpass.get(&pass_index) {
304            for sub_pass_index in sub_pass_indices {
305                let sub_pass = &mut graph.sub_passes[*sub_pass_index];
306                let sub_pass_label = sub_pass.label();
307                let sub_pass_desc = sub_pass.describe(world);
308                sub_passes.push((sub_pass_label, sub_pass_desc));
309            }
310        }
311
312        // Create the render pass from its description
313        trace!("Rendering render pass '{}'.", pass_label);
314        let _pass_span = debug_span!("render_pass", pass_id = *pass_id, pass_label).entered();
315        let mut render_pass =
316            match create_render_pass(world, &mut command_buffer, pass_label, &pass_desc) {
317                Ok(pass) => pass,
318                Err(e) => {
319                    debug!(
320                        "Failed to create render pass: '{}'. Skipping this frame.",
321                        e
322                    );
323                    continue;
324                }
325            };
326
327        // Execute sub-passes in order of addition
328        for (sub_pass_label, sub_pass_desc) in &sub_passes {
329            trace!("Rendering sub-pass '{}'.", sub_pass_label);
330            let _sub_pass_span =
331                debug_span!("render_sub_pass", sub_pass_label = *sub_pass_label).entered();
332            render_sub_pass(world, &mut render_pass, sub_pass_desc);
333        }
334    }
335
336    // Submit commands
337    let render_instance = world.get_resource::<RenderInstance>().unwrap();
338    command_buffer.submit(&render_instance.0.read().unwrap());
339}
340
341fn create_render_pass<'p>(
342    world: &'p World,
343    command_buffer: &'p mut CommandBuffer,
344    label: &str,
345    pass_desc: &RenderPassDesc
346) -> Result<RenderPassInstance<'p>, String> {
347    // Get generic handlers
348    let render_instance = world.get_resource::<RenderInstance>().unwrap();
349    let textures = world.get_resource::<RenderAssets<GpuTexture>>().unwrap();
350    let swapchain_frame = world.get_resource::<SwapchainFrame>().unwrap();
351    let (surface_width, surface_height) = {
352        let render_instance = render_instance.0.read().unwrap();
353        let config = render_instance.surface_config.as_ref().unwrap();
354        (config.width, config.height)
355    };
356
357    // Create the render pass
358    command_buffer.create_render_pass(label, |builder: &mut RenderPassBuilder| -> Result<(), String> {
359        // Set color attachments
360        if let Some(attachments_color) = &pass_desc.attachments_colors {
361            // Set specified color attachments
362            for color_attachment_desc in attachments_color.iter() {
363                if let Some(texture_id) = color_attachment_desc.texture
364                    && let Some(texture) = textures.get(texture_id) {
365                    if !(surface_width == texture.texture.size.0 && surface_height == texture.texture.size.1) {
366                        return Err(format!("Color attachment texture has invalid size: expected ({}, {}), got ({}, {})", surface_width, surface_height, texture.texture.size.0, texture.texture.size.1));
367                    }
368                    let resolve_target = if let Some(resolve_target) = color_attachment_desc.resolve_target {
369                        if let Some(resolve_target) = textures.get(resolve_target)
370                            && surface_width == resolve_target.texture.size.0 && surface_height == resolve_target.texture.size.1 {
371                            Some(resolve_target)
372                        } else {
373                            return Err("Invalid resolve target for color attachment".to_string());
374                        }
375                    } else { None };
376                    builder.add_color_attachment(RenderPassColorAttachment {
377                        texture: Some(&texture.texture.view),
378                        load: match color_attachment_desc.load {
379                            LoadOp::Load => LoadOp::Load,
380                            LoadOp::Clear(color) => LoadOp::Clear(color.into())
381                        },
382                        store: color_attachment_desc.store,
383                        resolve_target: resolve_target.map(|tex| &tex.texture.view)
384                    });
385                } else {
386                    return Err("Invalid texture for color attachment".to_string());
387                }
388            }
389        } else {
390            // Set swapchain as color attachment if no other color attachments are specified
391            let swapchain_frame = match swapchain_frame.data.as_ref() {
392                Some(frame) => frame,
393                None => return Err("No swapchain frame available".to_string())
394            };
395            builder.add_color_attachment(RenderPassColorAttachment {
396                texture: Some(&swapchain_frame.view),
397                ..default()
398            });
399        }
400
401        // Set depth attachments
402        if let Some(depth_attachment) = pass_desc.attachments_depth.as_ref() {
403            if let Some(depth_texture) = depth_attachment.texture
404               && let Some(depth_texture) = textures.get(depth_texture) {
405                if !(surface_width == depth_texture.texture.size.0 && surface_height == depth_texture.texture.size.1) {
406                    return Err(format!("Depth attachment texture has invalid size: expected ({}, {}), got ({}, {}).", surface_width, surface_height, depth_texture.texture.size.0, depth_texture.texture.size.1));
407                }
408                builder.set_depth_texture(RenderPassDepth {
409                    texture: Some(&depth_texture.texture.view),
410                    load: depth_attachment.load,
411                    store: depth_attachment.store
412                });
413            } else {
414                return Err("Invalid texture for depth attachment".to_string());
415            }
416        }
417        Ok(())
418    })
419}
420
421fn render_sub_pass<'p>(
422    world: &'p World,
423    render_pass: &mut RenderPassInstance<'p>,
424    sub_pass_desc: &'p RenderSubPassDesc
425) {
426    // Get generic handlers
427    let pipeline_manager = world.get_resource::<PipelineManager>().unwrap();
428    let meshes = world.get_resource::<RenderAssets<GpuMesh>>().unwrap();
429
430    // Issue global commands
431    for stage_command in &sub_pass_desc.0 {
432        match stage_command {
433            // Set pipeline
434            SubPassCommand::Pipeline(pipeline) => {
435                if let Some(pipeline) = pipeline
436                    && let CachedPipelineStatus::OkRender(pipeline) =
437                        pipeline_manager.get_pipeline(*pipeline)
438                {
439                    if let Err(e) = render_pass.set_pipeline(pipeline) {
440                        error!("Failed to set pipeline: {:?}.", e);
441                        return;
442                    }
443                } else {
444                    return;
445                }
446            }
447
448            // Set bind groups at given index
449            SubPassCommand::BindGroup(index, bind_group) => {
450                if let Some(bind_group) = bind_group {
451                    render_pass.set_bind_group(*index, bind_group);
452                } else {
453                    return;
454                }
455            }
456
457            // Bind mesh vertex and index buffers
458            SubPassCommand::Mesh(mesh) => {
459                if let Some(mesh) = mesh
460                    && let Some(mesh) = meshes.get(*mesh)
461                {
462                    render_pass.set_vertex_buffer(0, mesh.vertex_buffer.as_ref().unwrap());
463                    render_pass.set_index_buffer(mesh.index_buffer.as_ref().unwrap());
464                } else {
465                    return;
466                }
467            }
468
469            // Issue draw calls
470            SubPassCommand::DrawBatches(batches) => {
471                for batch in batches {
472                    // Set bind group
473                    if let Some((index, bind_group)) = &batch.bind_group {
474                        render_pass.set_bind_group(*index, bind_group);
475                    }
476
477                    // Draw the mesh
478                    match render_pass
479                        .draw_indexed(batch.index_range.clone(), batch.instance_range.clone())
480                    {
481                        Ok(_) => {}
482                        Err(e) => {
483                            error!("Failed to draw: {:?}.", e);
484                        }
485                    }
486                }
487            }
488
489            // Custom commands
490            SubPassCommand::Custom(custom_fn) => {
491                custom_fn(world, render_pass);
492            }
493        }
494    }
495}