mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 04:57:54 -06:00
GPU: the frame guard contains validation errors and drops the failed frame's textures (refs #1319)
This commit is contained in:
@@ -980,6 +980,18 @@ impl GpuContext {
|
||||
s.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// After a failed frame: forget every texture and buffer kept for reuse, since any of them
|
||||
/// may be one the device rejected (#1319). Later frames allocate afresh.
|
||||
pub(crate) fn discard_kept(&self) {
|
||||
self.clear_uploads();
|
||||
if let Ok(mut z) = self.zeros.lock() {
|
||||
z.clear();
|
||||
}
|
||||
if let Ok(mut c) = self.particle_states.lock() {
|
||||
c.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Records GPU work for one render (a command encoder submitted at readback or the end).
|
||||
|
||||
+17
-10
@@ -264,10 +264,13 @@ impl Gpu {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run work in native thread-local out-of-memory and internal-error scopes. A failed
|
||||
/// result is discarded and cached allocations are released; callers can retry with a fresh
|
||||
/// CPU renderer. Some drivers report a failed allocation as an internal error rather than
|
||||
/// out-of-memory (Adreno's `ERROR_INITIALIZATION_FAILED`, #591), so both count.
|
||||
/// Run work in native thread-local out-of-memory, internal-error and validation scopes. A
|
||||
/// failed result is discarded and the textures and buffers kept for reuse are released;
|
||||
/// callers can retry with a fresh CPU renderer. Some drivers report a failed allocation as an
|
||||
/// internal error rather than out-of-memory (Adreno's `ERROR_INITIALIZATION_FAILED`, #591),
|
||||
/// so both count. A failed allocation leaves an invalid texture whose use is then a
|
||||
/// validation error (`Texture::create_view`, #1319): caught here, it fails this frame only
|
||||
/// instead of retiring the GPU compositor for the session as an uncaptured error.
|
||||
/// Browser callers use the host/owned-device failure bridge and bounded readbacks instead
|
||||
/// of blocking their event loop on an error-scope future.
|
||||
pub fn within_memory<T>(&self, f: impl FnOnce() -> T) -> Result<T, String> {
|
||||
@@ -276,16 +279,20 @@ impl Gpu {
|
||||
{
|
||||
let oom = self.ctx.device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
|
||||
let internal = self.ctx.device.push_error_scope(wgpu::ErrorFilter::Internal);
|
||||
let validation = self.ctx.device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let out = f();
|
||||
let validation = pollster::block_on(validation.pop());
|
||||
let internal = pollster::block_on(internal.pop());
|
||||
let oom = pollster::block_on(oom.pop());
|
||||
match internal.or(oom) {
|
||||
Some(e) => {
|
||||
self.ctx.clear_uploads();
|
||||
Err(e.to_string())
|
||||
}
|
||||
None => self.ctx.check_health().map(|()| out),
|
||||
// The allocation failure first: a validation error is often its consequence.
|
||||
let errors: Vec<String> = [oom, internal, validation].into_iter().flatten().map(|e| e.to_string()).collect();
|
||||
if errors.is_empty() {
|
||||
return self.ctx.check_health().map(|()| out);
|
||||
}
|
||||
// Its textures go back to the pool when dropped: drop them before emptying it.
|
||||
drop(out);
|
||||
self.ctx.discard_kept();
|
||||
Err(errors.join("; then: "))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
|
||||
@@ -33,6 +33,13 @@ pub(crate) struct Checkpoints {
|
||||
clock: u64,
|
||||
}
|
||||
|
||||
impl Checkpoints {
|
||||
/// Forget every kept state (after a failed frame, whose buffers may be invalid).
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.list.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn pipes(g: &GpuContext) -> &Pipes {
|
||||
g.particles.get_or_init(|| {
|
||||
let d = &g.device;
|
||||
|
||||
@@ -746,6 +746,40 @@ fn within_memory_returns_the_work_when_there_is_room() {
|
||||
assert_eq!((img.width, img.height), (64, 32));
|
||||
}
|
||||
|
||||
/// A texture the device rejects, then used in the frame guard (its view: "Texture with '' label
|
||||
/// is invalid", #1319), fails that frame only. The validation error doesn't reach the device's
|
||||
/// handler (which retires the compositor for the session, and panics in tests), the frame's
|
||||
/// textures don't go back to the pool, and the next frame renders on the GPU.
|
||||
#[test]
|
||||
fn a_rejected_texture_fails_the_frame_not_the_compositor() {
|
||||
hold_gpu_lock();
|
||||
let Some(g) = Gpu::headless() else { return };
|
||||
let too_wide = g.ctx.device.limits().max_texture_dimension_2d.saturating_add(1);
|
||||
let err = g
|
||||
.within_memory(|| {
|
||||
let mut e = crate::context::Enc::new(&g.ctx);
|
||||
let kept = e.image(64, 32);
|
||||
let rejected = g.ctx.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("deliberately too wide"),
|
||||
size: wgpu::Extent3d { width: too_wide, height: 1, depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
(kept, rejected.create_view(&Default::default()))
|
||||
})
|
||||
.expect_err("the rejected texture fails the frame");
|
||||
assert!(err.contains("deliberately too wide"), "{err}");
|
||||
assert!(g.ctx.check_health().is_ok(), "the compositor stays on: {:?}", g.ctx.check_health());
|
||||
let (hits, _) = g.ctx.pool_stats();
|
||||
let img = g.within_memory(|| crate::context::Enc::new(&g.ctx).image(64, 32)).expect("the next frame renders");
|
||||
assert_eq!((img.width, img.height), (64, 32));
|
||||
assert_eq!(g.ctx.pool_stats().0, hits, "the failed frame's texture wasn't reused");
|
||||
}
|
||||
|
||||
/// A buffer whose allocation fails (here: over the device's buffer size limit; on Adreno an
|
||||
/// internal `ERROR_INITIALIZATION_FAILED`) is reported to the caller's error scope instead of
|
||||
/// panicking in `create_buffer_init`'s mapped range on a frame thread (#591).
|
||||
|
||||
Reference in New Issue
Block a user