Skip to content

Commit 0392012

Browse files
authored
fix(lambda-rs): Respect WindowBuilder vsync in render surface configuration (#175)
## Summary Fixes vsync configuration being effectively ignored in some cases by ensuring the requested present mode is respected end-to-end and that “vsync off” fallbacks prefer true non-vsync modes when available. ## Related Issues - Resolves #101 ## Changes - Respect window vsync preference when building a `RenderContext` (when no explicit present mode is set). - Prefer `AutoNoVsync` over `Mailbox` when the caller requests non-vsync presentation but the adapter does not support `Immediate`. - Add unit tests covering present mode resolution and platform present-mode fallback ordering. - Clarify `WindowBuilder::with_vsync` docs (best-effort based on platform capabilities). ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] Feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation (updates to docs, specs, tutorials, or comments) - [ ] Refactor (code change that neither fixes a bug nor adds a feature) - [ ] Performance (change that improves performance) - [x] Test (adding or updating tests) - [ ] Build/CI (changes to build process or CI configuration) ## Affected Crates - [x] `lambda-rs` - [x] `lambda-rs-platform` - [ ] `lambda-rs-args` - [ ] `lambda-rs-logging` - [ ] Other: ## Checklist - [ ] Code follows the repository style guidelines (`cargo +nightly fmt --all`) - [ ] Code passes clippy (`cargo clippy --workspace --all-targets -- -D warnings`) - [ ] Tests pass (`cargo test --workspace`) - [x] New code includes appropriate documentation - [ ] Public API changes are documented - [ ] Breaking changes are noted in this PR description ## Testing **Commands run:** ```bash cargo test -p lambda-rs-platform --lib cargo test -p lambda-rs --lib ``` **Manual verification steps (if applicable):** 1. (Optional) Run a demo with `WindowBuilder::with_vsync(false)` and confirm frame pacing is no longer refresh-capped on platforms that support non-vsync modes. ## Screenshots/Recordings N/A ## Platform Testing - [x] macOS - [ ] Windows - [ ] Linux ## Additional Notes
2 parents b96df38 + 153dc64 commit 0392012

3 files changed

Lines changed: 97 additions & 13 deletions

File tree

crates/lambda-rs-platform/src/wgpu/surface.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,23 @@ fn select_present_mode(
318318
return requested;
319319
}
320320

321+
// Note on ordering:
322+
// - When callers request non-vsync (Immediate/AutoNoVsync), prefer true
323+
// non-vsync modes (Immediate, AutoNoVsync) over low-latency vsync modes
324+
// (Mailbox). Mailbox still synchronizes presentation to refresh and can
325+
// appear "vsync-like" to users who disable vsync expecting uncapped loops.
321326
let candidates: &[wgpu::PresentMode] = match requested {
322-
wgpu::PresentMode::Immediate | wgpu::PresentMode::AutoNoVsync => &[
327+
wgpu::PresentMode::Immediate => &[
323328
wgpu::PresentMode::Immediate,
329+
wgpu::PresentMode::AutoNoVsync,
324330
wgpu::PresentMode::Mailbox,
331+
wgpu::PresentMode::Fifo,
332+
wgpu::PresentMode::AutoVsync,
333+
],
334+
wgpu::PresentMode::AutoNoVsync => &[
325335
wgpu::PresentMode::AutoNoVsync,
336+
wgpu::PresentMode::Immediate,
337+
wgpu::PresentMode::Mailbox,
326338
wgpu::PresentMode::Fifo,
327339
wgpu::PresentMode::AutoVsync,
328340
],
@@ -336,7 +348,15 @@ fn select_present_mode(
336348
wgpu::PresentMode::Fifo,
337349
wgpu::PresentMode::AutoVsync,
338350
],
339-
wgpu::PresentMode::Fifo | wgpu::PresentMode::AutoVsync => &[
351+
wgpu::PresentMode::AutoVsync => &[
352+
wgpu::PresentMode::AutoVsync,
353+
wgpu::PresentMode::Fifo,
354+
wgpu::PresentMode::FifoRelaxed,
355+
wgpu::PresentMode::Mailbox,
356+
wgpu::PresentMode::Immediate,
357+
wgpu::PresentMode::AutoNoVsync,
358+
],
359+
wgpu::PresentMode::Fifo => &[
340360
wgpu::PresentMode::Fifo,
341361
wgpu::PresentMode::AutoVsync,
342362
wgpu::PresentMode::FifoRelaxed,
@@ -414,4 +434,39 @@ mod tests {
414434
let selected = select_present_mode(wgpu::PresentMode::Immediate, available);
415435
assert_eq!(selected, wgpu::PresentMode::AutoNoVsync);
416436
}
437+
438+
#[test]
439+
fn select_present_mode_prefers_auto_no_vsync_over_mailbox_for_immediate_request(
440+
) {
441+
let available = &[
442+
wgpu::PresentMode::Mailbox,
443+
wgpu::PresentMode::AutoNoVsync,
444+
wgpu::PresentMode::Fifo,
445+
];
446+
let selected = select_present_mode(wgpu::PresentMode::Immediate, available);
447+
assert_eq!(selected, wgpu::PresentMode::AutoNoVsync);
448+
}
449+
450+
#[test]
451+
fn select_present_mode_prefers_auto_no_vsync_when_requested() {
452+
let available = &[
453+
wgpu::PresentMode::Immediate,
454+
wgpu::PresentMode::AutoNoVsync,
455+
wgpu::PresentMode::Fifo,
456+
];
457+
let selected =
458+
select_present_mode(wgpu::PresentMode::AutoNoVsync, available);
459+
assert_eq!(selected, wgpu::PresentMode::AutoNoVsync);
460+
}
461+
462+
#[test]
463+
fn select_present_mode_prefers_auto_vsync_when_requested() {
464+
let available = &[
465+
wgpu::PresentMode::Fifo,
466+
wgpu::PresentMode::AutoVsync,
467+
wgpu::PresentMode::Immediate,
468+
];
469+
let selected = select_present_mode(wgpu::PresentMode::AutoVsync, available);
470+
assert_eq!(selected, wgpu::PresentMode::AutoVsync);
471+
}
417472
}

crates/lambda-rs/src/render/mod.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,8 @@ impl RenderContextBuilder {
190190
})?;
191191

192192
let size = window.dimensions();
193-
let requested_present_mode = present_mode.unwrap_or_else(|| {
194-
if window.vsync_requested() {
195-
return PresentMode::Vsync;
196-
}
197-
return PresentMode::Immediate;
198-
});
199-
let platform_present_mode = match requested_present_mode {
200-
PresentMode::Vsync => targets::surface::PresentMode::Fifo,
201-
PresentMode::Immediate => targets::surface::PresentMode::Immediate,
202-
PresentMode::Mailbox => targets::surface::PresentMode::Mailbox,
203-
};
193+
let platform_present_mode =
194+
resolve_surface_present_mode(present_mode, window.vsync_requested());
204195
surface
205196
.configure_with_defaults(
206197
&gpu,
@@ -261,6 +252,23 @@ impl RenderContextBuilder {
261252
}
262253
}
263254

255+
fn resolve_surface_present_mode(
256+
builder_mode: Option<PresentMode>,
257+
window_vsync: bool,
258+
) -> targets::surface::PresentMode {
259+
let requested = builder_mode.unwrap_or(if window_vsync {
260+
PresentMode::Vsync
261+
} else {
262+
PresentMode::Immediate
263+
});
264+
265+
return match requested {
266+
PresentMode::Vsync => targets::surface::PresentMode::Fifo,
267+
PresentMode::Immediate => targets::surface::PresentMode::Immediate,
268+
PresentMode::Mailbox => targets::surface::PresentMode::Mailbox,
269+
};
270+
}
271+
264272
/// High‑level rendering context for a single window.
265273
///
266274
/// Purpose
@@ -1104,4 +1112,22 @@ mod tests {
11041112
.expect_err("must error");
11051113
assert!(err.to_string().contains("Unknown pipeline 7"));
11061114
}
1115+
1116+
#[test]
1117+
fn present_mode_defaults_to_window_vsync_true() {
1118+
let mode = resolve_surface_present_mode(None, true);
1119+
assert_eq!(mode, targets::surface::PresentMode::Fifo);
1120+
}
1121+
1122+
#[test]
1123+
fn present_mode_defaults_to_window_vsync_false() {
1124+
let mode = resolve_surface_present_mode(None, false);
1125+
assert_eq!(mode, targets::surface::PresentMode::Immediate);
1126+
}
1127+
1128+
#[test]
1129+
fn present_mode_builder_override_wins_over_window_setting() {
1130+
let mode = resolve_surface_present_mode(Some(PresentMode::Vsync), false);
1131+
assert_eq!(mode, targets::surface::PresentMode::Fifo);
1132+
}
11071133
}

crates/lambda-rs/src/render/window.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ impl WindowBuilder {
5656
///
5757
/// This value is consumed when building a `RenderContext` if no explicit
5858
/// present mode is provided to `RenderContextBuilder`.
59+
///
60+
/// Disabling vsync is best‑effort; the final present mode depends on the
61+
/// platform and adapter surface capabilities.
5962
pub fn with_vsync(mut self, vsync: bool) -> Self {
6063
self.vsync = vsync;
6164
return self;

0 commit comments

Comments
 (0)