use super::*; use crate::scene::model::{mesh_model::MeshLodSet, mesh_model::MeshModel, wire_model::WireModel}; use acadrust::{types::Transform, Handle}; use iced::futures::executor::block_on; // OCS_GPU_CHUNK_MIB=1 cargo test --lib bounded_gpu_uploads -- --ignored --nocapture #[test] #[ignore = "requires a GPU adapter and OCS_GPU_CHUNK_MIB=1"] fn bounded_gpu_uploads_preserve_geometry_and_instancing() { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()); let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) .expect("GPU adapter"); eprintln!("GPU regression adapter: {:?}", adapter.get_info()); let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { required_limits: adapter.limits(), ..Default::default() })) .expect("GPU device"); let budget = gpu_budget::buffer_budget(&device) as u64; assert_eq!(budget, 1024 * 1024, "run with OCS_GPU_CHUNK_MIB=1"); let validation = device.push_error_scope(wgpu::ErrorFilter::Validation); let pipeline = Pipeline::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb); let line = WireModel { points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], ..Default::default() }; let count = gpu_budget::max_storage_elements::(&device) + 2; let wires = vec![line; count]; for bgl in [pipeline.wire_const_bgl.as_ref(), None] { let chunks = wire_gpu::WireGpu::from_run(&device, &queue, &wires, &Default::default(), false, bgl); assert!(chunks.len() > 1); assert_eq!( chunks .iter() .map(|chunk| chunk.instance_count as usize) .sum::(), count ); assert!(chunks .iter() .all(|chunk| chunk.instance_buffer.size() <= budget)); } let segments = gpu_budget::max_elements::(&device) + 1; let dense = WireModel { points: (0..=segments).map(|i| [i as f32, 0.0, 0.0]).collect(), ..Default::default() }; let chunks = wire_gpu::WireGpu::from_run( &device, &queue, &[dense], &Default::default(), false, pipeline.wire_const_bgl.as_ref(), ); assert!(chunks.len() > 1); assert_eq!( chunks .iter() .map(|chunk| chunk.instance_count as usize) .sum::(), segments ); assert!(chunks .iter() .all(|chunk| chunk.instance_buffer.size() <= budget)); let vertices = vec![ bytemuck::Zeroable::zeroed(); gpu_budget::max_elements_grouped::(&device, 6) + 6 ]; let text = text_gpu::upload_vertices(&device, &queue, &vertices); assert_eq!(text.len(), 2); assert_eq!( text.iter() .map(|chunk| chunk.vertex_count as usize) .sum::(), vertices.len() ); assert!(text .iter() .all(|chunk| chunk.vertex_count % 6 == 0 && chunk.vertex_buffer.size() <= budget)); let source = |triangles: usize| { let mut set = MeshLodSet::from_single(MeshModel { name: "7".to_owned(), verts: (0..triangles) .flat_map(|_| [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) .collect(), verts_low: Vec::new(), normals: Vec::new(), indices: (0..triangles as u32 * 3).collect(), triangle_material_handles: Vec::new(), triangle_colors: Vec::new(), color: [1.0; 4], selected: false, }); set.prepare_instance_source(Handle::new(7)); set.lods.clear(); set.instance_transform = Some(Transform::identity()); set.instance_handle = Some(Handle::new(101)); set }; let triangles = gpu_budget::max_elements::(&device) / 3 + 1; let first = source(triangles); let mut second = first.clone(); second.instance_handle = Some(Handle::new(102)); let (mesh, total) = mesh_gpu::build_mesh_batch(&device, &queue, &[first, second]); assert!(mesh.len() > 1); assert_eq!(total, (triangles * 2) as u64); assert_eq!( mesh.iter() .map(|chunk| chunk.index_count as usize / 3) .sum::(), triangles ); for chunk in &mesh { assert_eq!( chunk.instance_count, 2, "large geometry must remain instanced" ); assert_eq!(chunk.handles.len(), 2); assert_eq!(chunk.highlight_ranges.len(), 2); assert!(chunk .highlight_ranges .iter() .all(|range| range.index_count == chunk.index_count && range.instance_start < 2)); for buffer in [ &chunk.vertex_buffer, &chunk.index_buffer, &chunk.transp_index_buffer, &chunk.edge_vertex_buffer, &chunk.wire_vertex_buffer, &chunk.instance_buffer, ] { assert!( buffer.size() <= budget, "buffer size {} exceeds {budget}", buffer.size() ); } } let instance_count = gpu_budget::max_elements::(&device) + 1; let mut repeated = vec![source(1); instance_count]; for (index, set) in repeated.iter_mut().enumerate() { set.instance_handle = Some(Handle::new(1000 + index as u64)); } let (mesh, total) = mesh_gpu::build_mesh_batch(&device, &queue, &repeated); assert_eq!(mesh.len(), 2); assert_eq!(total, instance_count as u64); assert_eq!(mesh[0].vertex_buffer, mesh[1].vertex_buffer); assert_eq!(mesh[0].index_buffer, mesh[1].index_buffer); assert_eq!( mesh.iter() .map(|chunk| chunk.instance_count as usize) .sum::(), instance_count ); for chunk in &mesh { assert!(chunk.instance_buffer.size() <= budget); assert_eq!(chunk.highlight_ranges.len(), chunk.instance_count as usize); assert!(chunk .highlight_ranges .iter() .all(|range| range.instance_start < chunk.instance_count)); } queue.submit([]); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); assert!( block_on(validation.pop()).is_none(), "GPU validation failed" ); }