Meshlet new error projection (#15846)

* New error projection code taken from @zeux's meshoptimizer nanite.cpp
demo for determining LOD (thanks zeux!)
* Builder: `compute_lod_group_data()`
* Runtime: `lod_error_is_imperceptible()`
This commit is contained in:
JMS55 2024-10-22 13:14:30 -07:00 committed by GitHub
parent 9930df83ed
commit 9d54fe0370
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 233 additions and 137 deletions

View file

@ -1213,7 +1213,7 @@ setup = [
"curl",
"-o",
"assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh",
],
]

View file

@ -18,7 +18,7 @@ shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"]
ios_simulator = ["bevy_render/ios_simulator"]
# Enables the meshlet renderer for dense high-poly scenes (experimental)
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"]
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"]
# Enables processing meshes into meshlet meshes
meshlet_processor = [
"meshlet",
@ -50,16 +50,17 @@ bevy_window = { path = "../bevy_window", version = "0.15.0-dev" }
# other
bitflags = "2.3"
fixedbitset = "0.5"
# meshlet
lz4_flex = { version = "0.11", default-features = false, features = [
"frame",
], optional = true }
derive_more = { version = "1", default-features = false, features = [
"error",
"from",
"display",
] }
# meshlet
lz4_flex = { version = "0.11", default-features = false, features = [
"frame",
], optional = true }
range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.3.0", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.13", optional = true }

View file

@ -9,6 +9,7 @@ use bevy_reflect::TypePath;
use bevy_tasks::block_on;
use bytemuck::{Pod, Zeroable};
use derive_more::derive::{Display, Error, From};
use half::f16;
use lz4_flex::frame::{FrameDecoder, FrameEncoder};
use std::io::{Read, Write};
@ -51,6 +52,8 @@ pub struct MeshletMesh {
pub(crate) meshlets: Arc<[Meshlet]>,
/// Spherical bounding volumes.
pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>,
/// Meshlet group and parent group simplification errors.
pub(crate) meshlet_simplification_errors: Arc<[MeshletSimplificationError]>,
}
/// A single meshlet within a [`MeshletMesh`].
@ -90,12 +93,12 @@ pub struct Meshlet {
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct MeshletBoundingSpheres {
/// The bounding sphere used for frustum and occlusion culling for this meshlet.
pub self_culling: MeshletBoundingSphere,
/// The bounding sphere used for determining if this meshlet is at the correct level of detail for a given view.
pub self_lod: MeshletBoundingSphere,
/// The bounding sphere used for determining if this meshlet's parent is at the correct level of detail for a given view.
pub parent_lod: MeshletBoundingSphere,
/// Bounding sphere used for frustum and occlusion culling for this meshlet.
pub culling_sphere: MeshletBoundingSphere,
/// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view.
pub lod_group_sphere: MeshletBoundingSphere,
/// Bounding sphere used for determining if this meshlet's parent group is at the correct level of detail for a given view.
pub lod_parent_group_sphere: MeshletBoundingSphere,
}
/// A spherical bounding volume used for a [`Meshlet`].
@ -106,6 +109,16 @@ pub struct MeshletBoundingSphere {
pub radius: f32,
}
/// Simplification error used for choosing level of detail for a [`Meshlet`].
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct MeshletSimplificationError {
/// Simplification error used for determining if this meshlet's group is at the correct level of detail for a given view.
pub group_error: f16,
/// Simplification error used for determining if this meshlet's parent group is at the correct level of detail for a given view.
pub parent_group_error: f16,
}
/// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
pub struct MeshletMeshSaver;
@ -139,6 +152,7 @@ impl AssetSaver for MeshletMeshSaver {
write_slice(&asset.indices, &mut writer)?;
write_slice(&asset.meshlets, &mut writer)?;
write_slice(&asset.meshlet_bounding_spheres, &mut writer)?;
write_slice(&asset.meshlet_simplification_errors, &mut writer)?;
writer.finish()?;
Ok(())
@ -179,6 +193,7 @@ impl AssetLoader for MeshletMeshLoader {
let indices = read_slice(reader)?;
let meshlets = read_slice(reader)?;
let meshlet_bounding_spheres = read_slice(reader)?;
let meshlet_simplification_errors = read_slice(reader)?;
Ok(MeshletMesh {
vertex_positions,
@ -187,6 +202,7 @@ impl AssetLoader for MeshletMeshLoader {
indices,
meshlets,
meshlet_bounding_spheres,
meshlet_simplification_errors,
})
}

View file

@ -1,6 +1,7 @@
#import bevy_pbr::meshlet_bindings::{
meshlet_cluster_meshlet_ids,
meshlet_bounding_spheres,
meshlet_simplification_errors,
meshlet_cluster_instance_ids,
meshlet_instance_uniforms,
meshlet_second_pass_candidates,
@ -13,6 +14,7 @@
meshlet_hardware_raster_indirect_args,
meshlet_raster_clusters,
meshlet_raster_cluster_rightmost_slot,
MeshletBoundingSphere,
}
#import bevy_render::maths::affine3_to_square
@ -48,8 +50,8 @@ fn cull_clusters(
let world_from_local = affine3_to_square(instance_uniform.world_from_local);
let world_scale = max(length(world_from_local[0]), max(length(world_from_local[1]), length(world_from_local[2])));
let bounding_spheres = meshlet_bounding_spheres[meshlet_id];
let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_culling.center, 1.0);
let culling_bounding_sphere_radius = world_scale * bounding_spheres.self_culling.radius;
let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0);
let culling_bounding_sphere_radius = world_scale * bounding_spheres.culling_sphere.radius;
#ifdef MESHLET_FIRST_CULLING_PASS
// Frustum culling
@ -60,19 +62,10 @@ fn cull_clusters(
}
}
// Calculate view-space LOD bounding sphere for the cluster
let lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_lod.center, 1.0);
let lod_bounding_sphere_radius = world_scale * bounding_spheres.self_lod.radius;
let lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(lod_bounding_sphere_center.xyz, 1.0)).xyz;
// Calculate view-space LOD bounding sphere for the cluster's parent
let parent_lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.parent_lod.center, 1.0);
let parent_lod_bounding_sphere_radius = world_scale * bounding_spheres.parent_lod.radius;
let parent_lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(parent_lod_bounding_sphere_center.xyz, 1.0)).xyz;
// Check LOD cut (cluster error imperceptible, and parent error not imperceptible)
let lod_is_ok = lod_error_is_imperceptible(lod_bounding_sphere_center_view_space, lod_bounding_sphere_radius);
let parent_lod_is_ok = lod_error_is_imperceptible(parent_lod_bounding_sphere_center_view_space, parent_lod_bounding_sphere_radius);
// Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible)
let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]);
let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale);
let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale);
if !lod_is_ok || parent_lod_is_ok { return; }
#endif
@ -80,8 +73,8 @@ fn cull_clusters(
#ifdef MESHLET_FIRST_CULLING_PASS
let previous_world_from_local = affine3_to_square(instance_uniform.previous_world_from_local);
let previous_world_from_local_scale = max(length(previous_world_from_local[0]), max(length(previous_world_from_local[1]), length(previous_world_from_local[2])));
let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.self_culling.center, 1.0);
let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.self_culling.radius;
let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0);
let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.culling_sphere.radius;
let occlusion_culling_bounding_sphere_center_view_space = (previous_view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz;
#else
let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center;
@ -148,14 +141,23 @@ fn cull_clusters(
meshlet_raster_clusters[buffer_slot] = cluster_id;
}
// https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space/21649403#21649403
fn lod_error_is_imperceptible(cp: vec3<f32>, r: f32) -> bool {
let d2 = dot(cp, cp);
let r2 = r * r;
let sphere_diameter_uv = view.clip_from_view[0][0] * r / sqrt(d2 - r2);
let view_size = f32(max(view.viewport.z, view.viewport.w));
let sphere_diameter_pixels = sphere_diameter_uv * view_size;
return sphere_diameter_pixels < 1.0;
// https://github.com/zeux/meshoptimizer/blob/1e48e96c7e8059321de492865165e9ef071bffba/demo/nanite.cpp#L115
fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4<f32>, world_scale: f32) -> bool {
let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz;
let radius_world_space = world_scale * lod_sphere.radius;
let error_world_space = world_scale * simplification_error;
var projected_error = error_world_space;
if view.clip_from_view[3][3] != 1.0 {
// Perspective
let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space;
let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]);
projected_error /= distance_to_closest_point_on_sphere_clamped_to_znear;
}
projected_error *= view.clip_from_view[1][1] * 0.5;
projected_error *= view.viewport.w;
return projected_error < 1.0;
}
// https://zeux.io/2023/01/12/approximate-projected-bounds

View file

@ -1,4 +1,6 @@
use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh};
use super::asset::{
Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh, MeshletSimplificationError,
};
use alloc::borrow::Cow;
use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles};
use bevy_render::{
@ -7,13 +9,12 @@ use bevy_render::{
};
use bevy_utils::HashMap;
use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
use core::ops::Range;
use core::{iter, ops::Range};
use derive_more::derive::{Display, Error};
use half::f16;
use itertools::Itertools;
use meshopt::{
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds,
ffi::{meshopt_Bounds, meshopt_Meshlet},
simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
};
use metis::Graph;
use smallvec::SmallVec;
@ -69,19 +70,21 @@ impl MeshletMesh {
let mut bounding_spheres = meshlets
.iter()
.map(|meshlet| compute_meshlet_bounds(meshlet, &vertices))
.map(convert_meshlet_bounds)
.map(|bounding_sphere| MeshletBoundingSpheres {
self_culling: bounding_sphere,
self_lod: MeshletBoundingSphere {
center: bounding_sphere.center,
culling_sphere: bounding_sphere,
lod_group_sphere: bounding_sphere,
lod_parent_group_sphere: MeshletBoundingSphere {
center: Vec3::ZERO,
radius: 0.0,
},
parent_lod: MeshletBoundingSphere {
center: bounding_sphere.center,
radius: f32::MAX,
},
})
.collect::<Vec<_>>();
let mut simplification_errors = iter::repeat(MeshletSimplificationError {
group_error: f16::ZERO,
parent_group_error: f16::MAX,
})
.take(meshlets.len())
.collect::<Vec<_>>();
// Build further LODs
let mut simplification_queue = 0..meshlets.len();
@ -107,22 +110,13 @@ impl MeshletMesh {
continue;
};
// Force parent error to be >= child error (we're currently building the parent from its children)
group_error = group_meshlets.iter().fold(group_error, |acc, meshlet_id| {
acc.max(bounding_spheres[*meshlet_id].self_lod.radius)
});
// Build a new LOD bounding sphere for the simplified group as a whole
let mut group_bounding_sphere = convert_meshlet_bounds(compute_cluster_bounds(
&simplified_group_indices,
&vertices,
));
group_bounding_sphere.radius = group_error;
// For each meshlet in the group set their parent LOD bounding sphere to that of the simplified group
for meshlet_id in group_meshlets {
bounding_spheres[meshlet_id].parent_lod = group_bounding_sphere;
}
// Compute LOD data for the group
let group_bounding_sphere = compute_lod_group_data(
&group_meshlets,
&mut group_error,
&mut bounding_spheres,
&mut simplification_errors,
);
// Build new meshlets using the simplified group
let new_meshlets_count = split_simplified_group_into_new_meshlets(
@ -131,22 +125,24 @@ impl MeshletMesh {
&mut meshlets,
);
// Calculate the culling bounding sphere for the new meshlets and set their LOD bounding spheres
// Calculate the culling bounding sphere for the new meshlets and set their LOD group data
let new_meshlet_ids = (meshlets.len() - new_meshlets_count)..meshlets.len();
bounding_spheres.extend(
new_meshlet_ids
.map(|meshlet_id| {
compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices)
})
.map(convert_meshlet_bounds)
.map(|bounding_sphere| MeshletBoundingSpheres {
self_culling: bounding_sphere,
self_lod: group_bounding_sphere,
parent_lod: MeshletBoundingSphere {
center: group_bounding_sphere.center,
radius: f32::MAX,
bounding_spheres.extend(new_meshlet_ids.clone().map(|meshlet_id| {
MeshletBoundingSpheres {
culling_sphere: compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices),
lod_group_sphere: group_bounding_sphere,
lod_parent_group_sphere: MeshletBoundingSphere {
center: Vec3::ZERO,
radius: 0.0,
},
}),
}
}));
simplification_errors.extend(
iter::repeat(MeshletSimplificationError {
group_error,
parent_group_error: f16::MAX,
})
.take(new_meshlet_ids.len()),
);
}
@ -179,6 +175,7 @@ impl MeshletMesh {
indices: meshlets.triangles.into(),
meshlets: bevy_meshlets.into(),
meshlet_bounding_spheres: bounding_spheres.into(),
meshlet_simplification_errors: simplification_errors.into(),
})
}
}
@ -303,7 +300,7 @@ fn simplify_meshlet_group(
group_meshlets: &[usize],
meshlets: &Meshlets,
vertices: &VertexDataAdapter<'_>,
) -> Option<(Vec<u32>, f32)> {
) -> Option<(Vec<u32>, f16)> {
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
let mut group_indices = Vec::new();
for meshlet_id in group_meshlets {
@ -330,10 +327,55 @@ fn simplify_meshlet_group(
return None;
}
// Convert error from diameter to radius
error *= 0.5;
Some((simplified_group_indices, f16::from_f32(error)))
}
Some((simplified_group_indices, error))
fn compute_lod_group_data(
group_meshlets: &[usize],
group_error: &mut f16,
bounding_spheres: &mut [MeshletBoundingSpheres],
simplification_errors: &mut [MeshletSimplificationError],
) -> MeshletBoundingSphere {
let mut group_bounding_sphere = MeshletBoundingSphere {
center: Vec3::ZERO,
radius: 0.0,
};
// Compute the lod group sphere center as a weighted average of the children spheres
let mut weight = 0.0;
for meshlet_id in group_meshlets {
let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere;
group_bounding_sphere.center +=
meshlet_lod_bounding_sphere.center * meshlet_lod_bounding_sphere.radius;
weight += meshlet_lod_bounding_sphere.radius;
}
group_bounding_sphere.center /= weight;
// Force parent group sphere to contain all child group spheres (we're currently building the parent from its children)
// TODO: This does not produce the absolute minimal bounding sphere. Doing so is non-trivial.
// "Smallest enclosing balls of balls" http://www.inf.ethz.ch/personal/emo/DoctThesisFiles/fischer05.pdf
for meshlet_id in group_meshlets {
let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere;
let d = meshlet_lod_bounding_sphere
.center
.distance(group_bounding_sphere.center);
group_bounding_sphere.radius = group_bounding_sphere
.radius
.max(meshlet_lod_bounding_sphere.radius + d);
}
// Force parent error to be >= child error (we're currently building the parent from its children)
for meshlet_id in group_meshlets {
*group_error = group_error.max(simplification_errors[*meshlet_id].group_error);
}
// Set the children's lod parent group data to the new lod group we just made
for meshlet_id in group_meshlets {
bounding_spheres[*meshlet_id].lod_parent_group_sphere = group_bounding_sphere;
simplification_errors[*meshlet_id].parent_group_error = *group_error;
}
group_bounding_sphere
}
fn split_simplified_group_into_new_meshlets(
@ -449,7 +491,11 @@ fn build_and_compress_meshlet_vertex_data(
});
}
fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere {
fn compute_meshlet_bounds(
meshlet: meshopt::Meshlet<'_>,
vertices: &VertexDataAdapter<'_>,
) -> MeshletBoundingSphere {
let bounds = meshopt::compute_meshlet_bounds(meshlet, vertices);
MeshletBoundingSphere {
center: bounds.center.into(),
radius: bounds.radius,

View file

@ -25,9 +25,9 @@ fn get_meshlet_triangle_count(meshlet: ptr<function, Meshlet>) -> u32 {
}
struct MeshletBoundingSpheres {
self_culling: MeshletBoundingSphere,
self_lod: MeshletBoundingSphere,
parent_lod: MeshletBoundingSphere,
culling_sphere: MeshletBoundingSphere,
lod_group_sphere: MeshletBoundingSphere,
lod_parent_group_sphere: MeshletBoundingSphere,
}
struct MeshletBoundingSphere {
@ -62,16 +62,17 @@ var<push_constant> cluster_count: u32;
var<push_constant> meshlet_raster_cluster_rightmost_slot: u32;
@group(0) @binding(0) var<storage, read> meshlet_cluster_meshlet_ids: array<u32>; // Per cluster
@group(0) @binding(1) var<storage, read> meshlet_bounding_spheres: array<MeshletBoundingSpheres>; // Per meshlet
@group(0) @binding(2) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
@group(0) @binding(3) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
@group(0) @binding(4) var<storage, read> meshlet_view_instance_visibility: array<u32>; // 1 bit per entity instance, packed as a bitmask
@group(0) @binding(5) var<storage, read_write> meshlet_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , packed as a bitmask
@group(0) @binding(6) var<storage, read_write> meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(7) var<storage, read_write> meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(8) var<storage, read_write> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(9) var depth_pyramid: texture_2d<f32>; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass
@group(0) @binding(10) var<uniform> view: View;
@group(0) @binding(11) var<uniform> previous_view: PreviousViewUniforms;
@group(0) @binding(2) var<storage, read> meshlet_simplification_errors: array<u32>; // Per meshlet
@group(0) @binding(3) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
@group(0) @binding(4) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
@group(0) @binding(5) var<storage, read> meshlet_view_instance_visibility: array<u32>; // 1 bit per entity instance, packed as a bitmask
@group(0) @binding(6) var<storage, read_write> meshlet_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , packed as a bitmask
@group(0) @binding(7) var<storage, read_write> meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(8) var<storage, read_write> meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(9) var<storage, read_write> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(10) var depth_pyramid: texture_2d<f32>; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass
@group(0) @binding(11) var<uniform> view: View;
@group(0) @binding(12) var<uniform> previous_view: PreviousViewUniforms;
fn should_cull_instance(instance_id: u32) -> bool {
let bit_offset = instance_id % 32u;

View file

@ -1,5 +1,5 @@
use super::{
asset::{Meshlet, MeshletBoundingSpheres},
asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError},
persistent_buffer::PersistentGpuBuffer,
MeshletMesh,
};
@ -26,7 +26,8 @@ pub struct MeshletMeshManager {
pub indices: PersistentGpuBuffer<Arc<[u8]>>,
pub meshlets: PersistentGpuBuffer<Arc<[Meshlet]>>,
pub meshlet_bounding_spheres: PersistentGpuBuffer<Arc<[MeshletBoundingSpheres]>>,
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 6]>,
pub meshlet_simplification_errors: PersistentGpuBuffer<Arc<[MeshletSimplificationError]>>,
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 7]>,
}
impl FromWorld for MeshletMeshManager {
@ -42,6 +43,10 @@ impl FromWorld for MeshletMeshManager {
"meshlet_bounding_spheres",
render_device,
),
meshlet_simplification_errors: PersistentGpuBuffer::new(
"meshlet_simplification_errors",
render_device,
),
meshlet_mesh_slices: HashMap::new(),
}
}
@ -81,6 +86,9 @@ impl MeshletMeshManager {
let meshlet_bounding_spheres_slice = self
.meshlet_bounding_spheres
.queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ());
let meshlet_simplification_errors_slice = self
.meshlet_simplification_errors
.queue_write(Arc::clone(&meshlet_mesh.meshlet_simplification_errors), ());
[
vertex_positions_slice,
@ -89,11 +97,12 @@ impl MeshletMeshManager {
indices_slice,
meshlets_slice,
meshlet_bounding_spheres_slice,
meshlet_simplification_errors_slice,
]
};
// If the MeshletMesh asset has not been uploaded to the GPU yet, queue it for uploading
let [_, _, _, _, meshlets_slice, _] = self
let [_, _, _, _, meshlets_slice, _, _] = self
.meshlet_mesh_slices
.entry(asset_id)
.or_insert_with_key(queue_meshlet_mesh)
@ -106,7 +115,7 @@ impl MeshletMeshManager {
pub fn remove(&mut self, asset_id: &AssetId<MeshletMesh>) {
if let Some(
[vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice],
[vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice, meshlet_simplification_errors_slice],
) = self.meshlet_mesh_slices.remove(asset_id)
{
self.vertex_positions
@ -117,6 +126,8 @@ impl MeshletMeshManager {
self.meshlets.mark_slice_unused(meshlets_slice);
self.meshlet_bounding_spheres
.mark_slice_unused(meshlet_bounding_spheres_slice);
self.meshlet_simplification_errors
.mark_slice_unused(meshlet_simplification_errors_slice);
}
}
}
@ -145,4 +156,7 @@ pub fn perform_pending_meshlet_mesh_writes(
meshlet_mesh_manager
.meshlet_bounding_spheres
.perform_writes(&render_queue, &render_device);
meshlet_mesh_manager
.meshlet_simplification_errors
.perform_writes(&render_queue, &render_device);
}

View file

@ -290,6 +290,7 @@ impl Plugin for MeshletPlugin {
}
}
/// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`].
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default)]
#[require(Transform, Visibility)]

View file

@ -1,10 +1,42 @@
use super::{
asset::{Meshlet, MeshletBoundingSpheres},
asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError},
persistent_buffer::PersistentGpuBufferable,
};
use alloc::sync::Arc;
use bevy_math::Vec2;
impl PersistentGpuBufferable for Arc<[Meshlet]> {
type Metadata = (u64, u64, u64);
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<Meshlet>()
}
fn write_bytes_le(
&self,
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
buffer_slice: &mut [u8],
) {
let vertex_position_offset = (vertex_position_offset * 8) as u32;
let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::<u32>()) as u32;
let index_offset = index_offset as u32;
for (i, meshlet) in self.iter().enumerate() {
let size = size_of::<Meshlet>();
let i = i * size;
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
start_vertex_position_bit: meshlet.start_vertex_position_bit
+ vertex_position_offset,
start_vertex_attribute_id: meshlet.start_vertex_attribute_id
+ vertex_attribute_offset,
start_index_id: meshlet.start_index_id + index_offset,
..*meshlet
});
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
}
}
}
impl PersistentGpuBufferable for Arc<[u8]> {
type Metadata = ();
@ -41,38 +73,6 @@ impl PersistentGpuBufferable for Arc<[Vec2]> {
}
}
impl PersistentGpuBufferable for Arc<[Meshlet]> {
type Metadata = (u64, u64, u64);
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<Meshlet>()
}
fn write_bytes_le(
&self,
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
buffer_slice: &mut [u8],
) {
let vertex_position_offset = (vertex_position_offset * 8) as u32;
let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::<u32>()) as u32;
let index_offset = index_offset as u32;
for (i, meshlet) in self.iter().enumerate() {
let size = size_of::<Meshlet>();
let i = i * size;
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
start_vertex_position_bit: meshlet.start_vertex_position_bit
+ vertex_position_offset,
start_vertex_attribute_id: meshlet.start_vertex_attribute_id
+ vertex_attribute_offset,
start_index_id: meshlet.start_index_id + index_offset,
..*meshlet
});
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
}
}
}
impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> {
type Metadata = ();
@ -84,3 +84,15 @@ impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> {
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
}
}
impl PersistentGpuBufferable for Arc<[MeshletSimplificationError]> {
type Metadata = ();
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<MeshletSimplificationError>()
}
fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) {
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
}
}

View file

@ -135,6 +135,7 @@ impl ResourceManager {
storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None),
storage_buffer_sized(false, None),
storage_buffer_sized(false, None),
storage_buffer_sized(false, None),
@ -624,6 +625,7 @@ pub fn prepare_meshlet_view_bind_groups(
let entries = BindGroupEntries::sequential((
cluster_meshlet_ids.as_entire_binding(),
meshlet_mesh_manager.meshlet_bounding_spheres.binding(),
meshlet_mesh_manager.meshlet_simplification_errors.binding(),
cluster_instance_ids.as_entire_binding(),
instance_manager.instance_uniforms.binding().unwrap(),
view_resources.instance_visibility.as_entire_binding(),
@ -652,6 +654,7 @@ pub fn prepare_meshlet_view_bind_groups(
let entries = BindGroupEntries::sequential((
cluster_meshlet_ids.as_entire_binding(),
meshlet_mesh_manager.meshlet_bounding_spheres.binding(),
meshlet_mesh_manager.meshlet_simplification_errors.binding(),
cluster_instance_ids.as_entire_binding(),
instance_manager.instance_uniforms.binding().unwrap(),
view_resources.instance_visibility.as_entire_binding(),

View file

@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin};
use std::{f32::consts::PI, path::Path, process::ExitCode};
const ASSET_URL: &str =
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh";
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh";
fn main() -> ExitCode {
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {