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..b3552d72 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,23 @@ impl RenderContextBuilder { } } +fn resolve_surface_present_mode( + builder_mode: Option, + window_vsync: bool, +) -> targets::surface::PresentMode { + let requested = builder_mode.unwrap_or(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 +1112,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;