Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unload render assets from RAM #10520

Merged
merged 48 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e3b4936
Unload render assets from RAM
JMS55 Nov 12, 2023
9e6a6e2
Rename, docs
JMS55 Nov 12, 2023
0e0cf83
Doc clarification
JMS55 Nov 12, 2023
d7796cf
Refactor
JMS55 Nov 12, 2023
bb1b9f1
Merge commit 'ea01c3e387d80994662aa54c2b8f70dd678903c3' into unload_r…
JMS55 Nov 22, 2023
22588ad
Send asset removal event on last strong handle drop, even if the asse…
JMS55 Nov 22, 2023
d28a695
Revert previous dumb commit
JMS55 Nov 22, 2023
3111a57
Add TODO
JMS55 Nov 22, 2023
63055d8
Remove Assets::remove
JMS55 Nov 22, 2023
29b1c71
Clarify doc
JMS55 Nov 22, 2023
5ce91e1
Add NoLongerUsed event
JMS55 Nov 22, 2023
6fe128d
Fix docs
JMS55 Nov 22, 2023
b91ddd7
Fix docs
JMS55 Nov 23, 2023
2d1a5c5
Read asset info for skip
JMS55 Nov 23, 2023
624fe80
Fixes
JMS55 Nov 23, 2023
9d7d16f
Fix tests
JMS55 Nov 23, 2023
1b1eb32
Misc
JMS55 Nov 27, 2023
18ffd40
Fix examples
JMS55 Nov 28, 2023
239b6a3
Ignore clippy
JMS55 Nov 28, 2023
e3de7ce
Merge commit 'a902ea6f85079777e61ea8627d86277c0d9abaf1' into unload_r…
JMS55 Nov 28, 2023
d00e845
Missed a clippy lint
JMS55 Nov 28, 2023
9829eec
Missed more clippy lints again
JMS55 Nov 28, 2023
9b47b6b
Remove unused import
JMS55 Nov 28, 2023
9478355
Fixes
JMS55 Nov 28, 2023
9c0f3bc
Add missed image loader
JMS55 Nov 28, 2023
aa538e2
Fix doc links
JMS55 Nov 28, 2023
19cd2bd
Fix examples
JMS55 Nov 28, 2023
23cf06f
Fix more examples
JMS55 Nov 28, 2023
69061f8
Merge remote-tracking branch 'bevy/gh-readonly-queue/main/pr-10785-0f…
JMS55 Nov 29, 2023
ae035b5
Add image loader persistence setting
JMS55 Dec 3, 2023
03c33de
Fixes
JMS55 Dec 3, 2023
d083e30
Appease clippy
JMS55 Dec 3, 2023
400f324
Merge commit '4d42713e7713bd8abaa3fe3164574b5b6895f40e' into unload_r…
JMS55 Dec 3, 2023
8b863af
Merge commit '11065974d487ca4b9680ae683a9d356f2f1b6c36' into unload_r…
JMS55 Dec 14, 2023
2c9c529
Merge commit '8067e46049f222d37ac394745805bad98979980f' into unload_r…
JMS55 Dec 28, 2023
e1f78c9
Use enum instead of bool
JMS55 Dec 28, 2023
8c92266
Misc changes
JMS55 Dec 28, 2023
6acc91d
Format
JMS55 Dec 28, 2023
6bcc067
Fix example imports
JMS55 Dec 28, 2023
9bb0f46
Fix tests and docs
JMS55 Dec 29, 2023
fb45b81
Rename
JMS55 Dec 30, 2023
ccb8afe
Tweak doc comment
JMS55 Dec 30, 2023
9088603
Merge commit '786abbf3f5e5be4b89c6b53d2d03162079f8e1f4' into unload_r…
JMS55 Dec 30, 2023
ea5af0d
Add missing import
JMS55 Dec 30, 2023
fec04d1
Fix doc
JMS55 Dec 30, 2023
8538833
Rename to Unused
JMS55 Jan 3, 2024
16e320e
Finish rename
JMS55 Jan 3, 2024
6efbb67
Fix another name I missed
JMS55 Jan 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 15 additions & 11 deletions crates/bevy_asset/src/assets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{self as bevy_asset, LoadState};
use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
use crate::{self as bevy_asset};
use crate::{
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, LoadState, UntypedHandle,
};
use bevy_ecs::{
prelude::EventWriter,
system::{Res, ResMut, Resource},
Expand Down Expand Up @@ -484,9 +486,7 @@ impl<A: Asset> Assets<A> {
}

/// A system that synchronizes the state of assets in this collection with the [`AssetServer`]. This manages
/// [`Handle`] drop events and adds queued [`AssetEvent`] values to their [`Events`] resource.
///
/// [`Events`]: bevy_ecs::event::Events
/// [`Handle`] drop events.
pub fn track_assets(mut assets: ResMut<Self>, asset_server: Res<AssetServer>) {
let assets = &mut *assets;
// note that we must hold this lock for the entire duration of this function to ensure
Expand All @@ -496,24 +496,28 @@ impl<A: Asset> Assets<A> {
let mut infos = asset_server.data.infos.write();
let mut not_ready = Vec::new();
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
let id = drop_event.id;
let id = drop_event.id.typed();

assets.queued_events.push(AssetEvent::NoLongerUsed { id });

if drop_event.asset_server_managed {
let untyped = id.untyped(TypeId::of::<A>());
if let Some(info) = infos.get(untyped) {
let untyped_id = drop_event.id.untyped(TypeId::of::<A>());
if let Some(info) = infos.get(untyped_id) {
if info.load_state == LoadState::Loading
|| info.load_state == LoadState::NotLoaded
{
not_ready.push(drop_event);
continue;
}
}
if infos.process_handle_drop(untyped) {
assets.remove_dropped(id.typed());
if infos.process_handle_drop(untyped_id) {
assets.remove_dropped(id);
}
} else {
assets.remove_dropped(id.typed());
assets.remove_dropped(id);
}
}

// TODO: this is _extremely_ inefficient find a better fix
// This will also loop failed assets indefinitely. Is that ok?
for event in not_ready {
Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_asset/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub enum AssetEvent<A: Asset> {
Modified { id: AssetId<A> },
/// Emitted whenever an [`Asset`] is removed.
Removed { id: AssetId<A> },
/// Emitted when the last [`super::Handle::Strong`] of an [`Asset`] is dropped.
NoLongerUsed { id: AssetId<A> },
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
/// Emitted whenever an [`Asset`] has been fully loaded (including its dependencies and all "recursive dependencies").
LoadedWithDependencies { id: AssetId<A> },
}
Expand All @@ -35,6 +37,11 @@ impl<A: Asset> AssetEvent<A> {
pub fn is_removed(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::Removed { id } if *id == asset_id.into())
}

/// Returns `true` if this event is [`AssetEvent::NoLongerUsed`] and matches the given `id`.
pub fn is_no_longer_used(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::NoLongerUsed { id } if *id == asset_id.into())
}
}

impl<A: Asset> Clone for AssetEvent<A> {
Expand All @@ -51,6 +58,7 @@ impl<A: Asset> Debug for AssetEvent<A> {
Self::Added { id } => f.debug_struct("Added").field("id", id).finish(),
Self::Modified { id } => f.debug_struct("Modified").field("id", id).finish(),
Self::Removed { id } => f.debug_struct("Removed").field("id", id).finish(),
Self::NoLongerUsed { id } => f.debug_struct("NoLongerUsed").field("id", id).finish(),
Self::LoadedWithDependencies { id } => f
.debug_struct("LoadedWithDependencies")
.field("id", id)
Expand All @@ -65,6 +73,7 @@ impl<A: Asset> PartialEq for AssetEvent<A> {
(Self::Added { id: l_id }, Self::Added { id: r_id })
| (Self::Modified { id: l_id }, Self::Modified { id: r_id })
| (Self::Removed { id: l_id }, Self::Removed { id: r_id })
| (Self::NoLongerUsed { id: l_id }, Self::NoLongerUsed { id: r_id })
| (
Self::LoadedWithDependencies { id: l_id },
Self::LoadedWithDependencies { id: r_id },
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl std::fmt::Debug for StrongHandle {
#[reflect(Component)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Strong(Arc<StrongHandle>),
/// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
/// nor will it keep assets alive.
Expand Down Expand Up @@ -189,7 +189,7 @@ impl<A: Asset> Handle<A> {

/// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
/// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
/// [`Handle::Weak`].
/// [`Handle::Weak`].
#[inline]
pub fn untyped(self) -> UntypedHandle {
self.into()
Expand Down
18 changes: 16 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub enum AssetMode {
///
/// When developing an app, you should enable the `asset_processor` cargo feature, which will run the asset processor at startup. This should generally
/// be used in combination with the `file_watcher` cargo feature, which enables hot-reloading of assets that have changed. When both features are enabled,
/// changes to "original/source assets" will be detected, the asset will be re-processed, and then the final processed asset will be hot-reloaded in the app.
/// changes to "original/source assets" will be detected, the asset will be re-processed, and then the final processed asset will be hot-reloaded in the app.
///
/// [`AssetMeta`]: meta::AssetMeta
/// [`AssetSource`]: io::AssetSource
Expand Down Expand Up @@ -872,13 +872,23 @@ mod tests {
id: id_results.d_id,
},
AssetEvent::Modified { id: a_id },
AssetEvent::NoLongerUsed { id: a_id },
AssetEvent::Removed { id: a_id },
AssetEvent::NoLongerUsed {
id: id_results.b_id,
},
AssetEvent::Removed {
id: id_results.b_id,
},
AssetEvent::NoLongerUsed {
id: id_results.c_id,
},
AssetEvent::Removed {
id: id_results.c_id,
},
AssetEvent::NoLongerUsed {
id: id_results.d_id,
},
AssetEvent::Removed {
id: id_results.d_id,
},
Expand Down Expand Up @@ -1062,7 +1072,11 @@ mod tests {
// remove event is emitted
app.update();
let events = std::mem::take(&mut app.world.resource_mut::<StoredEvents>().0);
let expected_events = vec![AssetEvent::Added { id }, AssetEvent::Removed { id }];
let expected_events = vec![
AssetEvent::Added { id },
AssetEvent::NoLongerUsed { id },
AssetEvent::Removed { id },
];
assert_eq!(events, expected_events);

let dep_handle = app.world.resource::<AssetServer>().load(dep_path);
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_core_pipeline/src/tonemapping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_reflect::Reflect;
use bevy_render::camera::Camera;
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
use bevy_render::extract_resource::{ExtractResource, ExtractResourcePlugin};
use bevy_render::render_asset::RenderAssets;
use bevy_render::render_asset::{RenderAssetPersistencePolicy, RenderAssets};
use bevy_render::render_resource::binding_types::{
sampler, texture_2d, texture_3d, uniform_buffer,
};
Expand Down Expand Up @@ -356,6 +356,7 @@ fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image {
CompressedImageFormats::NONE,
false,
image_sampler,
RenderAssetPersistencePolicy::Unload,
)
.unwrap()
}
Expand All @@ -381,5 +382,6 @@ pub fn lut_placeholder() -> Image {
},
sampler: ImageSampler::Default,
texture_view_descriptor: None,
cpu_persistent_access: RenderAssetPersistencePolicy::Unload,
}
}
24 changes: 12 additions & 12 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ use bevy_render::{
color::Color,
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
primitives::Aabb,
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_asset::{
PrepareAssetError, RenderAsset, RenderAssetPersistencePolicy, RenderAssetPlugin,
RenderAssets,
},
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
Expand Down Expand Up @@ -365,28 +368,25 @@ struct GpuLineGizmo {
}

impl RenderAsset for LineGizmo {
type ExtractedAsset = LineGizmo;

type PreparedAsset = GpuLineGizmo;

type Param = SRes<RenderDevice>;

fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
fn persistence_policy(&self) -> RenderAssetPersistencePolicy {
RenderAssetPersistencePolicy::Unload
}

fn prepare_asset(
line_gizmo: Self::ExtractedAsset,
self,
render_device: &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let position_buffer_data = cast_slice(&line_gizmo.positions);
) -> Result<Self::PreparedAsset, PrepareAssetError<Self>> {
let position_buffer_data = cast_slice(&self.positions);
let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Position Buffer"),
contents: position_buffer_data,
});

let color_buffer_data = cast_slice(&line_gizmo.colors);
let color_buffer_data = cast_slice(&self.colors);
let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Color Buffer"),
Expand All @@ -396,8 +396,8 @@ impl RenderAsset for LineGizmo {
Ok(GpuLineGizmo {
position_buffer,
color_buffer,
vertex_count: line_gizmo.positions.len() as u32,
strip: line_gizmo.strip,
vertex_count: self.positions.len() as u32,
strip: self.strip,
})
}
}
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use bevy_render::{
},
prelude::SpatialBundle,
primitives::Aabb,
render_asset::RenderAssetPersistencePolicy,
render_resource::{Face, PrimitiveTopology},
texture::{
CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings,
Expand Down Expand Up @@ -120,7 +121,7 @@ pub struct GltfLoader {
/// |s: &mut GltfLoaderSettings| {
/// s.load_cameras = false;
/// }
/// );
/// );
/// ```
#[derive(Serialize, Deserialize)]
pub struct GltfLoaderSettings {
Expand Down Expand Up @@ -390,7 +391,7 @@ async fn load_gltf<'a, 'b, 'c>(
let primitive_label = primitive_label(&gltf_mesh, &primitive);
let primitive_topology = get_primitive_topology(primitive.mode())?;

let mut mesh = Mesh::new(primitive_topology);
let mut mesh = Mesh::new(primitive_topology, RenderAssetPersistencePolicy::Unload);

// Read vertex attributes
for (semantic, accessor) in primitive.attributes() {
Expand Down Expand Up @@ -434,6 +435,7 @@ async fn load_gltf<'a, 'b, 'c>(
let morph_target_image = MorphTargetImage::new(
morph_target_reader.map(PrimitiveMorphAttributesIter),
mesh.count_vertices(),
RenderAssetPersistencePolicy::Unload,
)?;
let handle =
load_context.add_labeled_asset(morph_targets_label, morph_target_image.0);
Expand Down Expand Up @@ -725,6 +727,7 @@ async fn load_image<'a, 'b>(
supported_compressed_formats,
is_srgb,
ImageSampler::Descriptor(sampler_descriptor),
RenderAssetPersistencePolicy::Unload,
)?;
Ok(ImageOrPath::Image {
image,
Expand All @@ -746,6 +749,7 @@ async fn load_image<'a, 'b>(
supported_compressed_formats,
is_srgb,
ImageSampler::Descriptor(sampler_descriptor),
RenderAssetPersistencePolicy::Unload,
)?,
label: texture_label(&gltf_texture),
})
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ pub fn extract_materials<M: Material>(
let mut changed_assets = HashSet::default();
let mut removed = Vec::new();
for event in events.read() {
#[allow(clippy::match_same_arms)]
match event {
AssetEvent::Added { id } | AssetEvent::Modified { id } => {
changed_assets.insert(*id);
Expand All @@ -817,6 +818,7 @@ pub fn extract_materials<M: Material>(
changed_assets.remove(id);
removed.push(*id);
}
AssetEvent::NoLongerUsed { .. } => {}
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
AssetEvent::NoLongerUsed { .. } => {}
AssetEvent::NoLongerUsed { .. } |

(and remove the lint override)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did it that way because we're eventually going to want to handle LoadedWithDependencies differently, but only for now will it have the same match arm..

AssetEvent::LoadedWithDependencies { .. } => {
// TODO: handle this
}
Expand Down