Skip to main content

rustial_renderer_wgpu/
painter.rs

1//! Minimal painter / pass-plan model for the WGPU renderer.
2//!
3//! This is an architectural step toward MapLibre's `Painter`: render work is
4//! planned as explicit ordered passes rather than only being hard-coded inline
5//! inside `WgpuMapRenderer::render_full`.
6
7/// Ordered render-pass kinds used by the WGPU renderer.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum PainterPass {
10    /// Explicit sky / atmosphere background pass.
11    SkyAtmosphere,
12    /// Renderer-owned terrain depth / coordinate capture pass.
13    TerrainData,
14    /// Shadow depth passes (one per cascade, depth-only).
15    ShadowDepth,
16    /// Main opaque scene pass: terrain / tiles, vectors, models.
17    OpaqueScene,
18    /// Heatmap accumulation: Gaussian weights → off-screen R16Float.
19    HeatmapAccumulation,
20    /// Heatmap colour-map: fullscreen composite from R16Float → surface.
21    HeatmapColormap,
22    /// Hillshade overlay pass rendered on top of the opaque scene.
23    HillshadeOverlay,
24}
25
26/// Renderer pass plan for one frame.
27#[derive(Debug, Clone, Default)]
28pub struct PainterPlan {
29    passes: Vec<PainterPass>,
30}
31
32impl PainterPlan {
33    /// Build the pass plan for the current frame.
34    pub fn new(has_terrain_data: bool, has_hillshade_overlay: bool, has_heatmap: bool) -> Self {
35        Self::with_shadows(has_terrain_data, has_hillshade_overlay, has_heatmap, false)
36    }
37
38    /// Build the pass plan, optionally including shadow depth passes.
39    pub fn with_shadows(
40        has_terrain_data: bool,
41        has_hillshade_overlay: bool,
42        has_heatmap: bool,
43        has_shadows: bool,
44    ) -> Self {
45        let mut passes = vec![PainterPass::SkyAtmosphere];
46        if has_terrain_data {
47            passes.push(PainterPass::TerrainData);
48        }
49        if has_shadows {
50            passes.push(PainterPass::ShadowDepth);
51        }
52        passes.push(PainterPass::OpaqueScene);
53        if has_heatmap {
54            passes.push(PainterPass::HeatmapAccumulation);
55            passes.push(PainterPass::HeatmapColormap);
56        }
57        if has_hillshade_overlay {
58            passes.push(PainterPass::HillshadeOverlay);
59        }
60        Self { passes }
61    }
62
63    /// Iterate passes in execution order.
64    pub fn iter(&self) -> impl Iterator<Item = PainterPass> + '_ {
65        self.passes.iter().copied()
66    }
67
68    /// Whether a specific pass is present in this frame plan.
69    pub fn contains(&self, pass: PainterPass) -> bool {
70        self.passes.contains(&pass)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn opaque_is_always_present() {
80        let plan = PainterPlan::new(false, false, false);
81        assert_eq!(
82            plan.iter().collect::<Vec<_>>(),
83            vec![PainterPass::SkyAtmosphere, PainterPass::OpaqueScene]
84        );
85    }
86
87    #[test]
88    fn terrain_data_and_hillshade_are_appended_when_enabled() {
89        let plan = PainterPlan::new(true, true, false);
90        assert_eq!(
91            plan.iter().collect::<Vec<_>>(),
92            vec![
93                PainterPass::SkyAtmosphere,
94                PainterPass::TerrainData,
95                PainterPass::OpaqueScene,
96                PainterPass::HillshadeOverlay,
97            ]
98        );
99    }
100
101    #[test]
102    fn heatmap_passes_inserted_after_opaque() {
103        let plan = PainterPlan::new(false, true, true);
104        assert_eq!(
105            plan.iter().collect::<Vec<_>>(),
106            vec![
107                PainterPass::SkyAtmosphere,
108                PainterPass::OpaqueScene,
109                PainterPass::HeatmapAccumulation,
110                PainterPass::HeatmapColormap,
111                PainterPass::HillshadeOverlay,
112            ]
113        );
114    }
115}