Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/meshlet/asset.rs
9444 views
1
use alloc::sync::Arc;
2
use bevy_asset::{
3
io::{Reader, Writer},
4
saver::{AssetSaver, SavedAsset},
5
Asset, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext,
6
};
7
use bevy_math::{Vec2, Vec3};
8
use bevy_reflect::TypePath;
9
use bevy_render::render_resource::ShaderType;
10
use bevy_tasks::block_on;
11
use bytemuck::{Pod, Zeroable};
12
use lz4_flex::frame::{FrameDecoder, FrameEncoder};
13
use std::io::{Read, Write};
14
use thiserror::Error;
15
16
/// Unique identifier for the [`MeshletMesh`] asset format.
17
const MESHLET_MESH_ASSET_MAGIC: u64 = 1717551717668;
18
19
/// The current version of the [`MeshletMesh`] asset format.
20
pub const MESHLET_MESH_ASSET_VERSION: u64 = 3;
21
22
/// A mesh that has been pre-processed into multiple small clusters of triangles called meshlets.
23
///
24
/// A [`bevy_mesh::Mesh`] can be converted to a [`MeshletMesh`] using `MeshletMesh::from_mesh` when the `meshlet_processor` cargo feature is enabled.
25
/// The conversion step is very slow, and is meant to be ran once ahead of time, and not during runtime. This type of mesh is not suitable for
26
/// dynamically generated geometry.
27
///
28
/// There are restrictions on the [`Material`](`crate::Material`) functionality that can be used with this type of mesh.
29
/// * Materials have no control over the vertex shader or vertex attributes.
30
/// * Materials must be opaque. Transparent, alpha masked, and transmissive materials are not supported.
31
/// * Do not use normal maps baked from higher-poly geometry. Use the high-poly geometry directly and skip the normal map.
32
/// * If additional detail is needed, a smaller tiling normal map not baked from a mesh is ok.
33
/// * Material shaders must not use builtin functions that automatically calculate derivatives <https://gpuweb.github.io/gpuweb/wgsl/#derivatives>.
34
/// * Performing manual arithmetic on texture coordinates (UVs) is forbidden. Use the chain-rule version of arithmetic functions instead (TODO: not yet implemented).
35
/// * Limited control over [`bevy_render::render_resource::RenderPipelineDescriptor`] attributes.
36
/// * Materials must use the [`Material::meshlet_mesh_fragment_shader`](`crate::Material::meshlet_mesh_fragment_shader`) method (and similar variants for prepass/deferred shaders)
37
/// which requires certain shader patterns that differ from the regular material shaders.
38
///
39
/// See also [`MeshletMesh3d`](`super::MeshletMesh3d`) and [`MeshletPlugin`](`super::MeshletPlugin`).
40
#[derive(Asset, TypePath, Clone)]
41
pub struct MeshletMesh {
42
/// Quantized and bitstream-packed vertex positions for meshlet vertices.
43
pub(crate) vertex_positions: Arc<[u32]>,
44
/// Octahedral-encoded and 2x16snorm packed normals for meshlet vertices.
45
pub(crate) vertex_normals: Arc<[u32]>,
46
/// Uncompressed vertex texture coordinates for meshlet vertices.
47
pub(crate) vertex_uvs: Arc<[Vec2]>,
48
/// Triangle indices for meshlets.
49
pub(crate) indices: Arc<[u8]>,
50
/// The BVH8 used for culling and LOD selection of the meshlets. The root is at index 0.
51
pub(crate) bvh: Arc<[BvhNode]>,
52
/// The list of meshlets making up this mesh.
53
pub(crate) meshlets: Arc<[Meshlet]>,
54
/// Spherical bounding volumes.
55
pub(crate) meshlet_cull_data: Arc<[MeshletCullData]>,
56
/// The tight AABB of the meshlet mesh, used for frustum and occlusion culling at the instance
57
/// level.
58
pub(crate) aabb: MeshletAabb,
59
/// The depth of the culling BVH, used to determine the number of dispatches at runtime.
60
pub(crate) bvh_depth: u32,
61
}
62
63
/// A single BVH8 node in the BVH used for culling and LOD selection of a [`MeshletMesh`].
64
#[derive(Copy, Clone, Default, Pod, Zeroable)]
65
#[repr(C)]
66
pub struct BvhNode {
67
/// The tight AABBs of this node's children, used for frustum and occlusion during BVH
68
/// traversal.
69
pub aabbs: [MeshletAabbErrorOffset; 8],
70
/// The LOD bounding spheres of this node's children, used for LOD selection during BVH
71
/// traversal.
72
pub lod_bounds: [MeshletBoundingSphere; 8],
73
/// If `u8::MAX`, it indicates that the child of each children is a BVH node, otherwise it is the number of meshlets in the group.
74
pub child_counts: [u8; 8],
75
pub _padding: [u32; 2],
76
}
77
78
/// A single meshlet within a [`MeshletMesh`].
79
#[derive(Copy, Clone, Pod, Zeroable)]
80
#[repr(C)]
81
pub struct Meshlet {
82
/// The bit offset within the parent mesh's [`MeshletMesh::vertex_positions`] buffer where the vertex positions for this meshlet begin.
83
pub start_vertex_position_bit: u32,
84
/// The offset within the parent mesh's [`MeshletMesh::vertex_normals`] and [`MeshletMesh::vertex_uvs`] buffers
85
/// where non-position vertex attributes for this meshlet begin.
86
pub start_vertex_attribute_id: u32,
87
/// The offset within the parent mesh's [`MeshletMesh::indices`] buffer where the indices for this meshlet begin.
88
pub start_index_id: u32,
89
/// The amount of vertices in this meshlet (minus one to fit 256 in a u8).
90
pub vertex_count_minus_one: u8,
91
/// The amount of triangles in this meshlet.
92
pub triangle_count: u8,
93
/// Unused.
94
pub padding: u16,
95
/// Number of bits used to store the X channel of vertex positions within this meshlet.
96
pub bits_per_vertex_position_channel_x: u8,
97
/// Number of bits used to store the Y channel of vertex positions within this meshlet.
98
pub bits_per_vertex_position_channel_y: u8,
99
/// Number of bits used to store the Z channel of vertex positions within this meshlet.
100
pub bits_per_vertex_position_channel_z: u8,
101
/// Power of 2 factor used to quantize vertex positions within this meshlet.
102
pub vertex_position_quantization_factor: u8,
103
/// Minimum quantized X channel value of vertex positions within this meshlet.
104
pub min_vertex_position_channel_x: f32,
105
/// Minimum quantized Y channel value of vertex positions within this meshlet.
106
pub min_vertex_position_channel_y: f32,
107
/// Minimum quantized Z channel value of vertex positions within this meshlet.
108
pub min_vertex_position_channel_z: f32,
109
}
110
111
/// Bounding spheres used for culling and choosing level of detail for a [`Meshlet`].
112
#[derive(Copy, Clone, Pod, Zeroable)]
113
#[repr(C)]
114
pub struct MeshletCullData {
115
/// Tight bounding box, used for frustum and occlusion culling for this meshlet.
116
pub aabb: MeshletAabbErrorOffset,
117
/// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view.
118
pub lod_group_sphere: MeshletBoundingSphere,
119
}
120
121
/// An axis-aligned bounding box used for a [`Meshlet`].
122
#[derive(Copy, Clone, Default, Pod, Zeroable, ShaderType)]
123
#[repr(C)]
124
pub struct MeshletAabb {
125
pub center: Vec3,
126
pub half_extent: Vec3,
127
}
128
129
// An axis-aligned bounding box used for a [`Meshlet`].
130
#[derive(Copy, Clone, Default, Pod, Zeroable, ShaderType)]
131
#[repr(C)]
132
pub struct MeshletAabbErrorOffset {
133
pub center: Vec3,
134
pub error: f32,
135
pub half_extent: Vec3,
136
pub child_offset: u32,
137
}
138
139
/// A spherical bounding volume used for a [`Meshlet`].
140
#[derive(Copy, Clone, Default, Pod, Zeroable)]
141
#[repr(C)]
142
pub struct MeshletBoundingSphere {
143
pub center: Vec3,
144
pub radius: f32,
145
}
146
147
/// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
148
#[derive(TypePath)]
149
pub struct MeshletMeshSaver;
150
151
impl AssetSaver for MeshletMeshSaver {
152
type Asset = MeshletMesh;
153
type Settings = ();
154
type OutputLoader = MeshletMeshLoader;
155
type Error = MeshletMeshSaveOrLoadError;
156
157
async fn save(
158
&self,
159
writer: &mut Writer,
160
asset: SavedAsset<'_, '_, MeshletMesh>,
161
_settings: &(),
162
) -> Result<(), MeshletMeshSaveOrLoadError> {
163
// Write asset magic number
164
writer
165
.write_all(&MESHLET_MESH_ASSET_MAGIC.to_le_bytes())
166
.await?;
167
168
// Write asset version
169
writer
170
.write_all(&MESHLET_MESH_ASSET_VERSION.to_le_bytes())
171
.await?;
172
173
writer.write_all(bytemuck::bytes_of(&asset.aabb)).await?;
174
writer
175
.write_all(bytemuck::bytes_of(&asset.bvh_depth))
176
.await?;
177
178
// Compress and write asset data
179
let mut writer = FrameEncoder::new(AsyncWriteSyncAdapter(writer));
180
write_slice(&asset.vertex_positions, &mut writer)?;
181
write_slice(&asset.vertex_normals, &mut writer)?;
182
write_slice(&asset.vertex_uvs, &mut writer)?;
183
write_slice(&asset.indices, &mut writer)?;
184
write_slice(&asset.bvh, &mut writer)?;
185
write_slice(&asset.meshlets, &mut writer)?;
186
write_slice(&asset.meshlet_cull_data, &mut writer)?;
187
// BUG: Flushing helps with an async_fs bug, but it still fails sometimes. https://github.com/smol-rs/async-fs/issues/45
188
// ERROR bevy_asset::server: Failed to load asset with asset loader MeshletMeshLoader: failed to fill whole buffer
189
writer.flush()?;
190
writer.finish()?;
191
192
Ok(())
193
}
194
}
195
196
/// An [`AssetLoader`] for `.meshlet_mesh` [`MeshletMesh`] assets.
197
#[derive(TypePath)]
198
pub struct MeshletMeshLoader;
199
200
impl AssetLoader for MeshletMeshLoader {
201
type Asset = MeshletMesh;
202
type Settings = ();
203
type Error = MeshletMeshSaveOrLoadError;
204
205
async fn load(
206
&self,
207
reader: &mut dyn Reader,
208
_settings: &(),
209
_load_context: &mut LoadContext<'_>,
210
) -> Result<MeshletMesh, MeshletMeshSaveOrLoadError> {
211
// Load and check magic number
212
let magic = async_read_u64(reader).await?;
213
if magic != MESHLET_MESH_ASSET_MAGIC {
214
return Err(MeshletMeshSaveOrLoadError::WrongFileType);
215
}
216
217
// Load and check asset version
218
let version = async_read_u64(reader).await?;
219
if version != MESHLET_MESH_ASSET_VERSION {
220
return Err(MeshletMeshSaveOrLoadError::WrongVersion { found: version });
221
}
222
223
let mut bytes = [0u8; size_of::<MeshletAabb>()];
224
reader.read_exact(&mut bytes).await?;
225
let aabb = bytemuck::cast(bytes);
226
let mut bytes = [0u8; size_of::<u32>()];
227
reader.read_exact(&mut bytes).await?;
228
let bvh_depth = u32::from_le_bytes(bytes);
229
230
// Load and decompress asset data
231
let reader = &mut FrameDecoder::new(AsyncReadSyncAdapter(reader));
232
let vertex_positions = read_slice(reader)?;
233
let vertex_normals = read_slice(reader)?;
234
let vertex_uvs = read_slice(reader)?;
235
let indices = read_slice(reader)?;
236
let bvh = read_slice(reader)?;
237
let meshlets = read_slice(reader)?;
238
let meshlet_cull_data = read_slice(reader)?;
239
240
Ok(MeshletMesh {
241
vertex_positions,
242
vertex_normals,
243
vertex_uvs,
244
indices,
245
bvh,
246
meshlets,
247
meshlet_cull_data,
248
aabb,
249
bvh_depth,
250
})
251
}
252
253
fn extensions(&self) -> &[&str] {
254
&["meshlet_mesh"]
255
}
256
}
257
258
#[derive(Error, Debug)]
259
pub enum MeshletMeshSaveOrLoadError {
260
#[error("file was not a MeshletMesh asset")]
261
WrongFileType,
262
#[error("expected asset version {MESHLET_MESH_ASSET_VERSION} but found version {found}")]
263
WrongVersion { found: u64 },
264
#[error("failed to compress or decompress asset data")]
265
CompressionOrDecompression(#[from] lz4_flex::frame::Error),
266
#[error(transparent)]
267
Io(#[from] std::io::Error),
268
}
269
270
async fn async_read_u64(reader: &mut dyn Reader) -> Result<u64, std::io::Error> {
271
let mut bytes = [0u8; 8];
272
reader.read_exact(&mut bytes).await?;
273
Ok(u64::from_le_bytes(bytes))
274
}
275
276
fn read_u64(reader: &mut dyn Read) -> Result<u64, std::io::Error> {
277
let mut bytes = [0u8; 8];
278
reader.read_exact(&mut bytes)?;
279
Ok(u64::from_le_bytes(bytes))
280
}
281
282
fn write_slice<T: Pod>(
283
field: &[T],
284
writer: &mut dyn Write,
285
) -> Result<(), MeshletMeshSaveOrLoadError> {
286
writer.write_all(&(field.len() as u64).to_le_bytes())?;
287
writer.write_all(bytemuck::cast_slice(field))?;
288
Ok(())
289
}
290
291
fn read_slice<T: Pod>(reader: &mut dyn Read) -> Result<Arc<[T]>, std::io::Error> {
292
let len = read_u64(reader)? as usize;
293
294
let mut data: Arc<[T]> = core::iter::repeat_with(T::zeroed).take(len).collect();
295
let slice = Arc::get_mut(&mut data).unwrap();
296
reader.read_exact(bytemuck::cast_slice_mut(slice))?;
297
298
Ok(data)
299
}
300
301
// TODO: Use async for everything and get rid of this adapter
302
struct AsyncWriteSyncAdapter<'a>(&'a mut Writer);
303
304
impl Write for AsyncWriteSyncAdapter<'_> {
305
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
306
block_on(self.0.write(buf))
307
}
308
309
fn flush(&mut self) -> std::io::Result<()> {
310
block_on(self.0.flush())
311
}
312
}
313
314
// TODO: Use async for everything and get rid of this adapter
315
struct AsyncReadSyncAdapter<'a>(&'a mut dyn Reader);
316
317
impl Read for AsyncReadSyncAdapter<'_> {
318
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
319
block_on(self.0.read(buf))
320
}
321
}
322
323