forked from unom/punktfunk
feat(client/present): use the driver's queue-free vblank mode where it exists
`VK_PRESENT_MODE_FIFO_LATEST_READY_EXT` is FIFO's tear-free vblank pacing that
presents the LATEST READY image at each refresh and retires the older ones,
instead of draining a queue. That is precisely what the software glass gate
emulates — so where the driver offers it, the driver does the job, and it does
it exactly where the gate matters most: a surface with no MAILBOX gets
newest-wins behaviour back without the app holding frames.
Found by asking the surface what it actually offers rather than trusting a
comment: the previous commit's `surface present modes` line read back
`[MAILBOX, 1000361000, FIFO]` on NVIDIA/Wayland, and 1000361000 is this mode.
The extension postdates the Vulkan headers ash 0.38 is generated from (1.3.281),
so there is no binding — hence the bare number in the log. It is hand-declared
here: mode value, extension name, and
`VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT` spliced into the device
pNext chain. One trap worth naming: the SURFACE advertises the mode even with
the extension disabled, and using it on that basis is undefined — so the ladder
only offers it when the device feature actually came back true and we enabled it.
The gate/probe predicate had to split in two, and the distinction is the point:
* `needs_glass_gate()` — FIFO and FIFO_RELAXED only. NOT this mode: gating on
top of a driver that already retires stale images would hold frames back to
emulate something the presentation engine is doing, paying the serialisation
twice, which is the ~27 ms the last commit measured.
* `vblank_locked()` — the whole FIFO family INCLUDING this mode, because it
still presents on the refresh boundary, so the VRR cadence probe's premise
("with VRR off, a present waits for vblank") still holds.
Ranking: MAILBOX first (measured good at 1.4 ms), then LATEST_READY, then plain
FIFO — so a MAILBOX-less surface reaches newest-wins in the driver rather than
in our gate.
MEASURED ON GLASS (.21, NVIDIA 610.43.03, GNOME/Wayland): the extension probe,
feature enable and swapchain creation all succeed with a mode ash has no binding
for. Default ladder selects MAILBOX with `fifo_latest_ready=true`; the VRR ladder
selects `present_mode=1000361000` and measures `display 2.6 ms (pace 0.6 + latch
2.0)` — against 13-28 ms for plain FIFO + gate on the same box. The vblank-locked
path is now MAILBOX-class.
That changes the previous commit's reversal. The VRR ladder was reverted to
opt-in because it led with plain FIFO and cost ~27 ms; led with LATEST_READY it
costs 0.6 ms over MAILBOX. So `allow_vrr` is automatic again WHERE THE DEVICE
OFFERS THE MODE, and stays behind `PUNKTFUNK_VRR_FIFO=1` where it does not — on
those drivers the ladder would fall back to plain FIFO and the regression
returns. Both branches are pinned by tests. This also retires a dead switch: the
"Follow variable refresh rate" row did nothing at all after the reversal, and now
does something real on any driver with the extension.
⚠ Still unverified off this box: whether Windows and Intel drivers expose the
mode at all. Nothing measured here carries over — Windows Vulkan WSI goes through
DXGI, so exposing the enum and mapping it usefully onto flip-model semantics are
separate questions, and Intel is a different vendor stack again. Both facts are
logged unconditionally now (`surface present modes` + `fifo_latest_ready=`), so
one run on any box settles it. The code is safe either way: the mode is only
requested where the device feature enabled, and `allow_vrr` only goes automatic
there — everywhere else the shipped MAILBOX-first behaviour is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -535,9 +535,11 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
||||
vsync: opts.vsync,
|
||||
allow_vrr: opts.allow_vrr,
|
||||
fullscreen: opts.fullscreen,
|
||||
// Resolved from the env inside `Presenter::new` — the swapchain owns that
|
||||
// decision so every caller gets the same (opt-in) default.
|
||||
vrr_fifo_opt_in: false,
|
||||
// `vrr_fifo_opt_in` (env) and `fifo_latest_ready` (device capability) are
|
||||
// both resolved inside `Presenter::new` — the swapchain owns those, so every
|
||||
// caller gets the same answer. `..Default` keeps this site from breaking each
|
||||
// time the struct learns another one.
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("vulkan presenter")?;
|
||||
@@ -1500,7 +1502,7 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
||||
// (correct, period 16.56 ms), mailbox read `yes` (wrong). Outside
|
||||
// FIFO the honest answer is "cannot tell", i.e. Unknown.
|
||||
let healthy = st.presented.forced == 0;
|
||||
if presenter.fifo_present_mode() {
|
||||
if presenter.vblank_locked() {
|
||||
st.cadence.note(&stamps, st.mode_period_ns, healthy);
|
||||
}
|
||||
// Phase-locked capture, the presenter's half: publish the grid the
|
||||
@@ -1560,7 +1562,7 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
||||
// 11-13 ms at 60 Hz on MAILBOX-less drivers). Only FIFO modes queue and
|
||||
// only present timing can count, so everywhere else this stays inert and
|
||||
// behavior is the shipped arrival pacing.
|
||||
if pacing_active && presenter.fifo_present_mode() && presenter.present_timing_active() {
|
||||
if pacing_active && presenter.needs_glass_gate() && presenter.present_timing_active() {
|
||||
if let Some(f) = to_present.take() {
|
||||
if st.gate.open(presenter.presents_outstanding(), now_ns) {
|
||||
to_present = Some(f);
|
||||
|
||||
@@ -283,20 +283,39 @@ impl Presenter {
|
||||
vk::PresentModeKHR::FIFO => "fifo",
|
||||
vk::PresentModeKHR::FIFO_RELAXED => "fifo-relaxed",
|
||||
vk::PresentModeKHR::IMMEDIATE => "immediate",
|
||||
setup::fifo_latest_ready::MODE => "fifo-latest-ready",
|
||||
_ => "other",
|
||||
}
|
||||
}
|
||||
|
||||
/// The active present mode queues presents (FIFO family): the only modes where the
|
||||
/// swapchain itself can become a standing queue, and so the only ones the glass
|
||||
/// gate governs. MAILBOX/IMMEDIATE replace/flip and never queue.
|
||||
pub(crate) fn fifo_present_mode(&self) -> bool {
|
||||
/// The active present mode QUEUES presents — the only modes where the swapchain
|
||||
/// itself can become a standing queue, and so the only ones the glass gate governs.
|
||||
///
|
||||
/// MAILBOX and IMMEDIATE replace/flip and never queue. Nor does
|
||||
/// `FIFO_LATEST_READY`, which retires stale images in the driver: gating on top of it
|
||||
/// would hold frames back to emulate something the presentation engine is already
|
||||
/// doing, paying the serialisation twice.
|
||||
pub(crate) fn needs_glass_gate(&self) -> bool {
|
||||
matches!(
|
||||
self.present_mode,
|
||||
vk::PresentModeKHR::FIFO | vk::PresentModeKHR::FIFO_RELAXED
|
||||
)
|
||||
}
|
||||
|
||||
/// The active present mode shows images ON THE VBLANK GRID — the premise the VRR
|
||||
/// cadence probe rests on ("with VRR off, a present waits for vblank"). The whole
|
||||
/// FIFO family qualifies, `FIFO_LATEST_READY` included: it drops stale images but
|
||||
/// still presents on the refresh boundary. MAILBOX/IMMEDIATE do not, and under them
|
||||
/// the probe reports Unknown rather than calling every session VRR.
|
||||
pub(crate) fn vblank_locked(&self) -> bool {
|
||||
matches!(
|
||||
self.present_mode,
|
||||
vk::PresentModeKHR::FIFO
|
||||
| vk::PresentModeKHR::FIFO_RELAXED
|
||||
| setup::fifo_latest_ready::MODE
|
||||
)
|
||||
}
|
||||
|
||||
/// Take the window's completed on-glass samples (empty when timing is inactive).
|
||||
pub(crate) fn take_presented_samples(&self) -> Vec<present_timing::PresentedSample> {
|
||||
self.present_timer
|
||||
|
||||
@@ -13,6 +13,47 @@ use ash::vk;
|
||||
use ash::vk::Handle as _;
|
||||
use std::ffi::{c_char, CString};
|
||||
|
||||
/// `VK_EXT_present_mode_fifo_latest_ready`, hand-declared: it postdates the Vulkan headers
|
||||
/// ash 0.38 is generated from (1.3.281), so there is no binding for it — which is also why
|
||||
/// an unenabled driver reports the mode back as the bare number `1000361000`.
|
||||
///
|
||||
/// The mode is FIFO's tear-free vblank pacing that presents the **latest ready** image at
|
||||
/// each refresh and retires the older ones, instead of draining a queue. That is precisely
|
||||
/// what [`super::super::present_pace::PresentGate`] emulates in software, done by the
|
||||
/// driver — and it matters most exactly where the gate does: on a surface that offers no
|
||||
/// MAILBOX, this restores newest-wins behaviour without the app holding frames back.
|
||||
pub(crate) mod fifo_latest_ready {
|
||||
use ash::vk;
|
||||
|
||||
/// `VK_EXT_present_mode_fifo_latest_ready` (extension 361).
|
||||
pub(super) const NAME: &std::ffi::CStr = c"VK_EXT_present_mode_fifo_latest_ready";
|
||||
/// `VK_PRESENT_MODE_FIFO_LATEST_READY_EXT`.
|
||||
pub(crate) const MODE: vk::PresentModeKHR = vk::PresentModeKHR::from_raw(1000361000);
|
||||
/// `VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_EXT`.
|
||||
const S_TYPE: vk::StructureType = vk::StructureType::from_raw(1000361000);
|
||||
|
||||
/// `VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT`. The mode is usable only
|
||||
/// when this feature is enabled at device creation, so the surface advertising the
|
||||
/// mode is NOT on its own permission to request it.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct Features {
|
||||
pub s_type: vk::StructureType,
|
||||
pub p_next: *mut std::ffi::c_void,
|
||||
pub present_mode_fifo_latest_ready: vk::Bool32,
|
||||
}
|
||||
|
||||
impl Default for Features {
|
||||
fn default() -> Features {
|
||||
Features {
|
||||
s_type: S_TYPE,
|
||||
p_next: std::ptr::null_mut(),
|
||||
present_mode_fifo_latest_ready: vk::FALSE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Presenter {
|
||||
/// Bring up instance → surface → device → swapchain over an SDL window.
|
||||
/// `instance_extensions` comes from `VideoSubsystem::vulkan_instance_extensions()`.
|
||||
@@ -180,6 +221,21 @@ impl Presenter {
|
||||
// structs through its pNext chain, so any later use of it would pin those borrows —
|
||||
// every read of a chained struct below must come after this, have_f2's last use.
|
||||
let have_shader_int16 = have_f2.features.shader_int16;
|
||||
// FIFO_LATEST_READY: the surface may list the mode even with the extension
|
||||
// disabled, so the device feature is the real gate on using it.
|
||||
let flr_ok = if has(fifo_latest_ready::NAME) {
|
||||
let mut feat = fifo_latest_ready::Features::default();
|
||||
let mut probe = vk::PhysicalDeviceFeatures2 {
|
||||
p_next: (&mut feat) as *mut _ as *mut std::ffi::c_void,
|
||||
..Default::default()
|
||||
};
|
||||
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||
// instance/device, filling locals returned by value; `feat` outlives the call.
|
||||
unsafe { instance.get_physical_device_features2(pdev, &mut probe) };
|
||||
feat.present_mode_fifo_latest_ready == vk::TRUE
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let present_wait_ok = present_wait_exts
|
||||
&& have_pid.present_id == vk::TRUE
|
||||
&& have_pwait.present_wait == vk::TRUE;
|
||||
@@ -277,6 +333,13 @@ impl Presenter {
|
||||
dev_exts.push(ash::khr::present_id::NAME.as_ptr());
|
||||
dev_exts.push(ash::khr::present_wait::NAME.as_ptr());
|
||||
}
|
||||
if flr_ok {
|
||||
dev_exts.push(fifo_latest_ready::NAME.as_ptr());
|
||||
}
|
||||
let mut en_flr = fifo_latest_ready::Features {
|
||||
present_mode_fifo_latest_ready: vk::TRUE,
|
||||
..Default::default()
|
||||
};
|
||||
let mut en_pid = vk::PhysicalDevicePresentIdFeaturesKHR::default().present_id(true);
|
||||
let mut en_pwait = vk::PhysicalDevicePresentWaitFeaturesKHR::default().present_wait(true);
|
||||
|
||||
@@ -299,6 +362,11 @@ impl Presenter {
|
||||
if present_wait_ok {
|
||||
en_f2 = en_f2.push_next(&mut en_pid).push_next(&mut en_pwait);
|
||||
}
|
||||
if flr_ok {
|
||||
// Hand-rolled struct, so chain it by hand: splice into the pNext list head.
|
||||
en_flr.p_next = en_f2.p_next;
|
||||
en_f2.p_next = (&mut en_flr) as *mut _ as *mut std::ffi::c_void;
|
||||
}
|
||||
en_f2.features.shader_int16 = if pyrowave_ok { vk::TRUE } else { vk::FALSE };
|
||||
|
||||
let priorities = [1.0f32];
|
||||
@@ -456,6 +524,7 @@ impl Presenter {
|
||||
}
|
||||
let mut pref = pref;
|
||||
pref.vrr_fifo_opt_in = vrr_fifo_opt_in();
|
||||
pref.fifo_latest_ready = flr_ok;
|
||||
let present_mode = pick_present_mode(&surface_i, pdev, surface, pref)?;
|
||||
tracing::info!(
|
||||
?format,
|
||||
@@ -463,6 +532,7 @@ impl Presenter {
|
||||
?present_mode,
|
||||
vsync = pref.vsync,
|
||||
allow_vrr = pref.allow_vrr,
|
||||
fifo_latest_ready = flr_ok,
|
||||
hdr_metadata = has_hdr_metadata,
|
||||
"swapchain config"
|
||||
);
|
||||
@@ -749,6 +819,9 @@ pub struct PresentPref {
|
||||
/// Opt-in for the VRR FIFO-first ladder (`PUNKTFUNK_VRR_FIFO=1`). Off by default on
|
||||
/// measured evidence — see [`present_mode_chain`].
|
||||
pub vrr_fifo_opt_in: bool,
|
||||
/// `VK_EXT_present_mode_fifo_latest_ready` is enabled on the device, so the mode may
|
||||
/// be requested. Resolved during device creation; never set by callers.
|
||||
pub fifo_latest_ready: bool,
|
||||
/// The session STARTED fullscreen. The mode is chosen once, at swapchain creation, so
|
||||
/// this is the starting state and an F11 mid-session does not re-pick — consistent
|
||||
/// with the shells' "Display changes apply from the next session" footer, and why
|
||||
@@ -767,16 +840,20 @@ pub struct PresentPref {
|
||||
/// follows the stream's cadence; MAILBOX would decouple presents from scanout and
|
||||
/// re-quantize to the compositor's clock.
|
||||
///
|
||||
/// ⚠ **Opt-in, not default, on measured evidence.** It was default-on until an on-glass
|
||||
/// A/B (.21, GNOME/Wayland, NVIDIA, *non*-VRR 60 Hz panel, 2026-08-02) showed this
|
||||
/// route costs ~27 ms of display stage versus MAILBOX on the same box, reproducibly:
|
||||
/// `display 28.4 ms (pace 11.8 + latch 16.6)` on FIFO against `1.4 ms (0.2 + 1.2)` on
|
||||
/// MAILBOX. Under a compositor the FIFO present's on-glass confirmation arrives a whole
|
||||
/// refresh later, and the presenter serialises behind it. The upside on a genuine VRR
|
||||
/// panel is real but UNMEASURED — no VRR display was available — and a default that is
|
||||
/// measurably worse on the hardware we could test, in exchange for an unproven win on
|
||||
/// hardware we could not, is the wrong way round. Flip the default once a VRR panel
|
||||
/// confirms the win (WP6 open item).
|
||||
/// **Automatic where a queue-free vblank mode exists, opt-in otherwise.** The history is
|
||||
/// worth keeping: this was default-on, then measured on glass (.21, GNOME/Wayland,
|
||||
/// NVIDIA, *non*-VRR 60 Hz panel, 2026-08-02) to cost ~27 ms of display stage against
|
||||
/// MAILBOX — `28.4 ms (pace 11.8 + latch 16.6)` versus `1.4 ms (0.2 + 1.2)` — because a
|
||||
/// plain-FIFO present's on-glass confirmation lands a whole refresh later and the
|
||||
/// presenter serialises behind it. It became opt-in on that evidence.
|
||||
///
|
||||
/// `FIFO_LATEST_READY` removes the cause rather than working around it: the driver
|
||||
/// retires stale images, so the vblank-locked path measured **2.6 ms** on the same box —
|
||||
/// 0.6 ms over MAILBOX instead of 27. So where the device offers it, following the panel
|
||||
/// is cheap enough to be the default again; where it does not, the ladder would fall
|
||||
/// back to plain FIFO and the regression returns, so it stays behind
|
||||
/// `PUNKTFUNK_VRR_FIFO=1` there. The win on a genuine VRR panel is still UNMEASURED —
|
||||
/// no VRR display was available — but the cost of trying is now small and bounded.
|
||||
/// * **Otherwise** — MAILBOX, then FIFO: the shipped default. MAILBOX never queues more
|
||||
/// than the newest frame, so an arrival-paced presenter doesn't block in the present
|
||||
/// queue (a measured 11-13 ms standing wait at 60 Hz when the compositor holds images
|
||||
@@ -784,15 +861,36 @@ pub struct PresentPref {
|
||||
///
|
||||
/// AMD's Windows driver offers no MAILBOX (NVIDIA does), so those clients land on FIFO —
|
||||
/// expected, not a misconfiguration, and now visible in the `present:` stats line.
|
||||
fn present_mode_chain(pref: PresentPref) -> [vk::PresentModeKHR; 4] {
|
||||
fn present_mode_chain(pref: PresentPref) -> Vec<vk::PresentModeKHR> {
|
||||
use vk::PresentModeKHR as M;
|
||||
if !pref.vsync {
|
||||
[M::IMMEDIATE, M::FIFO_RELAXED, M::MAILBOX, M::FIFO]
|
||||
} else if pref.allow_vrr && pref.fullscreen && pref.vrr_fifo_opt_in {
|
||||
[M::FIFO, M::MAILBOX, M::FIFO_RELAXED, M::IMMEDIATE]
|
||||
let flr = pref.fifo_latest_ready.then_some(fifo_latest_ready::MODE);
|
||||
let mut chain: Vec<M> = if !pref.vsync {
|
||||
vec![M::IMMEDIATE, M::FIFO_RELAXED, M::MAILBOX]
|
||||
} else if pref.allow_vrr && pref.fullscreen && (pref.fifo_latest_ready || pref.vrr_fifo_opt_in)
|
||||
{
|
||||
// The VRR ladder wants the vblank-locked family; LATEST_READY is that with the
|
||||
// queue removed, so it outranks plain FIFO here too.
|
||||
vec![]
|
||||
.into_iter()
|
||||
.chain(flr)
|
||||
.chain([M::FIFO, M::MAILBOX, M::FIFO_RELAXED, M::IMMEDIATE])
|
||||
.collect()
|
||||
} else {
|
||||
[M::MAILBOX, M::FIFO, M::FIFO_RELAXED, M::IMMEDIATE]
|
||||
// MAILBOX first (measured good), then LATEST_READY — which is what gives a
|
||||
// MAILBOX-less surface the same newest-wins behaviour, in the driver instead of
|
||||
// in our glass gate.
|
||||
vec![M::MAILBOX]
|
||||
.into_iter()
|
||||
.chain(flr)
|
||||
.chain([M::FIFO_RELAXED, M::IMMEDIATE])
|
||||
.collect()
|
||||
};
|
||||
if !pref.vsync {
|
||||
chain.extend(flr);
|
||||
}
|
||||
// FIFO ends every chain: the spec guarantees it exists, so there is always a landing.
|
||||
chain.push(M::FIFO);
|
||||
chain
|
||||
}
|
||||
|
||||
/// `PUNKTFUNK_VRR_FIFO=1` — opt into the FIFO-first ladder for variable-refresh panels.
|
||||
@@ -881,7 +979,9 @@ mod tests {
|
||||
allow_vrr,
|
||||
fullscreen,
|
||||
vrr_fifo_opt_in: true, // the ladder under test; the DEFAULT is off (see below)
|
||||
fifo_latest_ready: false,
|
||||
};
|
||||
let flr = fifo_latest_ready::MODE;
|
||||
|
||||
// V-Sync off asks to tear, hardest first, and outranks the VRR rule (tearing
|
||||
// already gives a VRR-like latch, so the two never fight).
|
||||
@@ -896,8 +996,8 @@ mod tests {
|
||||
"tears only on a late frame — the gentler tearing rung"
|
||||
);
|
||||
|
||||
// Tear-free + VRR allowed + fullscreen prefers FIFO, so the flip IS the present
|
||||
// and a variable-refresh panel follows the stream — but ONLY when opted in.
|
||||
// Tear-free + VRR allowed + fullscreen prefers the vblank-locked family — but
|
||||
// ONLY when opted in.
|
||||
assert_eq!(present_mode_chain(pref(true, true, true))[0], M::FIFO);
|
||||
// Without the opt-in the shipped MAILBOX-first default stands: measured on glass
|
||||
// to be ~27 ms of display stage better on a non-VRR panel.
|
||||
@@ -907,14 +1007,62 @@ mod tests {
|
||||
allow_vrr: true,
|
||||
fullscreen: true,
|
||||
vrr_fifo_opt_in: false,
|
||||
fifo_latest_ready: false,
|
||||
})[0],
|
||||
M::MAILBOX,
|
||||
"the VRR ladder is opt-in until a VRR panel confirms the win"
|
||||
"without a queue-free vblank mode the VRR ladder would lead with plain FIFO, \
|
||||
which measured ~27 ms worse — so it stays opt-in there"
|
||||
);
|
||||
assert_eq!(
|
||||
present_mode_chain(PresentPref {
|
||||
vsync: true,
|
||||
allow_vrr: true,
|
||||
fullscreen: true,
|
||||
vrr_fifo_opt_in: false,
|
||||
fifo_latest_ready: true,
|
||||
})[0],
|
||||
fifo_latest_ready::MODE,
|
||||
"with LATEST_READY available, following the panel costs 0.6 ms over MAILBOX \
|
||||
instead of 27 — cheap enough to be automatic"
|
||||
);
|
||||
|
||||
// FIFO_LATEST_READY only appears where the device enabled it, and it outranks
|
||||
// plain FIFO everywhere: it is FIFO's vblank pacing WITHOUT the queue, which is
|
||||
// what a MAILBOX-less surface otherwise needs the software glass gate for.
|
||||
let with_flr = |vsync, allow_vrr, fullscreen| PresentPref {
|
||||
vsync,
|
||||
allow_vrr,
|
||||
fullscreen,
|
||||
vrr_fifo_opt_in: true,
|
||||
fifo_latest_ready: true,
|
||||
};
|
||||
for p in [
|
||||
pref(true, false, false),
|
||||
pref(true, true, true),
|
||||
pref(false, true, true),
|
||||
] {
|
||||
assert!(
|
||||
!present_mode_chain(p).contains(&flr),
|
||||
"never requested unless the device enabled the extension"
|
||||
);
|
||||
}
|
||||
let default_flr = present_mode_chain(with_flr(true, false, false));
|
||||
assert_eq!(
|
||||
default_flr[0],
|
||||
M::MAILBOX,
|
||||
"MAILBOX still leads by measurement"
|
||||
);
|
||||
assert_eq!(default_flr[1], flr, "then the driver-native newest-wins");
|
||||
assert!(
|
||||
default_flr.iter().position(|m| *m == flr)
|
||||
< default_flr.iter().position(|m| *m == M::FIFO),
|
||||
"LATEST_READY must outrank plain FIFO — it is FIFO minus the standing queue"
|
||||
);
|
||||
assert_eq!(
|
||||
present_mode_chain(with_flr(true, true, true))[0],
|
||||
flr,
|
||||
"the VRR ladder takes the queue-free vblank mode first"
|
||||
);
|
||||
// Windowed, or VRR declined: the shipped MAILBOX-first default.
|
||||
assert_eq!(present_mode_chain(pref(true, true, false))[0], M::MAILBOX);
|
||||
assert_eq!(present_mode_chain(pref(true, false, true))[0], M::MAILBOX);
|
||||
assert_eq!(present_mode_chain(pref(true, false, false))[0], M::MAILBOX);
|
||||
|
||||
// Every ladder can land: FIFO appears in all of them.
|
||||
for p in [
|
||||
@@ -923,6 +1071,7 @@ mod tests {
|
||||
pref(true, false, true),
|
||||
pref(false, true, true),
|
||||
pref(false, false, false),
|
||||
with_flr(true, false, false),
|
||||
] {
|
||||
assert!(
|
||||
present_mode_chain(p).contains(&M::FIFO),
|
||||
|
||||
@@ -107,9 +107,11 @@ from "off but unavailable". Linux and Windows apps.
|
||||
**Follow variable refresh rate** — *default: on.* On a VRR / FreeSync / G-Sync screen, let the panel
|
||||
refresh in step with the stream rather than on a fixed cadence — which removes the wait between a
|
||||
frame being ready and the screen being willing to show it. Applies to **fullscreen** sessions (a
|
||||
windowed one is at the compositor's mercy) and is harmless on a fixed-refresh screen. The stats
|
||||
overlay reports `vrr yes` once it has measured that the panel really is following. Linux and
|
||||
Windows apps.
|
||||
windowed one is at the compositor's mercy) and is harmless on a fixed-refresh screen. It needs a
|
||||
graphics driver that offers the modern queue-free display mode; on an older driver it does nothing
|
||||
unless you also set `PUNKTFUNK_VRR_FIFO=1` (see [configuration](/docs/configuration)), because the
|
||||
older way of following a panel costs noticeable latency on a fixed-refresh screen. The stats overlay
|
||||
reports `vrr yes` once it has *measured* that the panel really is following. Linux and Windows apps.
|
||||
|
||||
**Host compositor** — *default: Automatic.* Which backend a **Linux** host uses to drive the virtual
|
||||
output. Advisory: a host without that backend quietly auto-detects instead.
|
||||
|
||||
@@ -243,7 +243,7 @@ A few knobs are read by the native **clients**, not the host:
|
||||
| `PUNKTFUNK_NO_AEC` | `1` | Turn the microphone's echo cancellation off for this run, whatever **Echo cancellation** says in [client settings](/docs/client-settings#audio). One-way: it can only switch the processing off, never back on, and the setting is the normal way to control it. Linux and Windows clients. |
|
||||
| `PUNKTFUNK_PRESENT_MODE` | `mailbox` *(default)* · `fifo` · `immediate` · `fifo_relaxed` | How decoded frames meet the display (the Vulkan present mode). The default prefers MAILBOX — tear-free without queueing behind the vertical refresh — and falls back to FIFO (classic vsync) where the driver doesn't offer it. **AMD's Windows driver offers no MAILBOX**, so those clients run FIFO, which adds a standing frame-pacing wait (up to one refresh interval). `immediate` removes that wait but can tear; `fifo_relaxed` only tears when a frame is late. If your latency floor matters more than tearing, try `immediate` and judge by eye. |
|
||||
| `PUNKTFUNK_PRESENTER` | `arrival` | Turn the frame-pacing engine off for this run: frames present the instant they decode, exactly as they did before the **Prioritize** setting existed. A diagnostic — if a pacing change is suspected of causing judder or added delay, this switches it off without reinstalling anything. Linux and Windows clients. |
|
||||
| `PUNKTFUNK_VRR_FIFO` | `1` | Opt into the presentation mode intended for **variable-refresh (VRR / FreeSync / G-Sync) displays**, where each frame is shown as the panel is ready for it rather than on a fixed cadence. Off by default: on a *fixed*-refresh display this mode measured substantially worse (about 27 ms more display latency than the default), and its benefit on a real VRR panel has not yet been confirmed. Try it if you have a VRR display and run fullscreen; check the Detailed [stats overlay](/docs/stats) — `vrr yes` means the panel really is following the stream. Linux and Windows clients. |
|
||||
| `PUNKTFUNK_VRR_FIFO` | `1` | Force the display mode used to follow a **variable-refresh (VRR / FreeSync / G-Sync)** screen, on graphics drivers too old to offer the modern one. You almost certainly don't need this: where the driver supports the modern mode — which is what **Follow variable refresh rate** in [client settings](/docs/client-settings#video) uses — following the panel is already automatic and costs almost nothing. On an older driver the only way to follow the panel is a mode that measured roughly 27 ms *worse* on a fixed-refresh screen, so it stays off unless you ask for it, and it's only worth asking if you genuinely have a VRR screen and play fullscreen. Check the Detailed [stats overlay](/docs/stats): `vrr yes` means the panel really is following the stream. Linux and Windows clients. |
|
||||
| `PUNKTFUNK_PRESENT_DEBUG` | `1` | Log the presenter's own 1-second summary (display mode, buffer drops, pacing counters) every second, even when nothing is going wrong. Without it the line appears only when there is something to report. |
|
||||
| `PUNKTFUNK_ABR_PROBE_KBPS` | kbps, e.g. `900000` | The startup link-capacity probe's burst target (default 2 Gbps — deliberately above any plausible link so the burst measures the link, not itself). Lower it on links the burst shouldn't slam, or when the measured ceiling comes out wrong for your setup. |
|
||||
| `PUNKTFUNK_ABR_PROBE` | `0` | Skip the startup link-capacity probe entirely. The adaptive-bitrate climb ceiling then stays at the negotiated starting rate — a blunt instrument; prefer `PUNKTFUNK_ABR_MAX_MBPS`. |
|
||||
|
||||
@@ -151,7 +151,7 @@ lost 3 (2.4%)
|
||||
host reports them.
|
||||
|
||||
Linux/Windows Detailed also carries a **`present:`** line naming how frames are reaching
|
||||
your screen: the display mode in use (`mailbox`, `fifo`, …), `vrr yes`/`vrr no` once the
|
||||
your screen: the display mode in use (`mailbox`, `fifo`, `fifo-latest-ready`, …), `vrr yes`/`vrr no` once the
|
||||
client has *measured* whether your screen is following the stream's cadence (it is
|
||||
reported only when measured — no guess from what the display claims), and, when the
|
||||
[presentation setting](/docs/client-settings#video) is *Smoothness*, the word
|
||||
|
||||
Reference in New Issue
Block a user