use super::*; use crate::render_data::handle::SlotState; const POSITIONS: [[f32; 3]; 3] = [[-1.0, 2.0, 3.0], [4.0, -2.0, 1.0], [0.0, 1.0, -3.0]]; const NORMALS: [[f32; 3]; 3] = [[0.0, 1.0, 0.0]; 3]; const TANGENTS: [[f32; 4]; 3] = [[1.0, 0.0, 0.0, 1.0]; 3]; const UVS: [[f32; 2]; 3] = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]; const INDICES: [u32; 3] = [0, 1, 2]; fn info() -> MeshCreateInfo<'static> { MeshCreateInfo { positions: &POSITIONS, normals: &NORMALS, tangents: &TANGENTS, uvs: &UVS, indices: &INDICES, pipeline: PipelineKey::new(7), material: MaterialKey::new(11), flags: RenderFlags::from_bits_retain(2), default_instance_flags: RenderFlags::VISIBLE, default_transform: IDENTITY_MODEL_TRANSFORM, } } fn data() -> RenderData { RenderData::new(RenderDataConfig { initial_vertices: 0, initial_indices: 0, initial_meshes: 0, initial_instances: 0, ..RenderDataConfig::default() }) .unwrap() } #[test] fn affine_world_bounds_cover_translation_scale_shear_and_planes() { let local = Aabb { min: [-1.0, -2.0, 0.0], max: [1.0, 2.0, 0.0], }; assert_eq!( affine_world_aabb(local, IDENTITY_MODEL_TRANSFORM), Ok(local) ); let model = [ [-2.0, 0.0, 0.0, 0.0], [0.5, 3.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [10.0, -4.0, 2.0, 1.0], ]; assert_eq!( affine_world_aabb(local, model), Ok(Aabb { min: [7.0, -10.0, 2.0], max: [13.0, 2.0, 2.0], }) ); } #[test] fn world_bounds_reject_projective_and_overflowing_transforms() { let local = Aabb { min: [-1.0; 3], max: [1.0; 3], }; let mut projective = IDENTITY_MODEL_TRANSFORM; projective[0][3] = 0.5; assert_eq!( affine_world_aabb(local, projective), Err(RenderDataError::InvalidTransform) ); let mut overflowing = IDENTITY_MODEL_TRANSFORM; overflowing[0][0] = f32::MAX; overflowing[1][0] = f32::MAX; assert_eq!( affine_world_aabb(local, overflowing), Err(RenderDataError::InvalidTransform) ); } #[test] fn default_instance_is_protected_and_flags_are_separate() { let mut data = data(); let created = data.create_mesh(info()).unwrap(); assert!(data.instance(created.default_instance).unwrap().is_default); assert_eq!(data.mesh(created.mesh).unwrap().flags.bits(), 2); assert_eq!( data.mesh(created.mesh).unwrap().material, MaterialKey::new(11) ); assert_eq!( data.instance(created.default_instance).unwrap().flags, RenderFlags::VISIBLE ); assert_eq!( data.destroy_instance(created.default_instance), Err(RenderDataError::CannotDestroyDefaultInstance) ); data.set_mesh_flags(created.mesh, RenderFlags::NONE) .unwrap(); assert_eq!(data.mesh(created.mesh).unwrap().flags, RenderFlags::NONE); assert_eq!( data.instance(created.default_instance).unwrap().flags, RenderFlags::VISIBLE ); } #[test] fn stale_mesh_and_instance_handles_are_rejected_after_reuse() { let mut data = data(); let first = data.create_mesh(info()).unwrap(); let old_instance = data .create_instance(first.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); data.destroy_instance(old_instance).unwrap(); let replacement = data .create_instance(first.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); assert_eq!(old_instance.slot(), replacement.slot()); assert_ne!(old_instance.generation(), replacement.generation()); assert!(data.instance(old_instance).is_none()); data.destroy_mesh(first.mesh).unwrap(); let second = data.create_mesh(info()).unwrap(); assert_eq!(first.mesh.slot(), second.mesh.slot()); assert_ne!(first.mesh.generation(), second.mesh.generation()); assert!(data.mesh(first.mesh).is_none()); } #[test] fn clear_handles_all_slot_states_retains_capacity_and_never_reuses_retired() { let mut data = data(); let mesh = data.create_mesh(info()).unwrap(); let vacant = data .create_instance(mesh.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); data.destroy_instance(vacant).unwrap(); data.instances .slots .force_generation(mesh.default_instance.slot(), u32::MAX); let old_capacity = data.capacities(); data.clear().unwrap(); assert_eq!(data.capacities(), old_capacity); assert_eq!(data.mesh_count(), 0); assert_eq!(data.instance_count(), 0); assert!(data.mesh(mesh.mesh).is_none()); assert!(matches!( data.instances.slots.states[mesh.default_instance.slot() as usize], SlotState::Retired )); let new_mesh = data.create_mesh(info()).unwrap(); assert_ne!( new_mesh.default_instance.slot(), mesh.default_instance.slot() ); } #[test] fn capacity_math_has_exact_bounded_and_unbounded_overflow_behavior() { assert_eq!(next_capacity(0, 1, None, "x"), Ok(1)); assert_eq!(next_capacity(1, 2, None, "x"), Ok(2)); assert_eq!(next_capacity(2, 3, Some(3), "x"), Ok(3)); assert_eq!( next_capacity(u32::MAX - 1, u32::MAX, Some(u32::MAX), "x"), Ok(u32::MAX) ); assert!(matches!( next_capacity(u32::MAX - 1, u32::MAX, None, "x"), Err(RenderDataError::CapacityOverflow { .. }) )); assert!(matches!( next_capacity(2, 4, Some(3), "x"), Err(RenderDataError::CapacityExceeded { .. }) )); } #[test] fn all_storage_classes_grow_and_retired_slots_force_max_checked_append() { let mut data = data(); let mesh = data.create_mesh(info()).unwrap(); assert_eq!( data.capacities(), RenderDataCapacities { vertices: 3, indices: 3, meshes: 1, instances: 1, } ); data.create_instance(mesh.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); assert_eq!(data.capacities().instances, 2); let mut slots = SlotTable::new(0, Some(1), "test").unwrap(); slots.reserve_for_len(1, "test").unwrap(); let prepared = slots.prepare().unwrap(); slots.commit(prepared); slots.force_generation(0, u32::MAX); slots.remove(0, u32::MAX); assert!(matches!( slots.reserve_for_len(slots.required_len_for_prepare().unwrap(), "test"), Err(RenderDataError::CapacityExceeded { .. }) )); } #[test] fn allocator_checks_errors_splits_first_fit_coalesces_and_trims_tail() { let mut allocator = RangeAllocator::default(); assert_eq!(allocator.allocate(0), Err(RenderDataError::EmptyRange)); let left = allocator.allocate(2).unwrap(); let middle = allocator.allocate(4).unwrap(); let right = allocator.allocate(2).unwrap(); assert_eq!(allocator.free(middle.clone()), Ok(8)); assert_eq!(allocator.allocate(2).unwrap(), 2..4); assert_eq!(allocator.free(2..4), Ok(8)); assert_eq!(allocator.free(2..4), Err(RenderDataError::RangeOverlap)); assert_eq!(allocator.free(8..9), Err(RenderDataError::RangeOutOfBounds)); assert_eq!(allocator.free(3..3), Err(RenderDataError::EmptyRange)); assert_eq!(allocator.free(left), Ok(8)); assert_eq!(allocator.free(right), Ok(0)); let mut bridge = RangeAllocator::default(); bridge.allocate(6).unwrap(); bridge.free(0..2).unwrap(); bridge.free(4..6).unwrap(); bridge.free(2..4).unwrap(); assert_eq!(bridge.high_water(), 0); let mut overflow = RangeAllocator::default(); overflow.high_water = u32::MAX; assert_eq!(overflow.allocate(1), Err(RenderDataError::RangeOverflow)); } #[test] fn streams_remain_coordinated_across_interior_delete_tail_delete_and_reuse() { let mut data = data(); let first = data.create_mesh(info()).unwrap(); let second = data.create_mesh(info()).unwrap(); assert_eq!(data.streams().positions.len(), 6); assert_eq!(data.indices().len(), 6); data.destroy_mesh(first.mesh).unwrap(); assert_eq!(data.streams().positions.len(), 6); let reused = data.create_mesh(info()).unwrap(); assert_eq!(data.mesh(reused.mesh).unwrap().geometry.vertex_start, 0); data.destroy_mesh(second.mesh).unwrap(); assert_eq!(data.streams().positions.len(), 3); assert_eq!(data.streams().normals.len(), 3); assert_eq!(data.streams().tangents.len(), 3); assert_eq!(data.streams().uvs.len(), 3); assert_eq!(data.indices().len(), 3); } #[test] fn failed_default_instance_preparation_rolls_back_empty_and_existing_geometry() { let mut data = RenderData::new(RenderDataConfig { initial_vertices: 0, initial_indices: 0, initial_meshes: 0, initial_instances: 0, max_instances: Some(0), ..RenderDataConfig::default() }) .unwrap(); for _ in 0..2 { let generations = data.meshes.slots.generations.clone(); assert!(matches!( data.create_mesh(info()), Err(RenderDataError::CapacityExceeded { resource: "instances", .. }) )); assert_eq!(data.vertices.allocator.high_water(), 0); assert_eq!(data.indices.allocator.high_water(), 0); assert!(data.streams().positions.is_empty()); assert!(data.indices().is_empty()); assert_eq!(data.meshes.slots.generations, generations); } data.instances.slots.max_capacity = Some(1); let existing = data.create_mesh(info()).unwrap(); data.instances.slots.max_capacity = Some(0); assert!(data.create_mesh(info()).is_err()); assert_eq!(data.vertices.allocator.high_water(), 3); assert_eq!(data.mesh_count(), 1); assert!(data.mesh(existing.mesh).is_some()); } #[test] fn aabb_supports_one_point_and_multiple_points() { let point = [[2.0, -3.0, 4.0]]; let normal = [[0.0, 1.0, 0.0]]; let uv = [[0.0, 0.0]]; let index = [0]; let mut one = info(); one.positions = &point; one.normals = &normal; one.tangents = &[[1.0, 0.0, 0.0, 1.0]]; one.uvs = &uv; one.indices = &index; let mut data = data(); let mesh = data.create_mesh(one).unwrap(); assert_eq!( data.mesh(mesh.mesh).unwrap().aabb, Aabb { min: point[0], max: point[0] } ); let mesh = data.create_mesh(info()).unwrap(); assert_eq!( data.mesh(mesh.mesh).unwrap().aabb, Aabb { min: [-1.0, -2.0, -3.0], max: [4.0, 2.0, 3.0], } ); } #[test] fn malformed_geometry_matrix_is_rejected_without_consumption() { let mut data = data(); let mut candidate = info(); candidate.positions = &[]; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::EmptyVertices ); let short_normals = &NORMALS[..2]; let mut candidate = info(); candidate.normals = short_normals; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::MismatchedVertexStreams ); let short_tangents = &TANGENTS[..2]; let mut candidate = info(); candidate.tangents = short_tangents; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::MismatchedVertexStreams ); let short_uvs = &UVS[..2]; let mut candidate = info(); candidate.uvs = short_uvs; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::MismatchedVertexStreams ); let mut candidate = info(); candidate.indices = &[]; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::EmptyIndices ); for stream in 0..4 { for bad in [f32::NAN, f32::INFINITY] { let mut positions = POSITIONS; let mut normals = NORMALS; let mut tangents = TANGENTS; let mut uvs = UVS; match stream { 0 => positions[0][0] = bad, 1 => normals[0][0] = bad, 2 => tangents[0][0] = bad, _ => uvs[0][0] = bad, } let mut candidate = info(); candidate.positions = &positions; candidate.normals = &normals; candidate.tangents = &tangents; candidate.uvs = &uvs; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::NonFiniteGeometry ); } } let invalid = [3]; let mut candidate = info(); candidate.indices = &invalid; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::IndexOutOfBounds ); let valid_last = [2]; let mut candidate = info(); candidate.indices = &valid_last; assert!(data.create_mesh(candidate).is_ok()); } #[test] fn normal_matrices_and_failed_transform_operations_are_transactional() { let mut data = data(); let mesh = data.create_mesh(info()).unwrap(); assert_eq!( data.instance(mesh.default_instance).unwrap().normal, IDENTITY_NORMAL_MATRIX ); let translation = [ [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [4.0, 5.0, 6.0, 1.0], ]; data.set_instance_transform(mesh.default_instance, translation) .unwrap(); assert_eq!( data.instance(mesh.default_instance).unwrap().normal, IDENTITY_NORMAL_MATRIX ); let scale = [ [2.0, 0.0, 0.0, 0.0], [0.0, 4.0, 0.0, 0.0], [0.0, 0.0, 0.5, 0.0], [0.0, 0.0, 0.0, 1.0], ]; data.set_instance_transform(mesh.default_instance, scale) .unwrap(); assert_eq!( data.instance(mesh.default_instance).unwrap().normal, [[0.5, 0.0, 0.0], [0.0, 0.25, 0.0], [0.0, 0.0, 2.0]] ); let rotation = [ [0.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ]; data.set_instance_transform(mesh.default_instance, rotation) .unwrap(); let old = data.instance(mesh.default_instance).unwrap(); for invalid in [[[0.0; 4]; 4], { let mut value = IDENTITY_MODEL_TRANSFORM; value[0][0] = f32::INFINITY; value }] { assert_eq!( data.set_instance_transform(mesh.default_instance, invalid), Err(RenderDataError::InvalidTransform) ); assert_eq!(data.instance(mesh.default_instance).unwrap(), old); let count = data.instance_count(); assert_eq!( data.create_instance(mesh.mesh, invalid, RenderFlags::NONE), Err(RenderDataError::InvalidTransform) ); assert_eq!(data.instance_count(), count); let mut candidate = info(); candidate.default_transform = invalid; assert_eq!( data.create_mesh(candidate).unwrap_err(), RenderDataError::InvalidTransform ); } } #[test] fn destroying_mesh_invalidates_exact_owner_instances_with_reused_generations() { let mut data = data(); let first = data.create_mesh(info()).unwrap(); let second = data.create_mesh(info()).unwrap(); let first_extra = data .create_instance(first.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); let second_extra = data .create_instance(second.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); data.destroy_instance(first_extra).unwrap(); let reused = data .create_instance(second.mesh, IDENTITY_MODEL_TRANSFORM, RenderFlags::NONE) .unwrap(); assert_eq!(first_extra.slot(), reused.slot()); data.destroy_mesh(first.mesh).unwrap(); assert!(data.instance(first.default_instance).is_none()); assert!(data.instance(second.default_instance).is_some()); assert!(data.instance(second_extra).is_some()); assert!(data.instance(reused).is_some()); assert_eq!(data.instances().count(), 3); } #[test] fn revision_changes_only_after_success_and_replacement_rejects_old_handles() { let mut data = data(); assert_eq!(data.revision(), 0); assert!(data.destroy_mesh(MeshHandle::from_parts(9, 9)).is_err()); assert_eq!(data.revision(), 0); let old = data.create_mesh(info()).unwrap(); assert_eq!(data.revision(), 1); let mut stage = data.replacement_stage().unwrap(); let new = stage.create_mesh(info()).unwrap(); assert_ne!(old.mesh, new.mesh); data.replace_with(stage).unwrap(); assert_eq!(data.revision(), 2); assert!(data.mesh(old.mesh).is_none()); assert!(data.mesh(new.mesh).is_some()); } #[test] fn replacement_stage_is_rejected_after_source_mutation() { let mut data = data(); let original = data.create_mesh(info()).unwrap(); let mut stage = data.replacement_stage().unwrap(); stage.create_mesh(info()).unwrap(); data.destroy_mesh(original.mesh).unwrap(); let current = data.create_mesh(info()).unwrap(); assert_eq!(current.mesh.slot(), original.mesh.slot()); assert_eq!( data.replace_with(stage), Err(RenderDataError::StaleReplacementStage) ); assert!(data.mesh(current.mesh).is_some()); } #[test] fn replacement_stage_is_rejected_by_a_different_render_data() { let source = data(); let stage = source.replacement_stage().unwrap(); let mut other = data(); assert_eq!( other.replace_with(stage), Err(RenderDataError::StaleReplacementStage) ); } #[test] fn revision_overflow_rejects_mutation_without_committing() { let mut data = data(); data.revision = u64::MAX; assert_eq!( data.create_mesh(info()), Err(RenderDataError::RevisionOverflow) ); assert_eq!(data.mesh_count(), 0); assert_eq!(data.revision(), u64::MAX); }