Skip to content

Commit 3ffa26b

Browse files
authored
Parallel Mesh Collection (#22297)
# Objective - Speed up `collect_meshes_for_gpu_building`, a bottleneck for scenes with many moving meshes. ## Solution - Parallelize the gather step for mesh collection. - Immediately start up a task for serial collection of meshes, which cannot be parallelized. - Spawn many tasks for gathering meshes, and send batches of these to the collection task - This allows the serial collection step to start immediately, instead of being delayed until after all collection is finished. ## Testing - Built a new `bevymark_3d` stress test for benchmarking dynamic 3d mesh scenes. This is not currently covered by our stress tests. #22298 - With 200k meshes, this drops total frame times from 16.4ms to 12.3ms (-4.1ms) <img width="631" height="377" alt="image" src="https://github.com/user-attachments/assets/ff9de860-e1f2-4f98-8075-0b3720a82913" /> - Mesh collection itself drops from 7.9ms to 3.6ms (-4.3ms) <img width="623" height="372" alt="image" src="https://github.com/user-attachments/assets/cf52687a-503b-4f70-8e99-0ad2e63f4da0" />
1 parent bf72875 commit 3ffa26b

File tree

10 files changed

+559
-149
lines changed

10 files changed

+559
-149
lines changed

crates/bevy_pbr/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ bluenoise_texture = ["bevy_image/ktx2", "bevy_image/zstd"]
2222
shader_format_glsl = ["bevy_shader/shader_format_glsl"]
2323
trace = ["bevy_render/trace"]
2424
# Enables the meshlet renderer for dense high-poly scenes (experimental)
25-
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"]
25+
meshlet = ["dep:lz4_flex", "dep:range-alloc"]
2626
# Enables processing meshes into meshlet meshes
2727
meshlet_processor = [
2828
"meshlet",
@@ -56,7 +56,7 @@ bevy_render = { path = "../bevy_render", version = "0.19.0-dev", features = [
5656
"morph",
5757
] }
5858
bevy_camera = { path = "../bevy_camera", version = "0.19.0-dev" }
59-
bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev", optional = true }
59+
bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev" }
6060
bevy_transform = { path = "../bevy_transform", version = "0.19.0-dev" }
6161
bevy_utils = { path = "../bevy_utils", version = "0.19.0-dev" }
6262
bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-features = false, features = [

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 209 additions & 104 deletions
Large diffs are not rendered by default.

crates/bevy_platform/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ rayon = ["dep:rayon", "hashbrown/rayon"]
2121

2222
# Platform Compatibility
2323

24+
## Provides an implementation of `block_on` from `futures-lite`.
25+
futures-lite = ["std", "dep:futures-lite", "futures-lite?/std"]
26+
27+
## Provides an implementation of `block_on` from `async-io`.
28+
async-io = ["std", "dep:async-io"]
29+
2430
## Allows access to the `std` crate. Enabling this feature will prevent compilation
2531
## on `no_std` targets, but provides access to certain additional features on
2632
## supported platforms.
@@ -72,6 +78,8 @@ hashbrown = { version = "0.16.1", features = [
7278
], optional = true, default-features = false }
7379
serde = { version = "1", default-features = false, optional = true }
7480
rayon = { version = "1", default-features = false, optional = true }
81+
futures-lite = { version = "2.0.1", default-features = false, optional = true }
82+
async-io = { version = "2.0.0", optional = true }
7583

7684
[target.'cfg(target_arch = "wasm32")'.dependencies]
7785
web-time = { version = "1.1", default-features = false, optional = true }

crates/bevy_platform/src/future.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! Platform-aware future utilities.
2+
3+
crate::cfg::switch! {
4+
#[cfg(feature = "async-io")] => {
5+
pub use async_io::block_on;
6+
}
7+
#[cfg(feature = "futures-lite")] => {
8+
pub use futures_lite::future::block_on;
9+
}
10+
_ => {
11+
/// Blocks on the supplied `future`.
12+
/// This implementation will busy-wait until it is completed.
13+
/// Consider enabling the `async-io` or `futures-lite` features.
14+
pub fn block_on<T>(future: impl Future<Output = T>) -> T {
15+
use core::task::{Poll, Context};
16+
17+
// Pin the future on the stack.
18+
let mut future = core::pin::pin!(future);
19+
20+
// We don't care about the waker as we're just going to poll as fast as possible.
21+
let cx = &mut Context::from_waker(core::task::Waker::noop());
22+
23+
// Keep polling until the future is ready.
24+
loop {
25+
match future.as_mut().poll(cx) {
26+
Poll::Ready(output) => return output,
27+
Poll::Pending => core::hint::spin_loop(),
28+
}
29+
}
30+
}
31+
}
32+
}

crates/bevy_platform/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cfg::alloc! {
2121

2222
pub mod cell;
2323
pub mod cfg;
24+
pub mod future;
2425
pub mod hash;
2526
pub mod sync;
2627
pub mod thread;

crates/bevy_tasks/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ multi_threaded = [
2525
async_executor = ["bevy_platform/std", "dep:async-executor", "futures-lite"]
2626

2727
# Provide an implementation of `block_on` from `futures-lite`.
28-
futures-lite = ["bevy_platform/std", "futures-lite/std"]
28+
futures-lite = ["bevy_platform/futures-lite"]
2929

3030
# Use async-io's implementation of block_on instead of futures-lite's implementation.
3131
# This is preferred if your application uses async-io.
32-
async-io = ["bevy_platform/std", "dep:async-io"]
32+
async-io = ["bevy_platform/async-io"]
3333

3434
[dependencies]
3535
bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-features = false, features = [
@@ -46,7 +46,6 @@ derive_more = { version = "2", default-features = false, features = [
4646
] }
4747
async-executor = { version = "1.11", optional = true }
4848
async-channel = { version = "2.3.0", optional = true }
49-
async-io = { version = "2.0.0", optional = true }
5049
concurrent-queue = { version = "2.0.0", optional = true }
5150
atomic-waker = { version = "1", default-features = false }
5251
crossbeam-queue = { version = "0.3", default-features = false, features = [

crates/bevy_tasks/src/lib.rs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,6 @@ pub mod cfg {
2828
conditional_send
2929
}
3030

31-
#[cfg(feature = "async-io")] => {
32-
/// Indicates `async-io` will be used for the implementation of `block_on`.
33-
async_io
34-
}
35-
36-
#[cfg(feature = "futures-lite")] => {
37-
/// Indicates `futures-lite` will be used for the implementation of `block_on`.
38-
futures_lite
39-
}
4031
}
4132
}
4233

@@ -114,36 +105,7 @@ cfg::multi_threaded! {
114105
}
115106
}
116107

117-
cfg::switch! {
118-
cfg::async_io => {
119-
pub use async_io::block_on;
120-
}
121-
cfg::futures_lite => {
122-
pub use futures_lite::future::block_on;
123-
}
124-
_ => {
125-
/// Blocks on the supplied `future`.
126-
/// This implementation will busy-wait until it is completed.
127-
/// Consider enabling the `async-io` or `futures-lite` features.
128-
pub fn block_on<T>(future: impl Future<Output = T>) -> T {
129-
use core::task::{Poll, Context};
130-
131-
// Pin the future on the stack.
132-
let mut future = core::pin::pin!(future);
133-
134-
// We don't care about the waker as we're just going to poll as fast as possible.
135-
let cx = &mut Context::from_waker(core::task::Waker::noop());
136-
137-
// Keep polling until the future is ready.
138-
loop {
139-
match future.as_mut().poll(cx) {
140-
Poll::Ready(output) => return output,
141-
Poll::Pending => core::hint::spin_loop(),
142-
}
143-
}
144-
}
145-
}
146-
}
108+
pub use bevy_platform::future::block_on;
147109

148110
/// The tasks prelude.
149111
///

crates/bevy_utils/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ license = "MIT OR Apache-2.0"
99
keywords = ["bevy"]
1010

1111
[features]
12-
default = ["parallel"]
12+
default = ["parallel", "buffered_channel"]
1313

1414
# Provides access to the `Parallel` type.
1515
parallel = ["bevy_platform/std", "dep:thread_local"]
1616

17+
buffered_channel = ["bevy_platform/std", "dep:async-channel"]
18+
1719
std = ["disqualified/alloc"]
1820

1921
debug = ["bevy_platform/alloc"]
@@ -23,9 +25,13 @@ bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-fea
2325

2426
disqualified = { version = "1.0", default-features = false }
2527
thread_local = { version = "1.0", optional = true }
28+
async-channel = { version = "2.3.0", optional = true }
2629

2730
[dev-dependencies]
2831
static_assertions = "1.1.0"
32+
bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev", default-features = false }
33+
bevy_app = { path = "../bevy_app", version = "0.19.0-dev", default-features = false }
34+
bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev", default-features = false }
2935

3036
[lints]
3137
workspace = true

0 commit comments

Comments
 (0)