Skip to content

Solari BLAS compaction #20457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/bevy_solari/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
//! See [`SolariPlugins`] for more info.
//!
//! ![`bevy_solari` logo](https://raw.githubusercontent.com/bevyengine/bevy/refs/heads/main/assets/branding/bevy_solari.svg)

extern crate alloc;

pub mod pathtracer;
pub mod realtime;
pub mod scene;
Expand Down
68 changes: 60 additions & 8 deletions crates/bevy_solari/src/scene/blas.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloc::collections::VecDeque;
use bevy_asset::AssetId;
use bevy_ecs::{
resource::Resource,
Expand All @@ -15,12 +16,19 @@ use bevy_render::{
renderer::{RenderDevice, RenderQueue},
};

/// After compacting this many vertices worth of meshes per frame, no further BLAS will be compacted.
/// Lower this number to distribute the work across more frames.
const MAX_COMPACTION_VERTICES_PER_FRAME: u32 = 400_000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a plugin setting or resource?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually yes, for now nothing in solari is configurable. It's in the tracking issue.


#[derive(Resource, Default)]
pub struct BlasManager(HashMap<AssetId<Mesh>, Blas>);
pub struct BlasManager {
blas: HashMap<AssetId<Mesh>, Blas>,
compaction_queue: VecDeque<(AssetId<Mesh>, u32, bool)>,
}

impl BlasManager {
pub fn get(&self, mesh: &AssetId<Mesh>) -> Option<&Blas> {
self.0.get(mesh)
self.blas.get(mesh)
}
}

Expand All @@ -31,15 +39,13 @@ pub fn prepare_raytracing_blas(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
let blas_manager = &mut blas_manager.0;

// Delete BLAS for deleted or modified meshes
for asset_id in extracted_meshes
.removed
.iter()
.chain(extracted_meshes.modified.iter())
{
blas_manager.remove(asset_id);
blas_manager.blas.remove(asset_id);
}

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

blas_manager.insert(*asset_id, blas);
blas_manager.blas.insert(*asset_id, blas);
blas_manager
.compaction_queue
.push_back((*asset_id, blas_size.vertex_count, false));

(*asset_id, vertex_slice, index_slice, blas_size)
})
Expand All @@ -79,7 +88,7 @@ pub fn prepare_raytracing_blas(
transform_buffer_offset: None,
};
BlasBuildEntry {
blas: &blas_manager[asset_id],
blas: &blas_manager.blas[asset_id],
geometry: BlasGeometries::TriangleGeometries(vec![geometry]),
}
})
Expand All @@ -92,6 +101,48 @@ pub fn prepare_raytracing_blas(
render_queue.submit([command_encoder.finish()]);
}

pub fn compact_raytracing_blas(
mut blas_manager: ResMut<BlasManager>,
render_queue: Res<RenderQueue>,
) {
let mut first_mesh_processed = None;

let mut vertices_compacted = 0;
while vertices_compacted < MAX_COMPACTION_VERTICES_PER_FRAME
&& let Some((mesh, vertex_count, compaction_started)) =
blas_manager.compaction_queue.pop_front()
{
// Stop iterating once we loop back around to the start of the list
if Some(mesh) == first_mesh_processed {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if everything is already maximally compacted, this will iterate all entities every frame right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once something is compacted, it stops getting added back to the compaction queue. So the list would be empty.

break;
}
if first_mesh_processed.is_none() {
first_mesh_processed = Some(mesh);
}

let Some(blas) = blas_manager.get(&mesh) else {
continue;
};

if !compaction_started {
blas.prepare_compaction_async(|_| {});
}

if blas.ready_for_compaction() {
let compacted_blas = render_queue.compact_blas(blas);
blas_manager.blas.insert(mesh, compacted_blas);

vertices_compacted += vertex_count;
continue;
}

// BLAS not ready for compaction, put back in queue
blas_manager
.compaction_queue
.push_back((mesh, vertex_count, true));
}
}

fn allocate_blas(
vertex_slice: &MeshBufferSlice,
index_slice: &MeshBufferSlice,
Expand All @@ -109,7 +160,8 @@ fn allocate_blas(
let blas = render_device.wgpu_device().create_blas(
&CreateBlasDescriptor {
label: Some(&asset_id.to_string()),
flags: AccelerationStructureFlags::PREFER_FAST_TRACE,
flags: AccelerationStructureFlags::PREFER_FAST_TRACE
| AccelerationStructureFlags::ALLOW_COMPACTION,
update_mode: AccelerationStructureUpdateMode::Build,
},
BlasGeometrySizeDescriptors::Triangles {
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_solari/src/scene/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use bevy_render::{
ExtractSchedule, Render, RenderApp, RenderSystems,
};
use binder::prepare_raytracing_scene_bindings;
use blas::{prepare_raytracing_blas, BlasManager};
use blas::{compact_raytracing_blas, prepare_raytracing_blas, BlasManager};
use extract::{extract_raytracing_scene, StandardMaterialAssets};
use tracing::warn;

Expand Down Expand Up @@ -69,6 +69,9 @@ impl Plugin for RaytracingScenePlugin {
.in_set(RenderSystems::PrepareAssets)
.before(prepare_assets::<RenderMesh>)
.after(allocate_and_free_meshes),
compact_raytracing_blas
.in_set(RenderSystems::PrepareAssets)
.after(prepare_raytracing_blas),
prepare_raytracing_scene_bindings.in_set(RenderSystems::PrepareBindGroups),
),
);
Expand Down
6 changes: 3 additions & 3 deletions release-content/release-notes/bevy_solari.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Initial raytraced lighting progress (bevy_solari)
authors: ["@JMS55", "@SparkyPotato"]
pull_requests: [19058, 19620, 19790, 20020, 20113, 20156, 20213, 20242, 20259, 20406]
pull_requests: [19058, 19620, 19790, 20020, 20113, 20156, 20213, 20242, 20259, 20406, 20457]
---

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

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

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.
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.

(TODO: Diagrams of direct vs indirect light)

Expand All @@ -23,7 +23,7 @@ The problem with these methods is that they all have large downsides:
* 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.
* Screen-space methods have low quality and do not capture off-screen geometry and light.

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!
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!

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!

Expand Down
Loading