This adds a cross-platform ability to draw an overlay of debug
information directly on top of our terminal.
The debug overlay is drawn on the CPU using z2d and then rendered as an
image on the GPU (using the same subsystem and shaders as things like
the Kitty Graphics Protocol). This lets us iterate more quickly on
overlays since it's all simple imperative 2D drawing operations, and
also lets it be cross-platform immediately.
The goal of this PR is to **introduce the framework.** To enable the
overlays, you have to modify code and recompile it, but of course in the
future this will be runtime toggle-able via the inspector. Additionally,
the dummy overlay in this PR (OSC8 highlighting) isn't particularly
useful and has limitations; it's only meant to be a demo. More realistic
overlays will come later.
## Demo
Here is a demo of an overlay highlighting OSC8 hyperlinks:
<img width="1746" height="1648" alt="CleanShot_2026-01-30_at_15 04 582x"
src="https://github.com/user-attachments/assets/4c490d11-5baa-4ef6-b97b-1cfd321235d8"
/>
## Performance
```
Empty but overlay active:
generic_renderer: [rebuildOverlay time] start_micro=1769835575905814 duration=12ns
generic_renderer: [updateFrame time] start_micro=1769835575905766 duration=199ns
Hyperlink on every line:
generic_renderer: [rebuildOverlay time] start_micro=1769835573560797 duration=452ns
generic_renderer: [updateFrame time] start_micro=1769835573560682 duration=891ns
```
Pretty great! 🎉
With the overlay active but nothing to draw, the overlay adds ~6%
overhead. With the overlay active and hyperlinks on every line, the
overlay adds ~50% overhead. These are big percentages, but the absolute
numbers are miniscule. And, for a high value debug feature that is only
on some of the time, it's totally acceptable.
Also, as a reminder, for 120fps we have _8.3 million nanoseconds_ per
frame (including the time to draw, of course). If we're spending
sub-1000ns we're in the realm of 1M FPS. So, it's fine. lol.
(Edit: I previously had debug timings which are significantly worse but
have a lot more safety checks.)
## Other Notes
* **Damage/dirty tracking.** I don't do any for the overlay, and we just
rebuild it on every frame. I'm not convinced the complexity is worth it
for a debug overlay and the performance numbers at this point do not
necessitate it in any way.
* **Update vs draw times.** We call `draw` _far_ more often than
`update` so putting the overlay rebuild into `update` made way more
sense. I did measure both.
This extracts all our image renderer state into a separate struct,
blandly named `renderer.image.State`. This structure owns all the
storage of images and placements and exposes a limited public API to
manage them.
One motivation was to limit state access by our Kitty graphics functions
within the generic renderer. Another was to limit our own generic
renderer from getting our image system into an incoherent state. This is
prevented now on both sides due to some encapsulation.
This currently only supports Kitty images, since that's the only image
protocol we support. But I intend to add additional image types to this,
namely the ability to add overlay images for debug information. **There
are no plans to add new image protocols to the terminal,** the
extraction is purely to support some internal features. But, it could be
used for other protocols one day.
This extracts all our image renderer state into a separate struct,
blandly named `renderer.image.State`. This structure owns all the
storage of images and placements and exposes a limited public API
to manage them.
One motivation was to limit state access by our Kitty graphics functions
within the generic renderer. Another was to limit our own generic
renderer from getting our image system into an incoherent state. This is
prevented now on both sides due to some encapsulation.
This currently only supports Kitty images, since that's the only image
protocol we support. But I intend to add additional image types to this,
namely the ability to add overlay images for debug information.
**There are no plans to add new image protocols to the terminal,** the
extraction is purely to support some internal features. But, it could be
used for other protocols one day.
The current runVM function gets called only with a module and without a desc,
resulting in an error when running `nix flake show` or similar
commands.
This commit drops the `desc` argument to `runVM` and gets rid of that problem.
Inspector is getting some love! While working on some new functionality
I found a bunch of bugs. Sending the bug fixes separately.
- Mode checkboxes didn't have a unique ID, causing Imgui warnings
- renderer: we keep a draw timer active while the inspector is visible,
allowing animations to work
- GTK and macOS: we were always calculating a delta time of 0 since we
converted to float after int math, making hover timers not work
- macOS: precision scrolling made scrolling way too fast, slow it down
No AI, just meat sticks
As mentioned in
https://github.com/ghostty-org/ghostty/pull/10332#issuecomment-3800353166,
the Harfbuzz shaping tests that depend on specific fonts (that are on
macOS, but not whatever linux distro we use for CI) aren't being checked
in CI. The `build-macos-freetype` CI check is primarily to make sure
Freetype can build on Mac, but if we switch to the `coretext_freetype`
backend, we still use Freetype for rendering, but then we get Coretext
for font discovery which then enables these tests to run.