forked from 1b-agent/opencadstudio-upstream
fix: never cache a buffer the device rejected
After an out-of-memory the viewport stopped updating for the rest of the session — both Model and Layout frozen, the UI still responsive. The log shows why: `block-wire-geometry` prints *after* the errors it provoked. The failed uploads were cached. A rejected allocation still yields a `Buffer`. It is invalid, every later use reports again, and it is indistinguishable from a good one at the cache key. So the entry keeps hitting, nothing ever rebuilds, and the frame stays empty even once memory frees up. Sharing `block_geometry` across slots in the previous commit made that worse, not better: one poisoned entry now serves every viewport. The device's uncaptured-error counter was already there, private to the handler that throttles the log. It is now readable, and the three caches that can retain a bad upload — block definition geometry, the shared wire buffer cache, and the arena's content id — snapshot it across their build and decline to record what it condemns. The degraded frame still draws; the next frame tries again. Best effort by construction: wgpu surfaces some errors asynchronously, so a moved counter means something failed, while an unmoved one is not proof that nothing did. That asymmetry is the right way round here — believing a bad buffer costs a frozen viewport, rebuilding a good one costs a frame. `block-wire-geometry` gains `poisoned=` so a run says whether this happened rather than leaving it to be inferred. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6599b82776
commit
d9c998fa17
3 files changed
+62
-11
No files matched your search
@@ -714,6 +714,9 @@ impl shader::Primitive for Primitive {
|
||||
})
|
||||
})
|
||||
};
|
||||
// Snapshot before any arena allocation, so a rejected
|
||||
// upload cannot claim this content id below.
|
||||
let arena_errors_before = crate::scene::pipeline::gpu_errors_seen();
|
||||
let reg_ok = base_ok
|
||||
&& if let Some(arena) = inner.wire_arena.as_mut() {
|
||||
let patch = patch.unwrap();
|
||||
@@ -868,7 +871,15 @@ impl shader::Primitive for Primitive {
|
||||
inner.wire_handle_index =
|
||||
wire_arena::build_handle_index(&vp_wires[..]);
|
||||
}
|
||||
inner.wire_arena_id = vp.wire_content_id;
|
||||
// Only claim the content id when the device accepted
|
||||
// the upload; otherwise the slot believes it holds this
|
||||
// content and never rebuilds it.
|
||||
inner.wire_arena_id =
|
||||
if crate::scene::pipeline::gpu_errors_seen() == arena_errors_before {
|
||||
vp.wire_content_id
|
||||
} else {
|
||||
u64::MAX
|
||||
};
|
||||
arena_served = true;
|
||||
} else {
|
||||
inner.wire_arena = None;
|
||||
@@ -922,6 +933,8 @@ impl shader::Primitive for Primitive {
|
||||
);
|
||||
}
|
||||
let t_upload = _perf.then(iced::time::Instant::now);
|
||||
let errors_before =
|
||||
crate::scene::pipeline::gpu_errors_seen();
|
||||
let entry =
|
||||
inner.build_wire_buffers(device, queue, &vp_wires[..], &draw_depths);
|
||||
if let Some(start) = t_upload {
|
||||
@@ -933,9 +946,16 @@ impl shader::Primitive for Primitive {
|
||||
);
|
||||
}
|
||||
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
// Buffers the device rejected are still `Buffer`s.
|
||||
// Cached under a content id that keeps matching,
|
||||
// they render nothing for the rest of the session
|
||||
// and nothing ever rebuilds them. Draw the degraded
|
||||
// frame, but let the next one try again.
|
||||
if crate::scene::pipeline::gpu_errors_seen() == errors_before {
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
}
|
||||
entry
|
||||
}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user