khora_lanes/render_lane/shadows_lane/algo/
atlas_cube.rs1use std::borrow::Cow;
26use std::sync::RwLock;
27
28use khora_core::math::{Extent3D, Mat4};
29use khora_core::renderer::api::resource::{
30 CameraUniformData, ImageAspect, TextureDescriptor, TextureDimension, TextureId, TextureUsage,
31 TextureViewDescriptor, TextureViewDimension, TextureViewId,
32};
33use khora_core::renderer::api::util::dynamic_uniform_buffer::DynamicUniformRingBuffer;
34use khora_core::renderer::api::util::{SampleCount, TextureFormat};
35use khora_core::renderer::error::RenderError;
36use khora_core::renderer::GraphicsDevice;
37
38use super::pass::{build_draw_cmds, AttachmentPass};
39
40pub struct AtlasCube {
42 pub texture: RwLock<Option<TextureId>>,
44 pub view: RwLock<Option<TextureViewId>>,
47 pub face_views: RwLock<Vec<TextureViewId>>,
51}
52
53impl Default for AtlasCube {
54 fn default() -> Self {
55 Self {
56 texture: RwLock::new(None),
57 view: RwLock::new(None),
58 face_views: RwLock::new(Vec::new()),
59 }
60 }
61}
62
63impl AtlasCube {
64 pub fn create(
68 &self,
69 device: &dyn GraphicsDevice,
70 face_resolution: u32,
71 max_cubes: u32,
72 label: &str,
73 ) -> Result<(), RenderError> {
74 use crate::render_lane::util::lock::write_lock_render;
75
76 let cube_layers = max_cubes * 6;
77
78 let texture = device
79 .create_texture(&TextureDescriptor {
80 label: Some(Cow::Owned(format!("{label} Texture"))),
81 size: Extent3D {
82 width: face_resolution,
83 height: face_resolution,
84 depth_or_array_layers: cube_layers,
85 },
86 mip_level_count: 1,
87 sample_count: SampleCount::X1,
88 dimension: TextureDimension::D2,
89 format: TextureFormat::Depth32Float,
90 usage: TextureUsage::DEPTH_STENCIL_ATTACHMENT | TextureUsage::TEXTURE_BINDING,
91 view_formats: Cow::Borrowed(&[]),
92 })
93 .map_err(RenderError::ResourceError)?;
94
95 let view = device
96 .create_texture_view(
97 texture,
98 &TextureViewDescriptor {
99 label: Some(Cow::Owned(format!("{label} View"))),
100 format: Some(TextureFormat::Depth32Float),
101 dimension: Some(TextureViewDimension::CubeArray),
102 aspect: ImageAspect::DepthOnly,
103 base_mip_level: 0,
104 mip_level_count: Some(1),
105 base_array_layer: 0,
106 array_layer_count: Some(cube_layers),
107 },
108 )
109 .map_err(RenderError::ResourceError)?;
110
111 let mut face_views = Vec::with_capacity(cube_layers as usize);
112 for layer in 0..cube_layers {
113 let face_view = device
114 .create_texture_view(
115 texture,
116 &TextureViewDescriptor {
117 label: Some(Cow::Owned(format!("{label} Face View [{}]", layer))),
118 format: Some(TextureFormat::Depth32Float),
119 dimension: Some(TextureViewDimension::D2),
120 aspect: ImageAspect::DepthOnly,
121 base_mip_level: 0,
122 mip_level_count: Some(1),
123 base_array_layer: layer,
124 array_layer_count: Some(1),
125 },
126 )
127 .map_err(RenderError::ResourceError)?;
128 face_views.push(face_view);
129 }
130
131 *write_lock_render(&self.texture, "AtlasCube.texture")? = Some(texture);
132 *write_lock_render(&self.view, "AtlasCube.view")? = Some(view);
133 *write_lock_render(&self.face_views, "AtlasCube.face_views")? = face_views;
134 Ok(())
135 }
136
137 pub fn view_id(&self) -> Option<TextureViewId> {
139 self.view.read().ok().and_then(|g| *g)
140 }
141
142 pub fn face_views_snapshot(&self) -> Vec<TextureViewId> {
144 self.face_views
145 .read()
146 .map(|g| g.clone())
147 .unwrap_or_default()
148 }
149
150 pub fn destroy(&self, device: &dyn GraphicsDevice) {
152 if let Ok(mut face_views) = self.face_views.write() {
153 for view in face_views.drain(..) {
154 if let Err(e) = device.destroy_texture_view(view) {
155 log::warn!("AtlasCube: failed to destroy face view: {:?}", e);
156 }
157 }
158 }
159 if let Some(view) = self.view.write().ok().and_then(|mut g| g.take()) {
160 if let Err(e) = device.destroy_texture_view(view) {
161 log::warn!("AtlasCube: failed to destroy view: {:?}", e);
162 }
163 }
164 if let Some(texture) = self.texture.write().ok().and_then(|mut g| g.take()) {
165 if let Err(e) = device.destroy_texture(texture) {
166 log::warn!("AtlasCube: failed to destroy texture: {:?}", e);
167 }
168 }
169 }
170}
171
172#[allow(clippy::too_many_arguments)]
174pub fn collect_passes(
175 cube_face_views: &[TextureViewId],
176 cube_layer: u32,
177 light_pos: khora_core::math::Vec3,
178 face_view_projs: &[Mat4; 6],
179 device: &dyn GraphicsDevice,
180 render_world: &khora_data::render::RenderWorld,
181 gpu_meshes: &khora_data::assets::Assets<khora_core::renderer::api::scene::GpuMesh>,
182 camera_ring: &mut DynamicUniformRingBuffer,
183 model_ring: &mut DynamicUniformRingBuffer,
184) -> Vec<AttachmentPass> {
185 let mut passes = Vec::with_capacity(6);
186 for (face_idx, face_vp) in face_view_projs.iter().enumerate() {
187 let face_view_index = (cube_layer as usize) * 6 + face_idx;
188 let Some(face_view) = cube_face_views.get(face_view_index).copied() else {
189 log::error!(
190 "AtlasCube: missing cube face view {} (cube_layer={}, face={})",
191 face_view_index,
192 cube_layer,
193 face_idx
194 );
195 continue;
196 };
197
198 let camera_data = CameraUniformData {
199 view_projection: face_vp.to_cols_array_2d(),
200 camera_position: [light_pos.x, light_pos.y, light_pos.z, 1.0],
201 };
202 let camera_offset = match camera_ring.push(device, bytemuck::bytes_of(&camera_data)) {
203 Ok(off) => off,
204 Err(e) => {
205 log::error!("AtlasCube: failed to push cube face uniform: {:?}", e);
206 continue;
207 }
208 };
209 let camera_bg = *camera_ring.current_bind_group();
210
211 let draw_cmds = build_draw_cmds(device, render_world, gpu_meshes, model_ring);
212 passes.push(AttachmentPass {
213 target_view: face_view,
219 base_array_layer: face_view_index as u32,
220 camera_bg,
221 camera_offset,
222 draw_cmds,
223 });
224 }
225 passes
226}