rustial-renderer-wgpu 0.0.1

Pure WGPU renderer for the rustial 2.5D map engine
Documentation
//! Minimal painter / pass-plan model for the WGPU renderer.
//!
//! This is an architectural step toward MapLibre's `Painter`: render work is
//! planned as explicit ordered passes rather than only being hard-coded inline
//! inside `WgpuMapRenderer::render_full`.

/// Ordered render-pass kinds used by the WGPU renderer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PainterPass {
    /// Explicit sky / atmosphere background pass.
    SkyAtmosphere,
    /// Renderer-owned terrain depth / coordinate capture pass.
    TerrainData,
    /// Shadow depth passes (one per cascade, depth-only).
    ShadowDepth,
    /// Main opaque scene pass: terrain / tiles, vectors, models.
    OpaqueScene,
    /// Heatmap accumulation: Gaussian weights → off-screen R16Float.
    HeatmapAccumulation,
    /// Heatmap colour-map: fullscreen composite from R16Float → surface.
    HeatmapColormap,
    /// Hillshade overlay pass rendered on top of the opaque scene.
    HillshadeOverlay,
}

/// Renderer pass plan for one frame.
#[derive(Debug, Clone, Default)]
pub struct PainterPlan {
    passes: Vec<PainterPass>,
}

impl PainterPlan {
    /// Build the pass plan for the current frame.
    pub fn new(has_terrain_data: bool, has_hillshade_overlay: bool, has_heatmap: bool) -> Self {
        Self::with_shadows(has_terrain_data, has_hillshade_overlay, has_heatmap, false)
    }

    /// Build the pass plan, optionally including shadow depth passes.
    pub fn with_shadows(
        has_terrain_data: bool,
        has_hillshade_overlay: bool,
        has_heatmap: bool,
        has_shadows: bool,
    ) -> Self {
        let mut passes = vec![PainterPass::SkyAtmosphere];
        if has_terrain_data {
            passes.push(PainterPass::TerrainData);
        }
        if has_shadows {
            passes.push(PainterPass::ShadowDepth);
        }
        passes.push(PainterPass::OpaqueScene);
        if has_heatmap {
            passes.push(PainterPass::HeatmapAccumulation);
            passes.push(PainterPass::HeatmapColormap);
        }
        if has_hillshade_overlay {
            passes.push(PainterPass::HillshadeOverlay);
        }
        Self { passes }
    }

    /// Iterate passes in execution order.
    pub fn iter(&self) -> impl Iterator<Item = PainterPass> + '_ {
        self.passes.iter().copied()
    }

    /// Whether a specific pass is present in this frame plan.
    pub fn contains(&self, pass: PainterPass) -> bool {
        self.passes.contains(&pass)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn opaque_is_always_present() {
        let plan = PainterPlan::new(false, false, false);
        assert_eq!(
            plan.iter().collect::<Vec<_>>(),
            vec![PainterPass::SkyAtmosphere, PainterPass::OpaqueScene]
        );
    }

    #[test]
    fn terrain_data_and_hillshade_are_appended_when_enabled() {
        let plan = PainterPlan::new(true, true, false);
        assert_eq!(
            plan.iter().collect::<Vec<_>>(),
            vec![
                PainterPass::SkyAtmosphere,
                PainterPass::TerrainData,
                PainterPass::OpaqueScene,
                PainterPass::HillshadeOverlay,
            ]
        );
    }

    #[test]
    fn heatmap_passes_inserted_after_opaque() {
        let plan = PainterPlan::new(false, true, true);
        assert_eq!(
            plan.iter().collect::<Vec<_>>(),
            vec![
                PainterPass::SkyAtmosphere,
                PainterPass::OpaqueScene,
                PainterPass::HeatmapAccumulation,
                PainterPass::HeatmapColormap,
                PainterPass::HillshadeOverlay,
            ]
        );
    }
}