Skip to content

Conversation

Rohit-Bevinahally
Copy link
Contributor

@Rohit-Bevinahally Rohit-Bevinahally commented Jan 19, 2025

@Rohit-Bevinahally Rohit-Bevinahally marked this pull request as ready for review February 23, 2025 19:30
@Rohit-Bevinahally Rohit-Bevinahally requested a review from a team as a code owner February 23, 2025 19:30
@sagg0t
Copy link

sagg0t commented Mar 8, 2025

Hey @Rohit-Bevinahally, thank you for this PR, I can't wait to have it merged!
While using these changes in my local build, I noticed that the background image is separate for every split. I think many ghostty users (including me) would appreciate being able to have a single image for the whole window.

What's you opinion on this? Thanks again for the PR!

P.S. also noticed that the image doesn't cover vertical padding
image
image

@jcollie
Copy link
Member

jcollie commented Mar 8, 2025

While using these changes in my local build, I noticed that the background image is separate for every split. I think many ghostty users (including me) would appreciate being able to have a single image for the whole window.

This is because we don't have a renderer layer (Metal or OpenGL) that covers the entire window, only each individual surface. Adding that would require major surgery in how rendering is implemented and out-of-scope for this PR, and not something to be attempted without some in-depth discussion.

@Rohit-Bevinahally
Copy link
Contributor Author

P.S. also noticed that the image doesn't cover vertical padding

Hi @sagg0t. Sorry I missed your comment.
The current design on opengl and metal draws the image on the area covered by the terminal (i.e. excluding padding). Whether it is better to cover the full screen instead or add another config option to choose between the two will need some discussion.
To cover the full screen you can apply this patch

Patch
diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig
index dc3cf26b..54c155dd 100644
--- a/src/renderer/Metal.zig
+++ b/src/renderer/Metal.zig
@@ -1725,8 +1725,8 @@ fn drawBackgroundImage(
     const Buffer = mtl_buffer.Buffer(mtl_shaders.BgImage);
     var buf = try Buffer.initFill(self.gpu_state.device, &.{.{
         .terminal_size = .{
-            @as(f32, @floatFromInt(self.size.terminal().width)),
-            @as(f32, @floatFromInt(self.size.terminal().height)),
+            @as(f32, @floatFromInt(self.size.screen.width)),
+            @as(f32, @floatFromInt(self.size.screen.height)),
         },
         .mode = self.background_image_mode,
     }}, .{
diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal
index 3d18c646..34b88e22 100644
--- a/src/renderer/shaders/cell.metal
+++ b/src/renderer/shaders/cell.metal
@@ -780,6 +780,8 @@ vertex BgImageVertexOut bg_image_vertex(
         break;
   }
 
+  offset -= uniforms.grid_padding.wx;
+
   out.position = uniforms.projection_matrix * float4(final_image_size + offset, 0.0, 1.0);
   out.tex_coord = position;
   if (in.mode == MODE_TILED) {

Copy link

@yunusey yunusey left a comment

Choose a reason for hiding this comment

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

Thank you so much for this PR! I don't really know anything about the Metal shaders, but from what I can understand, it looks pretty nice.

@qwerasd205 qwerasd205 mentioned this pull request Jun 17, 2025
3 tasks
qwerasd205 added a commit that referenced this pull request Jun 22, 2025
It's here, the long-foretold and long-procrastinated renderer rework!
Hopefully this makes it easier to adapt and modify the renderer in the
future and ensures feature parity between Metal and OpenGL. Despite
having been a lot of work to write initially, with the abstraction layer
in place I feel like working on the renderer will be a much more
pleasant experience going forward.

## Key points
- CPU-side renderer logic is now mostly unified via a generic
`Renderer`.
- A graphics API abstraction layer over OpenGL and Metal has been
introduced.
- Minimum OpenGL version bumped to `4.3`, so can no longer be run on
macOS; I used the nix VM stuff for my testing during development. (Edit
by @mitchellh: Note for readers that Ghostty still works on macOS, but
the OpenGL backend doesn't, only the Metal one)
- The OpenGL backend now supports linear blending! Woohoo! The default
`alpha-blending` has been updated to `linear-corrected` since it's
essentially a strict improvement over `native`. The default on macOS is
still `native` though to match other mac apps in appearance, since macOS
users are more sensitive to text appearance.
- Custom shaders can now be hot reloaded.
- The background color is once again drawn by us, so custom shaders can
interact with it properly. In general, custom shaders should be a little
more robust.

## The abstraction layer
The general hierarchy of the abstraction layer is as such:
```
 [ GraphicsAPI ] - Responsible for configuring the runtime surface
    |     |        and providing render `Target`s that draw to it,
    |     |        as well as `Frame`s and `Pipeline`s.
    |     V
    | [ Target ] - Represents an abstract target for rendering, which
    |              could be a surface directly but is also used as an
    |              abstraction for off-screen frame buffers.
    V
 [ Frame ] - Represents the context for drawing a given frame,
    |        provides `RenderPass`es for issuing draw commands
    |        to, and reports the frame health when complete.
    V
 [ RenderPass ] - Represents a render pass in a frame, consisting of
   :              one or more `Step`s applied to the same target(s),
 [ Step ] - - - - each describing the input buffers and textures and
   :              the vertex/fragment functions and geometry to use.
   :_ _ _ _ _ _ _ _ _ _/
   v
 [ Pipeline ] - Describes a vertex and fragment function to be used
                for a `Step`; the `GraphicsAPI` is responsible for
                these and they should be constructed and cached
                ahead of time.

 [ Buffer ] - An abstraction over a GPU buffer.

 [ Texture ] - An abstraction over a GPU texture.
```
More specific documentation can be found on the relevant structures.

## Miscellany
Renderers (which effectively just means the generic renderer) are now
expected to only touch GPU resources in `init`, certain lifecycle
functions such as the `displayRealized`/`displayUnrealized` callbacks
from GTK-- and `drawFrame`; and are also expected to be thread-safe.
This allows the renderer thread to build the CPU-side buffers
(`updateFrame`) even if we can only *draw* from the app thread.

Because of this change, we can draw synchronously from the main thread
on macOS when necessary to always have a frame of the correct size
during a resize animation. This was necessary to allow the background to
be drawn by our GPU code (instead of setting a background color on the
layer) while still avoiding holes during resize.

The OpenGL backend now theoretically has access to multi-buffering, but
it's disabled (by setting the buffer count to 1) because it
synchronously waits for frames to complete anyway which means that the
extra buffers were just a waste of memory.

## Validation
To validate that there are no significant or obvious problems, I
exercised both backends with a variety of configurations, and visually
inspected the results. Everything looks to be in order.

The images are available in a gist here:
https://gist.github.com/qwerasd205/c1bd3e4c694d888e41612e53c0560179

## Memory
Here's a comparison of memory usage for ReleaseFast builds on macOS,
between `main` and this branch.
Memory figures given are values from Activity Monitor measuring windows
of the same size, with two tabs with 3 splits each.

||Before|After|
|-:|-|-|
|**Memory**|247.9 MB|224.2 MB|
|**Real Memory**|174.4 MB|172.5 MB|

Happily, the rework has slightly *reduced* the memory footprint- likely
due to removing the overhead of `CAMetalLayer`. (The footprint could be
reduced much further if we got rid of multi-buffering and satisfied
ourselves with blocking for each frame, but that's a discussion for
another day.)

If someone could do a similar comparison for Linux, that'd be much
appreciated!

## Notes / future work
- There are a couple structures that *can* be unified using the
abstraction layer, but I haven't gotten around to unifying yet.
Specifically, in `renderer/(opengl|metal)/`, there's `cell.zig` and
`image.zig`, both of which are substantially identical between the two
backends. `shaders.zig` may also be a candidate for unification, but
that might be *overly* DRY.
- ~~I did not double-check the documentation for config options, which
may mention whether certain options can be hot-reloaded; if it does then
that will need to be updated.~~ Fixed: be5908f
- The `fragCoord` for custom shaders originates at the top left for
Metal, but *bottom* left for OpenGL; fixing this will be a bit annoying,
since the screen texture is likewise vertically flipped between the two.
Some shaders rely on the fragcoord for things like falling particles, so
this does need to be fixed.
- `Target` should be improved to support multiple types of targets right
now it only represents a framebuffer or iosurface, but it should also be
able to represent a texture; right now a kind of messy tagged union is
used so that steps can accept both.
- Custom shader cursor uniforms (#6912) and terminal background images
(#4226, #5233) should be much more straightforward to implement on top
of this rework, and I plan to make follow-up PRs for them once this is
merged.
- I *do* want to do a rework of the pipelines themselves, since the way
we're rendering stuff is a bit messy currently, but this is already a
huge enough PR as it is- so for now the renderer still uses the same
rendering passes that Metal did before.
- We should probably add a system requirements section to the README
where we can note the minimum required OpenGL version of `4.3`, any even
slightly modern Linux system will support this, but it would be good to
document it somewhere user-facing anyway.

# TODO BEFORE MERGE
- [x] Have multiple people test this on both macOS and linux.
- [ ] ~~Have someone with a better dev setup on linux check for memory
leaks and other problems.~~ (Skipped, will merge and let tip users
figure this out, someone should *specifically* look for memory leaks
before the next versioned release though.)
- [x] Address any code review feedback.
qwerasd205 added a commit to qwerasd205/ghostty that referenced this pull request Jun 25, 2025
Adds support for background images via the `background-image` config.

Resolves ghostty-org#3645, supersedes PRs ghostty-org#4226 and ghostty-org#5233.

See docs of added config keys for usage details.
@qwerasd205
Copy link
Member

This PR is superseded by #7686. Thanks for this work, regardless!

@qwerasd205 qwerasd205 closed this Jun 25, 2025
mitchellh pushed a commit to qwerasd205/ghostty that referenced this pull request Jun 25, 2025
Adds support for background images via the `background-image` config.

Resolves ghostty-org#3645, supersedes PRs ghostty-org#4226 and ghostty-org#5233.

See docs of added config keys for usage details.
mitchellh added a commit that referenced this pull request Jun 25, 2025
Adds support for background images via the `background-image` config.

Resolves #3645, supersedes PRs #4226 and #5233.

See docs of added config keys for usage details.

> [!NOTE]
> Unlike what is implied by the original issue, because this is
implemented in the renderer it is inherently per-surface not per-window,
meaning a window with a split will have two copies of the background
image.

### Future work
- We should probably introduce code in the apprts that tells surfaces
their position and size relative to the window, which would allow us to
add a `background-image-area` config with options for `surface` and
`window` to control that behavior (and probably default it to `window`).
That apprt code would also allow for window-relative custom shader
locations, which is also a fairly common user request, so I think it's
worth it.
- Currently if you use a high res background image this is fairly
inefficient, since each surface independently loads a copy of the
background image. On systems with limited VRAM this could be an issue
for users who use a lot of surfaces, so it may be worth making a shared
image cache to avoid this problem.
- ~~It's probably worth using compressed texture formats for images,
I'll look in to doing that.~~ (c43702c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants