1use serde::Serialize;
20use serde_json::Value;
21
22use crate::render_graph::{RenderGraph, SceneMembers, ScenePlacements};
23use crate::shader::ShaderBinding;
24use crate::{Component, Entity, Instance, JsonError, Mesh, World};
25
26#[derive(Clone, Debug, PartialEq, Serialize)]
28pub struct StepBinding {
29 pub draw: String,
31 pub pass: Option<String>,
34 pub program: StepProgram,
35}
36
37#[derive(Clone, Debug, PartialEq, Serialize)]
39#[serde(rename_all = "snake_case")]
40pub enum StepProgram {
41 Main,
43 Bound(ShaderBinding),
45 Unbound { why: String },
47}
48
49impl StepBinding {
50 pub fn binding<'a>(&'a self, material: Entity, w: &'a World) -> Option<&'a ShaderBinding> {
53 match &self.program {
54 StepProgram::Main => w.get::<ShaderBinding>(material),
55 StepProgram::Bound(b) => Some(b),
56 StepProgram::Unbound { .. } => None,
57 }
58 }
59}
60
61#[derive(Clone, Debug, Default, PartialEq, Serialize)]
64pub struct StepBindings(pub Vec<StepBinding>);
65
66impl StepBindings {
67 pub fn of<'a>(&'a self, draw: &'a str) -> impl Iterator<Item = &'a StepBinding> + 'a {
69 self.0.iter().filter(move |b| b.draw == draw)
70 }
71}
72
73impl Component for StepBindings {
74 const NAME: &'static str = "core.step_bindings";
75 const GEOMETRY: crate::Geometry = crate::Geometry::None;
76 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
77 crate::world::Linearity::Invariant
78 }
79 fn to_json(&self) -> Result<Option<Value>, JsonError> {
80 crate::json::to_value(self).map(Some)
81 }
82 fn references(&self, out: &mut Vec<Entity>) {
83 for b in &self.0 {
84 if let StepProgram::Bound(binding) = &b.program {
85 binding.references(out);
86 }
87 }
88 }
89 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
94 if self.0.is_empty() {
95 return Err("step bindings that bind no draw".into());
96 }
97 let draws: Option<Vec<(&str, &SceneMembers)>> = w.resource::<RenderGraph>().map(|g| g.scene_draws());
98 for (i, b) in self.0.iter().enumerate() {
99 if b.draw.trim().is_empty() {
100 return Err("a step binding of no draw".into());
101 }
102 if self.0[..i]
103 .iter()
104 .any(|o| o.draw == b.draw && (o.pass.is_none() || o.pass == b.pass))
105 {
106 return Err(format!(
107 "the draw {} bound twice{}",
108 b.draw,
109 b.pass
110 .as_deref()
111 .map(|p| format!(" through the pass {p}"))
112 .unwrap_or_default()
113 ));
114 }
115 if b.pass.is_none() && self.0.iter().filter(|o| o.draw == b.draw).count() > 1 {
116 return Err(format!(
117 "the draw {} bound several times, not each through a named pass",
118 b.draw
119 ));
120 }
121 if let Some(draws) = &draws {
122 match draws.iter().find(|(d, _)| *d == b.draw).map(|(_, m)| m) {
123 None => {
124 return Err(format!(
125 "the draw {} is no scene step's of the world's core.render_graph",
126 b.draw
127 ));
128 }
129 Some(SceneMembers::NotRead { .. }) => {
130 return Err(format!(
131 "the draw {} is a scene step's of the world's core.render_graph that does not read what \
132 it draws",
133 b.draw
134 ));
135 }
136 Some(SceneMembers::Stated { .. }) => {}
137 }
138 }
139 if b.pass.as_deref().is_some_and(|p| p.trim().is_empty()) {
140 return Err(format!("the draw {}'s pass has an empty name", b.draw));
141 }
142 match &b.program {
143 StepProgram::Main if !w.has::<ShaderBinding>(e) => {
144 return Err(format!(
145 "the draw {} draws the material's main binding, and it has no core.shader_binding",
146 b.draw
147 ));
148 }
149 StepProgram::Main => {}
150 StepProgram::Bound(binding) => binding
151 .verify(e, w)
152 .map_err(|why| format!("the draw {}: {why}", b.draw))?,
153 StepProgram::Unbound { why } if why.trim().is_empty() => {
154 return Err(format!("the draw {} is unbound and says not why", b.draw));
155 }
156 StepProgram::Unbound { .. } => {}
157 }
158 }
159 Ok(())
160 }
161}
162
163#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
167pub struct StepDraws(pub Vec<String>);
168
169impl StepDraws {
170 pub fn draws_in(&self, draw: &str) -> bool {
172 self.0.iter().any(|d| d == draw)
173 }
174}
175
176impl Component for StepDraws {
177 const NAME: &'static str = "core.step_draws";
178 const GEOMETRY: crate::Geometry = crate::Geometry::None;
179 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
180 crate::world::Linearity::Invariant
181 }
182 fn to_json(&self) -> Result<Option<Value>, JsonError> {
183 crate::json::to_value(self).map(Some)
184 }
185 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
188 if !w.has::<Instance>(e) && !w.has::<Mesh>(e) {
189 return Err("step draws on an entity that draws nothing (no core.instance or core.mesh)".into());
190 }
191 if self.0.is_empty() {
192 return Err("step draws that name no draw".into());
193 }
194 let listed: Option<Vec<&str>> = w.resource::<RenderGraph>().map(|g| {
195 g.scene_draws()
196 .into_iter()
197 .filter(|(_, m)| {
198 **m == SceneMembers::Stated {
199 placements: ScenePlacements::Listed,
200 }
201 })
202 .map(|(d, _)| d)
203 .collect()
204 });
205 for (i, d) in self.0.iter().enumerate() {
206 if self.0[..i].contains(d) {
207 return Err(format!("the draw {d} named twice"));
208 }
209 if let Some(listed) = &listed
210 && !listed.contains(&d.as_str())
211 {
212 return Err(format!(
213 "the draw {d} is no scene step's of the world's core.render_graph that lists its placements"
214 ));
215 }
216 }
217 Ok(())
218 }
219}
220
221pub(crate) fn retain_made(w: &mut World) -> crate::Dropped {
226 let Some(graph) = w.resource::<RenderGraph>() else {
227 return crate::Dropped::default();
228 };
229 let made: std::collections::BTreeSet<String> = graph
230 .scene_draws()
231 .into_iter()
232 .map(|(d, _)| d.to_owned())
233 .collect();
234 let stale_bindings: Vec<Entity> = w
235 .iter::<StepBindings>()
236 .filter(|(e, _)| w.is_alive(*e))
237 .filter(|(_, b)| b.0.iter().any(|b| !made.contains(&b.draw)))
238 .map(|(e, _)| e)
239 .collect();
240 let stale_draws: Vec<Entity> = w
241 .iter::<StepDraws>()
242 .filter(|(e, _)| w.is_alive(*e))
243 .filter(|(_, d)| d.0.iter().any(|d| !made.contains(d)))
244 .map(|(e, _)| e)
245 .collect();
246 let mut bindings = Vec::new();
247 for e in stale_bindings {
248 let Some(mut b) = w.remove::<StepBindings>(e) else {
249 continue;
250 };
251 b.0.retain(|b| {
252 let keep = made.contains(&b.draw);
253 if !keep {
254 bindings.push((e, b.draw.clone()));
255 }
256 keep
257 });
258 if !b.0.is_empty() {
259 w.insert(e, b);
260 }
261 }
262 let mut draws = Vec::new();
263 for e in stale_draws {
264 let Some(mut d) = w.remove::<StepDraws>(e) else {
265 continue;
266 };
267 d.0.retain(|d| {
268 let keep = made.contains(d);
269 if !keep {
270 draws.push((e, d.clone()));
271 }
272 keep
273 });
274 if !d.0.is_empty() {
275 w.insert(e, d);
276 }
277 }
278 crate::Dropped {
279 step_bindings: bindings,
280 step_draws: draws,
281 ..crate::Dropped::default()
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::render_graph::{
289 DepthView, DrawBucket, DrawSorting, Outputs, PassWork, Presence, RenderPass, RenderTarget, Rounding,
290 Runs, SceneView, TargetFormat, TargetShape, TargetSize, TargetView,
291 };
292
293 fn graph(draws: &[(&str, SceneMembers)]) -> RenderGraph {
294 RenderGraph {
295 targets: vec![RenderTarget {
296 name: "depth".into(),
297 format: TargetFormat::Depth32Stencil8,
298 size: TargetSize::View {
299 numerator: 1,
300 denominator: 1,
301 depth: 1,
302 rounding: Rounding::Down,
303 },
304 shape: TargetShape::Plane,
305 mips: 1,
306 history: false,
307 shared: false,
308 }],
309 buffers: Vec::new(),
310 passes: draws
311 .iter()
312 .map(|(draw, members)| RenderPass {
313 name: (*draw).into(),
314 shared: false,
315 presence: Presence::Always,
316 runs: Runs::Once,
317 work: PassWork::Scene {
318 bucket: DrawBucket::Forward,
319 view: SceneView::Camera,
320 outputs: Outputs {
321 colour: Vec::new(),
322 depth: Some(DepthView {
323 view: TargetView {
324 target: 0,
325 mip: 0,
326 first_layer: 0,
327 layers: 0,
328 },
329 read_only_depth: false,
330 read_only_stencil: false,
331 }),
332 },
333 draw: (*draw).into(),
334 members: members.clone(),
335 frame: Vec::new(),
336 sorting: DrawSorting::NotStated {
337 why: "a fixture".into(),
338 },
339 },
340 })
341 .collect(),
342 picture: None,
343 lighting: None,
344 local_shadow_atlas: None,
345 left_out: Vec::new(),
346 }
347 }
348
349 #[test]
353 fn step_bindings_and_draws_verify_their_own_rules() {
354 let mut w = World::new();
355 let material = w.spawn();
356 let bound = |draw: &str, program| StepBinding {
357 draw: draw.into(),
358 pass: Some("DepthNormals".into()),
359 program,
360 };
361 let unbound = |draw: &str, why: &str| bound(draw, StepProgram::Unbound { why: why.into() });
362 assert!(
363 StepBindings(vec![unbound("depth_normals", "not read")])
364 .verify(material, &w)
365 .is_ok()
366 );
367 let err = |b: StepBindings, w: &World| b.verify(material, w).unwrap_err();
368 assert!(err(StepBindings(Vec::new()), &w).contains("no draw"));
369 assert!(err(StepBindings(vec![unbound("a", "x"), unbound("a", "y")]), &w).contains("twice"));
370 let through = |pass: Option<&str>| StepBinding {
372 draw: "a".into(),
373 pass: pass.map(str::to_owned),
374 program: StepProgram::Unbound { why: "x".into() },
375 };
376 let two = StepBindings(vec![
377 through(Some("DepthNormals")),
378 through(Some("DepthNormalsOnly")),
379 ]);
380 assert!(two.verify(material, &w).is_ok());
381 assert_eq!(two.of("a").count(), 2);
382 assert!(
383 err(
384 StepBindings(vec![through(Some("DepthNormals")), through(None)]),
385 &w
386 )
387 .contains("named pass")
388 );
389 assert!(
390 err(
391 StepBindings(vec![through(None), through(Some("DepthNormals"))]),
392 &w
393 )
394 .contains("named pass")
395 );
396 assert!(err(StepBindings(vec![unbound("a", " ")]), &w).contains("not why"));
397 assert!(err(StepBindings(vec![bound("a", StepProgram::Main)]), &w).contains("core.shader_binding"));
398 w.insert(material, ShaderBinding::default());
399 assert!(
400 StepBindings(vec![bound("a", StepProgram::Main)])
401 .verify(material, &w)
402 .is_ok()
403 );
404 assert_eq!(
405 StepBindings(vec![bound("a", StepProgram::Main)]).0[0].binding(material, &w),
406 Some(&ShaderBinding::default())
407 );
408 let stated = |placements| SceneMembers::Stated { placements };
410 w.insert_resource(graph(&[
411 ("forward", stated(ScenePlacements::All)),
412 ("depth_normals", stated(ScenePlacements::Listed)),
413 (
414 "velocity",
415 SceneMembers::NotRead {
416 why: "a fixture".into(),
417 },
418 ),
419 ]));
420 assert!(
421 StepBindings(vec![bound("forward", StepProgram::Main)])
422 .verify(material, &w)
423 .is_ok()
424 );
425 assert!(err(StepBindings(vec![bound("a", StepProgram::Main)]), &w).contains("no scene step's"));
426 assert!(err(StepBindings(vec![unbound("velocity", "x")]), &w).contains("does not read"));
428
429 let placement = w.spawn();
430 let draws = |d: &[&str]| StepDraws(d.iter().map(|s| (*s).to_owned()).collect());
431 assert!(
432 draws(&["depth_normals"])
433 .verify(placement, &w)
434 .unwrap_err()
435 .contains("draws nothing")
436 );
437 let prototype = w.spawn();
438 w.insert(
439 placement,
440 Instance {
441 prototype,
442 variant: 0,
443 },
444 );
445 assert!(draws(&["depth_normals"]).verify(placement, &w).is_ok());
446 assert!(draws(&["depth_normals"]).draws_in("depth_normals"));
447 assert!(draws(&[]).verify(placement, &w).unwrap_err().contains("no draw"));
448 assert!(
449 draws(&["depth_normals", "depth_normals"])
450 .verify(placement, &w)
451 .unwrap_err()
452 .contains("twice")
453 );
454 assert!(
456 draws(&["forward"])
457 .verify(placement, &w)
458 .unwrap_err()
459 .contains("lists its placements")
460 );
461 }
462
463 fn unbound(draw: &str) -> StepBinding {
465 StepBinding {
466 draw: draw.into(),
467 pass: None,
468 program: StepProgram::Unbound {
469 why: "a fixture".into(),
470 },
471 }
472 }
473
474 fn grouped() -> (World, [Entity; 4], RenderPass) {
478 use iridia_interface::Stage;
479 let mut w = World::new();
480 let program = w.spawn();
481 let (lit, depth_only, placement) = (w.spawn(), w.spawn(), w.spawn());
482 let stated = |placements| SceneMembers::Stated { placements };
483 let mut g = graph(&[
484 ("forward", stated(ScenePlacements::All)),
485 ("depth_normals", stated(ScenePlacements::Listed)),
486 ]);
487 let depth_step = g.passes.remove(1);
489 let screen = RenderPass {
490 name: "resolve".into(),
491 shared: false,
492 presence: Presence::Always,
493 runs: Runs::Once,
494 work: PassWork::Screen {
495 vertices: 3,
496 stages: vec![crate::shader::StageBinding::new(Stage::Pixel, program)],
497 state: Default::default(),
498 outputs: Outputs {
499 colour: Vec::new(),
500 depth: Some(DepthView {
501 view: TargetView {
502 target: 0,
503 mip: 0,
504 first_layer: 0,
505 layers: 0,
506 },
507 read_only_depth: false,
508 read_only_stencil: false,
509 }),
510 },
511 region: None,
512 },
513 };
514 g.passes.push(RenderPass {
515 name: "depth group".into(),
516 shared: false,
517 presence: Presence::Always,
518 runs: Runs::Once,
519 work: PassWork::Group {
520 steps: vec![screen, depth_step],
521 },
522 });
523 g.picture = Some(0);
524 let group = g.passes[1].clone();
525 w.insert_resource(g);
526 w.insert(
527 lit,
528 StepBindings(vec![unbound("forward"), unbound("depth_normals")]),
529 );
530 w.insert(depth_only, StepBindings(vec![unbound("depth_normals")]));
531 w.insert(placement, Mesh::new(Vec::new()));
532 w.insert(placement, StepDraws(vec!["depth_normals".into()]));
533 (w, [program, lit, depth_only, placement], group)
534 }
535
536 #[test]
541 fn a_part_drops_the_steps_reaching_outside_it_and_the_bindings_naming_their_draws() {
542 use crate::render_graph::LightingPipeline;
543 let (w, [program, lit, depth_only, placement], group) = grouped();
544 let stated = |placements| SceneMembers::Stated { placements };
545
546 let keep = std::collections::BTreeSet::from([lit, depth_only, placement]);
547 let part = w.part(&keep);
548 assert_eq!(
549 part.dropped.graph.steps,
550 vec![("depth group".to_owned(), program)]
551 );
552 assert!(part.dropped.graph.picture && !part.dropped.graph.lighting);
553 assert_eq!(
554 part.dropped.step_bindings,
555 vec![
556 (lit, "depth_normals".to_owned()),
557 (depth_only, "depth_normals".to_owned())
558 ]
559 );
560 assert_eq!(
561 part.dropped.step_draws,
562 vec![(placement, "depth_normals".to_owned())]
563 );
564 let p = &part.world;
565 assert_eq!(p.verify_components(), Vec::new());
566 assert_eq!(p.verify_resources(), Vec::new());
567 assert_eq!(
568 p.get::<StepBindings>(lit),
569 Some(&StepBindings(vec![unbound("forward")]))
570 );
571 assert!(!p.has::<StepBindings>(depth_only) && !p.has::<StepDraws>(placement));
572 let kept = p.resource::<RenderGraph>().unwrap();
573 assert_eq!((kept.passes.len(), kept.picture), (1, None));
574 assert!(w.part(&w.entities().collect()).dropped.is_empty());
576
577 let panicked =
579 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| w.subset(&keep))).unwrap_err();
580 let message = panicked.downcast_ref::<String>().unwrap();
581 assert!(
582 message.contains("depth group")
583 && message.contains("step bindings")
584 && message.contains("depth_normals"),
585 "{message}"
586 );
587
588 let mut despawned = w.clone();
590 despawned.despawn(program);
591 assert_eq!(despawned.take_dropped(), part.dropped);
592 assert_eq!(despawned.verify_components(), Vec::new());
593 assert!(despawned.take_dropped().is_empty());
594
595 let mut split = w.clone();
599 let plain = split.spawn();
600 split.insert(plain, Mesh::new(Vec::new()));
601 let mut moved = split.clone();
602 let counted = moved.split_part(&std::collections::BTreeSet::from([plain]));
603 assert_eq!(counted.dropped.graph.steps, part.dropped.graph.steps);
604 assert!(counted.dropped.step_bindings.is_empty() && counted.dropped.step_draws.is_empty());
605 let off = split.split_off(&std::collections::BTreeSet::from([plain]));
606 assert_eq!(split.resource::<RenderGraph>().map(|g| g.passes.len()), Some(2));
607 split.absorb(off).unwrap();
608 let panicked =
609 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| split.split_off(&keep))).unwrap_err();
610 let message = panicked.downcast_ref::<String>().unwrap();
611 assert!(
612 message.contains("split_off stripped") && message.contains("depth_normals"),
613 "{message}"
614 );
615
616 let mut g = graph(&[
618 ("a", stated(ScenePlacements::All)),
619 ("b", stated(ScenePlacements::All)),
620 ]);
621 g.passes.insert(0, group);
622 g.lighting = Some(LightingPipeline {
623 passes: vec![2],
624 ..LightingPipeline::default()
625 });
626 let mut renumbered = g.clone();
627 assert_eq!(renumbered.retain_steps(&|e| e != program).steps.len(), 1);
628 assert_eq!(renumbered.lighting.map(|l| l.passes), Some(vec![1]));
629 g.lighting = Some(LightingPipeline {
630 passes: vec![0, 2],
631 ..LightingPipeline::default()
632 });
633 assert!(g.retain_steps(&|e| e != program).lighting);
634 assert_eq!(g.lighting, None);
635 }
636
637 #[test]
641 fn a_run_records_what_the_world_dropped_outside_its_passes() {
642 use crate::pass::{Pass, PassLog, PassManager, PassRegistry, Pipeline, RunReport};
643 struct Despawns(Entity);
644 impl Pass for Despawns {
645 fn name(&self) -> &'static str {
646 "despawn-program"
647 }
648 fn despawns_entities(&self) -> bool {
649 true
650 }
651 fn run(&self, w: &mut World, _: &mut PassLog) -> std::result::Result<(), String> {
652 w.despawn(self.0);
653 Ok(())
654 }
655 }
656 struct Nothing;
657 impl Pass for Nothing {
658 fn name(&self) -> &'static str {
659 "nothing"
660 }
661 fn run(&self, _: &mut World, _: &mut PassLog) -> std::result::Result<(), String> {
662 Ok(())
663 }
664 }
665 let manager = PassManager::new(PassRegistry::new()).with_pass_checks(true);
666 let steps = "render graph steps dropped: they drew with an entity that left the world";
667 let bindings = "step bindings dropped: only dropped render graph steps made their draws";
668
669 let (mut w, [program, ..], _) = grouped();
671 let mut report = RunReport::default();
672 let pipeline = Pipeline {
673 passes: vec![Box::new(Despawns(program))],
674 };
675 manager.run(&mut w, &pipeline, &mut report).unwrap();
676 let counts = &report.steps[0].log.counts;
677 assert_eq!((counts.get(steps), counts.get(bindings)), (Some(&1), Some(&2)));
678 assert!(report.dropped_outside.is_empty());
679
680 let (mut w, [program, ..], _) = grouped();
682 w.despawn(program);
683 let mut report = RunReport::default();
684 let pipeline = Pipeline {
685 passes: vec![Box::new(Nothing)],
686 };
687 manager.run(&mut w, &pipeline, &mut report).unwrap();
688 assert_eq!(report.dropped_outside.len(), 1);
689 let (next, dropped) = &report.dropped_outside[0];
690 assert_eq!(
691 (
692 next.as_str(),
693 dropped.graph.steps.len(),
694 dropped.step_bindings.len()
695 ),
696 ("nothing", 1, 2)
697 );
698 assert!(!report.steps[0].log.counts.contains_key(steps));
699 assert!(
700 report
701 .to_string()
702 .contains("outside any pass, before nothing: dropped render graph steps depth group")
703 );
704 }
705}