Make image Cache eviction strategy less aggressive in iced_wgpu

Instead of trimming unconditionally at the end of
a frame, we now trim the cache only when there is a
cache miss.

This way, images that are not visible but still a
part of the layout will stay cached. Eviction will
only happen when the images are not a part of the
UI for two consectuive frames.
This commit is contained in:
Héctor Ramón Jiménez 2024-04-24 21:29:30 +02:00
parent fdcec03197
commit 493c36ac71
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
6 changed files with 41 additions and 5 deletions

View file

@ -94,7 +94,7 @@ impl Atlas {
entry entry
}; };
log::info!("Allocated atlas entry: {entry:?}"); log::debug!("Allocated atlas entry: {entry:?}");
// It is a webgpu requirement that: // It is a webgpu requirement that:
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
@ -147,13 +147,20 @@ impl Atlas {
} }
} }
log::info!("Current atlas: {self:?}"); if log::log_enabled!(log::Level::Debug) {
log::debug!(
"Atlas layers: {} (busy: {}, allocations: {})",
self.layer_count(),
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
self.layers.iter().map(Layer::allocations).sum::<usize>(),
);
}
Some(entry) Some(entry)
} }
pub fn remove(&mut self, entry: &Entry) { pub fn remove(&mut self, entry: &Entry) {
log::info!("Removing atlas entry: {entry:?}"); log::debug!("Removing atlas entry: {entry:?}");
match entry { match entry {
Entry::Contiguous(allocation) => { Entry::Contiguous(allocation) => {
@ -266,7 +273,7 @@ impl Atlas {
} }
fn deallocate(&mut self, allocation: &Allocation) { fn deallocate(&mut self, allocation: &Allocation) {
log::info!("Deallocating atlas: {allocation:?}"); log::debug!("Deallocating atlas: {allocation:?}");
match allocation { match allocation {
Allocation::Full { layer } => { Allocation::Full { layer } => {

View file

@ -33,6 +33,10 @@ impl Allocator {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.allocations == 0 self.allocations == 0
} }
pub fn allocations(&self) -> usize {
self.allocations
}
} }
pub struct Region { pub struct Region {

View file

@ -11,4 +11,12 @@ impl Layer {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
matches!(self, Layer::Empty) matches!(self, Layer::Empty)
} }
pub fn allocations(&self) -> usize {
match self {
Layer::Empty => 0,
Layer::Busy(allocator) => allocator.allocations(),
Layer::Full => 1,
}
}
} }

View file

@ -277,7 +277,7 @@ impl Pipeline {
let texture_version = cache.layer_count(); let texture_version = cache.layer_count();
if self.texture_version != texture_version { if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group..."); log::debug!("Atlas has grown. Recreating bind group...");
self.texture = self.texture =
cache.create_bind_group(device, &self.texture_layout); cache.create_bind_group(device, &self.texture_layout);

View file

@ -40,6 +40,7 @@ impl Memory {
pub struct Cache { pub struct Cache {
map: FxHashMap<u64, Memory>, map: FxHashMap<u64, Memory>,
hits: FxHashSet<u64>, hits: FxHashSet<u64>,
should_trim: bool,
} }
impl Cache { impl Cache {
@ -55,6 +56,8 @@ impl Cache {
Err(_) => Memory::Invalid, Err(_) => Memory::Invalid,
}; };
self.should_trim = true;
self.insert(handle, memory); self.insert(handle, memory);
self.get(handle).unwrap() self.get(handle).unwrap()
} }
@ -86,6 +89,11 @@ impl Cache {
/// Trim cache misses from cache /// Trim cache misses from cache
pub fn trim(&mut self, atlas: &mut Atlas) { pub fn trim(&mut self, atlas: &mut Atlas) {
// Only trim if new entries have landed in the `Cache`
if !self.should_trim {
return;
}
let hits = &self.hits; let hits = &self.hits;
self.map.retain(|k, memory| { self.map.retain(|k, memory| {
@ -101,6 +109,7 @@ impl Cache {
}); });
self.hits.clear(); self.hits.clear();
self.should_trim = false;
} }
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {

View file

@ -37,6 +37,7 @@ pub struct Cache {
rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
svg_hits: FxHashSet<u64>, svg_hits: FxHashSet<u64>,
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
should_trim: bool,
} }
type ColorFilter = Option<[u8; 4]>; type ColorFilter = Option<[u8; 4]>;
@ -76,6 +77,8 @@ impl Cache {
} }
} }
self.should_trim = true;
let _ = self.svgs.insert(handle.id(), svg); let _ = self.svgs.insert(handle.id(), svg);
self.svgs.get(&handle.id()).unwrap() self.svgs.get(&handle.id()).unwrap()
} }
@ -176,6 +179,10 @@ impl Cache {
/// Load svg and upload raster data /// Load svg and upload raster data
pub fn trim(&mut self, atlas: &mut Atlas) { pub fn trim(&mut self, atlas: &mut Atlas) {
if !self.should_trim {
return;
}
let svg_hits = &self.svg_hits; let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits; let rasterized_hits = &self.rasterized_hits;
@ -191,6 +198,7 @@ impl Cache {
}); });
self.svg_hits.clear(); self.svg_hits.clear();
self.rasterized_hits.clear(); self.rasterized_hits.clear();
self.should_trim = false;
} }
} }