fix(renderer+ux): frame-liveness watchdog (#558), dup-add button (#655)

- #558: a backend can be alive yet wedged (zero presented frames forever
  on legacy stacks). Vendored femtovg counts presented frames; a watchdog
  8s after window-ready re-arms the renderer sentinel and restarts, so
  the existing ladder degrades wgpu -> alt adapter -> GL -> software on
  its own instead of stranding the user on a blank window.
- #655: 'Add only new tracks' stays enabled with all-dupes (adds zero
  and closes) instead of disappearing as the primary action.

km
This commit is contained in:
vicrodh committed 2026-07-25 12:05:27 -06:00
1 parent f1eeaf9126
commit d99419f22d
4 files changed
+36 -6

No files matched your search

+20
View File
@@ -67,6 +67,26 @@ impl core::fmt::Display for FrameSkipped {
impl std::error::Error for FrameSkipped {}
/// QBZ vendor patch (issue #558): presented-frames counter feeding the
/// render-liveness watchdog in the qbz crate. A backend can be ALIVE yet
/// wedged — the #558 Outdated-surface skip loop above skips every frame
/// forever while the process looks healthy, and the crash-chain ladder only
/// escalates on DEATH, so a silent wedge never degrades. The watchdog reads
/// this a few seconds after startup: zero presented frames with a visible
/// window = escalate the degradation ladder and restart on the next rung.
static FRAMES_PRESENTED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
/// Total frames successfully presented by the active backend since process
/// start. Read by the qbz render-liveness watchdog (#558).
pub fn frames_presented() -> u64 {
FRAMES_PRESENTED.load(core::sync::atomic::Ordering::Relaxed)
}
/// Watchdog hook — each backend's `present_surface` calls this on success.
pub(crate) fn note_frame_presented() {
FRAMES_PRESENTED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
pub trait GraphicsBackend {
type Renderer: femtovg::Renderer + TextureImporter;
type WindowSurface: WindowSurface<Self::Renderer>;
+7 -1
View File
@@ -196,7 +196,13 @@ impl GraphicsBackend for OpenGLBackend {
&self,
_surface: GLWindowSurface,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.opengl_context.borrow().swap_buffers()
let result = self.opengl_context.borrow().swap_buffers();
if result.is_ok() {
// QBZ vendor patch (#558): count presented frames for the
// render-liveness watchdog.
crate::note_frame_presented();
}
result
}
#[cfg(not(target_family = "wasm"))]
+3
View File
@@ -209,6 +209,9 @@ impl GraphicsBackend for WGPUBackend {
surface: Self::WindowSurface,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
surface.surface_texture.present();
// QBZ vendor patch (#558): count presented frames for the
// render-liveness watchdog.
crate::note_frame_presented();
Ok(())
}