Skip to content

renderer: park DisplayLink while idle - #14035

Merged
mitchellh merged 1 commit into
mainfrom
displaylink
Aug 26, 2026
Merged

mitchellh merged 1 commit into
mainfrom
displaylink

Conversation

@mitchellh

Copy link
Copy Markdown
Contributor

#14033

Pause the CVDisplayLink when there isn't any real work to do.

Start the link after updateFrame rebuilds cells, keep it running while cell changes or animations remain pending, and resync it from the no-redraw path to sleep it again.

I also did some benchmark to measure the cost of starting/stopping the display link since this includes a lot more of that and I found that a continuously running link used 29 to 35 us of CPU per callback, while starting and stopping it for every frame used 103 to 121 us. So, in some pathological case this can be worse, but its still microseconds, and in the normal case this helps Ghostty sleep a lot more.

#14033

Pause the CVDisplayLink when there isn't any real work to do.

Start the link after updateFrame rebuilds cells, keep it running while
cell changes or animations remain pending, and resync it from the
no-redraw path to sleep it again. 

I also did some benchmark to measure the cost of starting/stopping the
display link since this includes a lot more of that and I found that
a continuously running link used 29 to 35 us of CPU per callback, while
starting and stopping it for every frame used 103 to 121 us. So, in some
pathological case this can be worse, but its still microseconds, and in
the normal case this helps Ghostty sleep a lot more.

Co-authored-by: Jesse Miller <jmiller@jmiller.com>
@mitchellh
mitchellh requested a review from a team as a code owner August 26, 2026 18:12
@ghostty-bot ghostty-bot Bot added the renderer label Aug 26, 2026
@mitchellh mitchellh added this to the 1.4.0 milestone Aug 26, 2026
@j-c-m

j-c-m commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Looks good to me. The removal of syncDisplayLink at 1913 also makes sense under the drawing case, I didn't even think to check the start & stop time, now that I see it this is a good tweak.

@mitchellh
mitchellh merged commit d9840f3 into main Aug 26, 2026
205 checks passed
@mitchellh
mitchellh deleted the displaylink branch August 26, 2026 18:35
@mitchellh

Copy link
Copy Markdown
Contributor Author

Nothing is free, not even stopping. 😄

mitchellh added a commit that referenced this pull request Aug 30, 2026
A follow on for #14035, we can now fix a long standing
effiency/performance bug now that we park the display link while idle.
This actually could cause "animated" un-focused windows to use more GPU
than their focused counterparts. (AI Agent interfaces seem to love
animation).

6ae1784

Unfocused surfaces stopped the CVDisplayLink and encoded a GPU frame on
every PTY wakeup. A burst of close writes became that many Metal submits
instead of one vsync.

Keep the link running while the surface is visible and dirty or
animating, whether or not it is focused. Idle surfaces still park.
danielitus added a commit to danielitus/ghostty that referenced this pull request Sep 5, 2026
The installed app hard-froze on 2026-09-04: a render thread was stuck
forever inside CVDisplayLinkStop (exposed by upstream's per-frame
display-link parking, ghostty-org#14035/ghostty-org#14068), its 64-slot mailbox filled, and
the app thread then blocked forever in occlusionCallback.

- Keep the display link running while visible and stop it only on
  occlusion, and never call CVDisplayLinkStop on a link that is not
  running (deinit, loopExit).
- Bound every app-thread push to the render mailbox (including the
  macOS display-id change) and the IO thread's resize push. After the
  first timeout the surface is marked wedged and later pushes fail fast
  instead of stalling the UI again.
- Never lose state-carrying messages: undelivered visibility, focus,
  display, config and font-grid messages are kept and retried in order
  before the next push and on every render request, so a recovered
  render thread converges. Ownership of the config allocation and the
  font grid ref stays with the surface until delivered; the cell size
  is applied only once the renderer holds the new grid.
- Closing a surface whose render thread is wedged detaches the thread
  and leaks the surface instead of joining it (embedded apprt), so a
  dead tab can be closed without hanging the app.
danielitus added a commit to danielitus/ghostty that referenced this pull request Sep 26, 2026
…fixes the deadlock"

This reverts commit 9b1da26.

The removal assumed upstream ghostty-org#14171 (72cf508) closed the
CVDisplayLinkStop deadlock. It only closes the draw_mutex variant.
Follow-ups on ghostty-org#14150 (2026-09-09 through 09-25) and
the open ghostty-org#14361 (2026-09-23) show the stall still happening on builds
that include it: the render thread wedges inside CoreVideo during a
display reconfiguration, and the app then freezes on the next
unbounded mailbox push from the main thread or when surface teardown
joins the thread. One reporter suspects the per-frame start/stop churn
from ghostty-org#14035 widens the window.

Keep the link running while visible and stop it only on occlusion, as
before, until upstream lands a fix that takes CVDisplayLinkStop off the
render and main threads entirely.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants