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::ui::UINode;
12use crate::passes::volumetric::VolumetricNode;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum PassId {
16 PreWorldPanel,
17 Geometry,
18 BackdropCopy,
19 BackdropBlur,
20 Volumetric,
21 Glass,
22 UI,
23 Flow,
24 ComputeParticle,
25 BloomExtract,
26 BloomBlur,
27 Composite,
28 Accessibility,
29 Present,
30 PostProcess {
31 pipeline_id: u64,
32 },
33 BackdropRegion,
35 Shadow,
37 Opaque3d,
39}
40
41pub struct PresentNode {
42 pub inputs: Vec<ResourceId>,
43 pub outputs: Vec<ResourceId>,
44}
45
46impl KvasirNode for PresentNode {
47 fn label(&self) -> &'static str {
48 "Present"
49 }
50
51 fn inputs(&self) -> &[ResourceId] {
52 &self.inputs
53 }
54
55 fn outputs(&self) -> &[ResourceId] {
56 &self.outputs
57 }
58
59 fn pass_id(&self) -> PassId {
60 PassId::Present
61 }
62
63 fn execute(&self, _ctx: &mut ExecutionContext) {
64 }
66}
67
68pub const RES_SCENE: ResourceId = ResourceId(1);
70pub const RES_SCENE_MSAA: ResourceId = ResourceId(5);
71pub const RES_BLUR_A: ResourceId = ResourceId(2);
72pub const RES_BLOOM_A: ResourceId = ResourceId(3);
73pub const RES_SWAPCHAIN: ResourceId = ResourceId(4);
74
75pub struct RenderGraphConfig<'a> {
77 pub has_glass: bool,
78 pub has_bloom: bool,
79 pub has_accessibility: bool,
80 pub has_volumetric: bool,
82 pub active_offscreens: &'a [crate::types::OffscreenEffectConfig],
83 pub portal_regions: &'a [cvkg_core::Rect],
84 pub world_space_panels: &'a [(u64, cvkg_vdom::WorldSpacePanel)],
86 pub width: u32,
87 pub height: u32,
88 pub scale: f32,
89 pub directional_light: Option<DirectionalLight>,
91 pub mesh_instances_3d: Vec<GpuMesh3d>,
93 pub scene_radius: f32,
95}
96
97pub fn build_render_graph(config: &RenderGraphConfig<'_>) -> super::graph::KvasirGraph {
99 let mut builder = super::graph::GraphBuilder::new();
100
101 let mut panel_outputs = Vec::new();
104 let mut panel_ids = Vec::new();
105 for (i, panel) in config.world_space_panels.iter().enumerate() {
106 let size = panel.1.texture_resolution();
107 let tex_id = ResourceId(2000 + i as u32);
108 panel_outputs.push(tex_id);
109 panel_ids.push(panel.0);
110 }
111
112 if !panel_outputs.is_empty() {
113 let pre_panel = builder.add_node(Box::new(PreWorldPanelNode::new(panel_outputs, panel_ids)));
114 }
117
118 let geometry = builder.add_node(Box::new(GeometryNode::new()));
119 let mut last_scene_node = geometry;
120
121 for offscreen in config.active_offscreens {
122 let tex_id = ResourceId(1000 + (offscreen.target_id as u32));
123 debug_assert!(offscreen.target_id <= u32::MAX as u64, "target_id overflow");
124
125 let off_geom = builder.add_node(Box::new(
126 crate::passes::effects::OffscreenGeometryNode::new(offscreen.target_id, tex_id),
127 ));
128
129 let composite =
130 builder.add_node(Box::new(crate::passes::effects::EffectCompositeNode::new(
131 offscreen.target_id,
132 tex_id,
133 offscreen.effect.clone(),
134 offscreen.blend_mode,
135 offscreen.effect_args,
136 )));
137
138 builder.connect(off_geom, tex_id, composite);
139 builder.connect(last_scene_node, RES_SCENE, composite);
140 last_scene_node = composite;
141 }
142
143 if config.has_glass {
144 let copy = builder.add_node(Box::new(BackdropCopyNode::new()));
145 builder.connect(last_scene_node, RES_SCENE, copy);
146
147 let blur = builder.add_node(Box::new(BackdropBlurNode::new(
148 config.width / 2,
149 config.height / 2,
150 )));
151 builder.connect(copy, RES_BLUR_A, blur);
152
153 for (i, region) in config.portal_regions.iter().enumerate() {
155 let region_id = ResourceId(2000 + i as u32);
156 let region_node =
157 builder.add_node(Box::new(BackdropRegionNode::new(*region, region_id)));
158 builder.connect(last_scene_node, RES_SCENE, region_node);
159 }
160
161 let glass = builder.add_node(Box::new(GlassNode::new(config.scale)));
162 builder.connect(blur, RES_BLUR_A, glass);
163 builder.connect(last_scene_node, RES_SCENE, glass);
164 last_scene_node = glass;
165 }
166
167 let ui = builder.add_node(Box::new(UINode::new()));
168 builder.connect(last_scene_node, RES_SCENE, ui);
169 last_scene_node = ui;
170
171 let has_volumetric = config.has_volumetric;
173 if has_volumetric {
174 let volumetric = builder.add_node(Box::new(VolumetricNode::new()));
175 builder.connect(last_scene_node, RES_SCENE, volumetric);
176 last_scene_node = volumetric;
177 }
178
179 if let Some(light) = &config.directional_light {
181 if !config.mesh_instances_3d.is_empty() {
182 let shadow_rid = ResourceId(10000); let shadow_node = builder.add_node(Box::new(ShadowNode {
184 light: *light,
185 shadow_map: shadow_rid,
186 mesh_instances: config.mesh_instances_3d.clone(),
187 scene_radius: config.scene_radius,
188 }));
189 let opaque_3d_node = builder.add_node(Box::new(Opaque3dNode {
193 mesh_instances: config.mesh_instances_3d.clone(),
194 light: *light,
195 shadow_map: shadow_rid,
196 }));
197 builder.connect(shadow_node, shadow_rid, opaque_3d_node);
198 builder.connect(opaque_3d_node, RES_SCENE, last_scene_node);
199 last_scene_node = opaque_3d_node;
201 }
202 }
203
204 let mut last_bloom_node = None;
206 if config.has_bloom {
207 let extract = builder.add_node(Box::new(BloomExtractNode::new()));
208 builder.connect(last_scene_node, RES_SCENE, extract);
209
210 let blur = builder.add_node(Box::new(BloomBlurNode::new(
211 config.width / 2,
212 config.height / 2,
213 )));
214 builder.connect(extract, RES_BLOOM_A, blur);
215 last_bloom_node = Some(blur);
216 }
217
218 if config.has_accessibility {
220 let a11y = builder.add_node(Box::new(AccessibilityNode::new()));
221 builder.connect(last_scene_node, RES_SCENE, a11y);
222 last_scene_node = a11y;
224 }
225
226 let composite = builder.add_node(Box::new(CompositeNode::new(
230 config.has_bloom,
231 !config.has_accessibility,
232 )));
233 builder.connect(last_scene_node, RES_SCENE, composite);
234 if let Some(bloom_node) = last_bloom_node {
235 builder.connect(bloom_node, RES_BLOOM_A, composite);
236 }
237
238 let present = builder.add_node(Box::new(PresentNode {
240 inputs: vec![RES_SCENE],
241 outputs: vec![],
242 }));
243 builder.connect(last_scene_node, RES_SCENE, present);
244
245 builder.build()
246}