diff --git a/crates/renderide/src/passes/clustered_light/froxel_cpu/bounds.rs b/crates/renderide/src/passes/clustered_light/froxel_cpu/bounds.rs index 354c4ca89..5ebb29ba2 100644 --- a/crates/renderide/src/passes/clustered_light/froxel_cpu/bounds.rs +++ b/crates/renderide/src/passes/clustered_light/froxel_cpu/bounds.rs @@ -1,4 +1,5 @@ use glam::{Mat4, Vec2, Vec3, Vec4}; +use rayon::prelude::*; use crate::gpu::GpuLight; use crate::world_mesh::cluster::{ClusterFrameParams, TILE_SIZE, sanitize_cluster_clip_planes}; @@ -190,6 +191,7 @@ pub(super) fn build_eye_froxel_spheres( return Some(Vec::new()); } + profiling::scope!("clustered_light::build_eye_froxel_spheres"); let mut all_spheres = Vec::with_capacity(eye_params.len()); for (params, &layout) in eye_params.iter().zip(layouts.iter()) { all_spheres.push(froxel_bounding_spheres(*params, layout)?); @@ -218,16 +220,37 @@ fn froxel_bounding_spheres( return None; } - let cluster_count = layout.cluster_count()?; - let mut spheres = Vec::with_capacity(cluster_count); - for z in 0..layout.cluster_count_z { - for y in 0..layout.cluster_count_y { - for x in 0..layout.cluster_count_x { - spheres.push(cluster_aabb(params, inv_proj, layout, x, y, z)?.bounding_sphere()); + (0..layout.cluster_count_z) + .into_par_iter() + .map(|z| { + let depth_bounds = cluster_z_depth_bounds( + z, + layout.cluster_count_z, + params.near_clip, + params.far_clip, + ); + ClusterDepthParams { + cluster_z: z, + near_depth: depth_bounds.0, + far_depth: depth_bounds.1, } - } - } - Some(spheres) + }) + .flat_map_iter(|depth_params| (0..layout.cluster_count_y).map(move |y| (y, depth_params))) + .flat_map_iter(|(y, depth_params)| { + (0..layout.cluster_count_x).map(move |x| (x, y, depth_params)) + }) + .map(|(x, y, depth_params)| { + cluster_aabb(params, inv_proj, layout, x, y, depth_params).map(|c| c.bounding_sphere()) + }) + .collect() +} + +/// Cached logarithmic clustered-depth bounds for one Z slice. +#[derive(Clone, Copy, Debug)] +struct ClusterDepthParams { + cluster_z: u32, + near_depth: f32, + far_depth: f32, } /// Computes the view-space AABB for one froxel. @@ -237,14 +260,13 @@ fn cluster_aabb( layout: FroxelLayout, cluster_x: u32, cluster_y: u32, - cluster_z: u32, + depth_params: ClusterDepthParams, ) -> Option { - let (near_depth, far_depth) = cluster_z_depth_bounds( + let ClusterDepthParams { cluster_z, - layout.cluster_count_z, - params.near_clip, - params.far_clip, - ); + near_depth, + far_depth, + } = depth_params; let tile_near = -near_depth; let tile_far = -far_depth; diff --git a/crates/renderide/src/passes/clustered_light/froxel_cpu/parallel.rs b/crates/renderide/src/passes/clustered_light/froxel_cpu/parallel.rs index d0bfd7a8b..6cd53f07a 100644 --- a/crates/renderide/src/passes/clustered_light/froxel_cpu/parallel.rs +++ b/crates/renderide/src/passes/clustered_light/froxel_cpu/parallel.rs @@ -1,3 +1,4 @@ +use std::num::Saturating; use std::sync::atomic::AtomicU32; use rayon::prelude::*; @@ -38,7 +39,7 @@ pub(super) fn build_parallel( let chunks = count_parallel_light_chunks(&inputs); let (counts, stats) = merge_parallel_chunk_counts(&chunks, total_clusters); let (ranges, total_indices) = prefix_counts_to_ranges(&counts)?; - let chunk_offsets = build_parallel_chunk_offsets(&chunks, &ranges, total_clusters); + let chunk_offsets = build_chunk_offsets(&chunks, &ranges, total_clusters); let indices = write_parallel_light_chunks(&inputs, &chunk_offsets, total_indices); Some(CpuClusterAssignments { @@ -103,14 +104,17 @@ fn merge_parallel_chunk_counts( chunks: &[CpuFroxelCountChunk], total_clusters: usize, ) -> (Vec, CpuFroxelStats) { + profiling::scope!("clustered_light::merge_parallel_chunk_counts"); let counts = if should_parallelize_cpu_froxel_prefix(total_clusters) { (0..total_clusters) .into_par_iter() .with_min_len(CPU_FROXEL_PREFIX_CHUNK_SIZE) .map(|cluster_id| { - chunks.iter().fold(0u32, |total, chunk| { - total.saturating_add(chunk.counts[cluster_id]) - }) + chunks + .iter() + .map(|chunk| Saturating(chunk.counts[cluster_id])) + .sum::>() + .0 }) .collect() } else { @@ -130,48 +134,28 @@ fn merge_parallel_chunk_counts( (counts, stats) } -fn build_parallel_chunk_offsets( +fn build_chunk_offsets( chunks: &[CpuFroxelCountChunk], ranges: &[[u32; 2]], total_clusters: usize, ) -> Vec> { + profiling::scope!("clustered_light::build_chunk_offsets"); let chunk_count = chunks.len(); - if should_parallelize_cpu_froxel_prefix(total_clusters) && chunk_count >= 2 { - let per_cluster_offsets = (0..total_clusters) - .into_par_iter() - .with_min_len(CPU_FROXEL_PREFIX_CHUNK_SIZE) - .map(|cluster_id| { - let mut next = ranges[cluster_id][0]; - chunks - .iter() - .map(|chunk| { - let offset = next; - next = next.saturating_add(chunk.counts[cluster_id]); - offset - }) - .collect::>() - }) - .collect::>(); - let mut chunk_offsets = (0..chunk_count) - .map(|_| vec![0u32; total_clusters]) - .collect::>(); - for (cluster_id, offsets) in per_cluster_offsets.into_iter().enumerate() { - for (chunk_idx, offset) in offsets.into_iter().enumerate() { - chunk_offsets[chunk_idx][cluster_id] = offset; - } - } - return chunk_offsets; - } - + // The amount of memory allocations/deallocations necessary + // to properly parallelize computations and then transpose the Vec> + // is not worth the amount of time spent iterating here let mut chunk_offsets = (0..chunk_count) .map(|_| vec![0u32; total_clusters]) .collect::>(); for cluster_id in 0..total_clusters { - let mut next = ranges[cluster_id][0]; - for (chunk_idx, chunk) in chunks.iter().enumerate() { - chunk_offsets[chunk_idx][cluster_id] = next; - next = next.saturating_add(chunk.counts[cluster_id]); - } + chunks + .iter() + .map(|chunk| chunk.counts[cluster_id]) + .enumerate() + .fold(ranges[cluster_id][0], |offset, (chunk_idx, chunk)| { + chunk_offsets[chunk_idx][cluster_id] = offset; + offset.saturating_add(chunk) + }); } chunk_offsets }