How Ghostty's Rendering Pipeline Uses Metal on macOS: Architecture and Implementation

Ghostty delegates all GPU rendering to Metal through a thin Swift UI layer that provides a CAMetalLayer backing, while the heavy lifting of vertex buffer construction, glyph atlas sampling, and draw call encoding happens entirely within the libghostty C library.

Ghostty is a modern terminal emulator that leverages native GPU acceleration for high-performance text rendering. According to the ghostty-org/ghostty repository source code, Ghostty's rendering pipeline on macOS uses a hybrid architecture where SwiftUI handles window composition while Metal handles the actual pixel processing. This design keeps the UI layer declarative and platform-agnostic while maximizing rendering performance through low-level GPU access.

Architecture Overview

The rendering pipeline follows a strict separation of concerns between the platform-specific UI layer and the core rendering engine. The Swift side is responsible only for creating the view hierarchy and forwarding geometry changes, while libghostty performs all actual graphics operations. This architecture allows Ghostty to use Metal on macOS, OpenGL on Linux, and maintain a consistent API across platforms.

The Swift layer creates a CAMetalLayer-backed view that acts as a rendering target, registers this surface with the C library, and forwards size and scale changes. Once initialized, libghostty takes full control of the Metal device, command queues, shaders, and draw loops.

The Three-Stage Rendering Flow

Ghostty's rendering pipeline follows a precise initialization sequence that bridges SwiftUI with the C-based graphics engine.

Stage 1: Metal Layer Initialization

The process begins when SwiftUI creates a view hierarchy that requires terminal rendering. In macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift, the SurfaceView class (a subclass of OSSurfaceView) overrides the layerClass property to return CAMetalLayer.self【/macos/Sources/Ghostty/Surface%20View/SurfaceView_UIKit.swift#L76-L78】. This ensures the backing layer is a Metal-compatible surface rather than a standard CALayer.

For SwiftUI integration, Ghostty provides the MetalView helper in macos/Sources/Helpers/MetalView.swift. This wrapper hosts an MTKView that serves as the rendering target bridge【/macos/Sources/Helpers/MetalView.swift#L4-L10】. The same pattern appears in SurfaceView_AppKit.swift for AppKit-based applications, ensuring consistent behavior across UI frameworks.

Stage 2: Surface Registration

Once the view creates its Metal layer, it instantiates a ghostty surface by calling the C API ghostty_surface_new. The constructor in SurfaceView_UIKit.swift builds the surface configuration and registers the view, storing the returned ghostty_surface_t pointer in the _surface property【/macos/Sources/Ghostty/Surface%20View/SurfaceView_UIKit.swift#L28-L31】.

This registration establishes the connection between the Swift view instance and the C rendering context. After this call, libghostty holds a reference to the CAMetalLayer and can access its drawable resources directly.

Stage 3: Dynamic Geometry Synchronization

Terminal windows must handle resize events, fullscreen transitions, and DPI changes without dropping frames. Ghostty accomplishes this through the sizeDidChange(_:) method in SurfaceView_UIKit.swift, which captures the view size and screen contentScaleFactor, then forwards these values to libghostty via ghostty_surface_set_content_scale and ghostty_surface_set_size【/macos/Sources/Ghostty/Surface%20View/SurfaceView_UIKit.swift#L61-L71】.

The corresponding AppKit implementation in SurfaceView_AppKit.swift performs identical forwarding, ensuring consistent behavior across macOS UI frameworks. These calls update the framebuffer dimensions and pixel density used by the Metal shaders to maintain crisp text rendering on Retina displays.

Metal Rendering Inside libghostty

Once the surface is registered, libghostty drives the Metal pipeline independently of the Swift runtime. The C library manages the complete GPU resource lifecycle:

  • Device and Command Queue: libghostty obtains an MTLDevice reference from the CAMetalLayer and creates a dedicated MTLCommandQueue for submitting rendering commands.
  • Render Pipeline State: The engine builds a MTLRenderPipelineState encoding a vertex shader that maps terminal cell coordinates to clip-space positions, paired with a fragment shader that samples from a pre-rendered glyph bitmap atlas.
  • Draw Loop: On each frame, the engine updates a texture with the latest cell contents, encodes draw calls for changed rows only (damage tracking), and presents the drawable obtained from CAMetalLayer. The loop triggers either through an internal timer or explicit ghostty_surface_render calls following new input.
  • Synchronization: A semaphore ensures GPU work completes before the next update, preventing tearing while maintaining UI responsiveness.

Implementation Examples

The following code illustrates the integration points between SwiftUI and libghostty's Metal backend:

// Create a Metal-backed view in SwiftUI
struct TerminalView: View {
    var body: some View {
        // MetalView hosts an MTKView that libghostty draws into
        MetalView<MTKView>()
            .frame(minWidth: 400, minHeight: 300)
    }
}
// The underlying UIView/NSView that provides the CAMetalLayer
class SurfaceView: OSSurfaceView {
    // ... init registers the C surface ...
    override class var layerClass: AnyClass { CAMetalLayer.self }

    override func sizeDidChange(_ size: CGSize) {
        // Forward geometry to libghostty
        ghostty_surface_set_content_scale(surface, scale, scale)
        ghostty_surface_set_size(surface,
                                 UInt32(size.width * scale),
                                 UInt32(size.height * scale))
    }
}
// libghostty snippet (simplified) – creates the Metal pipeline
static void create_metal_renderer(ghostty_surface_t *surf) {
    id<MTLDevice> device = surf->metal_layer.device;
    surf->commandQueue = [device newCommandQueue];
    // Build render pipeline, load glyph atlas texture, etc.
}
// libghostty draw loop – called on every frame
static void render_surface(ghostty_surface_t *surf) {
    id<CAMetalDrawable> drawable = [surf->metal_layer nextDrawable];
    id<MTLCommandBuffer> cmd = [surf->commandQueue commandBuffer];
    // Encode draw calls for changed terminal rows
    // ...
    [cmd presentDrawable:drawable];
    [cmd commit];
}

Key Source Files

Understanding Ghostty's rendering pipeline requires familiarity with these specific files in the ghostty-org/ghostty repository:

  • macos/Sources/Helpers/MetalView.swift: SwiftUI wrapper that hosts an MTKView used as the rendering target.
  • macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift: UIKit subclass providing CAMetalLayer and forwarding size/focus updates to libghostty.
  • macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift: AppKit counterpart for the macOS app bundle.
  • macos/Sources/Ghostty/Surface View/SurfaceView.swift: SwiftUI-compatible OSViewRepresentable tying the view hierarchy to the C surface.
  • include/ghostty/ghostty_surface.h: C header defining the ghostty_surface_* API used by Swift to configure the Metal renderer.

Summary

Ghostty's Metal rendering pipeline achieves high-performance terminal graphics through a clean separation between UI and rendering concerns:

  • The Swift layer creates a CAMetalLayer-backed view and forwards geometry changes, but performs no drawing itself.
  • Surface registration via ghostty_surface_new establishes the bridge to libghostty's C rendering engine.
  • libghostty manages the complete Metal pipeline including device selection, shader execution, glyph atlas sampling, and frame synchronization.
  • This architecture enables platform-specific optimizations while sharing the core rendering logic across macOS, Linux, and iOS builds.

Frequently Asked Questions

Why does Ghostty use Metal instead of Core Graphics for terminal rendering?

Metal provides significantly lower overhead and higher throughput for high-frequency text updates compared to Core Graphics. According to the source code in ghostty-org/ghostty, the Metal pipeline allows libghostty to submit draw calls directly to the GPU using pre-computed glyph atlases and efficient vertex buffers, whereas Core Graphics would require software rasterization for each frame. This approach achieves consistent 60fps+ performance even with rapid terminal scrollback and complex Unicode glyph rendering.

How does Ghostty handle Retina (high-DPI) display scaling?

Ghostty forwards the screen's contentScaleFactor to libghostty via ghostty_surface_set_content_scale. When sizeDidChange(_:) executes in SurfaceView_UIKit.swift, it multiplies the view dimensions by the scale factor before calling ghostty_surface_set_size, ensuring the Metal framebuffer matches the physical pixel resolution. The fragment shader then samples from high-resolution glyph atlases that respect the display's native DPI, producing crisp text on Retina displays.

Is the Metal rendering code shared with the Linux version of Ghostty?

No, the Metal-specific implementation is macOS-only, but the architecture is shared. The libghostty C library abstracts the graphics API behind the ghostty_surface_* interface. While macOS uses the Metal backend described here, the Linux build links against OpenGL or Vulkan implementations using the same C API surface registration calls. Only the CAMetalLayer setup code in the Swift files is platform-specific to macOS.

What triggers a redraw in Ghostty's Metal pipeline?

Redraws trigger via explicit ghostty_surface_render calls or internal surface invalidations. When terminal content changes (new output, cursor movement, or scrollback), libghostty marks affected regions as "damaged" and schedules a render pass. The draw loop acquires the next drawable from CAMetalLayer, encodes commands for dirty rectangles only, and presents the frame. This partial redraw optimization minimizes GPU workload by avoiding full-screen updates when only a few cells change.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →