mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
fix(render): prevent render hang and system lag (#2081)
This commit is contained in:
1 parent
9a7d3e7699
commit
c88d2b3a7f
2 files changed
+34
-1
No files matched your search
@@ -43,6 +43,9 @@ const UPLOAD_BUDGET: usize = 1536 << 20;
|
||||
/// Working textures kept for reuse once their last image is dropped (bytes).
|
||||
const POOL_BUDGET: usize = 768 << 20;
|
||||
|
||||
/// Readback staging buffers kept for reuse across sizes (bytes).
|
||||
const STAGING_BUDGET: u64 = 256 << 20;
|
||||
|
||||
/// A GPU image: premultiplied RGBA f32 in a texture (cheap to clone; immutable once written).
|
||||
/// Working images come from the context's texture pool and go back to it when the last clone
|
||||
/// is dropped.
|
||||
@@ -774,6 +777,12 @@ impl GpuContext {
|
||||
self.unavailable.push((entry, "dropped by a test".into()));
|
||||
}
|
||||
|
||||
/// Bytes of readback staging buffers kept for reuse (tests).
|
||||
#[cfg(test)]
|
||||
pub(crate) fn staging_bytes(&self) -> u64 {
|
||||
self.staging.lock().map(|s| s.iter().map(|(k, v)| k * v.len() as u64).sum()).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn can_readback(&self) -> bool {
|
||||
self.readbacks.check().is_ok() && (self.can_wait() || self.deferred.is_some())
|
||||
}
|
||||
@@ -1153,9 +1162,15 @@ impl<'g> Enc<'g> {
|
||||
}
|
||||
out.copy_from_slice(&bytes);
|
||||
// Keep a few staging buffers per size (frames read back at the same size every time).
|
||||
// Layers whose bounds change every frame read back at a new size each time: drop the
|
||||
// other sizes once over budget, or mapped host memory grows for the whole render (#2081).
|
||||
if let Ok(mut s) = self.g.staging.lock() {
|
||||
let held: u64 = s.iter().map(|(k, v)| k.saturating_mul(v.len() as u64)).sum();
|
||||
if held.saturating_add(size) > STAGING_BUDGET {
|
||||
s.retain(|k, _| *k == size);
|
||||
}
|
||||
let v = s.entry(size).or_default();
|
||||
if v.len() < 2 && size <= 256 << 20 {
|
||||
if v.len() < 2 && size <= STAGING_BUDGET {
|
||||
v.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,3 +839,21 @@ fn effects_whose_kernels_failed_render_on_the_cpu() {
|
||||
check(&format!("missing kernels {depth:?}"), Some(d), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer whose bounds change every frame reads back at a new size each time: the staging
|
||||
/// buffers kept for reuse stay within budget instead of growing for the whole render (#2081).
|
||||
#[test]
|
||||
fn readback_staging_stays_bounded_across_sizes() {
|
||||
let Some(g) = gpu() else { return };
|
||||
if !g.ctx.can_wait() {
|
||||
return;
|
||||
}
|
||||
let mut e = crate::context::Enc::new(&g.ctx);
|
||||
// 1024 × (256 + i) RGBA f32: ~4 MiB each, ~480 MiB over all sizes.
|
||||
for i in 0..120 {
|
||||
let img = g.ctx.image(1024, 256 + i);
|
||||
assert!(e.download(&img).is_some(), "readback {i}");
|
||||
}
|
||||
let held = g.ctx.staging_bytes();
|
||||
assert!(held <= (256 << 20) + 2 * 1024 * 16 * 376, "staging buffers hold {held} bytes");
|
||||
}
|
||||
Reference in new issue
Block a user