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

adopted: Track changed Image assets and use them to update image caches #547

Merged
merged 7 commits into from
Aug 1, 2024
Merged
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
6 changes: 0 additions & 6 deletions examples/hexagon_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ pub struct TileHandleHexRow(Handle<Image>);
#[derive(Deref, Resource)]
pub struct TileHandleHexCol(Handle<Image>);

#[derive(Deref, Resource)]
pub struct TileHandleSquare(Handle<Image>);

#[derive(Deref, Resource)]
pub struct TileHandleIso(Handle<Image>);

#[derive(Deref, Resource)]
pub struct FontHandle(Handle<Font>);

Expand Down
57 changes: 32 additions & 25 deletions src/render/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use super::{
pipeline::{TilemapPipeline, TilemapPipelineKey},
prepare,
queue::{ImageBindGroups, TilemapViewBindGroup},
ModifiedImageIds,
};

#[cfg(not(feature = "atlas"))]
Expand Down Expand Up @@ -498,6 +499,7 @@ pub fn bind_material_tilemap_meshes<M: MaterialTilemap>(
(standard_tilemap_meshes, materials): (Query<(&ChunkId, &TilemapId)>, Query<&Handle<M>>),
mut views: Query<(Entity, &VisibleEntities)>,
render_materials: Res<RenderMaterialsTilemap<M>>,
modified_image_ids: Res<ModifiedImageIds>,
#[cfg(not(feature = "atlas"))] (mut texture_array_cache, render_queue): (
ResMut<TextureArrayCache>,
Res<RenderQueue>,
Expand Down Expand Up @@ -567,31 +569,36 @@ pub fn bind_material_tilemap_meshes<M: MaterialTilemap>(
continue;
}

image_bind_groups
.values
.entry(chunk.texture.clone_weak())
.or_insert_with(|| {
#[cfg(not(feature = "atlas"))]
let gpu_image = texture_array_cache.get(&chunk.texture);
#[cfg(feature = "atlas")]
let gpu_image = gpu_images.get(chunk.texture.image_handle()).unwrap();
render_device.create_bind_group(
Some("sprite_material_bind_group"),
&tilemap_pipeline.material_layout,
&[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(
&gpu_image.texture_view,
),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
)
});
let create_bind_group = || {
#[cfg(not(feature = "atlas"))]
let gpu_image = texture_array_cache.get(&chunk.texture);
#[cfg(feature = "atlas")]
let gpu_image = gpu_images.get(chunk.texture.image_handle()).unwrap();
render_device.create_bind_group(
Some("sprite_material_bind_group"),
&tilemap_pipeline.material_layout,
&[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
)
};
if modified_image_ids.is_texture_modified(&chunk.texture) {
image_bind_groups
.values
.insert(chunk.texture.clone_weak(), create_bind_group());
} else {
image_bind_groups
.values
.entry(chunk.texture.clone_weak())
.or_insert_with(create_bind_group);
}
}
}
}
Expand Down
49 changes: 46 additions & 3 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ use bevy::{
core_pipeline::core_2d::Transparent2d,
prelude::*,
render::{
extract_resource::{extract_resource, ExtractResource},
mesh::MeshVertexAttribute,
render_phase::AddRenderCommand,
render_resource::{FilterMode, SpecializedRenderPipelines, VertexFormat},
texture::ImageSamplerDescriptor,
view::{check_visibility, VisibilitySystems},
Render, RenderApp, RenderSet,
},
utils::HashSet,
};

#[cfg(not(feature = "atlas"))]
Expand Down Expand Up @@ -122,6 +124,9 @@ impl Plugin for TilemapRenderingPlugin {
Handle::<StandardTilemapMaterial>::default().id(),
StandardTilemapMaterial::default(),
);

app.init_resource::<ModifiedImageIds>()
.add_systems(Update, collect_modified_image_asset_events);
}

fn finish(&self, app: &mut App) {
Expand Down Expand Up @@ -232,15 +237,20 @@ impl Plugin for TilemapRenderingPlugin {
#[cfg(not(feature = "atlas"))]
render_app
.init_resource::<TextureArrayCache>()
.add_systems(Render, prepare_textures.in_set(RenderSet::PrepareAssets));
.add_systems(Render, prepare_textures.in_set(RenderSet::PrepareAssets))
.add_systems(Render, texture_array_cache::remove_modified_textures);

render_app
.insert_resource(DefaultSampler(sampler))
.insert_resource(RenderChunk2dStorage::default())
.insert_resource(SecondsSinceStartup(0.0))
.add_systems(
ExtractSchedule,
(extract::extract, extract::extract_removal),
(
extract::extract,
extract::extract_removal,
extract_resource::<ModifiedImageIds>,
),
)
.add_systems(
Render,
Expand All @@ -255,7 +265,8 @@ impl Plugin for TilemapRenderingPlugin {
.init_resource::<ImageBindGroups>()
.init_resource::<SpecializedRenderPipelines<TilemapPipeline>>()
.init_resource::<MeshUniformResource>()
.init_resource::<TilemapUniformResource>();
.init_resource::<TilemapUniformResource>()
.init_resource::<ModifiedImageIds>();

render_app.add_render_command::<Transparent2d, DrawTilemap>();
}
Expand Down Expand Up @@ -340,3 +351,35 @@ fn prepare_textures(

texture_array_cache.prepare(&render_device, &render_images);
}

/// Resource to hold the ids of modified Image assets of a single frame.
#[derive(Resource, ExtractResource, Clone, Default)]
pub struct ModifiedImageIds(HashSet<AssetId<Image>>);

impl ModifiedImageIds {
// Determines whether `texture` contains any handles of modified images.
pub fn is_texture_modified(&self, texture: &TilemapTexture) -> bool {
texture
.image_handles()
.iter()
.any(|&image| self.0.contains(&image.id()))
}
}

/// A system to collect the asset events of modified images for one frame.
/// AssetEvents cannot be read from the render sub-app, so this system packs
/// them up into a convenient resource which can be extracted for rendering.
pub fn collect_modified_image_asset_events(
mut asset_events: EventReader<AssetEvent<Image>>,
mut modified_image_ids: ResMut<ModifiedImageIds>,
) {
modified_image_ids.0.clear();

for asset_event in asset_events.read() {
let id = match asset_event {
AssetEvent::Modified { id } => id,
_ => continue,
};
modified_image_ids.0.insert(*id);
}
}
25 changes: 24 additions & 1 deletion src/render/texture_array_cache.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::render::extract::ExtractedTilemapTexture;
use crate::{TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize};
use bevy::asset::Assets;
use bevy::prelude::Resource;
use bevy::prelude::{ResMut, Resource};
use bevy::{
prelude::{Image, Res, UVec2},
render::{
Expand All @@ -17,6 +17,8 @@ use bevy::{
utils::{HashMap, HashSet},
};

use super::ModifiedImageIds;

#[derive(Resource, Default, Debug, Clone)]
pub struct TextureArrayCache {
textures: HashMap<TilemapTexture, GpuImage>,
Expand Down Expand Up @@ -342,3 +344,24 @@ impl TextureArrayCache {
}
}
}

/// A system to remove any modified textures from the TextureArrayCache. Modified images will be
/// added back to the pipeline, and so will be reloaded. This allows the TextureArrayCache to be
/// responsive to hot-reloading, for example.
pub fn remove_modified_textures(
modified_image_ids: Res<ModifiedImageIds>,
mut texture_cache: ResMut<TextureArrayCache>,
) {
let texture_is_unmodified =
|texture: &TilemapTexture| !modified_image_ids.is_texture_modified(texture);

texture_cache
.textures
.retain(|texture, _| texture_is_unmodified(texture));
texture_cache
.meta_data
.retain(|texture, _| texture_is_unmodified(texture));
texture_cache.prepare_queue.retain(texture_is_unmodified);
texture_cache.queue_queue.retain(texture_is_unmodified);
texture_cache.bad_flag_queue.retain(texture_is_unmodified);
Copy link
Collaborator

Choose a reason for hiding this comment

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

only strange thing I noticed was we don't seem to actually be using bad_flag_queue anywhere. What is this for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fascinating. I don't think it has ever been used. It seems to have originated here: 020d10e

}
1 change: 1 addition & 0 deletions src/tiles/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl TileStorage {
/// Gets a tile entity for the given tile position, if:
/// 1) the tile position lies within the underlying tile map's extents *and*
/// 2) there is an entity associated with that tile position;
///
/// otherwise it returns `None`.
pub fn checked_get(&self, tile_pos: &TilePos) -> Option<Entity> {
if tile_pos.within_map_bounds(&self.size) {
Expand Down
Loading