Prepare GPU resources for all visible nodes. Must be called before render(). Creates/updates vertex buffers, index buffers, and uniform bind groups. `prev_vp_matrix` is the previous frame's view-projection — used together with each node's `prev_transform` to compute `prev_mvp` for the velocity buffer (motion blur + TAA per-object reprojection).
(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
vp_matrix: &[[f32; 4]; 4],
prev_vp_matrix: &[[f32; 4]; 4],
uniform_layout: &wgpu::BindGroupLayo
| 590 | /// with each node's `prev_transform` to compute `prev_mvp` for the velocity |
| 591 | /// buffer (motion blur + TAA per-object reprojection). |
| 592 | pub fn prepare( |
| 593 | &mut self, |
| 594 | device: &wgpu::Device, |
| 595 | queue: &wgpu::Queue, |
| 596 | vp_matrix: &[[f32; 4]; 4], |
| 597 | prev_vp_matrix: &[[f32; 4]; 4], |
| 598 | uniform_layout: &wgpu::BindGroupLayout, |
| 599 | ) { |
| 600 | let frustum = extract_frustum_planes(vp_matrix); |
| 601 | |
| 602 | // Phase 1: upload geometry for any freshly-added or dirty nodes, |
| 603 | // assign uniform slots, and count how many nodes we'll draw. |
| 604 | // Borrow splits so we can push to `pending_blas_builds` while |
| 605 | // iterating `nodes` mutably. |
| 606 | let hw_rt = self.hw_rt_enabled; |
| 607 | let pending_blas = &mut self.pending_blas_builds; |
| 608 | let pending_cards = &mut self.pending_card_captures; |
| 609 | let pending_sdf = &mut self.pending_sdf_bakes; |
| 610 | let next_card_slot = &mut self.next_card_slot; |
| 611 | let mut visible_count: u32 = 0; |
| 612 | for (handle, node) in self.nodes.iter_mut() { |
| 613 | if !node.visible || node.indices.is_empty() { |
| 614 | continue; |
| 615 | } |
| 616 | // Frustum cull against world-space AABB. Transform the local |
| 617 | // bounds into world space by applying the node's transform |
| 618 | // to the 8 corners; use the min/max of the result. |
| 619 | if node.bounds_min[0] <= node.bounds_max[0] { |
| 620 | let t = &node.transform; |
| 621 | let mut wmin = [f32::MAX; 3]; |
| 622 | let mut wmax = [f32::MIN; 3]; |
| 623 | for ix in 0..2 { |
| 624 | for iy in 0..2 { |
| 625 | for iz in 0..2 { |
| 626 | let lx = if ix == 0 { node.bounds_min[0] } else { node.bounds_max[0] }; |
| 627 | let ly = if iy == 0 { node.bounds_min[1] } else { node.bounds_max[1] }; |
| 628 | let lz = if iz == 0 { node.bounds_min[2] } else { node.bounds_max[2] }; |
| 629 | let wx = t[0][0]*lx + t[1][0]*ly + t[2][0]*lz + t[3][0]; |
| 630 | let wy = t[0][1]*lx + t[1][1]*ly + t[2][1]*lz + t[3][1]; |
| 631 | let wz = t[0][2]*lx + t[1][2]*ly + t[2][2]*lz + t[3][2]; |
| 632 | if wx < wmin[0] { wmin[0] = wx; } |
| 633 | if wy < wmin[1] { wmin[1] = wy; } |
| 634 | if wz < wmin[2] { wmin[2] = wz; } |
| 635 | if wx > wmax[0] { wmax[0] = wx; } |
| 636 | if wy > wmax[1] { wmax[1] = wy; } |
| 637 | if wz > wmax[2] { wmax[2] = wz; } |
| 638 | } |
| 639 | } |
| 640 | } |
| 641 | node.in_view_frustum = !aabb_outside_frustum(&frustum, wmin, wmax); |
| 642 | node.world_bounds_min = wmin; |
| 643 | node.world_bounds_max = wmax; |
| 644 | } else { |
| 645 | // Empty or uninitialized bounds — can't cull, play safe. |
| 646 | node.in_view_frustum = true; |
| 647 | node.world_bounds_min = [f32::MAX; 3]; |
| 648 | node.world_bounds_max = [f32::MIN; 3]; |
| 649 | } |
no test coverage detected