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