1use crate::gfx::render_types::{
10 DrawObject, InstancedCluster, MaterialUniforms, RtGeomEntry, SkinnedDrawObject,
11 albedo_pool_index, normal_pool_index,
12};
13
14pub(crate) const RT_SKINNED_FLAG: u32 = 0x8000_0000;
20
21pub fn skinned_index_buffer_bytes(index_count: usize) -> usize {
25 (index_count * core::mem::size_of::<u32>()).max(4)
26}
27
28#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
32pub enum RtDynamicMode {
33 Off,
36 #[default]
40 Auto,
41 Rebuild,
44 Tlas,
47}
48
49impl RtDynamicMode {
50 pub fn is_dynamic(self) -> bool {
52 self != Self::Off
53 }
54}
55
56pub(crate) fn pool_indices(
64 texture_slot: usize,
65 normal_map_slot: usize,
66 texture_count: u32,
67) -> (u32, u32) {
68 (
69 albedo_pool_index(texture_slot, texture_count),
70 normal_pool_index(normal_map_slot, texture_count),
71 )
72}
73
74fn entry(
77 index_offset: u32,
78 base_vertex: u32,
79 model: [[f32; 4]; 4],
80 albedo_index: u32,
81 normal_index: u32,
82 material: &MaterialUniforms,
83) -> RtGeomEntry {
84 RtGeomEntry {
85 index_offset,
86 base_vertex,
87 albedo_index,
88 normal_index,
89 tint: material.tint,
90 roughness: material.roughness,
91 metallic: material.metallic,
92 emissive: material.emissive,
93 model,
94 emissive_map_index: material.emissive_map_index,
95 _pad: [0; 3],
96 }
97}
98
99pub fn geom_entry(obj: &DrawObject, texture_count: u32) -> RtGeomEntry {
101 let (albedo_index, normal_index) =
102 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
103 entry(
104 obj.index_offset as u32,
105 obj.base_vertex as u32,
106 obj.model,
107 albedo_index,
108 normal_index,
109 &obj.material,
110 )
111}
112
113pub fn cluster_geom_entry(
117 cluster: &InstancedCluster,
118 model: [[f32; 4]; 4],
119 texture_count: u32,
120) -> RtGeomEntry {
121 let (albedo_index, normal_index) =
122 pool_indices(cluster.texture_slot, cluster.normal_map_slot, texture_count);
123 entry(
124 cluster.index_offset as u32,
125 0,
126 model,
127 albedo_index,
128 normal_index,
129 &cluster.material,
130 )
131}
132
133pub fn skinned_geom_entry(obj: &SkinnedDrawObject, texture_count: u32) -> RtGeomEntry {
141 let (albedo_index, normal_index) =
142 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
143 entry(
144 obj.index_offset as u32,
145 0,
146 obj.model,
147 albedo_index,
148 normal_index | RT_SKINNED_FLAG,
149 &obj.material,
150 )
151}
152
153pub fn models_dirty(cached: &[[[f32; 4]; 4]], current: &[[[f32; 4]; 4]]) -> bool {
156 cached.len() != current.len() || cached.iter().zip(current).any(|(a, b)| a != b)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 use alloc::vec::Vec;
164 #[test]
166 fn skinned_index_buffer_holds_one_word_per_index() {
167 assert_eq!(skinned_index_buffer_bytes(0), 4);
168 assert_eq!(skinned_index_buffer_bytes(1), 4);
169 assert_eq!(skinned_index_buffer_bytes(3), 12);
170 for count in 0..64usize {
171 let bytes = skinned_index_buffer_bytes(count);
172 assert!(
173 bytes >= count * 4,
174 "{count}: {bytes} cannot hold the indices"
175 );
176 assert_eq!(bytes % 4, 0, "{count}: {bytes} is not whole words");
177 }
178 }
179
180 #[test]
181 fn only_off_stops_updating_the_bvh() {
182 assert_eq!(RtDynamicMode::default(), RtDynamicMode::Auto);
183 assert!(RtDynamicMode::Auto.is_dynamic());
184 assert!(RtDynamicMode::Rebuild.is_dynamic());
185 assert!(RtDynamicMode::Tlas.is_dynamic());
186 assert!(!RtDynamicMode::Off.is_dynamic());
187 }
188
189 #[test]
190 fn pool_indices_share_one_handle_indexed_pool() {
191 use crate::gfx::render_types::NO_NORMAL_MAP_SLOT;
192 assert_eq!(pool_indices(2, 1, 5), (2, 1));
195 assert_eq!(pool_indices(9, 9, 5), (4, 4));
197 assert_eq!(pool_indices(2, NO_NORMAL_MAP_SLOT, 5), (2, 5));
199 }
200
201 #[test]
202 fn models_dirty_detects_a_changed_transform() {
203 let a = [[
204 [1.0, 0.0, 0.0, 0.0],
205 [0.0, 1.0, 0.0, 0.0],
206 [0.0, 0.0, 1.0, 0.0],
207 [0.0, 0.0, 0.0, 1.0],
208 ]];
209 let mut b = a;
210 assert!(!models_dirty(&a, &b));
211 b[0][3][0] = 5.0;
212 assert!(models_dirty(&a, &b));
213 assert!(models_dirty(&a, &[]));
215 }
216
217 #[test]
218 fn skinned_flag_is_bit_31_and_masks_back_to_the_pool_index() {
219 assert_eq!(RT_SKINNED_FLAG, 1u32 << 31);
222 for normal_index in [0u32, 1, 5, 96, 1000] {
223 let flagged = normal_index | RT_SKINNED_FLAG;
224 assert_ne!(flagged & RT_SKINNED_FLAG, 0, "flag set");
225 assert_eq!(flagged & !RT_SKINNED_FLAG, normal_index, "masks back");
226 }
227 assert_eq!(96u32 & RT_SKINNED_FLAG, 0);
230 }
231
232 #[test]
233 fn skinned_geom_entry_flags_and_zeroes_base_vertex() {
234 use crate::gfx::render_types::{MaterialUniforms, SkinnedDrawObject};
235 let material = MaterialUniforms {
236 tint: [0.2, 0.4, 0.6],
237 roughness: 0.3,
238 metallic: 0.5,
239 emissive: [0.1, 0.0, 0.0],
240 ..MaterialUniforms::DEFAULT
241 };
242 let obj = SkinnedDrawObject {
243 vertex_base: 7,
244 vertex_count: 100,
245 index_offset: 42,
246 index_count: 300,
247 model: [
248 [1.0, 0.0, 0.0, 0.0],
249 [0.0, 1.0, 0.0, 0.0],
250 [0.0, 0.0, 1.0, 0.0],
251 [3.0, 4.0, 5.0, 1.0],
252 ],
253 texture_slot: 9,
254 normal_map_slot: 3,
255 material,
256 visible: true,
257 joint_count: 12,
258 local_bb_min: [-1.0, -1.0, -1.0],
259 local_bb_max: [1.0, 1.0, 1.0],
260 lod_alternates: Vec::new(),
261 };
262 let texture_count = 12u32;
263 let e = skinned_geom_entry(&obj, texture_count);
264 assert_eq!(e.base_vertex, 0);
266 assert_ne!(e.normal_index & RT_SKINNED_FLAG, 0);
269 let (exp_albedo, exp_normal) =
270 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
271 assert_eq!(e.albedo_index, exp_albedo);
272 assert_eq!(e.normal_index & !RT_SKINNED_FLAG, exp_normal);
273 assert_eq!(e.index_offset, 42);
275 assert_eq!(e.tint, [0.2, 0.4, 0.6]);
276 assert_eq!(e.model[3], [3.0, 4.0, 5.0, 1.0]);
277 }
278
279 #[test]
283 fn a_static_entry_keeps_the_draws_own_mesh_slice() {
284 let obj = crate::test_support::draw_object();
285 let texture_count = 12u32;
286 let e = geom_entry(&obj, texture_count);
287
288 assert_eq!(e.index_offset, obj.index_offset as u32);
289 assert_eq!(e.base_vertex, obj.base_vertex as u32);
290 assert_eq!(e.normal_index & RT_SKINNED_FLAG, 0);
293 let (albedo, normal) = pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
294 assert_eq!((e.albedo_index, e.normal_index), (albedo, normal));
295 assert_eq!(e.model, obj.model);
296 assert_eq!(e.tint, obj.material.tint);
297 assert_eq!(e.roughness, obj.material.roughness);
298 }
299
300 #[test]
304 fn a_cluster_instance_zeroes_the_base_vertex_and_takes_its_own_model() {
305 use crate::gfx::render_types::{InstancedCluster, MaterialUniforms};
306 let cluster = InstancedCluster {
307 vertex_offset: 64,
308 vertex_count: 24,
309 index_offset: 36,
310 index_count: 36,
311 texture_slot: 4,
312 normal_map_slot: 2,
313 material: MaterialUniforms {
314 tint: [0.9, 0.8, 0.7],
315 roughness: 0.25,
316 ..MaterialUniforms::DEFAULT
317 },
318 cluster_bb_min: [-1.0; 3],
319 cluster_bb_max: [1.0; 3],
320 local_bb_min: [-1.0; 3],
321 local_bb_max: [1.0; 3],
322 cull_distance: 0.0,
323 instances: Vec::new(),
324 lod_alternates: Vec::new(),
325 };
326 let model = [
327 [1.0, 0.0, 0.0, 0.0],
328 [0.0, 1.0, 0.0, 0.0],
329 [0.0, 0.0, 1.0, 0.0],
330 [8.0, 9.0, 10.0, 1.0],
331 ];
332 let texture_count = 12u32;
333 let e = cluster_geom_entry(&cluster, model, texture_count);
334
335 assert_eq!(e.base_vertex, 0, "cluster indices are already absolute");
336 assert_eq!(e.index_offset, cluster.index_offset as u32);
337 assert_eq!(
338 e.model, model,
339 "the instance's own transform, not the cluster's"
340 );
341 assert_eq!(e.normal_index & RT_SKINNED_FLAG, 0);
342 let (albedo, normal) =
343 pool_indices(cluster.texture_slot, cluster.normal_map_slot, texture_count);
344 assert_eq!((e.albedo_index, e.normal_index), (albedo, normal));
345 assert_eq!(e.tint, [0.9, 0.8, 0.7]);
346 assert_eq!(e.roughness, 0.25);
347 }
348}