From 8b3d31a0f0b13cf0670888d4c2a1fa9d4b14d8bb Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 7 Feb 2026 13:47:29 -0800 Subject: [PATCH 1/2] [fix] how candidates are selected during vsync fallback selection so that vsync/no vsync requests are more likely to be satisfied --- crates/lambda-rs-platform/src/wgpu/surface.rs | 59 ++++++++++++++++++- crates/lambda-rs/src/render/mod.rs | 50 ++++++++++++---- crates/lambda-rs/src/render/window.rs | 3 + 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/crates/lambda-rs-platform/src/wgpu/surface.rs b/crates/lambda-rs-platform/src/wgpu/surface.rs index 77017f5c..0c0008ac 100644 --- a/crates/lambda-rs-platform/src/wgpu/surface.rs +++ b/crates/lambda-rs-platform/src/wgpu/surface.rs @@ -318,11 +318,23 @@ fn select_present_mode( return requested; } + // Note on ordering: + // - When callers request non-vsync (Immediate/AutoNoVsync), prefer true + // non-vsync modes (Immediate, AutoNoVsync) over low-latency vsync modes + // (Mailbox). Mailbox still synchronizes presentation to refresh and can + // appear "vsync-like" to users who disable vsync expecting uncapped loops. let candidates: &[wgpu::PresentMode] = match requested { - wgpu::PresentMode::Immediate | wgpu::PresentMode::AutoNoVsync => &[ + wgpu::PresentMode::Immediate => &[ wgpu::PresentMode::Immediate, + wgpu::PresentMode::AutoNoVsync, wgpu::PresentMode::Mailbox, + wgpu::PresentMode::Fifo, + wgpu::PresentMode::AutoVsync, + ], + wgpu::PresentMode::AutoNoVsync => &[ wgpu::PresentMode::AutoNoVsync, + wgpu::PresentMode::Immediate, + wgpu::PresentMode::Mailbox, wgpu::PresentMode::Fifo, wgpu::PresentMode::AutoVsync, ], @@ -336,7 +348,15 @@ fn select_present_mode( wgpu::PresentMode::Fifo, wgpu::PresentMode::AutoVsync, ], - wgpu::PresentMode::Fifo | wgpu::PresentMode::AutoVsync => &[ + wgpu::PresentMode::AutoVsync => &[ + wgpu::PresentMode::AutoVsync, + wgpu::PresentMode::Fifo, + wgpu::PresentMode::FifoRelaxed, + wgpu::PresentMode::Mailbox, + wgpu::PresentMode::Immediate, + wgpu::PresentMode::AutoNoVsync, + ], + wgpu::PresentMode::Fifo => &[ wgpu::PresentMode::Fifo, wgpu::PresentMode::AutoVsync, wgpu::PresentMode::FifoRelaxed, @@ -414,4 +434,39 @@ mod tests { let selected = select_present_mode(wgpu::PresentMode::Immediate, available); assert_eq!(selected, wgpu::PresentMode::AutoNoVsync); } + + #[test] + fn select_present_mode_prefers_auto_no_vsync_over_mailbox_for_immediate_request( + ) { + let available = &[ + wgpu::PresentMode::Mailbox, + wgpu::PresentMode::AutoNoVsync, + wgpu::PresentMode::Fifo, + ]; + let selected = select_present_mode(wgpu::PresentMode::Immediate, available); + assert_eq!(selected, wgpu::PresentMode::AutoNoVsync); + } + + #[test] + fn select_present_mode_prefers_auto_no_vsync_when_requested() { + let available = &[ + wgpu::PresentMode::Immediate, + wgpu::PresentMode::AutoNoVsync, + wgpu::PresentMode::Fifo, + ]; + let selected = + select_present_mode(wgpu::PresentMode::AutoNoVsync, available); + assert_eq!(selected, wgpu::PresentMode::AutoNoVsync); + } + + #[test] + fn select_present_mode_prefers_auto_vsync_when_requested() { + let available = &[ + wgpu::PresentMode::Fifo, + wgpu::PresentMode::AutoVsync, + wgpu::PresentMode::Immediate, + ]; + let selected = select_present_mode(wgpu::PresentMode::AutoVsync, available); + assert_eq!(selected, wgpu::PresentMode::AutoVsync); + } } diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index b29dc967..cacc6ecd 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -190,17 +190,8 @@ impl RenderContextBuilder { })?; let size = window.dimensions(); - let requested_present_mode = present_mode.unwrap_or_else(|| { - if window.vsync_requested() { - return PresentMode::Vsync; - } - return PresentMode::Immediate; - }); - let platform_present_mode = match requested_present_mode { - PresentMode::Vsync => targets::surface::PresentMode::Fifo, - PresentMode::Immediate => targets::surface::PresentMode::Immediate, - PresentMode::Mailbox => targets::surface::PresentMode::Mailbox, - }; + let platform_present_mode = + resolve_surface_present_mode(present_mode, window.vsync_requested()); surface .configure_with_defaults( &gpu, @@ -261,6 +252,25 @@ impl RenderContextBuilder { } } +fn resolve_surface_present_mode( + builder_mode: Option, + window_vsync: bool, +) -> targets::surface::PresentMode { + let requested = builder_mode.unwrap_or_else(|| { + if window_vsync { + PresentMode::Vsync + } else { + PresentMode::Immediate + } + }); + + return match requested { + PresentMode::Vsync => targets::surface::PresentMode::Fifo, + PresentMode::Immediate => targets::surface::PresentMode::Immediate, + PresentMode::Mailbox => targets::surface::PresentMode::Mailbox, + }; +} + /// High‑level rendering context for a single window. /// /// Purpose @@ -1104,4 +1114,22 @@ mod tests { .expect_err("must error"); assert!(err.to_string().contains("Unknown pipeline 7")); } + + #[test] + fn present_mode_defaults_to_window_vsync_true() { + let mode = resolve_surface_present_mode(None, true); + assert_eq!(mode, targets::surface::PresentMode::Fifo); + } + + #[test] + fn present_mode_defaults_to_window_vsync_false() { + let mode = resolve_surface_present_mode(None, false); + assert_eq!(mode, targets::surface::PresentMode::Immediate); + } + + #[test] + fn present_mode_builder_override_wins_over_window_setting() { + let mode = resolve_surface_present_mode(Some(PresentMode::Vsync), false); + assert_eq!(mode, targets::surface::PresentMode::Fifo); + } } diff --git a/crates/lambda-rs/src/render/window.rs b/crates/lambda-rs/src/render/window.rs index 3023c65e..17a2c89f 100644 --- a/crates/lambda-rs/src/render/window.rs +++ b/crates/lambda-rs/src/render/window.rs @@ -56,6 +56,9 @@ impl WindowBuilder { /// /// This value is consumed when building a `RenderContext` if no explicit /// present mode is provided to `RenderContextBuilder`. + /// + /// Disabling vsync is best‑effort; the final present mode depends on the + /// platform and adapter surface capabilities. pub fn with_vsync(mut self, vsync: bool) -> Self { self.vsync = vsync; return self; From 153dc64aebae4ff32398cd5b67c0765804ce8f17 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 7 Feb 2026 14:00:26 -0800 Subject: [PATCH 2/2] [fix] unnecessary closure. --- crates/lambda-rs/src/render/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index cacc6ecd..b3552d72 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -256,12 +256,10 @@ fn resolve_surface_present_mode( builder_mode: Option, window_vsync: bool, ) -> targets::surface::PresentMode { - let requested = builder_mode.unwrap_or_else(|| { - if window_vsync { - PresentMode::Vsync - } else { - PresentMode::Immediate - } + let requested = builder_mode.unwrap_or(if window_vsync { + PresentMode::Vsync + } else { + PresentMode::Immediate }); return match requested {