Skip to main content

flow_ngin/
render.rs

1//! Render composition and pipeline batching.
2//!
3//! This module defines the [`Render`] enum, which is used by scene nodes to specify
4//! how they should be rendered. The engine uses `Render` to sort objects into batches
5//! for different pipelines (basic, transparent, GUI, terrain, picking, etc.) and to
6//! support custom per-object render passes.
7//!
8//! # Key types
9//!
10//! - [`Render<'a, 'pass>`] is the primary enum describing render operations
11//! - [`Instanced<'a>`] contains data for instanced rendering (model + instance buffer)
12//! - [`Flat<'a>`] contains data for flat (2D / GUI) rendering (vertex + index buffers)
13//!
14
15use std::collections::{HashMap, HashSet};
16
17use wgpu::RenderPass;
18
19use crate::{
20    context::{Context, GPUResource},
21    data_structures::{block::BuildingBlocks, model::Model, scene_graph::SceneNode}, pick::PickId,
22};
23
24/// Data for instanced object rendering: a model, instance buffer, and pick ID.
25///
26/// Used for 3D objects rendered with GPU instancing. The instance buffer contains
27/// per-instance transformation data and other per-instance attributes.
28#[derive(Clone)]
29pub struct Instanced<'a> {
30    pub instance: &'a wgpu::Buffer,
31    pub model: &'a Model,
32    pub front_face: wgpu::FrontFace,
33    pub amount: usize,
34    pub id: PickId,
35}
36
37/// Data for flat (2D / GUI) object rendering: vertex and index buffers with a bind group.
38///
39/// Used for 2D GUI elements, terrain, or other flat geometry. The bind group
40/// contains textures and samplers for the rendered objects.
41#[derive(Clone)]
42pub struct Flat<'a> {
43    pub vertex: &'a wgpu::Buffer,
44    pub index: &'a wgpu::Buffer,
45    pub group: &'a wgpu::BindGroup,
46    pub amount: usize,
47    pub id: PickId,
48}
49
50/// Data for custom instanced vertex rendering.
51///
52/// Used for 2D GUI elements, terrain, or other flat geometry. The bind group
53/// contains textures and samplers for the rendered objects.
54#[derive(Clone)]
55pub struct Geometry<'a> {
56    pub instance: &'a wgpu::Buffer,
57    pub vertex: &'a wgpu::Buffer,
58    pub index: &'a wgpu::Buffer,
59    pub group: &'a wgpu::BindGroup,
60    pub amount: usize,
61    pub id: PickId,
62}
63
64/// Specifies how a scene object should be rendered.
65///
66/// `Render` is an enum that allows flexible composition of render operations.
67/// It can represent a single instanced object, a batch of objects, transparent
68/// objects, GUI elements, terrain, a composite of multiple renders, or a custom
69/// render closure for special effects.
70///
71/// # Variants
72///
73/// - `None` renders nothing
74/// - `Default(Instanced)` renders a single opaque instanced object
75/// - `Defaults(Vec<Instanced>)` renders a batch of opaque instanced objects
76/// - `Transparent(Instanced)` renders a single transparent instanced object
77/// - `Transparents(Vec<Instanced>)` renders a batch of transparent objects
78/// - `GUI(Flat)` renders 2D elements (flat geometry)
79/// - `Terrain(Flat)` renders terrain mesh
80/// - `Composed(Vec<Render>)` recursively renders composition of multiple renders
81/// - `Custom(...)` invokes a user-defined closure for custom rendering
82///
83#[derive(Default)]
84pub enum Render<'a, 'pass>
85where
86    'pass: 'a,
87{
88    #[default]
89    None,
90    Default(Instanced<'a>),
91    Defaults(Vec<Instanced<'a>>),
92    Transparent(Instanced<'a>),
93    Transparents(Vec<Instanced<'a>>),
94    GUI(Flat<'a>),
95    Terrain(Geometry<'a>),
96    Composed(Vec<Render<'a, 'pass>>),
97    Custom(Box<dyn 'a + FnOnce(&Context, &mut wgpu::RenderPass<'pass>) -> ()>),
98}
99impl<'a, 'pass> Render<'a, 'pass> {
100    /// Map object IDs to flow IDs for picking and selection.
101    ///
102    /// Internal helper used during picking setup to associate which flow owns
103    /// which object IDs. Walks the render tree and populates a map of object ID
104    /// to set of flow IDs.
105    pub(crate) fn map_ids(
106        &self,
107        // TODO: introduce id caching in ctx
108        flow_id: usize,
109        map: &mut HashMap<PickId, HashSet<usize>>,
110    ) {
111        match self {
112            Render::Default(instanced) => {
113                map.entry(instanced.id)
114                    .and_modify(|flows| _ = flows.insert(flow_id))
115                    .or_insert([flow_id].into());
116            }
117            Render::Defaults(vec) => vec.into_iter().for_each(|instanced| {
118                map.entry(instanced.id)
119                    .and_modify(|flows| {
120                        flows.insert(flow_id);
121                    })
122                    .or_insert([flow_id].into());
123            }),
124            Render::Transparents(vec) => vec.into_iter().for_each(|instanced| {
125                map.entry(instanced.id)
126                    .and_modify(|flows| {
127                        flows.insert(flow_id);
128                    })
129                    .or_insert([flow_id].into());
130            }),
131            Render::Transparent(instanced) => {
132                map.entry(instanced.id)
133                    .and_modify(|flows| _ = flows.insert(flow_id))
134                    .or_insert([flow_id].into());
135            }
136            Render::GUI(flat) => {
137                map.entry(flat.id)
138                    .and_modify(|flows| _ = flows.insert(flow_id))
139                    .or_insert([flow_id].into());
140            }
141            Render::Terrain(flat) => {
142                map.entry(flat.id)
143                    .and_modify(|flows| _ = flows.insert(flow_id))
144                    .or_insert([flow_id].into());
145            }
146            Render::Composed(renders) => renders
147                .into_iter()
148                .for_each(|render| render.map_ids(flow_id, map)),
149            Render::None | Render::Custom(_) => (),
150        }
151    }
152
153    pub(crate) fn set_pipelines(
154        self,
155        ctx: &Context,
156        render_pass: &mut RenderPass<'pass>,
157        basics: &mut Vec<Instanced<'a>>,
158        trans: &mut Vec<Instanced<'a>>,
159        guis: &mut Vec<Flat<'a>>,
160        terrain: &mut Vec<Geometry<'a>>,
161        customs: &mut Vec<Box<dyn 'a + FnOnce(&Context, &mut wgpu::RenderPass<'pass>) -> ()>>,
162    ) {
163        match self {
164            Render::Default(instanced) => {
165                basics.push(instanced);
166            }
167            Render::Defaults(mut vec) => basics.append(&mut vec),
168            Render::Transparent(instanced) => trans.push(instanced),
169            Render::Transparents(mut vec) => trans.append(&mut vec),
170            Render::GUI(flat) => guis.push(flat),
171            Render::Terrain(flat) => terrain.push(flat),
172            Render::Composed(renders) => renders
173                .into_iter()
174                .map(|render| render.set_pipelines(ctx, render_pass, basics, trans, guis, terrain, customs))
175                .collect(),
176            Render::Custom(f) => customs.push(f),
177            Render::None => (),
178        }
179    }
180
181    pub(crate) fn set_pick_pipelines(
182        self,
183        ctx: &Context,
184        render_pass: &mut RenderPass<'pass>,
185        basics: &mut Vec<Instanced<'a>>,
186        flats: &mut Vec<Flat<'a>>,
187        geoms: &mut Vec<Geometry<'a>>,
188    ) {
189        match self {
190            Render::Default(instanced) => {
191                basics.push(instanced);
192            }
193            Render::Defaults(mut vec) => basics.append(&mut vec),
194            Render::Transparent(instanced) => basics.push(instanced),
195            Render::Transparents(mut vec) => basics.append(&mut vec),
196            Render::GUI(flat) => flats.push(flat),
197            Render::Terrain(flat) => geoms.push(flat),
198            Render::Composed(renders) => renders
199                .into_iter()
200                .map(|render| render.set_pick_pipelines(ctx, render_pass, basics, flats, geoms))
201                .collect(),
202            // Picking is not supported for custom renders
203            Render::Custom(_) => (),
204            Render::None => (),
205        }
206    }
207}
208impl<'a, 'pass> From<&'a dyn SceneNode> for Render<'a, 'pass> {
209    fn from(sn: &'a dyn SceneNode) -> Self {
210        Render::Defaults(sn.get_render())
211    }
212}
213impl<'a, 'pass> From<&'a (dyn SceneNode + Send)> for Render<'a, 'pass> {
214    fn from(sn: &'a (dyn SceneNode + Send)) -> Self {
215        Render::Defaults(sn.get_render())
216    }
217}
218impl<'a, 'pass> From<&'a BuildingBlocks> for Render<'a, 'pass> {
219    fn from(blocks: &'a BuildingBlocks) -> Self {
220        blocks.get_render()
221    }
222}
223#[cfg(feature = "integration-tests")]
224impl<'b, 'pass> From<&'b Box<dyn SceneNode>> for Render<'b, 'pass> {
225    fn from(val: &'b Box<dyn SceneNode>) -> Self {
226        // Dereference the Box to get &dyn SceneNode, which Render implements From for
227        Render::from(val.as_ref())
228    }
229}
230#[cfg(feature = "integration-tests")]
231impl<'a, 'pass> From<&'a Box<dyn GPUResource<'a, 'pass> + Send>> for Render<'a, 'pass> {
232    fn from(val: &'a Box<dyn GPUResource<'a, 'pass> + Send>) -> Self {
233        val.get_render()
234    }
235}