Skip to content

Commit 7cac2db

Browse files
committed
Tune renderer rayon thresholds
1 parent 4801c61 commit 7cac2db

12 files changed

Lines changed: 179 additions & 34 deletions

File tree

crates/renderide/src/passes/clustered_light.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl ClusteredLightPass {
143143

144144
/// Returns whether this pass should use CPU froxel assignment for the current view.
145145
fn should_use_cpu_froxel(&self, view_idx: usize, _stereo: bool, light_count: u32) -> bool {
146-
view_idx == 0 && light_count >= AUTO_CPU_FROXEL_LIGHT_THRESHOLD
146+
should_use_cpu_froxel_for_view(view_idx, light_count)
147147
}
148148

149149
/// Selects and prepares the clustered-light work for the current graph view.
@@ -282,6 +282,11 @@ impl ClusteredLightPass {
282282
}
283283
}
284284

285+
/// Returns whether a graph view should use CPU froxel assignment for clustered lights.
286+
fn should_use_cpu_froxel_for_view(view_idx: usize, light_count: u32) -> bool {
287+
view_idx == 0 && light_count >= AUTO_CPU_FROXEL_LIGHT_THRESHOLD
288+
}
289+
285290
impl ComputePass for ClusteredLightPass {
286291
fn name(&self) -> &str {
287292
"ClusteredLight"
@@ -352,3 +357,24 @@ impl ComputePass for ClusteredLightPass {
352357
Ok(())
353358
}
354359
}
360+
361+
#[cfg(test)]
362+
mod tests {
363+
use super::*;
364+
365+
#[test]
366+
fn cpu_froxel_auto_gate_uses_first_view_and_light_threshold() {
367+
assert!(!should_use_cpu_froxel_for_view(
368+
0,
369+
AUTO_CPU_FROXEL_LIGHT_THRESHOLD - 1
370+
));
371+
assert!(should_use_cpu_froxel_for_view(
372+
0,
373+
AUTO_CPU_FROXEL_LIGHT_THRESHOLD
374+
));
375+
assert!(!should_use_cpu_froxel_for_view(
376+
1,
377+
AUTO_CPU_FROXEL_LIGHT_THRESHOLD
378+
));
379+
}
380+
}

crates/renderide/src/passes/clustered_light/froxel_cpu.rs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ use crate::world_mesh::cluster::{
1515
};
1616

1717
/// Light count at which `Auto` mode starts considering CPU froxel assignment.
18-
pub(super) const AUTO_CPU_FROXEL_LIGHT_THRESHOLD: u32 = 128;
19-
const CPU_FROXEL_PARALLEL_MIN_LIGHTS: usize = 128;
20-
const CPU_FROXEL_LIGHT_CHUNK_SIZE: usize = 64;
18+
pub(super) const AUTO_CPU_FROXEL_LIGHT_THRESHOLD: u32 = 64;
19+
/// Light count at which CPU froxel assignment fans out across worker chunks.
20+
const CPU_FROXEL_PARALLEL_MIN_LIGHTS: usize = 64;
21+
/// Lights assigned to one CPU froxel worker chunk.
22+
const CPU_FROXEL_LIGHT_CHUNK_SIZE: usize = 32;
2123
/// Froxel count at which count merge, offset, and prefix work uses Rayon.
22-
const CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS: usize = 1_024;
24+
const CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS: usize = 512;
2325
/// Cluster-count stride for local prefix-sum chunks.
24-
const CPU_FROXEL_PREFIX_CHUNK_SIZE: usize = 1_024;
26+
const CPU_FROXEL_PREFIX_CHUNK_SIZE: usize = 512;
2527

2628
/// Point light tag in [`GpuLight::light_type`].
2729
const LIGHT_TYPE_POINT: u32 = 0;
@@ -122,14 +124,26 @@ impl FroxelLightPlanner {
122124
return Some(CpuClusterAssignments::default());
123125
}
124126
let layouts = validated_eye_layouts(eye_params, clusters_per_eye)?;
125-
if lights.len() >= CPU_FROXEL_PARALLEL_MIN_LIGHTS {
127+
if should_parallelize_cpu_froxel_lights(lights.len()) {
126128
build_parallel(lights, eye_params, &layouts, clusters_per_eye)
127129
} else {
128130
build_serial(lights, eye_params, &layouts, clusters_per_eye)
129131
}
130132
}
131133
}
132134

135+
/// Returns whether CPU froxel assignment should split light ranges over Rayon.
136+
#[inline]
137+
fn should_parallelize_cpu_froxel_lights(light_count: usize) -> bool {
138+
light_count >= CPU_FROXEL_PARALLEL_MIN_LIGHTS
139+
}
140+
141+
/// Returns whether CPU froxel prefix and merge helpers should use Rayon.
142+
#[inline]
143+
fn should_parallelize_cpu_froxel_prefix(cluster_count: usize) -> bool {
144+
cluster_count >= CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS
145+
}
146+
133147
fn validated_eye_layouts(
134148
eye_params: &[ClusterFrameParams],
135149
clusters_per_eye: u32,
@@ -281,7 +295,7 @@ fn merge_parallel_chunk_counts(
281295
chunks: &[CpuFroxelCountChunk],
282296
total_clusters: usize,
283297
) -> (Vec<u32>, CpuFroxelStats) {
284-
let counts = if total_clusters >= CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS {
298+
let counts = if should_parallelize_cpu_froxel_prefix(total_clusters) {
285299
(0..total_clusters)
286300
.into_par_iter()
287301
.map(|cluster_id| {
@@ -312,7 +326,7 @@ fn build_parallel_chunk_offsets(
312326
total_clusters: usize,
313327
) -> Vec<Vec<u32>> {
314328
let chunk_count = chunks.len();
315-
if total_clusters >= CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS && chunk_count >= 2 {
329+
if should_parallelize_cpu_froxel_prefix(total_clusters) && chunk_count >= 2 {
316330
let per_cluster_offsets = (0..total_clusters)
317331
.into_par_iter()
318332
.map(|cluster_id| {
@@ -639,7 +653,7 @@ fn assign_bounded_light(
639653

640654
/// Converts per-froxel counts into compact `[offset, count]` rows.
641655
fn prefix_counts_to_ranges(counts: &[u32]) -> Option<(Vec<[u32; 2]>, usize)> {
642-
if counts.len() >= CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS {
656+
if should_parallelize_cpu_froxel_prefix(counts.len()) {
643657
return prefix_counts_to_ranges_parallel(counts);
644658
}
645659
prefix_counts_to_ranges_serial(counts)
@@ -809,6 +823,34 @@ mod tests {
809823
&assignments.indices[start..end]
810824
}
811825

826+
#[test]
827+
fn cpu_froxel_light_parallel_gate_starts_at_two_chunks() {
828+
assert_eq!(
829+
CPU_FROXEL_PARALLEL_MIN_LIGHTS,
830+
CPU_FROXEL_LIGHT_CHUNK_SIZE * 2
831+
);
832+
assert!(!should_parallelize_cpu_froxel_lights(
833+
CPU_FROXEL_PARALLEL_MIN_LIGHTS - 1
834+
));
835+
assert!(should_parallelize_cpu_froxel_lights(
836+
CPU_FROXEL_PARALLEL_MIN_LIGHTS
837+
));
838+
}
839+
840+
#[test]
841+
fn cpu_froxel_prefix_parallel_gate_starts_at_prefix_chunk() {
842+
assert_eq!(
843+
CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS,
844+
CPU_FROXEL_PREFIX_CHUNK_SIZE
845+
);
846+
assert!(!should_parallelize_cpu_froxel_prefix(
847+
CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS - 1
848+
));
849+
assert!(should_parallelize_cpu_froxel_prefix(
850+
CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS
851+
));
852+
}
853+
812854
#[test]
813855
fn empty_lights_write_zero_ranges_without_indices() {
814856
let params = test_params();

crates/renderide/src/passes/mesh_deform.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ struct MeshDeformDispatchCtx<'a> {
116116
head_output_transform: glam::Mat4,
117117
}
118118

119-
const DEFORM_COLLECT_PARALLEL_MIN_RENDERERS: usize = 256;
119+
/// Renderer count above which deform work collection fans out across two 64-renderer chunks.
120+
const DEFORM_COLLECT_PARALLEL_MIN_RENDERERS: usize = 128;
121+
/// Renderer count assigned to one deform collection worker chunk.
120122
const DEFORM_COLLECT_RENDERER_CHUNK_SIZE: usize = 64;
121123

122124
#[derive(Clone, Copy)]

crates/renderide/src/runtime/frame/extract.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,10 +466,15 @@ pub(in crate::runtime) fn select_inner_parallelism(
466466

467467
/// Prepared-draw count above which two outer-parallel views are allowed to keep inner chunk
468468
/// parallelism enabled. Below this, nested rayon scheduling usually costs more than it saves.
469-
const MIN_DRAWS_FOR_TWO_VIEW_INNER_PARALLELISM: usize = 512;
469+
///
470+
/// The gate is four prepared 64-draw chunks: enough independent work for two views without
471+
/// making small stereo frames recursively fan out.
472+
const MIN_DRAWS_FOR_TWO_VIEW_INNER_PARALLELISM: usize = 256;
470473

471474
/// Estimated total prepared draws above which view-level parallel collection pays for itself.
472-
const MIN_TOTAL_DRAWS_FOR_PARALLEL_VIEW_COLLECTION: usize = 256;
475+
///
476+
/// Two views with one 64-draw chunk each can overlap cull and draw collection work.
477+
const MIN_TOTAL_DRAWS_FOR_PARALLEL_VIEW_COLLECTION: usize = 128;
473478

474479
/// Refines the frame-level inner parallelism once the backend has built the prepared draw list.
475480
///

crates/renderide/src/scene/lights/cache.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ const LOCAL_LIGHT_PROPAGATION: Vec3 = Vec3::new(0.0, 0.0, 1.0);
3434
/// pass at the end of [`LightCache::fixup_for_transform_removals`].
3535
const DEAD_TRANSFORM_ID: usize = usize::MAX;
3636
/// Cached light count at which world-space light resolution uses Rayon.
37-
const LIGHT_RESOLVE_PARALLEL_MIN_LIGHTS: usize = 128;
37+
///
38+
/// Light resolution is math-heavy enough that two 32-light grains are a useful lower bound.
39+
const LIGHT_RESOLVE_PARALLEL_MIN_LIGHTS: usize = 64;
3840

3941
/// Dense buffer-renderer entry. Position in the per-space [`Vec`] equals the host's
4042
/// `RenderableIndex`; the pointed-to [`LightData`] rows live in [`LightCache::buffers`] keyed by

crates/renderide/src/world_mesh/draw_prep/arrange.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use super::item::{WorldMeshDrawArrangementStats, WorldMeshDrawItem};
1313
use super::sort::sort_order_sensitive_draws;
1414

1515
/// Draw count at which phase partitioning uses Rayon workers.
16-
const ARRANGE_PARALLEL_MIN_DRAWS: usize = 2_048;
16+
///
17+
/// Partitioning builds worker-local maps and then merges them, so this remains more conservative
18+
/// than simple per-renderer fan-out while still covering medium draw lists.
19+
const ARRANGE_PARALLEL_MIN_DRAWS: usize = 512;
1720

1821
/// Key for one nontransparent bin.
1922
#[derive(Clone, Debug, Eq, Hash, PartialEq)]

crates/renderide/src/world_mesh/draw_prep/prepared_renderables/expand.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ const MATERIAL_KEY_SIGNATURE_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
3131
const MATERIAL_KEY_SIGNATURE_PRIME: u64 = 0x0000_0100_0000_01b3;
3232

3333
/// Renderer count in one render space above which expansion fans out across Rayon chunks.
34+
///
35+
/// Two 64-renderer chunks provide real worker fan-out without waiting for very large spaces.
3436
#[cfg(test)]
35-
pub(in crate::world_mesh::draw_prep) const PREPARED_EXPAND_PARALLEL_MIN_RENDERERS: usize = 256;
37+
pub(in crate::world_mesh::draw_prep) const PREPARED_EXPAND_PARALLEL_MIN_RENDERERS: usize = 128;
3638
/// Renderer slice width used by aggressive prepared-renderable expansion.
3739
#[cfg(test)]
3840
pub(in crate::world_mesh::draw_prep) const PREPARED_EXPAND_RENDERER_CHUNK_SIZE: usize = 64;

crates/renderide/src/world_mesh/draw_prep/render_world.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ const DIRTY_SPACE_REFRESH_PARALLEL_MIN_SPACES: usize = 2;
2929
/// Active render-space count required before snapshot rebuild fan-out is considered.
3030
const SNAPSHOT_REBUILD_PARALLEL_MIN_SPACES: usize = 2;
3131
/// Retained draw-template count required before snapshot rebuild fan-out is considered.
32-
const SNAPSHOT_REBUILD_PARALLEL_MIN_DRAWS: usize = 512;
32+
///
33+
/// Snapshot rebuild fans out by render space, so the draw gate only filters tiny two-space cases.
34+
const SNAPSHOT_REBUILD_PARALLEL_MIN_DRAWS: usize = 256;
3335

3436
/// Maintenance counters for backend-owned retained render-world caches.
3537
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]

crates/renderide/src/world_mesh/draw_prep/render_world/refresh.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use super::super::prepared_renderables::{
1313
use super::state::{RenderWorldRendererRef, RenderWorldRendererTemplate, RenderWorldSpace};
1414

1515
/// Renderer count above which retained-template refresh uses Rayon.
16-
const RENDER_WORLD_PARALLEL_MIN_RENDERERS: usize = 256;
16+
///
17+
/// Two 64-renderer chunks are enough independent retained-table work to cover dispatch overhead.
18+
const RENDER_WORLD_PARALLEL_MIN_RENDERERS: usize = 128;
1719

1820
/// Records per worker chunk when refreshing dense retained renderer tables.
1921
const RENDER_WORLD_REFRESH_CHUNK_SIZE: usize = 64;

crates/renderide/src/world_mesh/draw_prep/render_world/state.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use crate::scene::{
1010
use super::super::prepared_renderables::{FramePreparedDraw, FramePreparedRenderables};
1111

1212
/// Renderer count at which reverse-index rebuilds use worker-local indexes.
13-
const REVERSE_INDEX_PARALLEL_MIN_RENDERERS: usize = 256;
13+
///
14+
/// Reverse-index rebuilds merge worker-local maps, so they start at two 64-renderer chunks.
15+
const REVERSE_INDEX_PARALLEL_MIN_RENDERERS: usize = 128;
1416
/// Renderer count assigned to one reverse-index worker chunk.
15-
const REVERSE_INDEX_PARALLEL_CHUNK_RENDERERS: usize = 128;
17+
const REVERSE_INDEX_PARALLEL_CHUNK_RENDERERS: usize = 64;
1618

1719
/// Retained draw-template storage for one render space.
1820
#[derive(Default)]

0 commit comments

Comments
 (0)