
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.
Packages
This folder contains packages written for and used by Ghostty that could potentially be useful for other projects. These are in-tree with Ghostty because I don't want to maintain them as separate projects (i.e. get dedicated issues, PRs, etc.). If you want to use them, you can copy and paste them into your project.
License
This license only applies to the contents of the pkg
folder within
the Ghostty project. This license does not apply to the rest of the
Ghostty project.
Copyright © 2024 Mitchell Hashimoto, Ghostty contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.