Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 26 additions & 20 deletions docs/10-fps-limit.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ divisor cap reads its target exactly.
| mailbox (default) | none, guest asked for FIFO (in-game VSync on) | fifo (`source=guest`) | the display (FIFO) |
| mailbox (default) | none, guest asked for mailbox or immediate | mailbox | nobody |
| mailbox | at the display's rate, guest asked for FIFO (in-game VSync on) | mailbox (`source=cap-rate-vsync`) | the shim's acquire gate, every vblank; the present wait is answered when the guest queues the present |
| mailbox | at the display's rate, guest asked for mailbox or immediate | fifo (`source=cap-rate`) | gamescope's limiter (divisor 1), one FIFO commit per vblank; no gate, as on master |
| mailbox | at the display's rate, guest asked for mailbox or immediate | mailbox (`source=cap-rate`) | the shim's acquire gate, every vblank |
| mailbox | below the display's rate | mailbox | the shim's acquire gate, on the display's vblank grid |
| fifo | none | fifo | the display (FIFO) |
| fifo | set | fifo | gamescope's divisor limiter |
Expand Down Expand Up @@ -148,12 +148,20 @@ divisor cap reads its target exactly.
`fifo_relaxed` included, is rejected as Mesa rejects it, the guest's own mode is
used, and the shim logs `Invalid MESA_VK_WSI_PRESENT_MODE value!` whether or not
`GNDRM_LOG_VKSHIM` is set, so a typo shows in a shipped session. It is
overridden in one case: a non-FIFO value under a cap at the display's rate is
declared FIFO (`source=cap-rate`, below). Mesa reads the same variable, so it
also sets the mode a zink guest's own swapchain asks for below the shim.
overridden in one case: a non-FIFO value under a cap is declared FIFO until the
daemon's stamp lead is known (`source=cap-compat`, below). Mesa reads the same
variable, so it also sets the mode a zink guest's own swapchain asks for below the shim.

**Why mailbox is not declared at the display's rate.** At that cap the panel
shows one frame per vblank under either mode, and under mailbox the shim has to
**Mailbox at the display's rate, and what it costs.** At that cap the panel
shows one frame per vblank under either mode. A self-paced guest was declared
FIFO here with no gate, on the assumption that gamescope's FIFO holds it; it
does not hold a guest faster than the panel. Retroid Pocket 6, Portal 2 main
menu, in-game VSync off, cap 60 on a 60 Hz panel: per 10 s ~1150 acquires,
commits and releases against the daemon's 600 publishes, 112-120 fps. Declared
the container's mailbox with the gate every vblank (`source=cap-rate`), the
same scene runs 600 acquires, commits, releases and `gate_holds` per 10 s,
597-600 publishes, 60.0 fps, 59.98 presents/s on `SurfaceFlinger` and no
doubled interval in 125. Under mailbox the shim has to
keep two commits out of one gamescope composite. It cannot see when gamescope
latches: gamescope takes commits at its wakeup, the target vblank minus its
rolling draw time and red zone (`vblankmanager.cpp` 142-199), and that draw time
Expand All @@ -165,11 +173,10 @@ divisor cap reads its target exactly.
VSync off, a guest pacing itself at ~59.3 fps on a 59.98 Hz panel): mailbox with
the gate gave 58.3 fps on `SurfaceFlinger` with ~1 commit a second replaced in
gamescope's mailbox; a one-commit-per-window queue in the shim at a guessed
2.5 ms boundary gave 59.0 with ~0.8 a second still replaced; FIFO on master
gives 59.22 on the same scene. Under FIFO gamescope latches one commit per
vblank at its own wakeup (`steamcompmgr.cpp` 8028-8049) and its limiter's
divisor at this cap is 1, which is master's behaviour in this cell. That holds
for a guest that asked for mailbox or immediate, which paces itself. A guest
2.5 ms boundary gave 59.0 with ~0.8 a second still replaced; FIFO gave 59.22
on the same scene. That cost, ~1 doubled frame a second for a guest that paces
itself just below the display's rate, is what mailbox pays here; a guest
faster than the cap is held on the grid by the gate and does not pay it. A guest
that asked for FIFO (in-game VSync on) waits on each frame's presentation
before starting the next; declared FIFO, that wait is gamescope's `presented`
at its latch, so the whole frame has to fit between two latches, and with
Expand All @@ -185,11 +192,10 @@ divisor cap reads its target exactly.
case answered at queue; every other mailbox or immediate presentation is
answered at the commit, as on master. A container set to
`MESA_VK_WSI_PRESENT_MODE=fifo` never reaches it, so setting a game's
container to fifo restores master's behaviour for that game. The
vblank-grid gate is not kept on top of it: FIFO's back-pressure already holds
the guest, and the two stacked turned a guest whose acquire-to-commit grew
past a period into a 40 fps session on a 60 Hz panel (DTD on a 1280x960
device after a live 120 to 60 Hz switch: `gate_holds` 0, `gate_late` 400 and
container to fifo restores master's behaviour for that game. FIFO with the
vblank-grid gate stacked on it is worse than either: the two turned a guest
whose acquire-to-commit grew past a period into a 40 fps session on a 60 Hz
panel (DTD on a 1280x960 device after a live 120 to 60 Hz switch: `gate_holds` 0, `gate_late` 400 and
`skipped` 200 per 10 s, commit latency 24 ms, 400/600 publishes). Below the
display's rate the gate's releases
are at least two vblanks apart and two frames in one composite are rare, so
Expand Down Expand Up @@ -227,10 +233,10 @@ divisor cap reads its target exactly.
whenever the declared mode, its source, the cap or the gate changes:
`present mode: declared=… requested=… source=env|guest|cap-rate|cap-compat
route=gamescope cap=… gate=vsync|clock|off lsfg=…` (`source=guest`: the guest's
own mode, no env or a FIFO request with no cap; `source=cap-rate`: declared FIFO
because the cap is the display's rate and the guest paces itself, with
`gate=off`; `source=cap-rate-vsync`: the container's mode with `gate=vsync`
because the cap is the display's rate and the guest asked for FIFO; `source=cap-compat`:
own mode, no env or a FIFO request with no cap; `source=cap-rate`: the
container's mode with `gate=vsync` because the cap is the display's rate and the
guest paces itself; `source=cap-rate-vsync`: the same because the cap is the
display's rate and the guest asked for FIFO; `source=cap-compat`:
declared FIFO for want of the stamp lead, with `gate=off`; `gate=clock` is frame
generation's source rate or a cap above the display's rate). With
`GNDRM_LOG_VKSHIM=1` the same line repeats every 10 s with the counters a device
Expand Down
44 changes: 24 additions & 20 deletions docs/15-fps-limiter-pacing-and-vsync.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ cursor, scan-out and mode patches.

| place | what it holds | set by |
|---|---|---|
| the shim's declared present mode | the container's `MESA_VK_WSI_PRESENT_MODE` (else the guest's mode); with no cap, a guest's FIFO request over a non-FIFO env; FIFO under a cap at the display's rate; mailbox under frame generation | the container, the guest and the cap (`docs/10`) |
| gamescope's own limiter | `nRefreshHz / nTargetFPS`, a divisor of the mode's rate; holds FIFO commits only | `debug_set_fps_limit`, sent only when generation is off |
| the shim's declared present mode | the container's `MESA_VK_WSI_PRESENT_MODE` (else the guest's mode); with no cap, a guest's FIFO request over a non-FIFO env; mailbox under frame generation | the container, the guest and the cap (`docs/10`) |
| gamescope's own limiter | `nRefreshHz / nTargetFPS`, a divisor of the mode's rate; holds FIFO commits only | `set_app_target_refresh_cycle`, sent by the daemon's injector only when generation is off |
| the shim's acquire gate | the cap, on the display's vblank grid, unless the container's mode is FIFO | `FrameCapNote{fps}` and `VsyncNote` from the daemon |
| the presenter's latch grid | the display's latch points, as measured | `VsyncNote` from the daemon (`docs/17`) |

Expand All @@ -22,12 +22,10 @@ fifo, or no env and a FIFO guest), gamescope's limiter passes every
and holds the guest's commits to it. Declared mailbox, immediate or FIFO-relaxed (mailbox
is the container default), the limiter does not hold them and the shim's acquire gate
releases the guest's frames on the display's vblank grid. A container in one of those
modes under a cap at the display's rate is declared FIFO with no gate when the guest
asked for mailbox or immediate (`source=cap-rate`), as on master, and keeps its mode with
the gate every vblank when the guest asked for FIFO (`source=cap-rate-vsync`, `docs/10`).
For the former: gamescope latches one commit per vblank at its own
wakeup, which the shim cannot see under mailbox, and its limiter (divisor 1) paces the
guest (`docs/10`). Under frame generation the app
modes under a cap at the display's rate keeps its mode with the gate every vblank,
whatever the guest asked for: `source=cap-rate` for a guest that asked for mailbox or
immediate, `source=cap-rate-vsync` for one that asked for FIFO (`docs/10`). Under frame
generation the app
disarms gamescope's limiter and the shim's time gate holds the source rate instead
(`docs/17`).

Expand Down Expand Up @@ -137,9 +135,7 @@ that rate is the display's.
commit per limited vblank, on the display's vblank stream above, and hands the
previous buffer back with `wl_buffer.release`, which is what frees the guest's next
acquire; a cap that is not the display's rate over a whole number runs at gamescope's
next divisor rate above it. Declared FIFO because the cap is the display's rate
(`source=cap-rate`), no gate runs either: gamescope's FIFO latch alone paces it.
Declared anything else,
next divisor rate above it. Declared anything else,
gamescope takes the newest commit at each composite and the shim's acquire gate is
the cap (`icd_shim/src/vsync_gate.rs`).
Its release points are the instants the flip events are stamped at: the daemon's
Expand Down Expand Up @@ -177,15 +173,12 @@ that rate is the display's.

## Behaviour changes in gamescope sessions

- **The container's present mode is declared, except under a cap at the display's
rate.** `MESA_VK_WSI_PRESENT_MODE` decides the mode on the gamescope route as on the
X11 Present route; without it the guest's own mode is declared. A container left at
- **The container's present mode is declared.** `MESA_VK_WSI_PRESENT_MODE` decides
the mode on the gamescope route as on the X11 Present route; without it the guest's own mode is declared. A container left at
the mailbox default runs, with no cap, FIFO for a guest that asked for FIFO (its
own VSync, `source=guest`) and mailbox for any other guest, and mailbox capped by the shim's
vblank-grid gate under a cap below the display's rate. Under a cap at the display's
rate it is declared FIFO with no gate (`source=cap-rate`), because the shim
cannot keep two mailbox commits out of one gamescope composite (`docs/10`). One set
to fifo runs FIFO under gamescope's limiter.
vblank-grid gate under any cap (`source=cap-rate` or `source=cap-rate-vsync` at the
display's rate, `docs/10`). One set to fifo runs FIFO under gamescope's limiter.
- **A game below the cap keeps its own rate under mailbox** (caps below the display's
rate). Under FIFO and gamescope's
limiter each frame is held to whole limited vblanks, so a game below the cap judders
Expand Down Expand Up @@ -251,8 +244,19 @@ declaration on the same scene (59.22 fps, 1.3 % doubles):
0-2 per 10 s (the guest paces itself at ~59.3); VSync on, 59.983 fps and no
doubles.
- **The same cap declared mailbox with the gate** lost ~1 commit a second inside
gamescope's mailbox (58.3 fps, 2.8 % doubles), which is why it is declared FIFO
(`docs/10`).
gamescope's mailbox (58.3 fps, 2.8 % doubles). It is declared that way now
anyway, because the FIFO declaration does not hold a guest faster than the panel
(below, and `docs/10`).

Retroid Pocket 6 (60 Hz), Portal 2 main menu, cap 60, in-game VSync off (the guest
asks for immediate):

- **Declared FIFO, no gate** (`source=cap-rate gate=off`): per 10 s 1131-1193
acquires, commits and releases against the daemon's 600 publishes; 112-120 fps.
- **Declared mailbox, the gate every vblank** (`source=cap-rate gate=vsync`): per
10 s 600 acquires, commits, releases and `gate_holds`, `gate_late` and
`slot_waits` 0, 597-600 publishes; 60.0 fps, 59.98 presents/s on
`SurfaceFlinger`, no doubled interval in 125.

Retroid Pocket Nova (120 Hz panel, a 60 Hz mode available), Hades II, Vulkan through
zink, measured before the vblank-grid gate, when the acquire gate ran on the cap's
Expand Down
69 changes: 34 additions & 35 deletions icd_shim/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6908,13 +6908,13 @@ mod tests {
choose_live_mode(mode, ModeSource::Guest, mode, false, 0, None),
(mode, Declared::Mode)
);
// A cap at the display's rate: FIFO for a guest that asked for
// mailbox or immediate, the swapchain's mode (with the gate) for
// one that asked for FIFO-relaxed.
// A cap at the display's rate: the swapchain's mode (with the
// gate), cap-rate for a guest that asked for mailbox or immediate,
// cap-rate-vsync for one that asked for FIFO-relaxed.
let at_rate = if mode == r {
(r, Declared::CapRateVsync)
} else {
(f, Declared::CapRate)
(mode, Declared::CapRate)
};
assert_eq!(
choose_live_mode(mode, ModeSource::Guest, mode, false, 60, g60),
Expand Down Expand Up @@ -6968,11 +6968,11 @@ mod tests {
gamescope_route_pacing((f, Declared::CapCompat), None, 60, None),
Pacing::Off
);
// FIFO at the display's rate: no gate either; gamescope's limiter
// (divisor 1) paces it, as on master.
// Mailbox at the display's rate for a self-paced guest: the gate,
// every vblank.
assert_eq!(
gamescope_route_pacing((f, Declared::CapRate), None, 60, grid),
Pacing::Off
gamescope_route_pacing((m, Declared::CapRate), None, 60, grid).clock(),
GateClock::Vsync
);
// Mailbox, immediate, FIFO-relaxed under a cap: the gate, on the grid.
for mode in [
Expand Down Expand Up @@ -7050,8 +7050,8 @@ mod tests {
let off = Pacing::Off;
// Cap at the display's rate (60 on 59.97 Hz), env mailbox: a guest
// that asked for FIFO (VSync on) or FIFO-relaxed gets mailbox with the
// gate every vblank, its present wait answered at commit; one that asked
// for mailbox or immediate (self-paced) gets FIFO with no gate.
// gate every vblank, its present wait answered at commit; so does one
// that asked for mailbox or immediate (self-paced), as cap-rate.
assert_eq!(
row(Some("mailbox"), f, 60, None, g60),
(m, "cap-rate-vsync", every(g60, 1))
Expand All @@ -7063,7 +7063,7 @@ mod tests {
for guest in [m, i] {
assert_eq!(
row(Some("mailbox"), guest, 60, None, g60),
(f, "cap-rate", off)
(m, "cap-rate", every(g60, 1))
);
}
// Capped below the display's rate, env mailbox, whatever the guest
Expand Down Expand Up @@ -7128,7 +7128,7 @@ mod tests {
// Every other mailbox or immediate presentation keeps master's answer
// at the commit: below-rate caps and no cap (the container's mode),
// frame generation, and a guest that asked for mailbox or immediate
// (declared FIFO under cap-rate or cap-compat).
// (mailbox under cap-rate, FIFO under cap-compat).
for requested in [f, r, m, i] {
for live in [m, i] {
assert_eq!(
Expand All @@ -7142,12 +7142,14 @@ mod tests {
);
}
for requested in [m, i] {
for why in [CapRate, CapCompat] {
assert_eq!(
present_wait_answer(true, why, requested, f),
WaitAnswer::AtCommit
);
}
assert_eq!(
present_wait_answer(true, CapRate, requested, m),
WaitAnswer::AtCommit
);
assert_eq!(
present_wait_answer(true, CapCompat, requested, f),
WaitAnswer::AtCommit
);
}
// FIFO and FIFO-relaxed out of a FIFO guest: the compositor answers.
assert_eq!(present_wait_answer(true, Mode, f, f), WaitAnswer::Later);
Expand Down Expand Up @@ -10290,8 +10292,9 @@ enum Declared {
Generating,
/// FIFO for want of the daemon's stamp lead (`source=cap-compat`).
CapCompat,
/// FIFO because the cap is the display's rate and the guest asked for
/// mailbox or immediate (`source=cap-rate`).
/// The container's mode with the vblank-grid gate because the cap is the
/// display's rate and the guest asked for mailbox or immediate
/// (`source=cap-rate`).
CapRate,
/// The container's mode with the vblank-grid gate because the cap is the
/// display's rate and the guest asked for FIFO or FIFO-relaxed
Expand Down Expand Up @@ -10321,18 +10324,14 @@ enum Declared {
/// each frame, and with ~16 ms of work per frame and three images it fell
/// to 40 fps at 60 Hz.
/// - a cap at the display's rate, a mode other than FIFO and a guest that
/// asked for mailbox or immediate (self-paced): FIFO, with no acquire gate
/// (`source=cap-rate`), as on master. At that cap one
/// frame per vblank is all the panel shows under either mode, and under
/// mailbox the shim would have to keep two commits out of one gamescope
/// composite without knowing when gamescope latches: its wakeup moves with
/// its rolling draw time (vblankmanager.cpp). A guest that paces itself just
/// below the display's rate had ~1 frame a second replaced in gamescope's
/// mailbox that way. Under FIFO gamescope latches one commit per vblank at
/// its own wakeup and its limiter (divisor 1) paces the guest. The gate is
/// not kept on top: FIFO's back-pressure already holds the guest, and the two
/// stacked turned a guest whose acquire-to-commit grew past a period into a
/// 40 fps session on a 60 Hz panel.
/// asked for mailbox or immediate (self-paced): the swapchain's mode with the
/// vblank-grid gate, one release per vblank (`source=cap-rate`). Declared
/// FIFO with no gate, gamescope did not hold a guest faster than the panel:
/// Portal 2 at cap 60 on a 60 Hz panel committed ~115 frames a second and
/// had each one released, against the daemon's 60 publishes. The cost of
/// mailbox here is a guest that paces itself just below the display's rate:
/// gamescope's wakeup moves with its rolling draw time (vblankmanager.cpp),
/// and ~1 frame a second of such a guest is replaced in its mailbox.
/// - no cap, the container's env naming a mode other than FIFO and a guest
/// that asked for FIFO or FIFO-relaxed: the guest's mode (`source=guest`);
/// see [`choose_live_mode`].
Expand Down Expand Up @@ -10405,7 +10404,7 @@ fn choose_live_mode(
if vsync_request {
(mode, Declared::CapRateVsync)
} else {
(VK_PRESENT_MODE_FIFO_KHR, Declared::CapRate)
(mode, Declared::CapRate)
}
}
Some(_) => (mode, Declared::Mode),
Expand Down Expand Up @@ -10436,8 +10435,8 @@ unsafe fn acquire_pacing(sc: *mut GnSwapchain) -> crate::vsync_gate::Pacing {
/// - frame generation sets the source rate: the gate holds it on the cap's own
/// clock (the app disarms gamescope's limiter then; generation paces the
/// output on the latch grid itself);
/// - any FIFO declaration (a FIFO swapchain, FIFO for want of the stamp lead,
/// FIFO because the cap is the display's rate): no gate. gamescope's limiter
/// - any FIFO declaration (a FIFO swapchain, FIFO for want of the stamp
/// lead): no gate. gamescope's limiter
/// holds FIFO commits, one per limited vblank, and is the cap;
/// - mailbox, immediate, FIFO-relaxed: gamescope's limiter does not hold them
/// (`wlserver_surface_is_fifo` is FIFO only), so the gate is the cap, on the
Expand Down