gpu: upload cache drops textures of freed buffers on every insert

Entries whose layer buffer was freed were pruned only once the cache went over its budget
(1.5 GiB), so an animated layer, which makes a new buffer every frame, left a dead 4K texture
per frame allocated until the cache filled up.
This commit is contained in:
Matej Kozlovský
2026-10-15 10:19:42 +02:00
parent 05955c08b4
commit 77a8d6ae73
2 changed files with 29 additions and 7 deletions
+13 -7
View File
@@ -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<Buf>) -> Option<GpuImage> {
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<usize> = 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<usize> = 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) {
+16
View File
@@ -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})");
}