Skip to main content

concinnity_core/gfx/
view_modes.rs

1//! Viewport view-mode and show-flag state: backend-agnostic per-frame render
2//! selection. The mode picks what the final image shows (the lit scene, a flat
3//! shading, or one G-buffer channel); the flags switch individual feature
4//! passes off for the frame without touching their resources. Consumed by the
5//! render graph (masking pass gates) and each backend's composite.
6
7/// What the viewport's final image shows. `Lit` is the shipping path; the
8/// other modes visualize one stage of the frame.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10#[repr(u32)]
11pub enum ViewMode {
12    #[default]
13    /// The fully lit shipping image.
14    Lit = 0,
15    /// Surface base color with no lighting.
16    Unlit = 1,
17    /// Triangle edges only.
18    Wireframe = 2,
19    /// View-space normals from the geometry prepass.
20    Normals = 3,
21    /// Perceptual roughness from the geometry prepass.
22    Roughness = 4,
23    /// Screen-space ambient occlusion.
24    Occlusion = 5,
25    /// Linear view depth.
26    Depth = 6,
27}
28
29impl ViewMode {
30    /// Every mode, in the cycling/UI order.
31    pub const ALL: [ViewMode; 7] = [
32        ViewMode::Lit,
33        ViewMode::Unlit,
34        ViewMode::Wireframe,
35        ViewMode::Normals,
36        ViewMode::Roughness,
37        ViewMode::Occlusion,
38        ViewMode::Depth,
39    ];
40
41    /// True for the flat-shaded modes (Unlit, Wireframe), which drop the
42    /// surface-effect passes for a clean readable image.
43    pub fn is_flat(self) -> bool {
44        matches!(self, ViewMode::Unlit | ViewMode::Wireframe)
45    }
46
47    /// True for the modes whose image is a geometry-prepass channel sampled
48    /// by the composite.
49    pub fn is_gbuffer_channel(self) -> bool {
50        matches!(
51            self,
52            ViewMode::Normals | ViewMode::Roughness | ViewMode::Occlusion | ViewMode::Depth
53        )
54    }
55
56    /// Short display label for menus.
57    pub fn label(self) -> &'static str {
58        match self {
59            ViewMode::Lit => "Lit",
60            ViewMode::Unlit => "Unlit",
61            ViewMode::Wireframe => "Wireframe",
62            ViewMode::Normals => "Normals",
63            ViewMode::Roughness => "Roughness",
64            ViewMode::Occlusion => "Occlusion",
65            ViewMode::Depth => "Depth",
66        }
67    }
68}
69
70/// Per-frame feature-pass toggles. A cleared bit skips the matching pass for
71/// the frame (the cheap runtime counterpart to the init-time trims); set bits
72/// leave the pass to its normal gates. Billboard icons are editor overlay
73/// sprites rather than a render pass, so they have no bit here.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct ShowFlags(pub u32);
76
77impl ShowFlags {
78    /// Directional and local shadow passes.
79    pub const SHADOWS: ShowFlags = ShowFlags(1 << 0);
80    /// Volumetric fog.
81    pub const FOG: ShowFlags = ShowFlags(1 << 1);
82    /// Bloom.
83    pub const BLOOM: ShowFlags = ShowFlags(1 << 2);
84    /// Screen-space global illumination.
85    pub const SSGI: ShowFlags = ShowFlags(1 << 3);
86    /// Screen-space reflections.
87    pub const SSR: ShowFlags = ShowFlags(1 << 4);
88    /// The debug line pass.
89    pub const LINES: ShowFlags = ShowFlags(1 << 5);
90
91    /// Every flag, paired with its display label, in UI order.
92    pub const LABELED: [(ShowFlags, &'static str); 6] = [
93        (ShowFlags::SHADOWS, "Shadows"),
94        (ShowFlags::FOG, "Fog"),
95        (ShowFlags::BLOOM, "Bloom"),
96        (ShowFlags::SSGI, "SSGI"),
97        (ShowFlags::SSR, "Reflections"),
98        (ShowFlags::LINES, "Lines"),
99    ];
100
101    /// Every flag set.
102    pub const fn all() -> ShowFlags {
103        ShowFlags(
104            ShowFlags::SHADOWS.0
105                | ShowFlags::FOG.0
106                | ShowFlags::BLOOM.0
107                | ShowFlags::SSGI.0
108                | ShowFlags::SSR.0
109                | ShowFlags::LINES.0,
110        )
111    }
112
113    /// Whether every bit in `other` is set here.
114    pub const fn contains(self, other: ShowFlags) -> bool {
115        self.0 & other.0 == other.0
116    }
117
118    #[must_use]
119    /// This set with `other`'s bits flipped.
120    pub const fn toggled(self, other: ShowFlags) -> ShowFlags {
121        ShowFlags(self.0 ^ other.0)
122    }
123}
124
125impl Default for ShowFlags {
126    fn default() -> Self {
127        ShowFlags::all()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn default_shows_everything() {
137        let all = ShowFlags::default();
138        for (flag, _) in ShowFlags::LABELED {
139            assert!(all.contains(flag));
140        }
141        assert_eq!(all, ShowFlags::all());
142    }
143
144    #[test]
145    fn toggling_clears_and_restores_one_bit() {
146        let some = ShowFlags::all().toggled(ShowFlags::FOG);
147        assert!(!some.contains(ShowFlags::FOG));
148        assert!(some.contains(ShowFlags::SHADOWS));
149        assert_eq!(some.toggled(ShowFlags::FOG), ShowFlags::all());
150    }
151
152    #[test]
153    fn mode_classes_partition_as_expected() {
154        assert!(!ViewMode::Lit.is_flat() && !ViewMode::Lit.is_gbuffer_channel());
155        assert!(ViewMode::Unlit.is_flat() && ViewMode::Wireframe.is_flat());
156        for m in [
157            ViewMode::Normals,
158            ViewMode::Roughness,
159            ViewMode::Occlusion,
160            ViewMode::Depth,
161        ] {
162            assert!(m.is_gbuffer_channel() && !m.is_flat());
163        }
164        assert_eq!(ViewMode::ALL.len(), 7);
165    }
166}