Skip to content

Windows GL: wall-clock pacing instead of driver vsync (windowed/borderless) - #723

Merged
mstan merged 4 commits into
masterfrom
fix/windows-gl-dwm-vsync
Oct 23, 2026
Merged

mstan merged 4 commits into
masterfrom
fix/windows-gl-dwm-vsync

Conversation

@tetrisgm

Copy link
Copy Markdown
Member

What: on Windows with the OpenGL renderer, windowed or borderless, host_driver_vsync_unreliable() now returns true, as it already does under WSL. The swap interval becomes 0 and the existing wall-clock pacer holds the guest rate. Exclusive fullscreen (fullscreen = 2) keeps driver vsync, and PSX_TRUST_DRIVER_VSYNC=1 restores it everywhere. This is a correctness fix, so it is on by default.

Why: DWM composes windowed and borderless GL windows and owns the display's vsync. On NVIDIA (RTX 4080 SUPER, driver 610.88), a vsync'd SwapBuffers holds the GPU queue the render thread submits to. Every render-thread frame then costs a full refresh, and the guest slows.

Measured on R4 0.8 at 7680x4320, 60 Hz panel, race savestate:

guest Hz render_busy render-thread GPU ms dynres
before, windowed Low 51.9–56.9 ~100% 16.68 (pinned) dropped to 4–9 for nothing
before, borderless Low 59.9 (5 s < 59.5) 98.7% 16.7 4
after, windowed Low 60.0 5–10% ~4 9 (ceiling)
after, borderless Ultra 60.0 8–19% — 14 (ceiling), Smooth motion 30 + 30

Same shape as the GL software-present self-heal comment (NVIDIA wglSwapBuffers stalls).

Siblings: #722 (match_display_max_lines), tetrisgm/RidgeRacerType4Recomp#47 (Max Detail pacing).

🤖 Generated with Claude Code

Windowed or borderless, DWM composes the window and owns the display's
vsync, so a driver swap interval only adds a stall. On NVIDIA (RTX 4080
SUPER, 610.88) a vsync'd SwapBuffers holds the GPU queue the render
thread submits to: every render-thread frame cost a full refresh
(16.7 ms "GPU" at any internal scale, render_busy 100 %), back-pressure
slowed the guest to 52-57 Hz, and dynamic resolution dropped for
nothing. Measured on R4 0.8 at 7680x4320, windowed and borderless: guest
60.0 Hz, render_busy 5-8 %, GPU 4.4 ms. Exclusive fullscreen keeps
driver vsync; PSX_TRUST_DRIVER_VSYNC=1 restores it everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@mstan mstan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 594b507.

The measured NVIDIA stall is a valuable correctness/performance finding. The proposed default policy is broader than that evidence: it disables driver synchronization for every Windows GL windowed/borderless path, including unthreaded rendering and other GPU/driver families, on the assumption that DWM composition always supplies equivalent synchronization. Borderless windows can use independent/direct presentation, and g_fullscreen is requested settings rather than proof of the effective presentation mode. Please qualify the affected path and default behavior with actual swap cadence/tearing/latency evidence, including an unthreaded path and another Windows driver family, or narrow the automatic workaround to the supported conditions. Verify switching windowed/borderless/exclusive and vsync at runtime, and retain a functional synchronized fallback. Coordinate with #712 and #713 so window geometry and cadence are not changed independently to treat the same symptom. A default correctness fix is appropriate once its supported scope is established; another enhancement opt-in is not the rollout solution. Tracking: beads-8zz2.

Narrow host_driver_vsync_unreliable() on Windows to the configuration the
stall was measured on: GL active, render thread running, GL_VENDOR NVIDIA,
not exclusive fullscreen. Other driver families and the unthreaded path
keep driver vsync; PSX_TRUST_DRIVER_VSYNC=1 still forces it everywhere.
Evaluated per call, so runtime renderer/fullscreen changes apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tetrisgm

Copy link
Copy Markdown
Member Author

Narrowed in cb17f2f: the workaround now applies only to the measured case: GL active, render thread running, GL_VENDOR NVIDIA, and not exclusive fullscreen. The unthreaded path, other driver families and exclusive fullscreen keep driver vsync, and PSX_TRUST_DRIVER_VSYNC=1 still forces it. The check runs on every call, so runtime renderer and fullscreen changes take effect. I have not yet measured swap cadence, tearing or latency on another driver family, or done the runtime mode-switch matrix. #712/#713 coordination is still open.

@mstan

mstan commented Oct 22, 2026

Copy link
Copy Markdown
Member

Re-reviewed cb17f2f. Narrowing to active threaded NVIDIA GL is a useful response; the other-driver/unthreaded default-policy objection is addressed by that scope. There is still a lifecycle gap: the new predicate becomes true only after render_thread_vblank starts the worker, but that success path never calls apply_present_cadence. The actual swap interval was configured earlier, when the thread was inactive; re-evaluating a predicate in cadence decisions does not update the driver interval. On an unchanged display refresh_host_display_cadence returns early, so ordinary startup can retain interval 1 while cadence logic assumes the workaround disabled it. Please apply the policy at the worker/backend lifecycle transition and verify the actual interval/pacer state at startup and windowed/borderless/exclusive transitions, with the trust-driver fallback. Keep this focused to the measured NVIDIA path; another broad hardware matrix is unnecessary for the narrowed policy. The new head is outside the current frozen batch. Tracking: beads-ek8k.

@mstan

mstan commented Oct 23, 2026

Copy link
Copy Markdown
Member

Maintainer fixes pushed in 3a2057c. The actual swap policy is reapplied immediately after successful render-worker startup and after successful fullscreen changes through the hotkey and live settings path. The predicate now uses the effective window mode, rather than the configured startup preference. Scope remains the measured Windows/NVIDIA/threaded GL path, with the trust-driver fallback. This is a default correctness/performance repair within that scope. The combined native/GL fixture passes; actual runtime startup interval and fallback checks are included in the pending title smoke batch. Tracking: beads-kgof.

@mstan mstan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer fixes reapply cadence after lazy render-worker startup and effective fullscreen changes. Actual driver readback is interval 0 on the scoped Windows/NVIDIA/threaded path; trust-driver threaded and ordinary unthreaded fallbacks read interval 1. This establishes configuration, not monitor/tearing measurement. The correctness repair applies by default within its narrow scope.

One bounded muted OpenGL boot/progression/visual smoke passed for Tomba, Tomba 2, Ape Escape and Mega Man X6. Tomba 2 reached its title screen after the opening FMV at native aspect; its unsupported widescreen package remains disabled. Supported debug quit returned exit 0 for all four; this is not extended gameplay, normal-teardown or native-card-write qualification.

Combined integration 9ee1a8c63996cc91e4068aa27f100d407ab1ac0e includes #710/#723/#736/#737/#746; #736 remains held for residency policy. Six focused native checks and the GL fixture passed. This evidence qualifies the reviewed heads as a compatible subset, not an exact final-master-tree claim. Tracking: beads-kgof.

@mstan
mstan merged commit 7c4bf09 into master Oct 23, 2026
1 check passed
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.

2 participants