From 4ec1698d30a665d7e0f4bef23e95147f16475d20 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Sat, 4 Jul 2026 17:04:05 +0200 Subject: [PATCH] =?UTF-8?q?feat(render):=20setPresentMode=20FFI=20?= =?UTF-8?q?=E2=80=94=20Fifo/Mailbox/Immediate=20at=20runtime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 audit F6: the present mode was hardcoded Fifo at both surface config sites, which also made setTargetFPS inert — its sleep-based cap only engages when vsync is off, and vsync could never be off. - Renderer::set_present_mode(0=Fifo, 1=Mailbox, 2=Immediate): reconfigures the live surface on change; no-op when unchanged; logs the switch. All three modes are DXGI-native on Windows. - FFI bloom_set_present_mode + TS setPresentMode + manifest entry (Perry silently no-ops undeclared natives). Validated on the live game at 4K: "bloom: present mode = Immediate" followed by getFPS pinning at a steady 29 under setTargetFPS(30) — the first time the cap has ever engaged. This is the knob that makes audit decision D1 (operating point) testable: cap-45 / cap-60 / uncapped can now be compared like-for-like. --- native/shared/src/ffi_core/visual.rs | 8 ++++++++ native/shared/src/renderer/mod.rs | 22 ++++++++++++++++++++++ package.json | 7 +++++++ src/core/index.ts | 11 +++++++++++ 4 files changed, 48 insertions(+) diff --git a/native/shared/src/ffi_core/visual.rs b/native/shared/src/ffi_core/visual.rs index a2218b8..589de12 100644 --- a/native/shared/src/ffi_core/visual.rs +++ b/native/shared/src/ffi_core/visual.rs @@ -218,6 +218,14 @@ macro_rules! __bloom_ffi_visual { }) } + // bloom_set_present_mode [round-2 audit F6] + #[no_mangle] + pub extern "C" fn bloom_set_present_mode(mode: f64) { + $crate::ffi::guard("bloom_set_present_mode", move || { + engine().renderer.set_present_mode(mode as u32); + }) + } + // bloom_set_sun_shafts [source: macos] #[no_mangle] pub extern "C" fn bloom_set_sun_shafts(strength: f64, decay: f64, r: f64, g: f64, b: f64) { diff --git a/native/shared/src/renderer/mod.rs b/native/shared/src/renderer/mod.rs index ad66ec3..099d3e8 100644 --- a/native/shared/src/renderer/mod.rs +++ b/native/shared/src/renderer/mod.rs @@ -7851,6 +7851,28 @@ impl Renderer { self.grain_strength = strength.max(0.0); } + /// Present mode: 0 = Fifo (vsync), 1 = Mailbox (uncapped, no tearing), + /// 2 = Immediate (uncapped, tearing allowed). Round-2 audit F6: the + /// mode was hardcoded Fifo, which also made `set_target_fps` inert + /// (its sleep-based cap only engages when vsync is off). All three + /// modes are supported by DXGI on Windows; on other backends an + /// unsupported request falls back to Fifo at configure time. + pub fn set_present_mode(&mut self, mode: u32) { + let requested = match mode { + 1 => wgpu::PresentMode::Mailbox, + 2 => wgpu::PresentMode::Immediate, + _ => wgpu::PresentMode::Fifo, + }; + if self.surface_config.present_mode == requested { + return; + } + self.surface_config.present_mode = requested; + if let Some(surface) = &self.surface { + surface.configure(&self.device, &self.surface_config); + } + eprintln!("bloom: present mode = {:?}", requested); + } + /// Sun shaft (screen-space god ray) strength. 0 (default) = off. /// 0.4 = subtle haze, 1.0+ = obvious cinematic shafts. The /// shafts are sampled from the depth buffer along a screen-space diff --git a/package.json b/package.json index dfa63c0..7094c3a 100644 --- a/package.json +++ b/package.json @@ -1229,6 +1229,13 @@ ], "returns": "void" }, + { + "name": "bloom_set_present_mode", + "params": [ + "f64" + ], + "returns": "void" + }, { "name": "bloom_set_sun_shafts", "params": [ diff --git a/src/core/index.ts b/src/core/index.ts index f1be5fe..0c218e6 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -20,6 +20,7 @@ declare function bloom_set_fog(r: number, g: number, b: number, density: number, declare function bloom_set_chromatic_aberration(strength: number): void; declare function bloom_set_vignette(strength: number, softness: number): void; declare function bloom_set_film_grain(strength: number): void; +declare function bloom_set_present_mode(mode: number): void; declare function bloom_set_sun_shafts(strength: number, decay: number, r: number, g: number, b: number): void; declare function bloom_set_auto_exposure(on: number): void; declare function bloom_set_taa_enabled(on: number): void; @@ -265,6 +266,16 @@ export function setFilmGrain(strength: number): void { bloom_set_film_grain(strength); } +/** + * Swapchain present mode: 0 = Fifo (vsync, default), 1 = Mailbox + * (uncapped, no tearing), 2 = Immediate (uncapped, tearing allowed). + * With a non-vsync mode active, `setTargetFPS`'s sleep-based cap + * becomes effective — under Fifo it is inert by design. + */ +export function setPresentMode(mode: number): void { + bloom_set_present_mode(mode); +} + /** Screen-space sun shafts (god rays). strength 0 = off. */ export function setSunShafts(strength: number, decay: number, r: number, g: number, b: number): void { bloom_set_sun_shafts(strength, decay, r, g, b);