mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
Add tools to avoid unnecessary AssetEvent::Modified events that lead to rendering performance costs (#16751) (#22460)
# Objective - Fixes #16751 ## Solution - `Assets::get_mut` now returns a wrapper `AssetMut` type instead of `&mut impl Asset`. - `AssetMut` implements `Deref` and `DerefMut`. - `DerefMut` marks assets as changed. - when dropped `AssetMut` will add `AssetEvent::Modified` event to a queue only in case asset was marked as changed. ## Testing - Did you test these changes? If so, how? - No unit tests were added, change is pretty straightforward. - Test project: https://github.com/MatrixDev/bevy-feature-16751-test. - With change: ~100 fps. - Without change: ~15 fps. - Are there any parts that need more testing? - I don't really see how this can break anything or add a measurable overhead. - `AssetEvent::Modified` will now be sent after the asset was modified instead of before. It should not affect anything but still worth noting. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Have a big amount of entities that constantly update their materials. - Properties of those materials should be animated in a stepped maned (like changing color every 0.1 seconds). - Update material only if value has actually changed: ```rust if material.base_color != new_color { material.base_color = new_color; } ``` - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - tested on macos (Mackbook M1) - not a platform-specific issue PS: This is my first PR, so please don’t judge.
This commit is contained in:
committed by
GitHub
parent
c062d92353
commit
9fd2637846
@@ -363,7 +363,7 @@ mod tests {
|
||||
.iter()
|
||||
.find_map(|(h, a)| (a.0 == i).then_some(h))
|
||||
.unwrap();
|
||||
let asset = assets.get_mut(id).unwrap();
|
||||
let mut asset = assets.get_mut(id).unwrap();
|
||||
println!("setting new value for {}", asset.0);
|
||||
asset.1 = "new_value";
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ use bevy_ecs::{
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{Reflect, TypePath};
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -348,7 +349,7 @@ impl<A: Asset> Assets<A> {
|
||||
&mut self,
|
||||
id: impl Into<AssetId<A>>,
|
||||
insert_fn: impl FnOnce() -> A,
|
||||
) -> Result<&mut A, InvalidGenerationError> {
|
||||
) -> Result<AssetMut<'_, A>, InvalidGenerationError> {
|
||||
let id: AssetId<A> = id.into();
|
||||
if self.get(id).is_none() {
|
||||
self.insert(id, insert_fn())?;
|
||||
@@ -436,16 +437,20 @@ impl<A: Asset> Assets<A> {
|
||||
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
|
||||
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
|
||||
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<AssetMut<'_, A>> {
|
||||
let id: AssetId<A> = id.into();
|
||||
let result = match id {
|
||||
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
|
||||
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
|
||||
};
|
||||
if result.is_some() {
|
||||
self.queued_events.push(AssetEvent::Modified { id });
|
||||
}
|
||||
result
|
||||
Some(AssetMut {
|
||||
asset: result?,
|
||||
guard: AssetMutChangeNotifier {
|
||||
changed: false,
|
||||
asset_id: id,
|
||||
queued_events: &mut self.queued_events,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
|
||||
@@ -618,6 +623,73 @@ impl<A: Asset> Assets<A> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique mutable borrow of an asset.
|
||||
///
|
||||
/// [`AssetEvent::Modified`] events will be only triggered if an asset itself is mutably borrowed.
|
||||
///
|
||||
/// Just as an example, this allows checking if a material property has changed
|
||||
/// before modifying it to avoid unnecessary material extraction down the pipeline.
|
||||
pub struct AssetMut<'a, A: Asset> {
|
||||
asset: &'a mut A,
|
||||
guard: AssetMutChangeNotifier<'a, A>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> AssetMut<'a, A> {
|
||||
/// Marks with inner asset as modified and returns reference to it.
|
||||
pub fn into_inner(mut self) -> &'a mut A {
|
||||
self.guard.changed = true;
|
||||
self.asset
|
||||
}
|
||||
|
||||
/// Returns reference to the inner asset but doesn't mark it as modified.
|
||||
pub fn into_inner_untracked(self) -> &'a mut A {
|
||||
self.asset
|
||||
}
|
||||
|
||||
/// Manually bypasses change detection, allowing you to mutate the underlying value
|
||||
/// without emitting [`AssetEvent::Modified`] event.
|
||||
///
|
||||
/// # Warning
|
||||
/// This is a risky operation, that can have unexpected consequences on any system relying on this code.
|
||||
/// However, it can be an essential escape hatch when, for example,
|
||||
/// you are trying to synchronize representations using change detection and need to avoid infinite recursion.
|
||||
pub fn bypass_change_detection(&mut self) -> &mut A {
|
||||
self.asset
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Deref for AssetMut<'a, A> {
|
||||
type Target = A;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.asset
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> DerefMut for AssetMut<'a, A> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.guard.changed = true;
|
||||
self.asset
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to allow safe destructuring of the [`AssetMut::into_inner`]
|
||||
/// while also keeping strong change tracking guarantees.
|
||||
struct AssetMutChangeNotifier<'a, A: Asset> {
|
||||
changed: bool,
|
||||
asset_id: AssetId<A>,
|
||||
queued_events: &'a mut Vec<AssetEvent<A>>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Drop for AssetMutChangeNotifier<'a, A> {
|
||||
fn drop(&mut self) {
|
||||
if self.changed {
|
||||
self.queued_events
|
||||
.push(AssetEvent::Modified { id: self.asset_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable iterator over [`Assets`].
|
||||
pub struct AssetsMutIterator<'a, A: Asset> {
|
||||
queued_events: &'a mut Vec<AssetEvent<A>>,
|
||||
@@ -673,7 +745,10 @@ pub enum InvalidGenerationError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::AssetIndex;
|
||||
use crate::tests::create_app;
|
||||
use crate::{Asset, AssetApp, AssetEvent, AssetIndex, Assets};
|
||||
use bevy_ecs::prelude::Messages;
|
||||
use bevy_reflect::TypePath;
|
||||
|
||||
#[test]
|
||||
fn asset_index_round_trip() {
|
||||
@@ -684,4 +759,64 @@ mod test {
|
||||
let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
|
||||
assert_eq!(asset_index, roundtripped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assets_mut_change_detection() {
|
||||
#[derive(Asset, TypePath, Default)]
|
||||
struct TestAsset {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
let mut app = create_app().0;
|
||||
app.init_asset::<TestAsset>();
|
||||
|
||||
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
|
||||
let my_asset_handle = assets.add(TestAsset::default());
|
||||
let my_asset_id = my_asset_handle.id();
|
||||
|
||||
// check a few times just in case there are some unexpected leftover events from previous runs
|
||||
for _ in 0..3 {
|
||||
// check that modifying the asset value triggers an event
|
||||
{
|
||||
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
|
||||
let mut asset = assets.get_mut(my_asset_id).unwrap();
|
||||
asset.value += 1;
|
||||
}
|
||||
|
||||
app.update();
|
||||
|
||||
let modified_count = app
|
||||
.world_mut()
|
||||
.resource_mut::<Messages<AssetEvent<TestAsset>>>()
|
||||
.drain()
|
||||
.filter(|event| event.is_modified(my_asset_id))
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
modified_count, 1,
|
||||
"Asset value was changed but AssetEvent::Modified was not triggered",
|
||||
);
|
||||
|
||||
// check that reading the asset value doesn't trigger an event
|
||||
{
|
||||
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
|
||||
let asset = assets.get_mut(my_asset_id).unwrap();
|
||||
let _temp = asset.value;
|
||||
}
|
||||
|
||||
app.update();
|
||||
|
||||
let modified_count = app
|
||||
.world_mut()
|
||||
.resource_mut::<Messages<AssetEvent<TestAsset>>>()
|
||||
.drain()
|
||||
.filter(|event| event.is_modified(my_asset_id))
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
modified_count, 0,
|
||||
"Asset value was not changed but AssetEvent::Modified was triggered",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1227,7 +1227,7 @@ mod tests {
|
||||
|
||||
{
|
||||
let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
|
||||
let a = texts.get_mut(a_id).unwrap();
|
||||
let mut a = texts.get_mut(a_id).unwrap();
|
||||
a.text = "Changed".to_string();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
|
||||
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
|
||||
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
|
||||
let asset = assets.get_mut(asset_id.typed_debug_checked());
|
||||
asset.map(|asset| asset as &mut dyn Reflect)
|
||||
asset.map(|asset| asset.into_inner() as &mut dyn Reflect)
|
||||
},
|
||||
add: |world, value| {
|
||||
let mut assets = world.resource_mut::<Assets<A>>();
|
||||
|
||||
@@ -114,7 +114,7 @@ fn update_frame_time_values(
|
||||
.map(|x| *x as f32 / 1000.0)
|
||||
.collect::<Vec<_>>();
|
||||
for (_, material) in frame_time_graph_materials.iter_mut() {
|
||||
let buffer = buffers.get_mut(&material.values).unwrap();
|
||||
let mut buffer = buffers.get_mut(&material.values).unwrap();
|
||||
|
||||
buffer.set_data(frame_times.clone());
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ fn update_plane_color(
|
||||
|
||||
if let Ok(material_node) = q_material_node.get(*inner_ent) {
|
||||
// Node component exists, update it
|
||||
if let Some(material) = r_materials.get_mut(material_node.id()) {
|
||||
if let Some(mut material) = r_materials.get_mut(material_node.id()) {
|
||||
// Update properties
|
||||
material.plane = *plane;
|
||||
material.fixed_channel = plane_value.0.z;
|
||||
|
||||
@@ -287,7 +287,7 @@ fn update_gizmo_meshes<Config: GizmoConfigGroup>(
|
||||
handles.handles.insert(TypeId::of::<Config>(), None);
|
||||
} else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
|
||||
if let Some(handle) = handle {
|
||||
let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
|
||||
let mut gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
|
||||
|
||||
gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
|
||||
gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
|
||||
|
||||
@@ -558,7 +558,7 @@ fn global_color_changed(
|
||||
mut materials: ResMut<Assets<WireframeMaterial>>,
|
||||
global_material: Res<GlobalWireframeMaterial>,
|
||||
) {
|
||||
if let Some(global_material) = materials.get_mut(&global_material.handle) {
|
||||
if let Some(mut global_material) = materials.get_mut(&global_material.handle) {
|
||||
global_material.color = config.default_color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,9 +433,9 @@ mod tests {
|
||||
|
||||
let mut fonts = world.resource_mut::<Assets<Font>>();
|
||||
|
||||
let font = fonts.get_mut(bevy_asset::AssetId::default()).unwrap();
|
||||
let mut font = fonts.get_mut(bevy_asset::AssetId::default()).unwrap();
|
||||
font.family_name = "Fira Mono".into();
|
||||
let data = font.data.as_ref().clone();
|
||||
let data = font.into_inner().data.as_ref().clone();
|
||||
|
||||
app.world_mut()
|
||||
.resource_mut::<CosmicFontSystem>()
|
||||
|
||||
@@ -546,7 +546,7 @@ fn global_color_changed(
|
||||
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
|
||||
global_material: Res<GlobalWireframeMaterial>,
|
||||
) {
|
||||
if let Some(global_material) = materials.get_mut(&global_material.handle) {
|
||||
if let Some(mut global_material) = materials.get_mut(&global_material.handle) {
|
||||
global_material.color = config.default_color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ pub fn update_tilemap_chunk_indices(
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(tile_data_image) = images.get_mut(&material.tile_data) else {
|
||||
let Some(mut tile_data_image) = images.get_mut(&material.tile_data) else {
|
||||
warn!(
|
||||
"TilemapChunkMaterial tile data image not found for tilemap chunk {}",
|
||||
chunk_entity
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn load_font_assets_into_fontdb_system(
|
||||
let font_system = &mut cosmic_font_system.0;
|
||||
for event in events.read() {
|
||||
if let AssetEvent::Added { id } = event
|
||||
&& let Some(font) = fonts.get_mut(*id)
|
||||
&& let Some(mut font) = fonts.get_mut(*id)
|
||||
{
|
||||
let data = Arc::clone(&font.data);
|
||||
font.ids = font_system
|
||||
|
||||
@@ -87,17 +87,18 @@ impl FontAtlas {
|
||||
texture: &Image,
|
||||
offset: IVec2,
|
||||
) -> Result<(), TextError> {
|
||||
let atlas_layout = atlas_layouts
|
||||
let mut atlas_layout = atlas_layouts
|
||||
.get_mut(&self.texture_atlas)
|
||||
.ok_or(TextError::MissingAtlasLayout)?;
|
||||
let atlas_texture = textures
|
||||
let mut atlas_texture = textures
|
||||
.get_mut(&self.texture)
|
||||
.ok_or(TextError::MissingAtlasTexture)?;
|
||||
|
||||
if let Ok(glyph_index) =
|
||||
self.dynamic_texture_atlas_builder
|
||||
.add_texture(atlas_layout, texture, atlas_texture)
|
||||
{
|
||||
if let Ok(glyph_index) = self.dynamic_texture_atlas_builder.add_texture(
|
||||
&mut atlas_layout,
|
||||
texture,
|
||||
&mut atlas_texture,
|
||||
) {
|
||||
self.glyph_to_atlas_index.insert(
|
||||
cache_key,
|
||||
GlyphAtlasLocation {
|
||||
|
||||
@@ -107,7 +107,7 @@ fn draw(
|
||||
}
|
||||
|
||||
// Get the image from Bevy's asset storage.
|
||||
let image = images.get_mut(&my_handle.0).expect("Image not found");
|
||||
let mut image = images.get_mut(&my_handle.0).expect("Image not found");
|
||||
|
||||
// Compute the position of the pixel to draw.
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ fn create_texture_atlas(
|
||||
let texture = textures.add(texture);
|
||||
|
||||
// Update the sampling settings of the texture atlas
|
||||
let image = textures.get_mut(&texture).unwrap();
|
||||
let mut image = textures.get_mut(&texture).unwrap();
|
||||
image.sampler = sampling.unwrap_or_default();
|
||||
|
||||
(texture_atlas_layout, texture_atlas_sources, texture)
|
||||
|
||||
@@ -50,7 +50,7 @@ fn animate_materials(
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
for material_handle in material_handles.iter() {
|
||||
if let Some(material) = materials.get_mut(material_handle)
|
||||
if let Some(mut material) = materials.get_mut(material_handle)
|
||||
&& let Color::Hsla(ref mut hsla) = material.base_color
|
||||
{
|
||||
*hsla = hsla.rotate_hue(time.delta_secs() * 100.0);
|
||||
|
||||
@@ -278,7 +278,7 @@ fn example_control_system(
|
||||
let randomize_colors = input.just_pressed(KeyCode::KeyC);
|
||||
|
||||
for (material_handle, controls) in &controllable {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
|
||||
if controls.color && randomize_colors {
|
||||
material.base_color = Srgba {
|
||||
|
||||
@@ -78,7 +78,7 @@ fn input_handler(
|
||||
if keyboard_input.just_pressed(KeyCode::Space) {
|
||||
let mesh_handle = mesh_query.single().expect("Query not successful");
|
||||
let mesh = meshes.get_mut(mesh_handle).unwrap();
|
||||
toggle_texture(mesh);
|
||||
toggle_texture(mesh.into_inner());
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyX) {
|
||||
for mut transform in &mut query {
|
||||
|
||||
@@ -371,7 +371,7 @@ impl FromWorld for Cubemaps {
|
||||
}
|
||||
|
||||
fn setup_environment_map_usage(cubemaps: Res<Cubemaps>, mut images: ResMut<Assets<Image>>) {
|
||||
if let Some(image) = images.get_mut(&cubemaps.specular_environment_map)
|
||||
if let Some(mut image) = images.get_mut(&cubemaps.specular_environment_map)
|
||||
&& !image
|
||||
.texture_descriptor
|
||||
.usage
|
||||
@@ -419,7 +419,7 @@ fn change_sphere_roughness(
|
||||
|
||||
// Update the sphere material
|
||||
for material_handle in sphere_query.iter() {
|
||||
if let Some(material) = materials.get_mut(&material_handle.0) {
|
||||
if let Some(mut material) = materials.get_mut(&material_handle.0) {
|
||||
material.perceptual_roughness = app_status.sphere_roughness;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,12 +153,13 @@ fn asset_loaded(
|
||||
) {
|
||||
if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle).is_loaded() {
|
||||
info!("Swapping to {}...", CUBEMAPS[cubemap.index].0);
|
||||
let image = images.get_mut(&cubemap.image_handle).unwrap();
|
||||
let mut image = images.get_mut(&cubemap.image_handle).unwrap();
|
||||
// NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture,
|
||||
// so they appear as one texture. The following code reconfigures the texture as necessary.
|
||||
if image.texture_descriptor.array_layer_count() == 1 {
|
||||
let layers = image.height() / image.width();
|
||||
image
|
||||
.reinterpret_stacked_2d_as_array(image.height() / image.width())
|
||||
.reinterpret_stacked_2d_as_array(layers)
|
||||
.expect("asset should be 2d texture and height will always be evenly divisible with the given layers");
|
||||
image.texture_view_descriptor = Some(TextureViewDescriptor {
|
||||
dimension: Some(TextureViewDimension::Cube),
|
||||
|
||||
@@ -390,7 +390,7 @@ fn add_raytracing_meshes_on_scene_load(
|
||||
.insert(RaytracingMesh3d(mesh_handle.clone()));
|
||||
|
||||
// Ensure meshes are Solari compatible
|
||||
let mesh = meshes.get_mut(mesh_handle).unwrap();
|
||||
let mut mesh = meshes.get_mut(mesh_handle).unwrap();
|
||||
if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) {
|
||||
let vertex_count = mesh.count_vertices();
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]);
|
||||
@@ -413,11 +413,11 @@ fn add_raytracing_meshes_on_scene_load(
|
||||
|
||||
// Adjust scene materials to better demo Solari features
|
||||
if material_name.map(|s| s.0.as_str()) == Some("material") {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
material.emissive = LinearRgba::BLACK;
|
||||
}
|
||||
if material_name.map(|s| s.0.as_str()) == Some("Lights") {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
material.emissive =
|
||||
LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
|
||||
material.alpha_mode = AlphaMode::Opaque;
|
||||
@@ -426,7 +426,7 @@ fn add_raytracing_meshes_on_scene_load(
|
||||
commands.insert_resource(RobotLightMaterial(material_handle.clone()));
|
||||
}
|
||||
if material_name.map(|s| s.0.as_str()) == Some("Glass_Dark_01") {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
material.alpha_mode = AlphaMode::Opaque;
|
||||
material.specular_transmission = 0.0;
|
||||
}
|
||||
@@ -473,7 +473,7 @@ fn toggle_lights(
|
||||
if key_input.just_pressed(KeyCode::Digit2)
|
||||
&& let Some(robot_light_material) = robot_light_material
|
||||
{
|
||||
let material = materials.get_mut(&robot_light_material.0).unwrap();
|
||||
let mut material = materials.get_mut(&robot_light_material.0).unwrap();
|
||||
if material.emissive == LinearRgba::BLACK {
|
||||
material.emissive = LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
|
||||
} else {
|
||||
|
||||
@@ -153,7 +153,7 @@ fn shift_hue(
|
||||
app_status.hue += HUE_SHIFT_SPEED;
|
||||
|
||||
for material_handle in objects_with_materials.iter() {
|
||||
let Some(material) = standard_materials.get_mut(material_handle) else {
|
||||
let Some(mut material) = standard_materials.get_mut(material_handle) else {
|
||||
continue;
|
||||
};
|
||||
material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0);
|
||||
@@ -192,7 +192,7 @@ fn toggle_specular_map(
|
||||
};
|
||||
|
||||
for material_handle in objects_with_materials.iter() {
|
||||
let Some(material) = standard_materials.get_mut(material_handle) else {
|
||||
let Some(mut material) = standard_materials.get_mut(material_handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ fn drag_drop_image(
|
||||
};
|
||||
|
||||
for mat_h in &image_mat {
|
||||
if let Some(mat) = materials.get_mut(mat_h) {
|
||||
if let Some(mut mat) = materials.get_mut(mat_h) {
|
||||
mat.base_color_texture = Some(new_image.clone());
|
||||
|
||||
// Despawn the image viewer instructions
|
||||
|
||||
@@ -431,7 +431,7 @@ fn example_control_system(
|
||||
let randomize_colors = input.just_pressed(KeyCode::KeyC);
|
||||
|
||||
for (material_handle, controls) in &controllable {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
if controls.specular_transmission {
|
||||
material.specular_transmission = state.specular_transmission;
|
||||
material.thickness = state.thickness;
|
||||
|
||||
@@ -155,7 +155,7 @@ fn setup_scene_once_loaded(
|
||||
AnimationNodeType::Clip(handle) => clips.get_mut(handle),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
clip.unwrap()
|
||||
clip.unwrap().into_inner()
|
||||
}
|
||||
|
||||
for (entity, mut player) in &mut players {
|
||||
|
||||
@@ -433,7 +433,7 @@ fn handle_button_toggles(
|
||||
// iterate just for clarity's sake.)
|
||||
for animation_graph_handle in animation_players.iter_mut() {
|
||||
// The animation graph needs to have loaded.
|
||||
let Some(animation_graph) = animation_graphs.get_mut(animation_graph_handle) else {
|
||||
let Some(mut animation_graph) = animation_graphs.get_mut(animation_graph_handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ fn update(
|
||||
if !image_data.is_empty() {
|
||||
for image in images_to_save.iter() {
|
||||
// Fill correct data from channel to image
|
||||
let img_bytes = images.get_mut(image.id()).unwrap();
|
||||
let mut img_bytes = images.get_mut(image.id()).unwrap();
|
||||
|
||||
// We need to ensure that this works regardless of the image dimensions
|
||||
// If the image became wider when copying from the texture to the buffer,
|
||||
|
||||
@@ -174,7 +174,7 @@ fn alter_mesh(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
// Obtain a mutable reference to the Mesh asset.
|
||||
let Some(mesh) = meshes.get_mut(*left_shape) else {
|
||||
let Some(mut mesh) = meshes.get_mut(*left_shape) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ fn alter_handle(
|
||||
|
||||
fn alter_asset(mut images: ResMut<Assets<Image>>, left_bird: Single<&Sprite, With<Left>>) {
|
||||
// Obtain a mutable reference to the Image asset.
|
||||
let Some(image) = images.get_mut(&left_bird.image) else {
|
||||
let Some(mut image) = images.get_mut(&left_bird.image) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ fn change_material(
|
||||
continue;
|
||||
};
|
||||
// Get the material of the descendant
|
||||
let Some(material) = asset_materials.get_mut(id.id()) else {
|
||||
let Some(material) = asset_materials.get(id.id()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ fn find_top_material_and_mesh(
|
||||
for (mat_handle, mesh_handle, name) in mat_query.iter() {
|
||||
// locate a material by material name
|
||||
if name.0 == "Top" {
|
||||
if let Some(material) = materials.get_mut(mat_handle) {
|
||||
if let Some(mut material) = materials.get_mut(mat_handle) {
|
||||
if let Color::Hsla(ref mut hsla) = material.base_color {
|
||||
*hsla = hsla.rotate_hue(time.delta_secs() * 100.0);
|
||||
} else {
|
||||
@@ -34,7 +34,7 @@ fn find_top_material_and_mesh(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mesh) = meshes.get_mut(mesh_handle)
|
||||
if let Some(mut mesh) = meshes.get_mut(mesh_handle)
|
||||
&& let Some(VertexAttributeValues::Float32x3(positions)) =
|
||||
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
|
||||
{
|
||||
|
||||
@@ -349,7 +349,7 @@ pub fn proc_scene(
|
||||
for entity in children.iter_descendants(scene_ready.entity) {
|
||||
// Sponza needs flipped normals
|
||||
if let Ok(mat_h) = has_std_mat.get(entity)
|
||||
&& let Some(mat) = materials.get_mut(mat_h)
|
||||
&& let Some(mut mat) = materials.get_mut(mat_h)
|
||||
{
|
||||
mat.flip_normal_map_y = true;
|
||||
match mat.alpha_mode {
|
||||
|
||||
@@ -271,7 +271,7 @@ pub fn generate_mipmaps<M: Material + GetImages>(
|
||||
material_handles.push(*material_h);
|
||||
continue; //There is already a task for this image
|
||||
}
|
||||
if let Some(image) = images.get_mut(image_h) {
|
||||
if let Some(mut image) = images.get_mut(image_h) {
|
||||
let mut descriptor = match image.sampler.clone() {
|
||||
ImageSampler::Default => default_sampler.0.clone(),
|
||||
ImageSampler::Descriptor(descriptor) => descriptor,
|
||||
@@ -279,7 +279,7 @@ pub fn generate_mipmaps<M: Material + GetImages>(
|
||||
descriptor.anisotropy_clamp = settings.anisotropic_filtering;
|
||||
image.sampler = ImageSampler::Descriptor(descriptor);
|
||||
if image.texture_descriptor.mip_level_count == 1
|
||||
&& check_image_compatible(image).is_ok()
|
||||
&& check_image_compatible(&image).is_ok()
|
||||
{
|
||||
let mut image = image.clone();
|
||||
let settings = settings.clone();
|
||||
@@ -313,7 +313,7 @@ pub fn generate_mipmaps<M: Material + GetImages>(
|
||||
tasks.retain(|image_h, (task, material_handles)| {
|
||||
match future::block_on(future::poll_once(task)) {
|
||||
Some(task_data) => {
|
||||
if let Some(image) = images.get_mut(image_h) {
|
||||
if let Some(mut image) = images.get_mut(image_h) {
|
||||
*image = task_data.image;
|
||||
progress.processed += 1;
|
||||
let prev_cached_data_gb = bytes_to_gb(progress.cached_data_size_bytes);
|
||||
|
||||
@@ -77,7 +77,7 @@ fn update(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
for (material, mut transform) in query.iter_mut() {
|
||||
let material = materials.get_mut(material).unwrap();
|
||||
let mut material = materials.get_mut(material).unwrap();
|
||||
material.time.x = time.elapsed_secs();
|
||||
if keys.just_pressed(KeyCode::Space) {
|
||||
material.party_mode = !material.party_mode;
|
||||
|
||||
@@ -233,7 +233,7 @@ fn toggle_prepass_view(
|
||||
color.0 = Color::WHITE;
|
||||
});
|
||||
|
||||
let mat = materials.get_mut(*material_handle).unwrap();
|
||||
let mut mat = materials.get_mut(*material_handle).unwrap();
|
||||
mat.settings.show_depth = (*prepass_view == 1) as u32;
|
||||
mat.settings.show_normals = (*prepass_view == 2) as u32;
|
||||
mat.settings.show_motion_vectors = (*prepass_view == 3) as u32;
|
||||
|
||||
@@ -73,7 +73,7 @@ fn update(
|
||||
) {
|
||||
let material = materials.get_mut(&material_handles.0).unwrap();
|
||||
|
||||
let buffer = buffers.get_mut(&material.colors).unwrap();
|
||||
let mut buffer = buffers.get_mut(&material.colors).unwrap();
|
||||
buffer.set_data(
|
||||
(0..5)
|
||||
.map(|i| {
|
||||
|
||||
@@ -92,7 +92,7 @@ fn animate_materials(
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
for (i, material_handle) in material_handles.iter().enumerate() {
|
||||
if let Some(material) = materials.get_mut(material_handle) {
|
||||
if let Some(mut material) = materials.get_mut(material_handle) {
|
||||
let color = Color::hsl(
|
||||
((i as f32 * 2.345 + time.elapsed_secs()) * 100.0) % 360.0,
|
||||
1.0,
|
||||
|
||||
@@ -132,7 +132,7 @@ fn scene_load_check(
|
||||
scene_handle.scene_index
|
||||
)
|
||||
});
|
||||
let scene = scenes.get_mut(gltf_scene_handle).unwrap();
|
||||
let mut scene = scenes.get_mut(gltf_scene_handle).unwrap();
|
||||
|
||||
let mut query = scene
|
||||
.world
|
||||
|
||||
@@ -94,7 +94,7 @@ fn animate(
|
||||
) {
|
||||
let duration = 2.0;
|
||||
for handle in &q {
|
||||
if let Some(material) = materials.get_mut(handle) {
|
||||
if let Some(mut material) = materials.get_mut(handle) {
|
||||
// rainbow color effect
|
||||
let new_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 1., 0.5);
|
||||
let border_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 0.75, 0.75);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Avoiding unnecessary `AssetEvent::Modified` events that lead to rendering performance costs"
|
||||
pull_requests: [22460]
|
||||
---
|
||||
|
||||
`Assets::get_mut` will now return `AssetMut<A: Asset>` instead of `&mut Asset`.
|
||||
Similar to `Mut`/`ResMut`, new implementation will trigger `AssetEvent::Modified`
|
||||
event only when the asset is actually mutated.
|
||||
|
||||
In some cases (like materials), triggering `AssetEvent::Modified` event might lead to
|
||||
measurable performance costs. To avoid this, it is now possible to check if the `Asset`
|
||||
will change before mutating it:
|
||||
|
||||
```rust
|
||||
fn update(
|
||||
query: Query<MeshMaterial3d<StandardMaterial>>,
|
||||
materials: ResMut<Assets<StandardMaterial>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for material_handle in query.iter_mut() {
|
||||
// material variable now needs to be marked as mut
|
||||
let Some(mut material) = materials.get_mut(material_handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let new_color = compute_new_color(&time);
|
||||
if material.base_color != new_color {
|
||||
// material will be marked as changed and extracted down the line
|
||||
// only if the color has actually changed
|
||||
material.base_color = new_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user