Skip to main content

khora_lanes/render_lane/shadows_lane/algo/
pass.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//! Shared types and helpers for the canonical shadow lane's depth passes.
16//!
17//! Both the 2D atlas (directional / spot) and the cube atlas (point) emit
18//! identical depth-only render passes; only the target view changes. This
19//! module factors the per-pass record types and the `record_depth_pass`
20//! helper so [`super::atlas_2d`] and [`super::atlas_cube`] stay focused on
21//! their own data flow.
22
23use khora_core::renderer::api::command::{
24    BindGroupId, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
25    StoreOp,
26};
27use khora_core::renderer::api::pipeline::RenderPipelineId;
28use khora_core::renderer::api::resource::{BufferId, TextureViewId};
29use khora_core::renderer::api::util::IndexFormat;
30use khora_core::renderer::traits::CommandEncoder;
31
32/// Pre-collected draw command for one mesh within a shadow pass.
33#[derive(Debug, Clone, Copy)]
34pub struct ShadowDrawCmd {
35    /// Bind group bound at slot 1 carrying the model uniforms (with a
36    /// dynamic offset to address this mesh's slot in the model ring).
37    pub model_bg: BindGroupId,
38    /// Dynamic offset into the model ring buffer.
39    pub model_offset: u32,
40    /// Vertex buffer to draw from.
41    pub vertex_buffer: BufferId,
42    /// Index buffer to draw from.
43    pub index_buffer: BufferId,
44    /// Index count to issue.
45    pub index_count: u32,
46    /// Index format (`Uint16` / `Uint32`).
47    pub index_format: IndexFormat,
48}
49
50/// One render pass's worth of state — used for both 2D atlas slots and
51/// individual cube faces, hence the generic name.
52pub struct AttachmentPass {
53    /// Depth target view this pass writes into.
54    pub target_view: TextureViewId,
55    /// Layer to render into within `target_view`. For 2D atlas passes
56    /// this is the light's slot in the array; for cube faces the per-face
57    /// view already targets the right layer so this is `0`.
58    pub base_array_layer: u32,
59    /// Bind group bound at slot 0 carrying the camera (light view-proj)
60    /// uniforms with a dynamic offset.
61    pub camera_bg: BindGroupId,
62    /// Dynamic offset into the camera ring buffer.
63    pub camera_offset: u32,
64    /// Pre-built draw command list for this pass.
65    pub draw_cmds: Vec<ShadowDrawCmd>,
66}
67
68/// Records one depth-only render pass into `encoder`.
69///
70/// Pulled out so [`super::atlas_2d`] and [`super::atlas_cube`] can drive
71/// the GPU recorder identically — only their pass-collection step
72/// differs.
73pub fn record_depth_pass(
74    encoder: &mut dyn CommandEncoder,
75    pipeline: &RenderPipelineId,
76    pass_data: &AttachmentPass,
77) {
78    let depth_attachment = RenderPassDepthStencilAttachment {
79        view: &pass_data.target_view,
80        depth_ops: Some(Operations {
81            load: LoadOp::Clear(1.0),
82            store: StoreOp::Store,
83        }),
84        stencil_ops: None,
85        base_array_layer: pass_data.base_array_layer,
86    };
87
88    let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
89        label: Some("Shadow Pass"),
90        color_attachments: &[],
91        depth_stencil_attachment: Some(depth_attachment),
92    });
93
94    pass.set_pipeline(pipeline);
95    pass.set_bind_group(0, &pass_data.camera_bg, &[pass_data.camera_offset]);
96
97    for cmd in &pass_data.draw_cmds {
98        pass.set_bind_group(1, &cmd.model_bg, &[cmd.model_offset]);
99        pass.set_vertex_buffer(0, &cmd.vertex_buffer, 0);
100        pass.set_index_buffer(&cmd.index_buffer, 0, cmd.index_format);
101        pass.draw_indexed(0..cmd.index_count, 0, 0..1);
102    }
103}
104
105/// Helper used by both atlas modules: builds the per-mesh draw command
106/// list for one pass by pushing model uniforms into the shared model
107/// ring buffer.
108///
109/// Each pass needs its own list because the ring-buffer push offsets
110/// differ between passes — even though the mesh transforms are identical.
111pub fn build_draw_cmds(
112    device: &dyn khora_core::renderer::GraphicsDevice,
113    render_world: &khora_data::render::RenderWorld,
114    gpu_meshes: &khora_data::assets::Assets<khora_core::renderer::api::scene::GpuMesh>,
115    model_ring: &mut khora_core::renderer::api::util::dynamic_uniform_buffer::DynamicUniformRingBuffer,
116) -> Vec<ShadowDrawCmd> {
117    let mut draw_cmds = Vec::with_capacity(render_world.meshes.len());
118    for mesh in &render_world.meshes {
119        if let Some(gpu_mesh) = gpu_meshes.get(&mesh.cpu_mesh_uuid) {
120            let model_mat = mesh.transform.to_matrix();
121            let normal_mat = if let Some(inv) = model_mat.inverse() {
122                inv.transpose()
123            } else {
124                continue;
125            };
126            let model_uniforms = khora_core::renderer::api::scene::ModelUniforms {
127                model_matrix: model_mat.to_cols_array_2d(),
128                normal_matrix: normal_mat.to_cols_array_2d(),
129            };
130            let model_offset = match model_ring.push(device, bytemuck::bytes_of(&model_uniforms)) {
131                Ok(off) => off,
132                Err(e) => {
133                    log::error!("StandardShadowsLane: failed to push model uniform: {:?}", e);
134                    continue;
135                }
136            };
137            draw_cmds.push(ShadowDrawCmd {
138                model_bg: *model_ring.current_bind_group(),
139                model_offset,
140                vertex_buffer: gpu_mesh.vertex_buffer,
141                index_buffer: gpu_mesh.index_buffer,
142                index_count: gpu_mesh.index_count,
143                index_format: gpu_mesh.index_format,
144            });
145        }
146    }
147    draw_cmds
148}