Skip to content

display(macOS): pace the VM display with CAMetalDisplayLink - #7983

Open
dragoshont wants to merge 1 commit into
utmapp:mainfrom
dragoshont:display/metal-display-link
Open

dragoshont wants to merge 1 commit into
utmapp:mainfrom
dragoshont:display/metal-display-link

Conversation

@dragoshont

@dragoshont dragoshont commented Oct 18, 2026 •

Copy link
Copy Markdown

On my MacBook Pro with an external 60 Hz main display, a VM window on the built-in 120 Hz ProMotion panel never shows more than 60 fps.

The display is drawn from MTKView's own timer, and on that panel the timer stayed at 60 callbacks per second even with preferredFramesPerSecond = 120 (confirmed in lldb). A CAMetalDisplayLink on the same layer runs at the rate it requests, and its frames reach the screen at 120 Hz. I haven't tried a ProMotion Mac without an external display.

On macOS 14+ this drives the renderer from a CAMetalDisplayLink. VMMetalView hands the renderer the drawable from each update, so CSMetalRenderer doesn't change. Behaviour:

Related: #7820. With #7982 the guest itself can also run at 120 Hz.

Testing: macOS 27.0.1, MacBook Pro M5 Max. Debug builds with and without this change, and a disposable Alpine VM whose console scrolls constantly. With the VM window on the 120 Hz panel, I counted distinct frames per second with ScreenCaptureKit and checked them against the Metal HUD:

Build Window fps (3 runs) Metal HUD
main 59.9 / 60.0 / 60.1 59.8
this PR 119.6 / 119.9 / 120.1 120.0

Also with this PR:

  • Moving the window to the 60 Hz display and back: 59.6, then 119.5 fps.
  • Pause and resume work.
  • Fullscreen scales correctly at 119.2 fps.
  • Closing the window is clean.
  • Idle cost rises from 1.5% to 2.1% of a core, because the link ticks at 120 Hz.

Windows 11 Pro ARM64 (build 26300, viogpu3d, D3DMetal), window on the 120 Hz panel and the guest at 120 Hz through #7982. A D3D11 test presenting ~560 fps in the guest:

  • Window: 76 fps over 10 s (ScreenCaptureKit, median frame 11.9 ms); Metal HUD 95 fps.
  • UTM 5.0.8 on the same panel stayed at or below 60 (58.5 fps in an earlier Hollow Knight run).

The Mac was busy with another app using 3+ cores during the Windows run, so those numbers are noisy. The point is that the window gets past 60.

Not tested yet: macOS 12/13, secondary displays.

An AI agent ran these tests on my Mac.

I have read the AI contribution guidelines and have followed them to the best of my ability.

On a MacBook Pro with an external 60 Hz main display, a VM window on the
built-in 120 Hz ProMotion panel never showed more than 60 frames per
second. MTKView's own timer stayed at 60 callbacks per second there even
with preferredFramesPerSecond = 120. A CAMetalDisplayLink on the same
layer runs at the rate it requests, and its frames reach the screen at
120 Hz.

On macOS 14 and later, drive the renderer from a CAMetalDisplayLink and
hand it the drawable from each update. The rate follows the screen the
window is on, and an FPS limit changed in Settings now applies the next
time the VM starts, not only when its window is created. Earlier macOS
versions keep the view's own timer.

Assisted-by: GitHub Copilot:claude-opus-5.5
@dragoshont
dragoshont force-pushed the display/metal-display-link branch from dde2e47 to 47b96f7 Compare October 18, 2026 10:00
@dragoshont
dragoshont marked this pull request as ready for review October 18, 2026 13:04
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