Skip to content
Open
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
11 changes: 6 additions & 5 deletions crates/bevy_asset/src/render_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ use serde::{Deserialize, Serialize};
bitflags::bitflags! {
/// Defines where the asset will be used.
///
/// If an asset is set to the `RENDER_WORLD` but not the `MAIN_WORLD`, the asset will be
/// unloaded from the asset server once it's been extracted and prepared in the render world.
/// If an asset is set to the `RENDER_WORLD` but not the `MAIN_WORLD`, the asset data (pixel data,
/// mersh vertex data, etc) will be removed from the cpu-side asset once it's been extracted and prepared
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// mersh vertex data, etc) will be removed from the cpu-side asset once it's been extracted and prepared
/// mesh vertex data, etc) will be removed from the cpu-side asset once it's been extracted and prepared

/// in the render world. The asset will remain in the assets collection, but with only metadata.
///
/// Unloading the asset saves on memory, as for most cases it is no longer necessary to keep
/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you can no longer
/// access the asset from the CPU (via the `Assets<T>` resource) once unloaded (without re-loading it).
/// Unloading the asset data saves on memory, as for most cases it is no longer necessary to keep
/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you cannot access the
/// asset data from the CPU (via the `Assets<T>` resource) once unloaded (without re-loading it).
///
/// If you never need access to the asset from the CPU past the first frame it's loaded on,
/// or only need very infrequent access, then set this to `RENDER_WORLD`. Otherwise, set this to
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_camera/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ pub trait MeshAabb {

impl MeshAabb for Mesh {
fn compute_aabb(&self) -> Option<Aabb> {
if let Some(extents) = self.final_aabb_extents {
// use precomputed extents
return Some(Aabb::from_min_max(extents.0, extents.1));
}

let Some(VertexAttributeValues::Float32x3(values)) =
self.attribute(Mesh::ATTRIBUTE_POSITION)
else {
Expand Down
128 changes: 102 additions & 26 deletions crates/bevy_mesh/src/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ pub struct Mesh {
/// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order,
/// which allows easy stable `VertexBuffers` (i.e. same buffer order)
#[reflect(ignore, clone)]
attributes: BTreeMap<MeshVertexAttributeId, MeshAttributeData>,
indices: Option<Indices>,
attributes: Option<BTreeMap<MeshVertexAttributeId, MeshAttributeData>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This Option needs a comment explaining why it's an option. Something like "we use an option to indicate whether the data has been extracted into the render world".

indices: Option<Option<Indices>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this an enum instead? These options don't convey anything semantic and are just a hack to represent 3 possible states, so let's just accurately represent those three states!

#[cfg(feature = "morph")]
morph_targets: Option<Handle<Image>>,
#[cfg(feature = "morph")]
Expand All @@ -150,6 +150,9 @@ pub struct Mesh {
/// Does nothing if not used with `bevy_solari`, or if the mesh is not compatible
/// with `bevy_solari` (see `bevy_solari`'s docs).
pub enable_raytracing: bool,
/// Precomputed min and max extents of the mesh position data. Used mainly for constructing `Aabb`s for frustum culling.
/// This data will be set if/when a mesh is extracted to the GPU
pub final_aabb_extents: Option<(Vec3, Vec3)>,
}

impl Mesh {
Expand Down Expand Up @@ -233,14 +236,15 @@ impl Mesh {
pub fn new(primitive_topology: PrimitiveTopology, asset_usage: RenderAssetUsages) -> Self {
Mesh {
primitive_topology,
attributes: Default::default(),
indices: None,
attributes: Some(Default::default()),
indices: Some(None),
#[cfg(feature = "morph")]
morph_targets: None,
#[cfg(feature = "morph")]
morph_target_names: None,
asset_usage,
enable_raytracing: true,
final_aabb_extents: None,
}
}

Expand Down Expand Up @@ -272,6 +276,8 @@ impl Mesh {
}

self.attributes
.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.insert(attribute.id, MeshAttributeData { attribute, values });
}

Expand Down Expand Up @@ -301,6 +307,8 @@ impl Mesh {
attribute: impl Into<MeshVertexAttributeId>,
) -> Option<VertexAttributeValues> {
self.attributes
.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.remove(&attribute.into())
.map(|data| data.values)
}
Expand All @@ -316,7 +324,9 @@ impl Mesh {

#[inline]
pub fn contains_attribute(&self, id: impl Into<MeshVertexAttributeId>) -> bool {
self.attributes.contains_key(&id.into())
self.attributes. as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.contains_key(&id.into())
}

/// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
Expand All @@ -325,7 +335,10 @@ impl Mesh {
&self,
id: impl Into<MeshVertexAttributeId>,
) -> Option<&VertexAttributeValues> {
self.attributes.get(&id.into()).map(|data| &data.values)
self.attributes
.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.get(&id.into()).map(|data| &data.values)
}

/// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
Expand All @@ -334,7 +347,9 @@ impl Mesh {
&self,
id: impl Into<MeshVertexAttributeId>,
) -> Option<&MeshAttributeData> {
self.attributes.get(&id.into())
self.attributes.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.get(&id.into())
}

/// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
Expand All @@ -344,6 +359,8 @@ impl Mesh {
id: impl Into<MeshVertexAttributeId>,
) -> Option<&mut VertexAttributeValues> {
self.attributes
.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.get_mut(&id.into())
.map(|data| &mut data.values)
}
Expand All @@ -353,6 +370,8 @@ impl Mesh {
&self,
) -> impl Iterator<Item = (&MeshVertexAttribute, &VertexAttributeValues)> {
self.attributes
.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.values()
.map(|data| (&data.attribute, &data.values))
}
Expand All @@ -362,6 +381,8 @@ impl Mesh {
&mut self,
) -> impl Iterator<Item = (&MeshVertexAttribute, &mut VertexAttributeValues)> {
self.attributes
.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.values_mut()
.map(|data| (&data.attribute, &mut data.values))
}
Expand All @@ -371,7 +392,9 @@ impl Mesh {
/// that use triangles.
#[inline]
pub fn insert_indices(&mut self, indices: Indices) {
self.indices = Some(indices);
let mesh_indices = self.indices.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");
*mesh_indices = Some(indices);
}

/// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles
Expand All @@ -389,19 +412,21 @@ impl Mesh {
/// Retrieves the vertex `indices` of the mesh.
#[inline]
pub fn indices(&self) -> Option<&Indices> {
self.indices.as_ref()
self.indices.as_ref().expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD").as_ref()
Copy link
Member

Choose a reason for hiding this comment

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

Could we swap these methods to return Result and avoid the expects here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could … it is a usage error though, rather than “something that can happen and you should deal with”, like result usually implies.

I don’t feel strongly, will change if you prefer it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the intent is for a user to "deal with it", but rather "let's not crash the program because one asset has the wrong flags set". So I am in favour of making these Results.

}

/// Retrieves the vertex `indices` of the mesh mutably.
#[inline]
pub fn indices_mut(&mut self) -> Option<&mut Indices> {
Copy link
Contributor

Choose a reason for hiding this comment

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

All these mutation methods essentially stop functioning when the asset is extracted. What about runtime generated (and mutated) assets? Is the intent that users will like fully replace the Mesh with like *mesh_mut = Mesh::new()?

If so, this probably needs a migration guide.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the pattern would be either to fully initialize in one step, or to initialize with RenderAssetUsages::MAIN_WORLD, make any modifications, then when it's finished change the usage to RenderAssetUsages::RENDER_WORLD. after that no more modifications are possible since the data is only on GPU and we can't get it back.

if users want to make continuous runtime modifications, then they should use MAIN_WORLD || RENDER_WORLD, or fully replacing will also work.

this PR doesn't change anything in those scenarios, modifying an asset with RENDER_WORLD and not MAIN_WORLD is not possible currently either since the asset gets removed from the Assets collection. i can write something if you like, but the usage doesn't change.

self.indices.as_mut()
self.indices.as_mut().expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD").as_mut()
}

/// Removes the vertex `indices` from the mesh and returns them.
#[inline]
pub fn remove_indices(&mut self) -> Option<Indices> {
core::mem::take(&mut self.indices)
let mesh_indices = self.indices.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");
core::mem::take(mesh_indices)
}

/// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh.
Expand All @@ -416,6 +441,8 @@ impl Mesh {
/// Returns the size of a vertex in bytes.
pub fn get_vertex_size(&self) -> u64 {
self.attributes
.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.values()
.map(|data| data.attribute.format.size())
.sum()
Expand All @@ -431,7 +458,10 @@ impl Mesh {
/// Computes and returns the index data of the mesh as bytes.
/// This is used to transform the index data into a GPU friendly format.
pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> {
self.indices.as_ref().map(|indices| match &indices {
let mesh_indices = self.indices.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

mesh_indices.as_ref().map(|indices| match &indices {
Indices::U16(indices) => cast_slice(&indices[..]),
Indices::U32(indices) => cast_slice(&indices[..]),
})
Expand All @@ -442,10 +472,13 @@ impl Mesh {
&self,
mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
) -> MeshVertexBufferLayoutRef {
let mut attributes = Vec::with_capacity(self.attributes.len());
let mut attribute_ids = Vec::with_capacity(self.attributes.len());
let mesh_attributes = self.attributes.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

let mut attributes = Vec::with_capacity(mesh_attributes.len());
let mut attribute_ids = Vec::with_capacity(mesh_attributes.len());
let mut accumulated_offset = 0;
for (index, data) in self.attributes.values().enumerate() {
for (index, data) in mesh_attributes.values().enumerate() {
attribute_ids.push(data.attribute.id);
attributes.push(VertexAttribute {
offset: accumulated_offset,
Expand All @@ -471,12 +504,14 @@ impl Mesh {
/// If the attributes have different vertex counts, the smallest is returned.
pub fn count_vertices(&self) -> usize {
let mut vertex_count: Option<usize> = None;
for (attribute_id, attribute_data) in &self.attributes {
let mesh_attributes = self.attributes.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

for (attribute_id, attribute_data) in mesh_attributes {
let attribute_len = attribute_data.values.len();
if let Some(previous_vertex_count) = vertex_count {
if previous_vertex_count != attribute_len {
let name = self
.attributes
let name = mesh_attributes
.get(attribute_id)
.map(|data| data.attribute.name.to_string())
.unwrap_or_else(|| format!("{attribute_id:?}"));
Expand Down Expand Up @@ -515,11 +550,14 @@ impl Mesh {
/// If the vertex attributes have different lengths, they are all truncated to
/// the length of the smallest.
pub fn write_packed_vertex_buffer_data(&self, slice: &mut [u8]) {
let mesh_attributes = self.attributes.as_ref()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

let vertex_size = self.get_vertex_size() as usize;
let vertex_count = self.count_vertices();
// bundle into interleaved buffers
let mut attribute_offset = 0;
for attribute_data in self.attributes.values() {
for attribute_data in mesh_attributes.values() {
let attribute_size = attribute_data.attribute.format.size() as usize;
let attributes_bytes = attribute_data.values.get_bytes();
for (vertex_index, attribute_bytes) in attributes_bytes
Expand All @@ -544,11 +582,17 @@ impl Mesh {
indices.map(|i| values[i]).collect()
}

let Some(indices) = self.indices.take() else {
let mesh_indices = self.indices.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

let Some(indices) = mesh_indices.take() else {
return;
};

for attributes in self.attributes.values_mut() {
let mesh_attributes = self.attributes.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

for attributes in mesh_attributes.values_mut() {
let indices = indices.iter();
#[expect(
clippy::match_same_arms,
Expand Down Expand Up @@ -640,7 +684,11 @@ impl Mesh {
_ => Err(MeshWindingInvertError::WrongTopology),
}
}
match &mut self.indices {

let mesh_indices = self.indices.as_mut()
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD");

match mesh_indices {
Some(Indices::U16(vec)) => invert(vec, self.primitive_topology),
Some(Indices::U32(vec)) => invert(vec, self.primitive_topology),
None => Ok(()),
Expand Down Expand Up @@ -1362,6 +1410,33 @@ impl Mesh {
})
}
}

pub fn take_gpu_data(&mut self) -> Option<Self> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Doc comment please! Also linking to RenderAsset::take_gpu_data would be good.

let attributes = self.attributes.take()?;
let indices = self.indices.take()?;

// store the aabb extents as they cannot be computed after extraction
if let Some(MeshAttributeData {
values: VertexAttributeValues::Float32x3(position_values),
..
}) = attributes.get(&Self::ATTRIBUTE_POSITION.id)
{
let mut iter = position_values.iter().map(|p| Vec3::from_slice(p));
let mut min = iter.next().unwrap();
let mut max = min;
for v in iter {
min = Vec3::min(min, v);
max = Vec3::max(max, v);
}
self.final_aabb_extents = Some((min, max));
}

Some(Self {
attributes: Some(attributes),
indices: Some(indices),
..self.clone()
})
}
}

#[cfg(feature = "morph")]
Expand Down Expand Up @@ -1470,6 +1545,7 @@ impl SerializedMesh {
primitive_topology: mesh.primitive_topology,
attributes: mesh
.attributes
.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD")
.into_iter()
.map(|(id, data)| {
(
Expand All @@ -1478,7 +1554,7 @@ impl SerializedMesh {
)
})
.collect(),
indices: mesh.indices,
indices: mesh.indices.expect("Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD"),
}
}

Expand Down Expand Up @@ -1542,7 +1618,7 @@ impl MeshDeserializer {
/// See the documentation for [`SerializedMesh`] for caveats.
pub fn deserialize(&self, serialized_mesh: SerializedMesh) -> Mesh {
Mesh {
attributes:
attributes: Some(
serialized_mesh
.attributes
.into_iter()
Expand All @@ -1559,8 +1635,8 @@ impl MeshDeserializer {
};
Some((id, data))
})
.collect(),
indices: serialized_mesh.indices,
.collect()),
indices: Some(serialized_mesh.indices),
..Mesh::new(serialized_mesh.primitive_topology, RenderAssetUsages::default())
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,7 @@ pub fn build_dummy_white_gpu_image(
sampler,
size: image.texture_descriptor.size,
mip_level_count: image.texture_descriptor.mip_level_count,
had_data: true,
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_render/src/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ impl RenderAsset for RenderMesh {
mesh.asset_usage
}

fn take_gpu_data(
source: &mut Self::SourceAsset,
_previous_gpu_asset: Option<&Self>,
) -> Option<Self::SourceAsset> {
source.take_gpu_data()
}

fn byte_len(mesh: &Self::SourceAsset) -> Option<usize> {
let mut vertex_size = 0;
for attribute_data in mesh.attributes() {
Expand Down
Loading