Skip to content

feat(scanout): direct-level latest-wins supersession — kill the direct/composed thrash - #125

Open
ariel3259 wants to merge 32 commits into
joske:masterfrom
ariel3259:feat/direct-scanout-latest-wins
Open

feat(scanout): direct-level latest-wins supersession — kill the direct/composed thrash#125
ariel3259 wants to merge 32 commits into
joske:masterfrom
ariel3259:feat/direct-scanout-latest-wins

Conversation

@ariel3259

Copy link
Copy Markdown
Contributor

Summary

DEPENDS ON PR #124 (fix/fullscreen-novsync-stutter, Phase A + Phase B). This branch contains all of #124 plus the direct-level latest-wins supersession on top. Review and merge #124 first.

Kills the direct/composed thrash of the Phase B direct-scanout path under a no-vsync fullscreen synced-present flood (CS2): after Phase B, the direct path flipped, unflipped, re-composed, and re-flipped — page_flip/s fell to 54–56 and composed unflips hit ~553/session. With this PR the direct path holds steady at refresh and composed unflips drop to ~0.

What changed

Two pieces, both scoped to the direct-scanout path (synced-present behavior on the composed path is bit-for-bit unchanged):

Piece 1 — the core sees the in-flight direct frame

  • present_flip_in_flight() and present_completion_is_idle() now count scanout_m2.pending as a flip in flight (326299a9), so the core's existing parking + synced same-target supersession coalesce the flood to ~1 present/flip before the direct path.

Piece 2 — direct-level latest-wins supersession (the queued slot)

  • DirectPresentFrame retains its probe framebuffer by Arc (e6907993): the m1 cache owns an Arc<DirectScanoutProbeFramebuffer>, so a queued frame survives an m1 cache clear (topology change) without a dangling handle or double-free.
  • Queued-store branch in try_present_direct (1da50aa1): a present arriving while the direct flip is in flight goes into a single scanout_m2.queued slot (latest-wins) instead of tearing the direct frame down. A newer eligible present replaces the queued one (completed as Skip, per-window present_id ordering preserved).
  • Chain-flip promotion + deferred submit (c16f7bae): retire_direct_output promotes queued → pending; maybe_composite submits the promoted frame on the next tick. Includes the phantom-retire guard (a promoted-unsubmitted frame is NOT on the KMS pipe and must not consume scene retires).
  • pending_is_submitted flag separates "in flight" from "promoted but unsubmitted" in the maybe_composite gate.

Supporting changes

  • Skip-ordering regression pin in yserver-core (153a5a45): a queued victim's Skip (synced, effective_target_msc = 0) must not overtake a same-window smaller-present_id entry still in the store.
  • DRM DRM_IOCTL_CRTC_QUEUE_SEQUENCE probe without taking master (f948352b): live-session probe for the pacing fallback.
  • CS2 hardware-validation harness (7c207956): tools/yserver-cinnamon-hw-cs2.sh + queued-slot Skip path logging.
  • Adversarial-review fixes folded in: borrow-conflict clone-to-owned-Arc, phantom-retire guard ordering, pending_is_submitted reset on teardown, chain-submit failure contract (release pins, complete as Skip/Copy, reentry-blocked), wake-pin single registration point.

Key design decisions

  1. Latest-wins, not a deep queue. A single queued slot supersedes; a deeper (N>1) queue adds latency for no observable benefit (spec §Out of scope).
  2. Chain-flip is deferred to the next compose tick, gated on no scene flips and no unflip — never races a composed commit against the all-output direct transaction.
  3. Xorg ground truth: the superseded-queued Skip semantics mirror Xorg's scrap; ordering for the X11 event stream stays per-window present_id (synced only).
  4. YSERVER_HW_CURSOR_NVIDIA=1 override (6dd4384f) is the A/B lever to engage Phase B on nvidia-drm (HW cursor disabled by default there); it is an opt-in, unchanged by this PR.

Hardware validation (2026-08-13, nvidia box, CS2 no-vsync, Cinnamon, YSERVER_HW_CURSOR_NVIDIA=1)

  • page_flip/s sustained 60.0 during gameplay (was 54–56 pre-fix); the only sub-40 samples are session-exit teardown.
  • composed unflip retired = 0 (was 553/session). Direct scanout stays engaged through the flood.
  • m1_probe_pass=11, m1_probe_reject=0, m1_probe_error=0 — Phase B engaged on the nvidia box this time (the earlier session saw m1_probe_pass=0 because cursor_hw was false).
  • Chain-flip path exercised: chain direct submit failed = 0; chain-flip promoted + chain direct submit lines present.
  • Queued-slot Skip path: 21 (sources 58/59) — small/non-zero as allowed by the plan; Piece 1 coalescing keeps the slot mostly empty.
  • request_exit = none; missed_pageflips/s = 0; present_skips/s mean 106 — the synced supersession keeps coalescing the flood.
  • DRM_IOCTL_CRTC_QUEUE_SEQUENCE EOPNOTSUPP appears once at startup (NVIDIA absolute-vblank arm unsupported) → falls back to flip-driven MSC. Benign and pre-existing.

Files

  • crates/yserver/src/kms/render/backend.rs — the whole direct-scanout state machine: ScanoutM2State.queued/pending_is_submitted, prepare_direct_frame, complete_queued_as_skip, submit_chain_direct_frame, retire_direct_output, maybe_composite, scanout_m1 Arc cache.
  • crates/yserver-core/src/core_loop/process_request.rs — Skip-ordering regression pin.
  • crates/yserver/src/drm/page_flip.rs — CRTC queue-sequence probe.
  • tools/yserver-cinnamon-hw-cs2.sh — hardware-validation harness.
  • Docs: spec (2026-08-12-direct-scanout-latest-wins-supersession-design.md), plan (3 adversarial rounds), findings update, handoff.

Testing

  • cargo test -p yserver --lib — 807 passed.
  • cargo test -p yserver-core --lib — 1155 passed.
  • cargo clippy --all-targets -- -D warnings — clean.

…ializes late

A Picture created over a host xid whose backing is not yet in the store
took no incref (render_create_picture); a later free_pixmap then reached
refcount 0 and destroyed the drawable under the live Picture - the
game-start transparency bug. Record a deferred ref and apply it when the
backing materializes (store_alloc), so the store refcount always pins a
picture's drawable regardless of materialization timing.
…een stutter

- findings: CS2 fullscreen no-vsync page-flip collapse (root cause + two
  adversarial review rounds)
- spec: async present defer + supersession (the primary flood fix)
- plan: 9-task TDD implementation plan (Phase A async defer, Phase B
  direct scanout efficiency)
- findings: transparency bug mechanism confirmed and fixed
- handoff doc for the transparency test
…nable

The probe panicked on CI because the runner exposes /dev/dri entries it
cannot open (Permission denied). Mirror the imported_syncobj render_node
pattern: skip instead of fail when no card node can be opened.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant