Skip to content

Commit 62d570f

Browse files
committed
Normalize renderer rayon thresholds
1 parent 7cac2db commit 62d570f

26 files changed

Lines changed: 436 additions & 208 deletions

File tree

crates/renderide-test/src/scene_dsl/suite.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,21 @@ fn run_suite_with(
139139
.map_err(|e| HarnessError::QueueOptions(format!("build suite thread pool: {e}")))?;
140140

141141
let runner = &config.runner;
142-
let case_reports = pool.install(|| {
142+
let case_reports = if jobs >= 2 && config.cases.len() >= 2 {
143+
pool.install(|| {
144+
config
145+
.cases
146+
.par_iter()
147+
.map(|case| run_case(case, runner))
148+
.collect::<Vec<_>>()
149+
})
150+
} else {
143151
config
144152
.cases
145-
.par_iter()
153+
.iter()
146154
.map(|case| run_case(case, runner))
147155
.collect::<Vec<_>>()
148-
});
156+
};
149157

150158
let report = SuiteReport::from_cases(case_reports);
151159
let report_path = write_suite_report(&config.runner.output_root, &report)?;

crates/renderide/src/assets/mesh/gpu_mesh/tangent_generation.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ const DEFAULT_TANGENT: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
2222
const DEFAULT_TANGENT_VEC: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0);
2323
const DEFAULT_RAW_TANGENT_PAYLOAD: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
2424
const TANGENT_EPSILON_SQUARED: f32 = 1.0e-20;
25+
/// Vertices assigned to one tangent extraction or encoding worker chunk.
26+
const VERTEX_STREAM_PARALLEL_CHUNK_VERTICES: usize = 512;
27+
2528
/// Vertex count above which vertex-stream extraction and tangent encoding fan out across rayon.
2629
///
27-
/// Production meshes cluster around 1k-8k vertices, so a threshold of 2048 lets medium avatar and
28-
/// prop meshes use the worker pool while tiny meshes stay serial.
29-
const VERTEX_STREAM_PARALLEL_MIN: usize = 1_024;
30+
/// Production meshes cluster around 1k-8k vertices, so a two-chunk threshold lets medium avatar
31+
/// and prop meshes use the worker pool while tiny meshes stay serial.
32+
const VERTEX_STREAM_PARALLEL_MIN: usize = VERTEX_STREAM_PARALLEL_CHUNK_VERTICES * 2;
3033

3134
/// CPU-side mesh source used to extract or generate tangent streams.
3235
#[derive(Copy, Clone)]
@@ -134,6 +137,7 @@ fn host_tangent_stream_bytes(
134137
};
135138
if vertex_count >= VERTEX_STREAM_PARALLEL_MIN {
136139
out.par_chunks_exact_mut(16)
140+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
137141
.enumerate()
138142
.for_each(|(vertex, slot)| copy_one(slot, vertex));
139143
} else {
@@ -231,7 +235,11 @@ fn read_vertex_stream3(
231235
.unwrap_or(Vec3::ZERO)
232236
};
233237
let out: Vec<Vec3> = if vertex_count >= VERTEX_STREAM_PARALLEL_MIN {
234-
(0..vertex_count).into_par_iter().map(read_one).collect()
238+
(0..vertex_count)
239+
.into_par_iter()
240+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
241+
.map(read_one)
242+
.collect()
235243
} else {
236244
(0..vertex_count).map(read_one).collect()
237245
};
@@ -255,7 +263,11 @@ fn read_vertex_stream2(
255263
.unwrap_or(Vec2::ZERO)
256264
};
257265
let out: Vec<Vec2> = if vertex_count >= VERTEX_STREAM_PARALLEL_MIN {
258-
(0..vertex_count).into_par_iter().map(read_one).collect()
266+
(0..vertex_count)
267+
.into_par_iter()
268+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
269+
.map(read_one)
270+
.collect()
259271
} else {
260272
(0..vertex_count).map(read_one).collect()
261273
};
@@ -337,7 +349,12 @@ fn encode_tangents(tangents: &[Vec4]) -> Vec<u8> {
337349
};
338350
if tangents.len() >= VERTEX_STREAM_PARALLEL_MIN {
339351
out.par_chunks_exact_mut(16)
340-
.zip(tangents.par_iter())
352+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
353+
.zip(
354+
tangents
355+
.par_iter()
356+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES),
357+
)
341358
.for_each(|(slot, tangent)| write_one(slot, tangent));
342359
} else {
343360
for (slot, tangent) in out.chunks_exact_mut(16).zip(tangents.iter()) {

crates/renderide/src/assets/mesh/layout/streams.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use crate::shared::{VertexAttributeDescriptor, VertexAttributeFormat, VertexAttr
77
use super::super::gpu_mesh::attribute_reader::AttributeReader;
88
use super::buffer_layout::vertex_format_size;
99

10+
/// Vertices assigned to one stream expansion worker chunk.
11+
const VERTEX_STREAM_PARALLEL_CHUNK_VERTICES: usize = 512;
1012
/// Vertex count above which stream expansion fans out across Rayon workers.
11-
const VERTEX_STREAM_PARALLEL_MIN: usize = 1_024;
13+
const VERTEX_STREAM_PARALLEL_MIN: usize = VERTEX_STREAM_PARALLEL_CHUNK_VERTICES * 2;
1214

1315
/// Host UV channels exposed through the mesh-forward vertex path.
1416
pub const UV_VERTEX_ATTRIBUTE_TYPES: [VertexAttributeType; 8] = [
@@ -98,7 +100,12 @@ pub fn extract_float3_position_normal_as_vec4_streams(
98100
if should_parallelize_vertex_stream(vertex_count) {
99101
pos_out
100102
.par_chunks_exact_mut(16)
101-
.zip(nrm_out.par_chunks_exact_mut(16))
103+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
104+
.zip(
105+
nrm_out
106+
.par_chunks_exact_mut(16)
107+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES),
108+
)
102109
.enumerate()
103110
.try_for_each(|(i, (pos_slot, nrm_slot))| {
104111
write_position_normal_vertex(
@@ -163,7 +170,9 @@ fn fill_normal_stream_with_forward_z(out: &mut [u8]) {
163170
chunk[12..16].copy_from_slice(&zero);
164171
};
165172
if should_parallelize_vertex_stream(out.len() / 16) {
166-
out.par_chunks_exact_mut(16).for_each(write_chunk);
173+
out.par_chunks_exact_mut(16)
174+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
175+
.for_each(write_chunk);
167176
} else {
168177
out.chunks_exact_mut(16).for_each(write_chunk);
169178
}
@@ -220,6 +229,7 @@ pub fn vertex_float2_stream_bytes(
220229
};
221230
if should_parallelize_vertex_stream(vertex_count) {
222231
out.par_chunks_exact_mut(8)
232+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
223233
.enumerate()
224234
.try_for_each(|(i, slot)| write_vertex_float2(&reader, i, slot))?;
225235
} else {
@@ -269,6 +279,7 @@ pub fn wide_uv_stream_bytes(
269279

270280
if should_parallelize_vertex_stream(vertex_count) {
271281
out.par_chunks_exact_mut(WIDE_UV_VERTEX_STRIDE_BYTES)
282+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
272283
.enumerate()
273284
.try_for_each(|(vertex, slot)| write_wide_uv_vertex(&readers, vertex, slot))?;
274285
} else {
@@ -327,6 +338,7 @@ fn vertex_float4_stream_bytes_with_kind(
327338
};
328339
if should_parallelize_vertex_stream(vertex_count) {
329340
out.par_chunks_exact_mut(16)
341+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
330342
.enumerate()
331343
.try_for_each(|(i, slot)| write_vertex_float4(&reader, i, slot, default))?;
332344
} else {
@@ -428,6 +440,7 @@ pub fn color_float4_stream_bytes(
428440

429441
if should_parallelize_vertex_stream(vertex_count) {
430442
out.par_chunks_exact_mut(16)
443+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
431444
.enumerate()
432445
.try_for_each(|(i, slot)| write_vertex_float4(&reader, i, slot, [1.0; 4]))?;
433446
} else {
@@ -448,7 +461,9 @@ fn fill_float4_stream_with_default(out: &mut [u8], default: [f32; 4]) {
448461
}
449462
};
450463
if should_parallelize_vertex_stream(out.len() / 16) {
451-
out.par_chunks_exact_mut(16).for_each(write_chunk);
464+
out.par_chunks_exact_mut(16)
465+
.with_min_len(VERTEX_STREAM_PARALLEL_CHUNK_VERTICES)
466+
.for_each(write_chunk);
452467
} else {
453468
out.chunks_exact_mut(16).for_each(write_chunk);
454469
}

crates/renderide/src/assets/texture/upload/write_mip_chain/conversion.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ use super::super::mip_write_common::{
1111
mip_src_to_upload_pixels as shared_mip_src_to_upload_pixels,
1212
};
1313

14-
const DOWNSAMPLE_PARALLEL_MIN_TEXELS: usize = 8_192;
14+
/// Destination texels assigned to one downsample worker chunk.
15+
const DOWNSAMPLE_PARALLEL_CHUNK_TEXELS: usize = 4_096;
16+
/// Destination texel count above which downsampling may use Rayon.
17+
const DOWNSAMPLE_PARALLEL_MIN_TEXELS: usize = DOWNSAMPLE_PARALLEL_CHUNK_TEXELS * 2;
18+
/// Destination rows required before row-parallel downsampling can produce multiple chunks.
19+
const DOWNSAMPLE_PARALLEL_MIN_ROWS: usize = 2;
1520

1621
#[inline]
1722
fn should_parallelize_downsample(dst_w: usize, dst_h: usize) -> bool {
18-
dst_w.saturating_mul(dst_h) >= DOWNSAMPLE_PARALLEL_MIN_TEXELS
23+
dst_h >= DOWNSAMPLE_PARALLEL_MIN_ROWS
24+
&& dst_w.saturating_mul(dst_h) >= DOWNSAMPLE_PARALLEL_MIN_TEXELS
1925
}
2026

2127
/// Converts host mip bytes into a buffer suitable for [`write_one_mip`] (decode, optional row flip).

crates/renderide/src/mesh_deform/per_draw_uniforms.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,13 @@ impl PaddedPerDrawUniforms {
148148
}
149149
}
150150

151+
/// Draw slots assigned to one slab-copy worker chunk.
152+
const PER_DRAW_SLAB_PARALLEL_CHUNK_SLOTS: usize = 64;
151153
/// Slot count above which slab writes fan out to a rayon worker pool.
152154
///
153-
/// Each slot is a 256-byte copy. At 256 slots the slab is already 64 KiB, large enough for
155+
/// Each slot is a 256-byte copy. Two 64-slot chunks make the slab large enough for
154156
/// memory-bandwidth fan-out to pay off on typical desktop CPUs.
155-
const PER_DRAW_SLAB_PARALLEL_MIN: usize = 256;
156-
const PER_DRAW_SLAB_PARALLEL_CHUNK_SLOTS: usize = 64;
157+
const PER_DRAW_SLAB_PARALLEL_MIN: usize = PER_DRAW_SLAB_PARALLEL_CHUNK_SLOTS * 2;
157158

158159
/// Writes `count` consecutive [`PaddedPerDrawUniforms`] into `out` (must be `count * 256` bytes).
159160
///

crates/renderide/src/mesh_deform/skinning_palette.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use rayon::prelude::*;
66
use crate::scene::{RenderSpaceId, SceneCoordinator};
77
use crate::shared::RenderingContext;
88

9+
/// Bone count assigned to one palette construction worker chunk.
10+
const SKINNING_PALETTE_PARALLEL_CHUNK_BONES: usize = 64;
911
/// Bone count above which palette construction fans out across rayon.
1012
///
11-
/// Per-bone work is one world lookup plus a Mat4 multiply, so medium skinned meshes can amortize
12-
/// worker dispatch once the palette reaches a few hundred bones.
13-
const SKINNING_PALETTE_PARALLEL_MIN: usize = 128;
13+
/// Per-bone work is one world lookup plus a Mat4 multiply, so two chunks are enough to amortize
14+
/// worker dispatch once the palette reaches medium sizes.
15+
const SKINNING_PALETTE_PARALLEL_MIN: usize = SKINNING_PALETTE_PARALLEL_CHUNK_BONES * 2;
1416

1517
/// Bytes per column-major `mat4<f32>` slot in the GPU-facing palette buffer.
1618
const PALETTE_BONE_BYTES: usize = 64;
@@ -104,6 +106,7 @@ pub fn build_skinning_palette(params: SkinningPaletteParams<'_>) -> Option<Vec<M
104106
params
105107
.skinning_bind_matrices
106108
.par_iter()
109+
.with_min_len(SKINNING_PALETTE_PARALLEL_CHUNK_BONES)
107110
.enumerate()
108111
.map(|(bi, bind_mat)| resolver.matrix(bi, bind_mat))
109112
.collect()
@@ -146,7 +149,14 @@ pub fn write_skinning_palette_bytes(
146149
out.set_len(total_bytes);
147150
}
148151
out.par_chunks_exact_mut(PALETTE_BONE_BYTES)
149-
.zip(params.skinning_bind_matrices.par_iter().enumerate())
152+
.with_min_len(SKINNING_PALETTE_PARALLEL_CHUNK_BONES)
153+
.zip(
154+
params
155+
.skinning_bind_matrices
156+
.par_iter()
157+
.with_min_len(SKINNING_PALETTE_PARALLEL_CHUNK_BONES)
158+
.enumerate(),
159+
)
150160
.for_each(|(slot, (bi, bind_mat))| {
151161
let pal = resolver.matrix(bi, bind_mat);
152162
slot.copy_from_slice(bytemuck::cast_slice(&pal.to_cols_array()));

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ use crate::world_mesh::cluster::{
1616

1717
/// Light count at which `Auto` mode starts considering CPU froxel assignment.
1818
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;
2119
/// Lights assigned to one CPU froxel worker chunk.
2220
const CPU_FROXEL_LIGHT_CHUNK_SIZE: usize = 32;
23-
/// Froxel count at which count merge, offset, and prefix work uses Rayon.
24-
const CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS: usize = 512;
21+
/// Light count at which CPU froxel assignment fans out across worker chunks.
22+
const CPU_FROXEL_PARALLEL_MIN_LIGHTS: usize = CPU_FROXEL_LIGHT_CHUNK_SIZE * 2;
2523
/// Cluster-count stride for local prefix-sum chunks.
26-
const CPU_FROXEL_PREFIX_CHUNK_SIZE: usize = 512;
24+
const CPU_FROXEL_PREFIX_CHUNK_SIZE: usize = 256;
25+
/// Froxel count at which count merge, offset, and prefix work uses Rayon.
26+
const CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS: usize = CPU_FROXEL_PREFIX_CHUNK_SIZE * 2;
2727

2828
/// Point light tag in [`GpuLight::light_type`].
2929
const LIGHT_TYPE_POINT: u32 = 0;
@@ -298,6 +298,7 @@ fn merge_parallel_chunk_counts(
298298
let counts = if should_parallelize_cpu_froxel_prefix(total_clusters) {
299299
(0..total_clusters)
300300
.into_par_iter()
301+
.with_min_len(CPU_FROXEL_PREFIX_CHUNK_SIZE)
301302
.map(|cluster_id| {
302303
chunks.iter().fold(0u32, |total, chunk| {
303304
total.saturating_add(chunk.counts[cluster_id])
@@ -329,6 +330,7 @@ fn build_parallel_chunk_offsets(
329330
if should_parallelize_cpu_froxel_prefix(total_clusters) && chunk_count >= 2 {
330331
let per_cluster_offsets = (0..total_clusters)
331332
.into_par_iter()
333+
.with_min_len(CPU_FROXEL_PREFIX_CHUNK_SIZE)
332334
.map(|cluster_id| {
333335
let mut next = ranges[cluster_id][0];
334336
chunks
@@ -841,7 +843,7 @@ mod tests {
841843
fn cpu_froxel_prefix_parallel_gate_starts_at_prefix_chunk() {
842844
assert_eq!(
843845
CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS,
844-
CPU_FROXEL_PREFIX_CHUNK_SIZE
846+
CPU_FROXEL_PREFIX_CHUNK_SIZE * 2
845847
);
846848
assert!(!should_parallelize_cpu_froxel_prefix(
847849
CPU_FROXEL_PREFIX_PARALLEL_MIN_CLUSTERS - 1

crates/renderide/src/passes/mesh_deform.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ struct MeshDeformDispatchCtx<'a> {
116116
head_output_transform: glam::Mat4,
117117
}
118118

119-
/// Renderer count above which deform work collection fans out across two 64-renderer chunks.
120-
const DEFORM_COLLECT_PARALLEL_MIN_RENDERERS: usize = 128;
121119
/// Renderer count assigned to one deform collection worker chunk.
122120
const DEFORM_COLLECT_RENDERER_CHUNK_SIZE: usize = 64;
121+
/// Renderer count above which deform work collection fans out across two chunks.
122+
const DEFORM_COLLECT_PARALLEL_MIN_RENDERERS: usize = DEFORM_COLLECT_RENDERER_CHUNK_SIZE * 2;
123123

124124
#[derive(Clone, Copy)]
125125
enum DeformCollectChunkKind {

crates/renderide/src/passes/world_mesh_forward/slab.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ use crate::world_mesh::draw_prep::WorldMeshDrawItem;
1616

1717
use super::vp::compute_per_draw_vp_matrices;
1818

19-
/// Minimum draws before parallelizing per-draw VP / model uniform packing (rayon overhead).
20-
///
21-
/// Each draw performs scene lookups and matrix packing, so medium draw lists can amortize worker
22-
/// dispatch earlier than the raw slab copy path.
23-
const PER_DRAW_VP_PARALLEL_MIN_DRAWS: usize = 128;
19+
/// Draws assigned to one per-draw VP / model uniform packing worker chunk.
2420
const PER_DRAW_VP_PARALLEL_CHUNK_DRAWS: usize = 64;
21+
/// Minimum draws before parallelizing per-draw VP / model uniform packing.
22+
const PER_DRAW_VP_PARALLEL_MIN_DRAWS: usize = PER_DRAW_VP_PARALLEL_CHUNK_DRAWS * 2;
2523

2624
/// Per-frame inputs to [`pack_and_upload_per_draw_slab`].
2725
///

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -464,17 +464,15 @@ pub(in crate::runtime) fn select_inner_parallelism(
464464
}
465465
}
466466

467-
/// Prepared-draw count above which two outer-parallel views are allowed to keep inner chunk
468-
/// parallelism enabled. Below this, nested rayon scheduling usually costs more than it saves.
467+
/// Prepared-draw count above which two outer-parallel views keep inner chunk parallelism enabled.
469468
///
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;
469+
/// The gate is two prepared 64-draw chunks per view.
470+
const MIN_DRAWS_FOR_TWO_VIEW_INNER_PARALLELISM: usize = 128;
473471

474472
/// Estimated total prepared draws above which view-level parallel collection pays for itself.
475473
///
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;
474+
/// Two non-empty view chunks can overlap cull and draw collection work.
475+
const MIN_TOTAL_DRAWS_FOR_PARALLEL_VIEW_COLLECTION: usize = 2;
478476

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

0 commit comments

Comments
 (0)