1use crate::kvasir::{ExecutionContext, KvasirNode, ResourceId};
2use crate::passes::accessibility::AccessibilityNode;
3use crate::passes::backdrop_region::BackdropRegionNode;
4use crate::passes::bloom::{BloomBlurNode, BloomExtractNode};
5use crate::passes::composite::CompositeNode;
6use crate::passes::geometry::GeometryNode;
7use crate::passes::glass::{BackdropBlurNode, BackdropCopyNode, GlassNode};
8use crate::passes::opaque3d::Opaque3dNode;
9use crate::passes::pre_world_panel::PreWorldPanelNode;
10use crate::passes::shadow::{DirectionalLight, GpuMesh3d, ShadowNode};
11use crate::passes::skinning::SkinningNode;
12use crate::passes::transparent::TransparentNode;
13use crate::passes::ui::UINode;
14use crate::passes::volumetric::VolumetricNode;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum PassId {
18 PreWorldPanel,
19 Geometry,
20 BackdropCopy,
21 BackdropBlur,
22 Volumetric,
23 Glass,
24 UI,
25 Flow,
26 ComputeParticle,
27 BloomExtract,
28 BloomBlur,
29 Composite,
30 Accessibility,
31 Present,
32 PostProcess {
33 pipeline_id: u64,
34 },
35 BackdropRegion,
37 Shadow,
39 Opaque3d,
41 Transparent3d,
43 ComputeSkinning,
44}
45
46pub struct PresentNode {
47 pub inputs: Vec<ResourceId>,
48 pub outputs: Vec<ResourceId>,
49}
50
51impl KvasirNode for PresentNode {
52 fn label(&self) -> &'static str {
53 "Present"
54 }
55
56 fn inputs(&self) -> &[ResourceId] {
57 &self.inputs
58 }
59
60 fn outputs(&self) -> &[ResourceId] {
61 &self.outputs
62 }
63
64 fn pass_id(&self) -> PassId {
65 PassId::Present
66 }
67
68 fn execute(&self, _ctx: &mut ExecutionContext) {
69 }
71}
72
73pub const RES_SCENE: ResourceId = ResourceId(1);
75pub const RES_SCENE_MSAA: ResourceId = ResourceId(5);
76pub const RES_BLUR_A: ResourceId = ResourceId(2);
77pub const RES_BLOOM_A: ResourceId = ResourceId(3);
78pub const RES_SWAPCHAIN: ResourceId = ResourceId(4);
79
80pub struct RenderGraphConfig<'a> {
82 pub has_glass: bool,
83 pub has_bloom: bool,
84 pub has_accessibility: bool,
85 pub has_ibl: bool,
86 pub has_volumetric: bool,
88 pub active_offscreens: &'a [crate::types::OffscreenEffectConfig],
89 pub portal_regions: &'a [cvkg_core::Rect],
90 pub world_space_panels: &'a [(u64, cvkg_vdom::WorldSpacePanel)],
92 pub width: u32,
93 pub height: u32,
94 pub scale: f32,
95 pub directional_light: Option<DirectionalLight>,
97 pub mesh_instances_3d: Vec<GpuMesh3d>,
99 pub transparent_meshes_3d: Vec<GpuMesh3d>,
101 pub cascade_splits: [f32; 4],
103 pub camera_view_proj: glam::Mat4,
105 pub camera_pos: glam::Vec3,
107}
108
109pub fn build_render_graph(config: &RenderGraphConfig<'_>) -> super::graph::KvasirGraph {
111 let mut builder = super::graph::GraphBuilder::new();
112
113 let mut panel_outputs = Vec::new();
116 let mut panel_ids = Vec::new();
117 for (i, panel) in config.world_space_panels.iter().enumerate() {
118 let size = panel.1.texture_resolution();
119 let tex_id = ResourceId(2000 + i as u32);
120 panel_outputs.push(tex_id);
121 panel_ids.push(panel.0);
122 }
123
124 if !panel_outputs.is_empty() {
125 let pre_panel =
126 builder.add_node(Box::new(PreWorldPanelNode::new(panel_outputs, panel_ids)));
127 }
130
131 let geometry = builder.add_node(Box::new(GeometryNode::new()));
132 let mut last_scene_node = geometry;
133
134 for offscreen in config.active_offscreens {
135 let tex_id = ResourceId(1000 + (offscreen.target_id as u32));
136 debug_assert!(offscreen.target_id <= u32::MAX as u64, "target_id overflow");
137
138 let off_geom = builder.add_node(Box::new(
139 crate::passes::effects::OffscreenGeometryNode::new(offscreen.target_id, tex_id),
140 ));
141
142 let composite =
143 builder.add_node(Box::new(crate::passes::effects::EffectCompositeNode::new(
144 offscreen.target_id,
145 tex_id,
146 offscreen.effect.clone(),
147 offscreen.blend_mode,
148 offscreen.effect_args,
149 )));
150
151 builder.connect(off_geom, tex_id, composite);
152 builder.connect(last_scene_node, RES_SCENE, composite);
153 last_scene_node = composite;
154 }
155
156 if config.has_glass {
157 let copy = builder.add_node(Box::new(BackdropCopyNode::new()));
158 builder.connect(last_scene_node, RES_SCENE, copy);
159
160 let blur = builder.add_node(Box::new(BackdropBlurNode::new(
161 config.width / 2,
162 config.height / 2,
163 )));
164 builder.connect(copy, RES_BLUR_A, blur);
165
166 for (i, region) in config.portal_regions.iter().enumerate() {
168 let region_id = ResourceId(2000 + i as u32);
169 let region_node =
170 builder.add_node(Box::new(BackdropRegionNode::new(*region, region_id)));
171 builder.connect(last_scene_node, RES_SCENE, region_node);
172 }
173
174 let glass = builder.add_node(Box::new(GlassNode::new(config.scale)));
175 builder.connect(blur, RES_BLUR_A, glass);
176 builder.connect(last_scene_node, RES_SCENE, glass);
177 last_scene_node = glass;
178 }
179
180 let ui = builder.add_node(Box::new(UINode::new()));
181 builder.connect(last_scene_node, RES_SCENE, ui);
182 last_scene_node = ui;
183
184 let has_volumetric = config.has_volumetric;
186 if has_volumetric {
187 let volumetric = builder.add_node(Box::new(VolumetricNode::new()));
188 builder.connect(last_scene_node, RES_SCENE, volumetric);
189 last_scene_node = volumetric;
190 }
191
192 let skinning_node = builder.add_node(Box::new(SkinningNode {
194 inputs: vec![],
195 outputs: vec![],
196 }));
197 if let Some(light) = &config.directional_light
202 && (!config.mesh_instances_3d.is_empty() || !config.transparent_meshes_3d.is_empty())
203 {
204 let shadow_rid = ResourceId(10000); let shadow_node = builder.add_node(Box::new(ShadowNode {
206 light: *light,
207 shadow_map: shadow_rid,
208 mesh_instances: config.mesh_instances_3d.clone(),
209 cascade_splits: config.cascade_splits,
210 camera_view_proj: config.camera_view_proj,
211 }));
212 let opaque_3d_node = builder.add_node(Box::new(Opaque3dNode {
218 mesh_instances: config.mesh_instances_3d.clone(),
219 light: *light,
220 shadow_map: shadow_rid,
221 }));
222 builder.connect(shadow_node, shadow_rid, opaque_3d_node);
223 builder.connect(opaque_3d_node, RES_SCENE, last_scene_node);
224 last_scene_node = opaque_3d_node;
226
227 if !config.transparent_meshes_3d.is_empty() {
230 let mut transparent_meshes = config.transparent_meshes_3d.clone();
231 transparent_meshes.sort_by(|a, b| {
233 b.view_depth
234 .partial_cmp(&a.view_depth)
235 .unwrap_or(std::cmp::Ordering::Equal)
236 });
237
238 let transparent_node = builder.add_node(Box::new(TransparentNode {
239 mesh_instances: transparent_meshes,
240 shadow_map: shadow_rid,
241 camera_pos: config.camera_pos,
242 }));
243 builder.connect(last_scene_node, RES_SCENE, transparent_node);
244 last_scene_node = transparent_node;
245 }
246 }
247
248 let mut last_bloom_node = None;
250 if config.has_bloom {
251 let extract = builder.add_node(Box::new(BloomExtractNode::new()));
252 builder.connect(last_scene_node, RES_SCENE, extract);
253
254 let blur = builder.add_node(Box::new(BloomBlurNode::new(
255 config.width / 2,
256 config.height / 2,
257 )));
258 builder.connect(extract, RES_BLOOM_A, blur);
259 last_bloom_node = Some(blur);
260 }
261
262 if config.has_accessibility {
264 let a11y = builder.add_node(Box::new(AccessibilityNode::new()));
265 builder.connect(last_scene_node, RES_SCENE, a11y);
266 last_scene_node = a11y;
268 }
269
270 let composite = builder.add_node(Box::new(CompositeNode::new(
274 config.has_bloom,
275 !config.has_accessibility,
276 )));
277 builder.connect(last_scene_node, RES_SCENE, composite);
278 if let Some(bloom_node) = last_bloom_node {
279 builder.connect(bloom_node, RES_BLOOM_A, composite);
280 }
281
282 let present = builder.add_node(Box::new(PresentNode {
284 inputs: vec![RES_SCENE],
285 outputs: vec![],
286 }));
287 builder.connect(last_scene_node, RES_SCENE, present);
288
289 builder.build()
290}