From aba8ac727df25226fe3a0d78cd0d2d608e9388d9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 2 Jun 2026 17:47:46 +1000 Subject: [PATCH] wgpu: query surface capabilities before configuring present mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default WGPUPresentMode was a static Mailbox. That works on DX12 and on Vulkan with most drivers, but the Metal backend in wgpu-native v29 only exposes [Fifo, Immediate], and asking for Mailbox makes wgpuSurfaceConfigure panic from Rust: thread '' panicked at src/lib.rs:605:5: Error in wgpuSurfaceConfigure: Validation Error Caused by: Requested present mode Mailbox is not in the list of supported present modes: [Fifo, Immediate] fatal runtime error: failed to initiate panic, error 5, aborting # Fix Query wgpuSurfaceGetCapabilities and walk a preference list (Mailbox → FifoRelaxed → Immediate → Fifo), picking the first mode the surface actually lists. Fifo is the only spec-required mode and will always be present, so the loop always finds something. The env override (WGPU_PRESENT_MODE=...) still wins when set, but also drops back to Fifo if the requested mode isn't supported on the current backend — no panic. # Outcomes per backend DX12 / Vulkan: picks Mailbox (low input lag, our previous default). Metal (macOS): picks Immediate (Mailbox unavailable). On Metal the CAMetalLayer presents through CoreAnimation, so the compositor still vsync-aligns; Immediate is effectively low-latency-with-no-tearing on macOS. Co-Authored-By: Claude Opus 4.7 --- src/ifcviewer-wgpu/WgpuViewportWindow.cpp | 59 ++++++++++++++++++----- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp index 3a733d7ca2..621795982b 100644 --- a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp +++ b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp @@ -1947,23 +1947,60 @@ void WgpuViewportWindow::configureSurface(int width_px, int height_px) { // immediate — no vsync at all. Frames presented as soon as // ready, may tear on motion. Useful for raw // throughput benchmarking. - WGPUPresentMode pm = WGPUPresentMode_Mailbox; + // Preference order. Override with WGPU_PRESENT_MODE=...; otherwise we + // try Mailbox → FifoRelaxed → Immediate → Fifo and pick the first + // mode actually advertised by the surface. Asking for a mode that + // the backend doesn't list aborts the process (wgpu-native panics + // from Rust at wgpuSurfaceConfigure). On Metal in particular only + // Fifo + Immediate are exposed today, so a static Mailbox default + // crashes there. + WGPUPresentMode preferred[4] = { + WGPUPresentMode_Mailbox, + WGPUPresentMode_FifoRelaxed, + WGPUPresentMode_Immediate, + WGPUPresentMode_Fifo, + }; const char* pm_name = "mailbox"; if (const char* s = std::getenv("WGPU_PRESENT_MODE")) { - if (std::strcmp(s, "fifo") == 0) { - pm = WGPUPresentMode_Fifo; pm_name = "fifo"; - } else if (std::strcmp(s, "fifo_relaxed") == 0) { - pm = WGPUPresentMode_FifoRelaxed; pm_name = "fifo_relaxed"; - } else if (std::strcmp(s, "mailbox") == 0) { - pm = WGPUPresentMode_Mailbox; pm_name = "mailbox"; - } else if (std::strcmp(s, "immediate") == 0) { - pm = WGPUPresentMode_Immediate; pm_name = "immediate"; - } else { + WGPUPresentMode override_pm = WGPUPresentMode_Fifo; + bool known = true; + if (std::strcmp(s, "fifo") == 0) { override_pm = WGPUPresentMode_Fifo; pm_name = "fifo"; } + else if (std::strcmp(s, "fifo_relaxed") == 0) { override_pm = WGPUPresentMode_FifoRelaxed; pm_name = "fifo_relaxed"; } + else if (std::strcmp(s, "mailbox") == 0) { override_pm = WGPUPresentMode_Mailbox; pm_name = "mailbox"; } + else if (std::strcmp(s, "immediate") == 0) { override_pm = WGPUPresentMode_Immediate; pm_name = "immediate"; } + else { + known = false; qWarning().noquote().nospace() << "[wgpu] unknown WGPU_PRESENT_MODE=" << s - << " (expected fifo|fifo_relaxed|mailbox|immediate); using mailbox"; + << " (expected fifo|fifo_relaxed|mailbox|immediate); falling back to preference order"; + } + if (known) { + preferred[0] = override_pm; + preferred[1] = WGPUPresentMode_Fifo; // Fifo is the only guaranteed-supported mode + preferred[2] = preferred[3] = WGPUPresentMode_Fifo; } } + + WGPUSurfaceCapabilities caps = {}; + wgpuSurfaceGetCapabilities(surface_, adapter_, &caps); + auto supports = [&](WGPUPresentMode mode) { + for (size_t i = 0; i < caps.presentModeCount; ++i) { + if (caps.presentModes[i] == mode) return true; + } + return false; + }; + WGPUPresentMode pm = WGPUPresentMode_Fifo; // spec-guaranteed fallback + for (WGPUPresentMode candidate : preferred) { + if (supports(candidate)) { pm = candidate; break; } + } + switch (pm) { + case WGPUPresentMode_Mailbox: pm_name = "mailbox"; break; + case WGPUPresentMode_FifoRelaxed: pm_name = "fifo_relaxed"; break; + case WGPUPresentMode_Immediate: pm_name = "immediate"; break; + case WGPUPresentMode_Fifo: pm_name = "fifo"; break; + default: break; + } + wgpuSurfaceCapabilitiesFreeMembers(caps); cfg.presentMode = pm; if (!surface_configured_) { const char* note = "";