use glam::Mat4;
#[derive(Clone, Copy, Debug)]
pub(super) struct ShadowAlloc {
pub descriptor_base: u32,
pub view_base: u32,
pub reserved_descriptors: u32,
pub reserved_views: u32,
}
impl ShadowAlloc {
pub(super) fn try_new(
active_descriptor_count: u32,
active_view_count: u32,
descriptors: u32,
views: u32,
max_descriptors: u32,
max_views: u32,
) -> Option<Self> {
if active_descriptor_count.saturating_add(descriptors) > max_descriptors {
return None;
}
if active_view_count.saturating_add(views) > max_views {
return None;
}
Some(Self {
descriptor_base: active_descriptor_count,
view_base: active_view_count,
reserved_descriptors: descriptors,
reserved_views: views,
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct EvsmDispatchEntry {
pub descriptor_index: u32,
pub pcf_rect: [u32; 4],
pub evsm_rect: [u32; 4],
pub params_slot: u32,
pub cascade_layer: u32,
pub should_render: bool,
}
#[derive(Clone, Debug)]
pub struct LightShadowRecord {
pub views: Vec<LightShadowView>,
pub descriptor_base: u32,
}
#[derive(Clone, Debug)]
pub struct LightShadowView {
pub view_projection: Mat4,
pub atlas_rect: [u32; 4],
pub cube_layer: Option<u32>,
pub cascade_layer: Option<u32>,
pub update_period: u64,
pub should_render: bool,
pub shadow_view_slot: u32,
}
#[derive(Clone, Debug)]
pub struct ShadowViewThrottle {
pub last_rendered_frame: u64,
pub last_view_projection: Mat4,
pub last_atlas_rect: [u32; 4],
pub last_cascade_layer: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::ShadowAlloc;
const MAX_D: u32 = 32; const MAX_V: u32 = 96;
#[test]
fn alloc_succeeds_from_empty() {
let a = ShadowAlloc::try_new(0, 0, 1, 1, MAX_D, MAX_V).unwrap();
assert_eq!(a.descriptor_base, 0);
assert_eq!(a.view_base, 0);
assert_eq!(a.reserved_descriptors, 1);
assert_eq!(a.reserved_views, 1);
}
#[test]
fn alloc_advances_bases() {
let used_d = 8;
let used_v = 48;
let a = ShadowAlloc::try_new(used_d, used_v, 1, 6, MAX_D, MAX_V).unwrap();
assert_eq!(a.descriptor_base, 8);
assert_eq!(a.view_base, 48);
}
#[test]
fn alloc_rejects_descriptor_overflow() {
assert!(ShadowAlloc::try_new(MAX_D, 0, 1, 1, MAX_D, MAX_V).is_none());
assert!(ShadowAlloc::try_new(MAX_D - 2, 0, 4, 1, MAX_D, MAX_V).is_none());
assert!(ShadowAlloc::try_new(MAX_D - 4, 0, 4, 4, MAX_D, MAX_V).is_some());
}
#[test]
fn alloc_rejects_view_overflow() {
assert!(ShadowAlloc::try_new(16, 96, 1, 6, MAX_D, MAX_V).is_none());
assert!(ShadowAlloc::try_new(15, 90, 1, 6, MAX_D, MAX_V).is_some());
}
#[test]
fn alloc_seventeen_point_lights_scenario() {
let mut active_d = 0u32;
let mut active_v = 0u32;
for i in 0..17 {
let alloc = ShadowAlloc::try_new(active_d, active_v, 1, 6, MAX_D, MAX_V);
if i < 16 {
let a = alloc.unwrap_or_else(|| panic!("point light #{i} should have fit"));
active_d = a.descriptor_base + 1;
active_v = a.view_base + 6;
} else {
assert!(
alloc.is_none(),
"the 17th point light must be rejected — \
it would overflow the 96-view buffer"
);
}
}
assert_eq!(active_d, 16);
assert_eq!(active_v, 96);
}
#[test]
fn alloc_mixed_directional_and_point() {
let mut active_d = 0u32;
let mut active_v = 0u32;
let a = ShadowAlloc::try_new(active_d, active_v, 4, 4, MAX_D, MAX_V).unwrap();
active_d = a.descriptor_base + 4;
active_v = a.view_base + 4;
for _ in 0..8 {
let a = ShadowAlloc::try_new(active_d, active_v, 1, 6, MAX_D, MAX_V).unwrap();
active_d = a.descriptor_base + 1;
active_v = a.view_base + 6;
}
assert_eq!(active_d, 12);
assert_eq!(active_v, 52);
for i in 0..21 {
let alloc = ShadowAlloc::try_new(active_d, active_v, 1, 1, MAX_D, MAX_V);
if i < 20 {
let a = alloc.unwrap_or_else(|| panic!("spot #{i} should have fit"));
active_d = a.descriptor_base + 1;
active_v = a.view_base + 1;
} else {
assert!(alloc.is_none(), "the 21st spot must be rejected");
}
}
assert_eq!(active_d, MAX_D);
assert_eq!(active_v, 72);
}
#[test]
fn alloc_partial_commit() {
let alloc = ShadowAlloc::try_new(0, 0, 4, 4, MAX_D, MAX_V).unwrap();
let landed = 3u32;
assert!(landed <= alloc.reserved_descriptors);
assert!(landed <= alloc.reserved_views);
let next = ShadowAlloc::try_new(3, 3, 1, 1, MAX_D, MAX_V).unwrap();
assert_eq!(next.descriptor_base, 3);
assert_eq!(next.view_base, 3);
}
#[test]
fn alloc_saturating_add_doesnt_panic() {
assert!(ShadowAlloc::try_new(u32::MAX - 1, 0, 100, 1, MAX_D, MAX_V).is_none());
assert!(ShadowAlloc::try_new(0, u32::MAX - 1, 1, 100, MAX_D, MAX_V).is_none());
}
}