1use super::compile::CompiledGraph;
16use super::passes::PassId;
17use super::types::{ReadStages, ResourceState};
18use alloc::string::ToString;
19use alloc::vec;
20use alloc::vec::Vec;
21
22#[derive(Copy, Clone, Debug, Eq, PartialEq)]
24pub enum GapKind {
25 UncoveredRead,
28 UncoveredWrite,
31 MissingReadStage,
35}
36
37#[derive(Copy, Clone, Debug, Eq, PartialEq)]
39pub struct BarrierGap {
40 pub pass: PassId,
41 pub resource_label: &'static str,
42 pub kind: GapKind,
43}
44
45impl core::fmt::Display for BarrierGap {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 let what = match self.kind {
48 GapKind::UncoveredRead => "reads",
49 GapKind::UncoveredWrite => "writes",
50 GapKind::MissingReadStage => "reads (stage not in the run union)",
51 };
52 write!(f, "pass {:?} {} {}", self.pass, what, self.resource_label)
53 }
54}
55
56#[cfg(test)]
59pub(crate) fn barrier_coverage_gaps(graph: &CompiledGraph) -> Vec<BarrierGap> {
60 gaps_over(graph, &|_| true)
61}
62
63pub fn barrier_coverage_gaps_for_driven(graph: &CompiledGraph, driven: &[bool]) -> Vec<BarrierGap> {
70 gaps_over(graph, &|i| driven.get(i).copied().unwrap_or(false))
71}
72
73pub fn final_states(graph: &CompiledGraph) -> Vec<(ResourceState, ReadStages)> {
83 let mut state = vec![(ResourceState::Undefined, ReadStages::empty()); graph.resources.len()];
84 for pass in &graph.passes {
85 for op in &pass.barriers_before {
86 state[op.resource_index()] = (op.to_state(), op.read_stages());
87 }
88 }
89 state
90}
91
92fn gaps_over(graph: &CompiledGraph, driven: &dyn Fn(usize) -> bool) -> Vec<BarrierGap> {
93 let mut state = vec![ResourceState::Undefined; graph.resources.len()];
94 let mut run_stages = vec![ReadStages::empty(); graph.resources.len()];
97 let mut gaps = Vec::new();
98
99 for pass in &graph.passes {
100 for op in &pass.barriers_before {
101 let i = op.resource_index();
102 state[i] = op.to_state();
103 if op.to_state() == ResourceState::Read {
104 run_stages[i] = op.read_stages();
105 }
106 }
107
108 let stage = ReadStages::for_pass_kind(pass.kind);
109 for w in &pass.writes {
112 let i = w.resource_index();
113 if !driven(i) {
114 continue;
115 }
116 if state[i] != ResourceState::Write {
117 gaps.push(BarrierGap {
118 pass: pass.id,
119 resource_label: graph.resources[i].label,
120 kind: GapKind::UncoveredWrite,
121 });
122 }
123 }
124 for r in &pass.reads {
125 let i = r.resource_index();
126 if !driven(i) || pass.writes.iter().any(|w| w.resource_index() == i) {
127 continue;
128 }
129 if state[i] != ResourceState::Read {
130 gaps.push(BarrierGap {
131 pass: pass.id,
132 resource_label: graph.resources[i].label,
133 kind: GapKind::UncoveredRead,
134 });
135 } else if !run_stages[i].contains(stage) {
136 gaps.push(BarrierGap {
137 pass: pass.id,
138 resource_label: graph.resources[i].label,
139 kind: GapKind::MissingReadStage,
140 });
141 }
142 }
143 }
144
145 gaps
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::render_graph::builder::GraphBuilder;
152 use crate::render_graph::frame::{FrameGraphInputs, build_frame_graph};
153 use crate::render_graph::types::{
154 PassKind, PixelFormat, TextureDesc, TextureSize, TextureUsage,
155 };
156
157 type FlagSetter = fn(&mut FrameGraphInputs);
161 const FLAGS: &[(&str, FlagSetter)] = &[
162 ("shadow", |i| i.shadow_enabled = true),
163 ("bindless_cull", |i| i.bindless_cull_enabled = true),
164 ("auto_exposure", |i| i.auto_exposure_enabled = true),
165 ("bloom", |i| i.bloom_enabled = true),
166 ("velocity", |i| i.velocity_enabled = true),
167 ("taa", |i| i.taa_enabled = true),
168 ("ssr", |i| i.ssr_enabled = true),
169 ("particles", |i| i.particles_enabled = true),
170 ("fog", |i| i.fog_enabled = true),
171 ("decals", |i| i.decals_enabled = true),
172 ("ssr_prepass", |i| i.ssr_prepass_enabled = true),
173 ("ssao", |i| i.ssao_enabled = true),
174 ("upscale", |i| i.upscale_enabled = true),
175 ("transparent", |i| i.transparent_enabled = true),
176 ("lines", |i| i.lines_enabled = true),
177 ("raymarch", |i| i.raymarch_enabled = true),
178 ("two_pass_occlusion", |i| {
179 i.two_pass_occlusion_enabled = true
180 }),
181 ("ssgi", |i| i.ssgi_enabled = true),
182 ("rt_reflections", |i| i.rt_reflections_enabled = true),
183 ("unified_gbuffer", |i| i.unified_gbuffer_prepass = true),
184 ("world_hidden", |i| i.world_hidden = true),
185 ("clustered_lighting", |i| {
186 i.clustered_lighting_enabled = true
187 }),
188 ("composite_reads_ao", |i| i.composite_reads_ao = true),
189 ("shadowed_spots", |i| i.shadowed_spot_count = 2),
190 ("hiz_build", |i| i.hiz_build_enabled = true),
191 ];
192
193 fn assert_covered(combo: &[usize]) {
195 let mut inputs = FrameGraphInputs::all_off();
196 for &f in combo {
197 FLAGS[f].1(&mut inputs);
198 }
199 let names: Vec<&str> = combo.iter().map(|&f| FLAGS[f].0).collect();
200 let graph = build_frame_graph(&inputs)
201 .unwrap_or_else(|e| panic!("graph failed to compile for {names:?}: {e}"));
202 let gaps = barrier_coverage_gaps(&graph);
203 assert!(
204 gaps.is_empty(),
205 "barrier gaps for {names:?}: {}",
206 gaps.iter()
207 .map(|g| g.to_string())
208 .collect::<Vec<_>>()
209 .join(", ")
210 );
211 }
212
213 #[test]
214 fn every_single_and_paired_flag_graph_is_fully_covered() {
215 assert_covered(&[]);
221 for a in 0..FLAGS.len() {
222 assert_covered(&[a]);
223 for b in (a + 1)..FLAGS.len() {
224 assert_covered(&[a, b]);
225 }
226 }
227 }
228
229 #[test]
230 fn the_driven_subset_check_ignores_resources_outside_it() {
231 let mut g = GraphBuilder::new();
237 let a = g.create_texture("a", tex());
238 let b = g.create_texture("b", tex());
239 let (a1, b1) = {
240 let mut p = g.add_pass(PassId::Main, PassKind::Render);
241 (p.write_texture(a), p.write_texture(b))
242 };
243 g.add_pass(PassId::Composite, PassKind::Render)
244 .read_texture(a1)
245 .read_texture(b1)
246 .presents();
247 let mut g = g.compile().expect("compiles");
248 let composite = g.passes.len() - 1;
249 g.passes[composite].barriers_before.clear();
250
251 let only_a = {
252 let mut d = vec![false; g.resources.len()];
253 d[a.resource.index()] = true;
254 d
255 };
256 let gaps = barrier_coverage_gaps_for_driven(&g, &only_a);
257 assert_eq!(gaps.len(), 1, "{gaps:?}");
258 assert_eq!(gaps[0].resource_label, "a");
259
260 let none = vec![false; g.resources.len()];
261 assert_eq!(barrier_coverage_gaps_for_driven(&g, &none), vec![]);
262 }
263
264 #[test]
265 fn every_frame_graph_resource_classifies_as_its_backend_expects() {
266 use crate::render_graph::types::GraphResourceClass as C;
271
272 let mut inputs = FrameGraphInputs::all_off();
273 for (name, set) in FLAGS {
274 if *name != "world_hidden" {
275 set(&mut inputs);
276 }
277 }
278 inputs.hdr_sample_count = 4;
282 let graph = build_frame_graph(&inputs).expect("compiles");
283
284 let expected = [
285 ("draw_args", C::IndirectBuffer),
286 ("draw_args2", C::IndirectBuffer),
287 ("cull_status", C::UnorderedBuffer),
288 ("cluster_light_list", C::StorageBuffer),
289 ("ao_output", C::ColorTarget),
290 ("shadow_map", C::DepthTarget),
291 ("spot_shadow_map", C::DepthTarget),
292 ("fog_froxel_volume", C::StorageImage),
293 ("hdr_depth", C::DepthTarget),
294 ("hdr_color", C::ColorTarget),
295 ("hiz_pyramid", C::StorageImage),
296 ("gbuffer_normal_depth", C::ColorTarget),
300 ("gbuffer_roughness", C::ColorTarget),
301 ("gbuffer_velocity", C::ColorTarget),
302 ("gbuffer_depth", C::DepthTarget),
303 ];
304 for (label, want) in expected {
305 let res = graph
306 .resources
307 .iter()
308 .find(|r| r.label == label)
309 .unwrap_or_else(|| panic!("{label} missing from the fully-loaded graph"));
310 assert_eq!(res.class(), Some(want), "{label}");
311 }
312
313 for res in &graph.resources {
316 assert!(res.class().is_some(), "{} has no class", res.label);
317 }
318 }
319
320 #[test]
321 fn the_fully_loaded_graph_is_covered() {
322 let all: Vec<usize> = (0..FLAGS.len())
325 .filter(|&f| FLAGS[f].0 != "world_hidden")
326 .collect();
327 assert_covered(&all);
328 }
329
330 fn tex() -> TextureDesc {
331 TextureDesc::texture_2d(
332 TextureSize::Drawable,
333 TextureSize::Drawable,
334 PixelFormat::Rgba16Float,
335 TextureUsage::SHADER_READ | TextureUsage::RENDER_TARGET,
336 )
337 }
338
339 #[test]
340 fn a_well_formed_graph_has_no_gaps() {
341 let mut g = GraphBuilder::new();
342 let t = g.create_texture("t", tex());
343 let t1 = g.add_pass(PassId::Main, PassKind::Render).write_texture(t);
344 g.add_pass(PassId::Composite, PassKind::Render)
345 .read_texture(t1)
346 .presents();
347 let g = g.compile().expect("compiles");
348 assert_eq!(barrier_coverage_gaps(&g), vec![]);
349 }
350
351 #[test]
352 fn a_mixed_stage_read_run_is_covered_for_both_consumers() {
353 let mut g = GraphBuilder::new();
357 let t = g.create_texture("t", tex());
358 let t1 = g.add_pass(PassId::Main, PassKind::Render).write_texture(t);
359 g.add_pass(PassId::AutoExposure, PassKind::Compute)
360 .read_texture(t1);
361 g.add_pass(PassId::Composite, PassKind::Render)
362 .read_texture(t1)
363 .presents();
364 let g = g.compile().expect("compiles");
365 assert_eq!(barrier_coverage_gaps(&g), vec![]);
366 }
367
368 #[test]
369 fn a_dropped_barrier_is_reported() {
370 let mut g = GraphBuilder::new();
374 let t = g.create_texture("t", tex());
375 let t1 = g.add_pass(PassId::Main, PassKind::Render).write_texture(t);
376 g.add_pass(PassId::Composite, PassKind::Render)
377 .read_texture(t1)
378 .presents();
379 let mut g = g.compile().expect("compiles");
380 let composite = g.passes.len() - 1;
381 g.passes[composite].barriers_before.clear();
382
383 let gaps = barrier_coverage_gaps(&g);
384 assert_eq!(gaps.len(), 1, "{gaps:?}");
385 assert_eq!(gaps[0].kind, GapKind::UncoveredRead);
386 assert_eq!(gaps[0].pass, PassId::Composite);
387 assert_eq!(gaps[0].resource_label, "t");
388 }
389
390 #[test]
391 fn a_read_run_missing_a_consumer_stage_is_reported() {
392 let mut g = GraphBuilder::new();
396 let t = g.create_texture("t", tex());
397 let t1 = g.add_pass(PassId::Main, PassKind::Render).write_texture(t);
398 g.add_pass(PassId::AutoExposure, PassKind::Compute)
399 .read_texture(t1);
400 g.add_pass(PassId::Composite, PassKind::Render)
401 .read_texture(t1)
402 .presents();
403 let mut g = g.compile().expect("compiles");
404 for pass in &mut g.passes {
405 for op in &mut pass.barriers_before {
406 if op.to_state() == ResourceState::Read {
407 op.read_stages = ReadStages::FRAGMENT;
408 }
409 }
410 }
411
412 let gaps = barrier_coverage_gaps(&g);
413 assert_eq!(gaps.len(), 1, "{gaps:?}");
414 assert_eq!(gaps[0].kind, GapKind::MissingReadStage);
415 assert_eq!(gaps[0].pass, PassId::AutoExposure);
416 }
417}