Skip to content

Commit 3562b13

Browse files
authored
Solari BLAS compaction (#20457)
Compact raytracing BLASes to save memory. No compaction: <img width="3838" height="2075" alt="image" src="https://github.com/user-attachments/assets/eb0831e8-d3fb-49ea-8552-207e5902d1cb" /> Compaction: <img width="3838" height="2075" alt="image" src="https://github.com/user-attachments/assets/d6215f05-57f9-4adc-80f2-a7ca81a5e9d1" />
1 parent 5be9c68 commit 3562b13

File tree

4 files changed

+70
-12
lines changed

4 files changed

+70
-12
lines changed

crates/bevy_solari/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
//! See [`SolariPlugins`] for more info.
66
//!
77
//! ![`bevy_solari` logo](https://raw.githubusercontent.com/bevyengine/bevy/refs/heads/main/assets/branding/bevy_solari.svg)
8+
9+
extern crate alloc;
10+
811
pub mod pathtracer;
912
pub mod realtime;
1013
pub mod scene;

crates/bevy_solari/src/scene/blas.rs

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use alloc::collections::VecDeque;
12
use bevy_asset::AssetId;
23
use bevy_ecs::{
34
resource::Resource,
@@ -15,12 +16,19 @@ use bevy_render::{
1516
renderer::{RenderDevice, RenderQueue},
1617
};
1718

19+
/// After compacting this many vertices worth of meshes per frame, no further BLAS will be compacted.
20+
/// Lower this number to distribute the work across more frames.
21+
const MAX_COMPACTION_VERTICES_PER_FRAME: u32 = 400_000;
22+
1823
#[derive(Resource, Default)]
19-
pub struct BlasManager(HashMap<AssetId<Mesh>, Blas>);
24+
pub struct BlasManager {
25+
blas: HashMap<AssetId<Mesh>, Blas>,
26+
compaction_queue: VecDeque<(AssetId<Mesh>, u32, bool)>,
27+
}
2028

2129
impl BlasManager {
2230
pub fn get(&self, mesh: &AssetId<Mesh>) -> Option<&Blas> {
23-
self.0.get(mesh)
31+
self.blas.get(mesh)
2432
}
2533
}
2634

@@ -31,15 +39,13 @@ pub fn prepare_raytracing_blas(
3139
render_device: Res<RenderDevice>,
3240
render_queue: Res<RenderQueue>,
3341
) {
34-
let blas_manager = &mut blas_manager.0;
35-
3642
// Delete BLAS for deleted or modified meshes
3743
for asset_id in extracted_meshes
3844
.removed
3945
.iter()
4046
.chain(extracted_meshes.modified.iter())
4147
{
42-
blas_manager.remove(asset_id);
48+
blas_manager.blas.remove(asset_id);
4349
}
4450

4551
if extracted_meshes.extracted.is_empty() {
@@ -58,7 +64,10 @@ pub fn prepare_raytracing_blas(
5864
let (blas, blas_size) =
5965
allocate_blas(&vertex_slice, &index_slice, asset_id, &render_device);
6066

61-
blas_manager.insert(*asset_id, blas);
67+
blas_manager.blas.insert(*asset_id, blas);
68+
blas_manager
69+
.compaction_queue
70+
.push_back((*asset_id, blas_size.vertex_count, false));
6271

6372
(*asset_id, vertex_slice, index_slice, blas_size)
6473
})
@@ -79,7 +88,7 @@ pub fn prepare_raytracing_blas(
7988
transform_buffer_offset: None,
8089
};
8190
BlasBuildEntry {
82-
blas: &blas_manager[asset_id],
91+
blas: &blas_manager.blas[asset_id],
8392
geometry: BlasGeometries::TriangleGeometries(vec![geometry]),
8493
}
8594
})
@@ -92,6 +101,48 @@ pub fn prepare_raytracing_blas(
92101
render_queue.submit([command_encoder.finish()]);
93102
}
94103

104+
pub fn compact_raytracing_blas(
105+
mut blas_manager: ResMut<BlasManager>,
106+
render_queue: Res<RenderQueue>,
107+
) {
108+
let mut first_mesh_processed = None;
109+
110+
let mut vertices_compacted = 0;
111+
while vertices_compacted < MAX_COMPACTION_VERTICES_PER_FRAME
112+
&& let Some((mesh, vertex_count, compaction_started)) =
113+
blas_manager.compaction_queue.pop_front()
114+
{
115+
// Stop iterating once we loop back around to the start of the list
116+
if Some(mesh) == first_mesh_processed {
117+
break;
118+
}
119+
if first_mesh_processed.is_none() {
120+
first_mesh_processed = Some(mesh);
121+
}
122+
123+
let Some(blas) = blas_manager.get(&mesh) else {
124+
continue;
125+
};
126+
127+
if !compaction_started {
128+
blas.prepare_compaction_async(|_| {});
129+
}
130+
131+
if blas.ready_for_compaction() {
132+
let compacted_blas = render_queue.compact_blas(blas);
133+
blas_manager.blas.insert(mesh, compacted_blas);
134+
135+
vertices_compacted += vertex_count;
136+
continue;
137+
}
138+
139+
// BLAS not ready for compaction, put back in queue
140+
blas_manager
141+
.compaction_queue
142+
.push_back((mesh, vertex_count, true));
143+
}
144+
}
145+
95146
fn allocate_blas(
96147
vertex_slice: &MeshBufferSlice,
97148
index_slice: &MeshBufferSlice,
@@ -109,7 +160,8 @@ fn allocate_blas(
109160
let blas = render_device.wgpu_device().create_blas(
110161
&CreateBlasDescriptor {
111162
label: Some(&asset_id.to_string()),
112-
flags: AccelerationStructureFlags::PREFER_FAST_TRACE,
163+
flags: AccelerationStructureFlags::PREFER_FAST_TRACE
164+
| AccelerationStructureFlags::ALLOW_COMPACTION,
113165
update_mode: AccelerationStructureUpdateMode::Build,
114166
},
115167
BlasGeometrySizeDescriptors::Triangles {

crates/bevy_solari/src/scene/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use bevy_render::{
2222
ExtractSchedule, Render, RenderApp, RenderSystems,
2323
};
2424
use binder::prepare_raytracing_scene_bindings;
25-
use blas::{prepare_raytracing_blas, BlasManager};
25+
use blas::{compact_raytracing_blas, prepare_raytracing_blas, BlasManager};
2626
use extract::{extract_raytracing_scene, StandardMaterialAssets};
2727
use tracing::warn;
2828

@@ -69,6 +69,9 @@ impl Plugin for RaytracingScenePlugin {
6969
.in_set(RenderSystems::PrepareAssets)
7070
.before(prepare_assets::<RenderMesh>)
7171
.after(allocate_and_free_meshes),
72+
compact_raytracing_blas
73+
.in_set(RenderSystems::PrepareAssets)
74+
.after(prepare_raytracing_blas),
7275
prepare_raytracing_scene_bindings.in_set(RenderSystems::PrepareBindGroups),
7376
),
7477
);

release-content/release-notes/bevy_solari.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Initial raytraced lighting progress (bevy_solari)
33
authors: ["@JMS55", "@SparkyPotato"]
4-
pull_requests: [19058, 19620, 19790, 20020, 20113, 20156, 20213, 20242, 20259, 20406]
4+
pull_requests: [19058, 19620, 19790, 20020, 20113, 20156, 20213, 20242, 20259, 20406, 20457]
55
---
66

77
(TODO: Embed solari example screenshot here)
@@ -10,7 +10,7 @@ In Bevy 0.17, we've made the first steps towards realtime raytraced lighting in
1010

1111
For some background, lighting in video games can be split into two parts: direct and indirect lighting.
1212

13-
Direct lighting is light that is emitted from a light source, bounces off of one surface, and then reaches the camera. Indirect lighting by contrast is light that bounces off of different surfaces many times before reaching the camera, and is often called global illumination.
13+
Direct lighting is light that is emitted from a light source, bounces off of one surface, and then reaches the camera. Indirect lighting by contrast is light that bounces off of different surfaces many times before reaching the camera. Indirect lighting is also often called global illumination.
1414

1515
(TODO: Diagrams of direct vs indirect light)
1616

@@ -23,7 +23,7 @@ The problem with these methods is that they all have large downsides:
2323
* Baked lighting does not update in realtime as objects and lights move around, is low resolution/quality, and requires time to bake, slowing down game production.
2424
* Screen-space methods have low quality and do not capture off-screen geometry and light.
2525

26-
Bevy Solari is intended as a completely alternate, high-end lighting solution for Bevy that uses GPU-accelerated raytracing to fix all of the above problems. Emissive meshes will properly cast light and shadows, you will be able to have hundreds of shadow casting lights, quality will be much better, it will require no baking time, and it will support _fully_ dynamic scenes!
26+
Bevy Solari is intended as a completely alternate, high-end lighting solution for Bevy that uses GPU-accelerated raytracing to fix all of the above problems. Emissive meshes properly cast light and shadows, you can have hundreds of shadow casting lights, quality is much better, it requires no baking time, and it supports _fully_ dynamic scenes!
2727

2828
While Bevy 0.17 adds the bevy_solari crate, it's intended as a long-term project. It is not yet usable by game developers. However, feel free to run the solari example (`cargo run --release --example solari --features bevy_solari` (realtime, no denoising) or `cargo run --release --example solari --features bevy_solari -- --pathtracer` (non-realtime)) to check out the progress we've made, and look forward to more work on Bevy Solari in future releases!
2929

0 commit comments

Comments
 (0)