Skip to main content

concinnity_core/render/render_graph/
view_mask.rs

1// src/render_graph/view_mask.rs
2//
3// Per-frame masking of the graph inputs by the viewport's view mode + show
4// flags: the runtime counterpart to the init-time requirement trims. Pure
5// like `build_frame_graph`, so every backend makes identical decisions, and
6// the compiled-graph cache (keyed on the masked inputs) absorbs the cost of a
7// toggle as one rebuild.
8
9use super::frame::FrameGraphInputs;
10use crate::gfx::view_modes::{ShowFlags, ViewMode};
11
12/// The frame's effective graph inputs under the view state. Cleared show flags
13/// gate their passes off; the flat modes (Unlit, Wireframe) additionally drop
14/// the surface-effect stack; the G-buffer channel modes keep the prepass alive
15/// and, for the occlusion view, route `ao_output` into the composite.
16pub fn apply_view(inputs: &FrameGraphInputs, mode: ViewMode, show: ShowFlags) -> FrameGraphInputs {
17    let mut out = *inputs;
18    out.composite_reads_ao = false;
19    if !show.contains(ShowFlags::SHADOWS) {
20        out.shadow_enabled = false;
21        out.shadowed_spot_count = 0;
22    }
23    if !show.contains(ShowFlags::FOG) {
24        out.fog_enabled = false;
25    }
26    if !show.contains(ShowFlags::BLOOM) {
27        out.bloom_enabled = false;
28    }
29    if !show.contains(ShowFlags::SSGI) {
30        out.ssgi_enabled = false;
31    }
32    if !show.contains(ShowFlags::SSR) {
33        out.ssr_enabled = false;
34        out.rt_reflections_enabled = false;
35    }
36    if !show.contains(ShowFlags::LINES) {
37        out.lines_enabled = false;
38    }
39    if mode.is_flat() {
40        out.ssao_enabled = false;
41        out.ssr_enabled = false;
42        out.rt_reflections_enabled = false;
43        out.ssgi_enabled = false;
44        out.bloom_enabled = false;
45        out.fog_enabled = false;
46        out.decals_enabled = false;
47        out.taa_enabled = false;
48        out.upscale_enabled = false;
49        out.velocity_enabled = false;
50        out.ssr_prepass_enabled = false;
51    }
52    if mode.is_gbuffer_channel() {
53        // The prepass must run for its channels to be current this frame.
54        out.ssr_prepass_enabled = true;
55        // The composite presents the channel, not the scene, so bloom is
56        // wasted; keeping it off also keeps its pooled target from sharing a
57        // heap slot lifetime with the AO output the occlusion view extends.
58        out.bloom_enabled = false;
59        out.composite_reads_ao = mode == ViewMode::Occlusion && out.ssao_enabled;
60    }
61    out
62}
63
64#[cfg(test)]
65mod tests {
66    use super::super::frame::build_frame_graph;
67    use super::super::passes::PassId;
68    use super::*;
69
70    fn everything_on() -> FrameGraphInputs {
71        FrameGraphInputs {
72            shadow_enabled: true,
73            auto_exposure_enabled: true,
74            bloom_enabled: true,
75            velocity_enabled: true,
76            taa_enabled: true,
77            ssr_enabled: true,
78            fog_enabled: true,
79            decals_enabled: true,
80            ssr_prepass_enabled: true,
81            ssao_enabled: true,
82            transparent_enabled: true,
83            lines_enabled: true,
84            ssgi_enabled: true,
85            shadowed_spot_count: 2,
86            ..FrameGraphInputs::all_off()
87        }
88    }
89
90    #[test]
91    fn lit_with_all_flags_is_identity() {
92        let inputs = everything_on();
93        assert_eq!(apply_view(&inputs, ViewMode::Lit, ShowFlags::all()), inputs);
94    }
95
96    #[test]
97    fn each_flag_masks_its_passes() {
98        let inputs = everything_on();
99        let m = |f| apply_view(&inputs, ViewMode::Lit, ShowFlags::all().toggled(f));
100        let no_shadows = m(ShowFlags::SHADOWS);
101        assert!(!no_shadows.shadow_enabled);
102        assert_eq!(no_shadows.shadowed_spot_count, 0);
103        assert!(!m(ShowFlags::FOG).fog_enabled);
104        assert!(!m(ShowFlags::BLOOM).bloom_enabled);
105        assert!(!m(ShowFlags::SSGI).ssgi_enabled);
106        let no_ssr = m(ShowFlags::SSR);
107        assert!(!no_ssr.ssr_enabled && !no_ssr.rt_reflections_enabled);
108        assert!(!m(ShowFlags::LINES).lines_enabled);
109    }
110
111    #[test]
112    fn flat_modes_drop_the_surface_effect_stack() {
113        let inputs = everything_on();
114        for mode in [ViewMode::Unlit, ViewMode::Wireframe] {
115            let out = apply_view(&inputs, mode, ShowFlags::all());
116            assert!(!out.ssao_enabled && !out.ssr_enabled && !out.ssgi_enabled);
117            assert!(!out.bloom_enabled && !out.fog_enabled && !out.decals_enabled);
118            assert!(!out.taa_enabled && !out.upscale_enabled && !out.velocity_enabled);
119            // Shadows follow their show flag, not the mode.
120            assert!(out.shadow_enabled);
121        }
122    }
123
124    #[test]
125    fn occlusion_view_routes_ao_into_the_composite() {
126        let inputs = everything_on();
127        let out = apply_view(&inputs, ViewMode::Occlusion, ShowFlags::all());
128        assert!(out.composite_reads_ao && out.ssao_enabled);
129        assert!(!out.bloom_enabled, "bloom never overlaps the extended AO");
130        // With SSAO unavailable there is nothing to route.
131        let mut no_ssao = everything_on();
132        no_ssao.ssao_enabled = false;
133        let out = apply_view(&no_ssao, ViewMode::Occlusion, ShowFlags::all());
134        assert!(!out.composite_reads_ao);
135    }
136
137    #[test]
138    fn channel_modes_keep_the_prepass_alive() {
139        let mut inputs = everything_on();
140        inputs.ssr_prepass_enabled = false;
141        for mode in [
142            ViewMode::Normals,
143            ViewMode::Roughness,
144            ViewMode::Occlusion,
145            ViewMode::Depth,
146        ] {
147            assert!(apply_view(&inputs, mode, ShowFlags::all()).ssr_prepass_enabled);
148        }
149    }
150
151    #[test]
152    fn masked_inputs_still_compile_and_ao_read_orders_composite_last() {
153        let inputs = everything_on();
154        for mode in ViewMode::ALL {
155            let out = apply_view(&inputs, mode, ShowFlags::all());
156            let graph = build_frame_graph(&out).expect("masked graph compiles");
157            assert_eq!(
158                graph.passes.last().map(|p| p.id),
159                Some(PassId::Composite),
160                "{mode:?}"
161            );
162        }
163        // The occlusion view's composite read extends ao_output's lifetime to
164        // the last pass.
165        let out = apply_view(&inputs, ViewMode::Occlusion, ShowFlags::all());
166        let graph = build_frame_graph(&out).unwrap();
167        let composite_idx = graph.passes.len() - 1;
168        let ao = graph
169            .resources
170            .iter()
171            .find(|r| r.label == "ao_output")
172            .expect("ao_output present");
173        assert_eq!(ao.lifetime.last, composite_idx);
174    }
175}