Skip to main content

khora_lanes/render_lane/shadows_lane/
medium.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//! Medium-resolution shadows lane.
16//!
17//! Same algorithm as [`super::StandardShadowsLane`] (CSM / spot / cube
18//! point), with half the per-side resolution everywhere — the middle
19//! rung between Standard and [`super::LowResShadowsLane`]. Same output
20//! contract: produces a [`khora_data::render::ShadowGpuBindings`] bundle
21//! plus [`khora_data::render::ShadowEntries`]; lit consumer lanes never
22//! need to know which strategy ran.
23//!
24//! `ShadowAgent` selects this lane for `StrategyId::Balanced`, giving
25//! GORNA a genuine intermediate trade-off instead of a binary
26//! Standard/LowRes choice.
27
28use std::any::Any;
29
30use khora_core::lane::{Lane, LaneContext, LaneError, LaneKind, Ref};
31use khora_core::renderer::GraphicsDevice;
32use khora_data::render::RenderWorld;
33
34use super::algo::ShadowsLaneState;
35
36/// Stable strategy name advertised to the agent / GORNA.
37pub const STRATEGY_NAME: &str = "MediumShadows";
38
39/// Medium-resolution shadows: 1024² × 4-layer 2D atlas + 256² × 4-cube
40/// cube atlas. Same algorithm and output types as
41/// [`super::StandardShadowsLane`]; half the per-side resolution —
42/// VRAM ≈ 16 + 6 MiB instead of 64 + 24 MiB.
43#[derive(Default)]
44pub struct MediumShadowsLane {
45    state: ShadowsLaneState,
46}
47
48impl MediumShadowsLane {
49    /// 2D depth atlas resolution (per layer).
50    pub const ATLAS_2D_RESOLUTION: u32 = 1024;
51    /// Number of directional / spot shadow casters per frame.
52    pub const ATLAS_2D_MAX_LIGHTS: u32 = 4;
53    /// Cube atlas per-face resolution.
54    pub const CUBE_FACE_RESOLUTION: u32 = 256;
55    /// Number of point shadow casters per frame.
56    pub const CUBE_MAX_LIGHTS: u32 = 4;
57
58    /// Creates a new lane in its uninitialised state.
59    pub fn new() -> Self {
60        Self::default()
61    }
62}
63
64impl Lane for MediumShadowsLane {
65    fn strategy_name(&self) -> &'static str {
66        STRATEGY_NAME
67    }
68
69    fn lane_kind(&self) -> LaneKind {
70        LaneKind::Shadow
71    }
72
73    fn estimate_cost(&self, ctx: &LaneContext) -> f32 {
74        let render_world = match ctx.get::<Ref<RenderWorld>>() {
75            Some(slot) => slot.get(),
76            None => return 0.75,
77        };
78        // Quarter the per-texel cost of Standard at the same scene (4× fewer
79        // texels per layer), floored so GORNA still sees real work.
80        (super::cost_estimate(render_world) * 0.25).max(0.1)
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                "MediumShadows",
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}