Skip to content

Commit 5f7e43c

Browse files
robtfmjasmine-nominalgreeble-dev
authored
Retain asset without data for RENDER_WORLD-only assets (#21732)
# Objective when `RenderAssets` with `RenderAssetUsages::RENDER_WORLD` and without `RenderAssetUsages::MAIN_WORLD` are extracted, the asset is removed from the assets collection. this causes some issues: - systems which rely on the asset, like picking with meshes, fail with "asset not found" errors which are unintuitive. - loading the asset by path a second time results in the asset being reloaded from storage, re-extracted and re-transferred to gpu, replacing the existing asset - knowledge about the asset state is lost, we cannot tell if an asset is already loaded with `AssetServer::get_handle` - metadata (image size, e.g.) is no longer available for the asset ## Solution ### extraction: - add `take_gpu_data` to the `RenderAsset` trait. use it to pull the data out of the asset for transfer, and leave the empty asset in the collection. default implementation just `clone`s the asset. - if the data has already been taken, ~~panic. this follows from modifying an asset after extraction, which is always a code error, so i think panic here makes sense~~ _log an error_ ### Mesh/RenderMesh: - make `Mesh::attributes` and `Mesh::indices` options - take them on extraction - `expect` operations which access or modify the vertex data or indices if it has been extracted. accessing the vertex data after extraction is always a code error. fixes #19737 by resulting in the error `Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh must have RenderAssetUsages::MAIN_WORLD` - provide `try_xxx` operations which allow users to handle the access error gracefully if required (no usages as part of this pr, but provided for future) - compute the mesh `Aabb` when gpu data is taken and store the result. this allows extracted meshes to still use frustum culling (otherwise using multiple copies of an extracted mesh now panics as `compute_aabb` relied on vertex positions). there's a bit of a tradeoff here: users may not need the Aabb and we needlessly compute it. but i think users almost always do want them, and computing once (for extracted meshes) is cheaper than the alternative, keeping position data and computing a fresh `Aabb` every time the mesh is used on a new entity. ### Image/GpuImage: images are a little more complex because the data can be deliberately `None` for render-targets / GPU-written textures where we only want an uninitialized gpu-side texture. - take `Image::data` on extraction - record on the resulting `GpuImage` whether any data was found initially - on subsequent modifications with no data, panic if there was data previously corner case / issue: when used with `RenderAssetBytesPerFrameLimiter` there may be no previous gpu asset if it is still queued pending upload due to the bandwidth limit. this can result in a modified image with initial data skipping the `had_data` check, resulting in a blank texture. i think this is sufficiently rare that it's not a real problem, users would still hit the panic if the asset is transferred in time and the problem/solution should be clear when they do hit it. ### ShaderStorageBuffer/GpuShaderStorageBuffer follows the same pattern as Image/GpuImage: - take `ShaderStorageBuffer::data` on extraction - record on the resulting `GpuShaderStorageBuffer` whether any data was found initially - on modifications with no data, panic if there was data previously we don't have the queue issue here because `GpuShaderStorageBuffer` doesn't implement `byte_len` so we can't end up queueing them. #### other RenderAssets i didn't modify the other `RenderAsset` types (`GpuAutoExposureCompensationCurve`, `GpuLineGizmo`, `RenderWireframeMaterial`, `PreparedMaterial`, `PreparedMaterial2d`, `PreparedUiMaterial`) on the assumption that ~~cloning these is cheap enough anyway~~ _the asset usages are not exposed so we should never call `take_gpu_data`. the default implementation panics with a message directing users to implement the method if required_ ## Testing only really tested within my work project. i can add some explicit tests if required. --------- Co-authored-by: Jasmine S <[email protected]> Co-authored-by: Greeble <[email protected]>
1 parent 2e9ef69 commit 5f7e43c

File tree

11 files changed

+1155
-128
lines changed

11 files changed

+1155
-128
lines changed

crates/bevy_asset/src/render_asset.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use serde::{Deserialize, Serialize};
44
bitflags::bitflags! {
55
/// Defines where the asset will be used.
66
///
7-
/// If an asset is set to the `RENDER_WORLD` but not the `MAIN_WORLD`, the asset will be
8-
/// unloaded from the asset server once it's been extracted and prepared in the render world.
7+
/// If an asset is set to the `RENDER_WORLD` but not the `MAIN_WORLD`, the asset data (pixel data,
8+
/// mesh vertex data, etc) will be removed from the cpu-side asset once it's been extracted and prepared
9+
/// in the render world. The asset will remain in the assets collection, but with only metadata.
910
///
10-
/// Unloading the asset saves on memory, as for most cases it is no longer necessary to keep
11-
/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you can no longer
12-
/// access the asset from the CPU (via the `Assets<T>` resource) once unloaded (without re-loading it).
11+
/// Unloading the asset data saves on memory, as for most cases it is no longer necessary to keep
12+
/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you cannot access the
13+
/// asset data from the CPU (via the `Assets<T>` resource) once unloaded (without re-loading it).
1314
///
1415
/// If you never need access to the asset from the CPU past the first frame it's loaded on,
1516
/// or only need very infrequent access, then set this to `RENDER_WORLD`. Otherwise, set this to

crates/bevy_camera/src/primitives.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ pub trait MeshAabb {
1818

1919
impl MeshAabb for Mesh {
2020
fn compute_aabb(&self) -> Option<Aabb> {
21-
let Some(VertexAttributeValues::Float32x3(values)) =
22-
self.attribute(Mesh::ATTRIBUTE_POSITION)
21+
if let Some(aabb) = self.final_aabb {
22+
// use precomputed extents
23+
return Some(aabb.into());
24+
}
25+
26+
let Ok(VertexAttributeValues::Float32x3(values)) =
27+
self.try_attribute(Mesh::ATTRIBUTE_POSITION)
2328
else {
2429
return None;
2530
};

crates/bevy_mesh/src/index.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize};
66
use thiserror::Error;
77
use wgpu_types::IndexFormat;
88

9+
use crate::MeshAccessError;
10+
911
/// A disjunction of four iterators. This is necessary to have a well-formed type for the output
1012
/// of [`Mesh::triangles`](super::Mesh::triangles), which produces iterators of four different types depending on the
1113
/// branch taken.
@@ -56,6 +58,8 @@ pub enum MeshWindingInvertError {
5658
/// * [`PrimitiveTopology::LineList`](super::PrimitiveTopology::LineList), but the indices are not in chunks of 2.
5759
#[error("Indices weren't in chunks according to topology")]
5860
AbruptIndicesEnd,
61+
#[error("Mesh access error: {0}")]
62+
MeshAccessError(#[from] MeshAccessError),
5963
}
6064

6165
/// An error that occurred while trying to extract a collection of triangles from a [`Mesh`](super::Mesh).
@@ -64,17 +68,13 @@ pub enum MeshTrianglesError {
6468
#[error("Source mesh does not have primitive topology TriangleList or TriangleStrip")]
6569
WrongTopology,
6670

67-
#[error("Source mesh lacks position data")]
68-
MissingPositions,
69-
7071
#[error("Source mesh position data is not Float32x3")]
7172
PositionsFormat,
7273

73-
#[error("Source mesh lacks face index data")]
74-
MissingIndices,
75-
7674
#[error("Face index data references vertices that do not exist")]
7775
BadIndices,
76+
#[error("mesh access error: {0}")]
77+
MeshAccessError(#[from] MeshAccessError),
7878
}
7979

8080
/// An array of indices into the [`VertexAttributeValues`](super::VertexAttributeValues) for a mesh.

0 commit comments

Comments
 (0)