use alloc::collections::VecDeque;
use bevy_asset::AssetId;
use bevy_ecs::{
resource::Resource,
system::{Res, ResMut},
};
use bevy_mesh::{Indices, Mesh};
use bevy_platform::collections::HashMap;
use bevy_render::{
mesh::{
allocator::{MeshAllocator, MeshBufferSlice},
RenderMesh,
},
render_asset::ExtractedAssets,
render_resource::*,
renderer::{RenderDevice, RenderQueue},
};
const MAX_COMPACTION_VERTICES_PER_FRAME: u32 = 400_000;
#[derive(Resource, Default)]
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.blas.get(mesh)
}
}
pub fn prepare_raytracing_blas(
mut blas_manager: ResMut<BlasManager>,
extracted_meshes: Res<ExtractedAssets<RenderMesh>>,
mesh_allocator: Res<MeshAllocator>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
for asset_id in extracted_meshes
.removed
.iter()
.chain(extracted_meshes.modified.iter())
{
blas_manager.blas.remove(asset_id);
}
if extracted_meshes.extracted.is_empty() {
return;
}
let blas_resources = extracted_meshes
.extracted
.iter()
.filter(|(_, mesh)| is_mesh_raytracing_compatible(mesh))
.map(|(asset_id, _)| {
let vertex_slice = mesh_allocator.mesh_vertex_slice(asset_id).unwrap();
let index_slice = mesh_allocator.mesh_index_slice(asset_id).unwrap();
let (blas, blas_size) =
allocate_blas(&vertex_slice, &index_slice, asset_id, &render_device);
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)
})
.collect::<Vec<_>>();
let build_entries = blas_resources
.iter()
.map(|(asset_id, vertex_slice, index_slice, blas_size)| {
let geometry = BlasTriangleGeometry {
size: blas_size,
vertex_buffer: vertex_slice.buffer,
first_vertex: vertex_slice.range.start,
vertex_stride: 48,
index_buffer: Some(index_slice.buffer),
first_index: Some(index_slice.range.start),
transform_buffer: None,
transform_buffer_offset: None,
};
BlasBuildEntry {
blas: &blas_manager.blas[asset_id],
geometry: BlasGeometries::TriangleGeometries(vec![geometry]),
}
})
.collect::<Vec<_>>();
let mut command_encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("build_blas_command_encoder"),
});
command_encoder.build_acceleration_structures(&build_entries, &[]);
render_queue.submit([command_encoder.finish()]);
}
pub fn compact_raytracing_blas(
mut blas_manager: ResMut<BlasManager>,
render_queue: Res<RenderQueue>,
) {
let queue_size = blas_manager.compaction_queue.len();
let mut meshes_processed = 0;
let mut vertices_compacted = 0;
while !blas_manager.compaction_queue.is_empty()
&& vertices_compacted < MAX_COMPACTION_VERTICES_PER_FRAME
&& meshes_processed < queue_size
{
meshes_processed += 1;
let (mesh, vertex_count, compaction_started) =
blas_manager.compaction_queue.pop_front().unwrap();
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_manager
.compaction_queue
.push_back((mesh, vertex_count, true));
}
}
fn allocate_blas(
vertex_slice: &MeshBufferSlice,
index_slice: &MeshBufferSlice,
asset_id: &AssetId<Mesh>,
render_device: &RenderDevice,
) -> (Blas, BlasTriangleGeometrySizeDescriptor) {
let blas_size = BlasTriangleGeometrySizeDescriptor {
vertex_format: Mesh::ATTRIBUTE_POSITION.format,
vertex_count: vertex_slice.range.len() as u32,
index_format: Some(IndexFormat::Uint32),
index_count: Some(index_slice.range.len() as u32),
flags: AccelerationStructureGeometryFlags::OPAQUE,
};
let blas = render_device.wgpu_device().create_blas(
&CreateBlasDescriptor {
label: Some(&asset_id.to_string()),
flags: AccelerationStructureFlags::PREFER_FAST_TRACE
| AccelerationStructureFlags::ALLOW_COMPACTION,
update_mode: AccelerationStructureUpdateMode::Build,
},
BlasGeometrySizeDescriptors::Triangles {
descriptors: vec![blas_size.clone()],
},
);
(blas, blas_size)
}
fn is_mesh_raytracing_compatible(mesh: &Mesh) -> bool {
let triangle_list = mesh.primitive_topology() == PrimitiveTopology::TriangleList;
let vertex_attributes = mesh.attributes().map(|(attribute, _)| attribute.id).eq([
Mesh::ATTRIBUTE_POSITION.id,
Mesh::ATTRIBUTE_NORMAL.id,
Mesh::ATTRIBUTE_UV_0.id,
Mesh::ATTRIBUTE_TANGENT.id,
]);
let indexed_32 = matches!(mesh.indices(), Some(Indices::U32(..)));
mesh.enable_raytracing && triangle_list && vertex_attributes && indexed_32
}