Skip to main content

khora_lanes/render_lane/shadows_lane/
low_res.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Low-resolution shadows lane.
16//!
17//! Same algorithm as [`super::StandardShadowsLane`] (CSM / spot / cube
18//! point), but with smaller atlases — quarter the per-side resolution
19//! everywhere. Same output contract: produces a
20//! [`khora_data::render::ShadowGpuBindings`] bundle plus
21//! [`khora_data::render::ShadowEntries`]; lit consumer lanes never need
22//! to know which strategy ran.
23//!
24//! `ShadowAgent` selects this lane when GORNA reports a tight time /
25//! VRAM budget (`StrategyId::LowPower`).
26
27use std::any::Any;
28
29use khora_core::lane::{Lane, LaneContext, LaneError, LaneKind, Ref};
30use khora_core::renderer::GraphicsDevice;
31use khora_data::render::RenderWorld;
32
33use super::algo::ShadowsLaneState;
34
35/// Stable strategy name advertised to the agent / GORNA.
36pub const STRATEGY_NAME: &str = "LowResShadows";
37
38/// Low-resolution shadows: 512² × 4-layer 2D atlas + 128² × 4-cube cube
39/// atlas. Same algorithm and output types as
40/// [`super::StandardShadowsLane`]; just lower-resolution textures —
41/// VRAM ≈ 1.5 + 0.25 MiB instead of 64 + 24 MiB.
42#[derive(Default)]
43pub struct LowResShadowsLane {
44    state: ShadowsLaneState,
45}
46
47impl LowResShadowsLane {
48    /// 2D depth atlas resolution (per layer).
49    pub const ATLAS_2D_RESOLUTION: u32 = 512;
50    /// Number of directional / spot shadow casters per frame.
51    pub const ATLAS_2D_MAX_LIGHTS: u32 = 4;
52    /// Cube atlas per-face resolution.
53    pub const CUBE_FACE_RESOLUTION: u32 = 128;
54    /// Number of point shadow casters per frame.
55    pub const CUBE_MAX_LIGHTS: u32 = 4;
56
57    /// Creates a new lane in its uninitialised state.
58    pub fn new() -> Self {
59        Self::default()
60    }
61}
62
63impl Lane for LowResShadowsLane {
64    fn strategy_name(&self) -> &'static str {
65        STRATEGY_NAME
66    }
67
68    fn lane_kind(&self) -> LaneKind {
69        LaneKind::Shadow
70    }
71
72    fn estimate_cost(&self, ctx: &LaneContext) -> f32 {
73        let render_world = match ctx.get::<Ref<RenderWorld>>() {
74            Some(slot) => slot.get(),
75            None => return 0.5,
76        };
77        // Roughly 1/16th the per-texel cost of Standard at the same scene
78        // (16× fewer texels per layer), capped so GORNA always sees this
79        // strategy as cheaper.
80        (super::cost_estimate(render_world) * 0.0625).max(0.05)
81    }
82
83    fn on_initialize(&self, ctx: &mut LaneContext) -> Result<(), LaneError> {
84        let device = ctx
85            .get::<std::sync::Arc<dyn GraphicsDevice>>()
86            .ok_or(LaneError::missing("Arc<dyn GraphicsDevice>"))?
87            .clone();
88        let pipeline_system = ctx
89            .get::<std::sync::Arc<dyn khora_core::renderer::traits::PipelineSystem>>()
90            .ok_or(LaneError::missing("Arc<dyn PipelineSystem>"))?
91            .clone();
92        self.state
93            .init_gpu(
94                device.as_ref(),
95                pipeline_system.as_ref(),
96                Self::ATLAS_2D_RESOLUTION,
97                Self::ATLAS_2D_MAX_LIGHTS,
98                Self::CUBE_FACE_RESOLUTION,
99                Self::CUBE_MAX_LIGHTS,
100                "LowResShadows",
101            )
102            .map_err(|e| LaneError::InitializationFailed(Box::new(e)))
103    }
104
105    fn execute(&self, ctx: &mut LaneContext) -> Result<(), LaneError> {
106        super::execute_shared(
107            &self.state,
108            Self::ATLAS_2D_MAX_LIGHTS,
109            Self::CUBE_MAX_LIGHTS,
110            STRATEGY_NAME,
111            ctx,
112        )
113    }
114
115    fn on_shutdown(&self, ctx: &mut LaneContext) {
116        if let Some(device) = ctx.get::<std::sync::Arc<dyn GraphicsDevice>>() {
117            self.state.shutdown(device.as_ref());
118        }
119    }
120
121    fn as_any(&self) -> &dyn Any {
122        self
123    }
124
125    fn as_any_mut(&mut self) -> &mut dyn Any {
126        self
127    }
128}