use crate::render_types::{
DrawObject, InstancedCluster, RtGeomEntry, SkinnedDrawObject, albedo_pool_index,
normal_pool_index,
};
pub(crate) const RT_SKINNED_FLAG: u32 = 0x8000_0000;
pub fn skinned_index_buffer_bytes(index_count: usize) -> usize {
(index_count * core::mem::size_of::<u32>()).max(4)
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub enum RtDynamicMode {
Off,
#[default]
Auto,
Rebuild,
Tlas,
}
impl RtDynamicMode {
pub fn is_dynamic(self) -> bool {
self != Self::Off
}
}
pub(crate) fn pool_indices(
texture_slot: usize,
normal_map_slot: usize,
texture_count: u32,
) -> (u32, u32) {
(
albedo_pool_index(texture_slot, texture_count),
normal_pool_index(normal_map_slot, texture_count),
)
}
pub fn geom_entry(obj: &DrawObject, texture_count: u32) -> RtGeomEntry {
let (albedo_index, normal_index) =
pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
RtGeomEntry {
index_offset: obj.index_offset as u32,
base_vertex: obj.base_vertex as u32,
albedo_index,
normal_index,
tint: obj.material.tint,
roughness: obj.material.roughness,
metallic: obj.material.metallic,
emissive: obj.material.emissive,
model: obj.model,
emissive_map_index: obj.material.emissive_map_index,
_pad: [0; 3],
}
}
pub fn cluster_geom_entry(
cluster: &InstancedCluster,
model: [[f32; 4]; 4],
texture_count: u32,
) -> RtGeomEntry {
let (albedo_index, normal_index) =
pool_indices(cluster.texture_slot, cluster.normal_map_slot, texture_count);
RtGeomEntry {
index_offset: cluster.index_offset as u32,
base_vertex: 0,
albedo_index,
normal_index,
tint: cluster.material.tint,
roughness: cluster.material.roughness,
metallic: cluster.material.metallic,
emissive: cluster.material.emissive,
model,
emissive_map_index: cluster.material.emissive_map_index,
_pad: [0; 3],
}
}
pub fn skinned_geom_entry(obj: &SkinnedDrawObject, texture_count: u32) -> RtGeomEntry {
let (albedo_index, normal_index) =
pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
RtGeomEntry {
index_offset: obj.index_offset as u32,
base_vertex: 0,
albedo_index,
normal_index: normal_index | RT_SKINNED_FLAG,
tint: obj.material.tint,
roughness: obj.material.roughness,
metallic: obj.material.metallic,
emissive: obj.material.emissive,
model: obj.model,
emissive_map_index: obj.material.emissive_map_index,
_pad: [0; 3],
}
}
pub fn models_dirty(cached: &[[[f32; 4]; 4]], current: &[[[f32; 4]; 4]]) -> bool {
cached.len() != current.len() || cached.iter().zip(current).any(|(a, b)| a != b)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn skinned_index_buffer_holds_one_word_per_index() {
assert_eq!(skinned_index_buffer_bytes(0), 4);
assert_eq!(skinned_index_buffer_bytes(1), 4);
assert_eq!(skinned_index_buffer_bytes(3), 12);
for count in 0..64usize {
let bytes = skinned_index_buffer_bytes(count);
assert!(
bytes >= count * 4,
"{count}: {bytes} cannot hold the indices"
);
assert_eq!(bytes % 4, 0, "{count}: {bytes} is not whole words");
}
}
#[test]
fn only_off_stops_updating_the_bvh() {
assert_eq!(RtDynamicMode::default(), RtDynamicMode::Auto);
assert!(RtDynamicMode::Auto.is_dynamic());
assert!(RtDynamicMode::Rebuild.is_dynamic());
assert!(RtDynamicMode::Tlas.is_dynamic());
assert!(!RtDynamicMode::Off.is_dynamic());
}
#[test]
fn pool_indices_share_one_handle_indexed_pool() {
use crate::render_types::NO_NORMAL_MAP_SLOT;
assert_eq!(pool_indices(2, 1, 5), (2, 1));
assert_eq!(pool_indices(9, 9, 5), (4, 4));
assert_eq!(pool_indices(2, NO_NORMAL_MAP_SLOT, 5), (2, 5));
}
#[test]
fn models_dirty_detects_a_changed_transform() {
let a = [[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]];
let mut b = a;
assert!(!models_dirty(&a, &b));
b[0][3][0] = 5.0;
assert!(models_dirty(&a, &b));
assert!(models_dirty(&a, &[]));
}
#[test]
fn skinned_flag_is_bit_31_and_masks_back_to_the_pool_index() {
assert_eq!(RT_SKINNED_FLAG, 1u32 << 31);
for normal_index in [0u32, 1, 5, 96, 1000] {
let flagged = normal_index | RT_SKINNED_FLAG;
assert_ne!(flagged & RT_SKINNED_FLAG, 0, "flag set");
assert_eq!(flagged & !RT_SKINNED_FLAG, normal_index, "masks back");
}
assert_eq!(96u32 & RT_SKINNED_FLAG, 0);
}
#[test]
fn skinned_geom_entry_flags_and_zeroes_base_vertex() {
use crate::render_types::{MaterialUniforms, SkinnedDrawObject};
let material = MaterialUniforms {
tint: [0.2, 0.4, 0.6],
roughness: 0.3,
metallic: 0.5,
emissive: [0.1, 0.0, 0.0],
..MaterialUniforms::DEFAULT
};
let obj = SkinnedDrawObject {
vertex_base: 7,
vertex_count: 100,
index_offset: 42,
index_count: 300,
model: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[3.0, 4.0, 5.0, 1.0],
],
texture_slot: 9,
normal_map_slot: 3,
material,
visible: true,
joint_count: 12,
local_bb_min: [-1.0, -1.0, -1.0],
local_bb_max: [1.0, 1.0, 1.0],
lod_alternates: Vec::new(),
};
let texture_count = 12u32;
let e = skinned_geom_entry(&obj, texture_count);
assert_eq!(e.base_vertex, 0);
assert_ne!(e.normal_index & RT_SKINNED_FLAG, 0);
let (exp_albedo, exp_normal) =
pool_indices(obj.texture_slot, obj.normal_map_slot, texture_count);
assert_eq!(e.albedo_index, exp_albedo);
assert_eq!(e.normal_index & !RT_SKINNED_FLAG, exp_normal);
assert_eq!(e.index_offset, 42);
assert_eq!(e.tint, [0.2, 0.4, 0.6]);
assert_eq!(e.model[3], [3.0, 4.0, 5.0, 1.0]);
}
}