diff --git a/crates/gpu/src/context.rs b/crates/gpu/src/context.rs index 51221abc..0c5763a7 100644 --- a/crates/gpu/src/context.rs +++ b/crates/gpu/src/context.rs @@ -919,6 +919,12 @@ impl GpuContext { /// Upload a (layer cache) buffer, reusing the texture while the same `Arc` is alive: static /// layers upload once, not every frame. + /// Entries in the layer upload cache. + #[cfg(test)] + pub(crate) fn upload_entries(&self) -> usize { + self.uploads.lock().map(|u| u.map.len()).unwrap_or_default() + } + pub(crate) fn upload_buf(&self, buf: &Arc) -> Option { let key = Arc::as_ptr(buf) as usize; if let Ok(mut u) = self.uploads.lock() { @@ -939,14 +945,14 @@ impl GpuContext { u.bytes -= old.bytes; } u.bytes += bytes; - // Drop textures of freed buffers, then the least recently used over budget. + // Drop textures of freed buffers at once (a layer whose buffer changes every frame + // makes a new one each time, and nothing can hit the old one again), then the least + // recently used over budget. let budget = self.upload_budget(); - if u.bytes > budget { - let dead: Vec = u.map.iter().filter(|(_, e)| e.buf.strong_count() == 0).map(|(k, _)| *k).collect(); - for k in dead { - if let Some(e) = u.map.remove(&k) { - u.bytes -= e.bytes; - } + let dead: Vec = u.map.iter().filter(|(_, e)| e.buf.strong_count() == 0).map(|(k, _)| *k).collect(); + for k in dead { + if let Some(e) = u.map.remove(&k) { + u.bytes -= e.bytes; } } if u.bytes > budget && !self.upload_full_warned.swap(true, std::sync::atomic::Ordering::Relaxed) { diff --git a/crates/gpu/src/tests.rs b/crates/gpu/src/tests.rs index 696402d6..532e313f 100644 --- a/crates/gpu/src/tests.rs +++ b/crates/gpu/src/tests.rs @@ -1044,3 +1044,19 @@ fn static_effect_layers_draw_from_the_cache() { assert!(frames[1] == frames[2]); check("static effect layer vs CPU", compare_at(&s, opts(), t(2)), 0.0); } + +/// The layer upload cache drops the textures of freed buffers when it inserts, not only once +/// it is over budget (an animated layer makes a new buffer every frame). +#[test] +fn upload_cache_drops_textures_of_freed_buffers() { + let Some(g) = gpu() else { return }; + let img = effectcraft_raster::Image::new(256, 256); + let before = g.ctx.upload_entries(); + for _ in 0..6 { + let buf = std::sync::Arc::new(effectcraft_effects::Buf { img: img.clone(), offset: [0.0; 2], scale: 1.0 }); + let up = g.ctx.upload_buf(&buf).expect("upload"); + drop((up, buf)); + } + let kept = g.ctx.upload_entries(); + assert!(kept <= before + 1, "the upload cache kept {kept} textures of freed buffers (had {before})"); +}