1use crate::render_types::{
10 DrawObject, InstancedCluster, RtGeomEntry, SkinnedDrawObject, albedo_pool_index,
11 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
74pub fn geom_entry(obj: &DrawObject, texture_count: u32) -> RtGeomEntry {
76 let (albedo_index, normal_index) =
77 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
78 RtGeomEntry {
79 index_offset: obj.index_offset as u32,
80 base_vertex: obj.base_vertex as u32,
81 albedo_index,
82 normal_index,
83 tint: obj.material.tint,
84 roughness: obj.material.roughness,
85 metallic: obj.material.metallic,
86 emissive: obj.material.emissive,
87 model: obj.model,
88 emissive_map_index: obj.material.emissive_map_index,
89 _pad: [0; 3],
90 }
91}
92
93pub fn cluster_geom_entry(
97 cluster: &InstancedCluster,
98 model: [[f32; 4]; 4],
99 texture_count: u32,
100) -> RtGeomEntry {
101 let (albedo_index, normal_index) =
102 pool_indices(cluster.texture_slot, cluster.normal_map_slot, texture_count);
103 RtGeomEntry {
104 index_offset: cluster.index_offset as u32,
105 base_vertex: 0,
106 albedo_index,
107 normal_index,
108 tint: cluster.material.tint,
109 roughness: cluster.material.roughness,
110 metallic: cluster.material.metallic,
111 emissive: cluster.material.emissive,
112 model,
113 emissive_map_index: cluster.material.emissive_map_index,
114 _pad: [0; 3],
115 }
116}
117
118pub fn skinned_geom_entry(obj: &SkinnedDrawObject, texture_count: u32) -> RtGeomEntry {
126 let (albedo_index, normal_index) =
127 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
128 RtGeomEntry {
129 index_offset: obj.index_offset as u32,
130 base_vertex: 0,
131 albedo_index,
132 normal_index: normal_index | RT_SKINNED_FLAG,
133 tint: obj.material.tint,
134 roughness: obj.material.roughness,
135 metallic: obj.material.metallic,
136 emissive: obj.material.emissive,
137 model: obj.model,
138 emissive_map_index: obj.material.emissive_map_index,
139 _pad: [0; 3],
140 }
141}
142
143pub fn models_dirty(cached: &[[[f32; 4]; 4]], current: &[[[f32; 4]; 4]]) -> bool {
146 cached.len() != current.len() || cached.iter().zip(current).any(|(a, b)| a != b)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 use alloc::vec::Vec;
154 #[test]
156 fn skinned_index_buffer_holds_one_word_per_index() {
157 assert_eq!(skinned_index_buffer_bytes(0), 4);
158 assert_eq!(skinned_index_buffer_bytes(1), 4);
159 assert_eq!(skinned_index_buffer_bytes(3), 12);
160 for count in 0..64usize {
161 let bytes = skinned_index_buffer_bytes(count);
162 assert!(
163 bytes >= count * 4,
164 "{count}: {bytes} cannot hold the indices"
165 );
166 assert_eq!(bytes % 4, 0, "{count}: {bytes} is not whole words");
167 }
168 }
169
170 #[test]
171 fn only_off_stops_updating_the_bvh() {
172 assert_eq!(RtDynamicMode::default(), RtDynamicMode::Auto);
173 assert!(RtDynamicMode::Auto.is_dynamic());
174 assert!(RtDynamicMode::Rebuild.is_dynamic());
175 assert!(RtDynamicMode::Tlas.is_dynamic());
176 assert!(!RtDynamicMode::Off.is_dynamic());
177 }
178
179 #[test]
180 fn pool_indices_share_one_handle_indexed_pool() {
181 use crate::render_types::NO_NORMAL_MAP_SLOT;
182 assert_eq!(pool_indices(2, 1, 5), (2, 1));
185 assert_eq!(pool_indices(9, 9, 5), (4, 4));
187 assert_eq!(pool_indices(2, NO_NORMAL_MAP_SLOT, 5), (2, 5));
189 }
190
191 #[test]
192 fn models_dirty_detects_a_changed_transform() {
193 let a = [[
194 [1.0, 0.0, 0.0, 0.0],
195 [0.0, 1.0, 0.0, 0.0],
196 [0.0, 0.0, 1.0, 0.0],
197 [0.0, 0.0, 0.0, 1.0],
198 ]];
199 let mut b = a;
200 assert!(!models_dirty(&a, &b));
201 b[0][3][0] = 5.0;
202 assert!(models_dirty(&a, &b));
203 assert!(models_dirty(&a, &[]));
205 }
206
207 #[test]
208 fn skinned_flag_is_bit_31_and_masks_back_to_the_pool_index() {
209 assert_eq!(RT_SKINNED_FLAG, 1u32 << 31);
212 for normal_index in [0u32, 1, 5, 96, 1000] {
213 let flagged = normal_index | RT_SKINNED_FLAG;
214 assert_ne!(flagged & RT_SKINNED_FLAG, 0, "flag set");
215 assert_eq!(flagged & !RT_SKINNED_FLAG, normal_index, "masks back");
216 }
217 assert_eq!(96u32 & RT_SKINNED_FLAG, 0);
220 }
221
222 #[test]
223 fn skinned_geom_entry_flags_and_zeroes_base_vertex() {
224 use crate::render_types::{MaterialUniforms, SkinnedDrawObject};
225 let material = MaterialUniforms {
226 tint: [0.2, 0.4, 0.6],
227 roughness: 0.3,
228 metallic: 0.5,
229 emissive: [0.1, 0.0, 0.0],
230 ..MaterialUniforms::DEFAULT
231 };
232 let obj = SkinnedDrawObject {
233 vertex_base: 7,
234 vertex_count: 100,
235 index_offset: 42,
236 index_count: 300,
237 model: [
238 [1.0, 0.0, 0.0, 0.0],
239 [0.0, 1.0, 0.0, 0.0],
240 [0.0, 0.0, 1.0, 0.0],
241 [3.0, 4.0, 5.0, 1.0],
242 ],
243 texture_slot: 9,
244 normal_map_slot: 3,
245 material,
246 visible: true,
247 joint_count: 12,
248 local_bb_min: [-1.0, -1.0, -1.0],
249 local_bb_max: [1.0, 1.0, 1.0],
250 lod_alternates: Vec::new(),
251 };
252 let texture_count = 12u32;
253 let e = skinned_geom_entry(&obj, texture_count);
254 assert_eq!(e.base_vertex, 0);
256 assert_ne!(e.normal_index & RT_SKINNED_FLAG, 0);
259 let (exp_albedo, exp_normal) =
260 pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
261 assert_eq!(e.albedo_index, exp_albedo);
262 assert_eq!(e.normal_index & !RT_SKINNED_FLAG, exp_normal);
263 assert_eq!(e.index_offset, 42);
265 assert_eq!(e.tint, [0.2, 0.4, 0.6]);
266 assert_eq!(e.model[3], [3.0, 4.0, 5.0, 1.0]);
267 }
268}