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
@@ -4956,12 +4956,25 @@ impl MultiPipeline {
|
||||
/// `Pipeline::new`, which runs again for every pane `ensure_len` adds. If iced
|
||||
/// ever rebuilds the device it builds a new `MultiPipeline` too, so the fresh
|
||||
/// device is covered (and the throttle restarts with it).
|
||||
static GPU_ERRORS_SEEN: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
/// How many uncaptured device errors have been reported since this device was
|
||||
/// created.
|
||||
///
|
||||
/// Callers snapshot it around an allocation to find out whether what they just
|
||||
/// built is usable. Best effort: wgpu surfaces some errors asynchronously, so a
|
||||
/// move means "something failed", while no move is not proof that nothing did.
|
||||
/// That is the right asymmetry here — the cost of believing a bad buffer is a
|
||||
/// frozen viewport, the cost of rebuilding a good one is a frame.
|
||||
pub(crate) fn gpu_errors_seen() -> usize {
|
||||
GPU_ERRORS_SEEN.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn install_gpu_error_handler(device: &wgpu::Device) {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
static SEEN: AtomicUsize = AtomicUsize::new(0);
|
||||
SEEN.store(0, Ordering::Relaxed);
|
||||
use std::sync::atomic::Ordering;
|
||||
GPU_ERRORS_SEEN.store(0, Ordering::Relaxed);
|
||||
device.on_uncaptured_error(std::sync::Arc::new(|e: wgpu::Error| {
|
||||
let n = SEEN.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
let n = GPU_ERRORS_SEEN.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if n.is_power_of_two() {
|
||||
eprintln!("[gpu] uncaptured wgpu error #{n} (frame degraded, session kept alive): {e}");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user