I Built a 10 MB GPU-Accelerated Terminal in Rust + Metal
DEV Community

I Built a 10 MB GPU-Accelerated Terminal in Rust + Metal

A few days ago, I released Metalterm, a new native terminal emulator for macOS built with Rust and Metal. Yes, another terminal emulator. But I wanted to see how far I could push a terminal if I treated rendering performance, latency, memory usage, and smoothness as first-class features rather than implementation details. The result is a native macOS app that's currently around 10 MB, uses a custom Metal renderer, and spends roughly 0.22 ms of GPU time per frame. At 120 Hz, that's only 2.6% of the 8.33 ms frame budget. Why build another terminal? Modern terminals are already fast. Ghostty, Rio, Kitty, Alacritty and others have pushed terminal performance pretty far. But there were a few things I wanted to explore myself: - extremely smooth scrolling - low rendering overhead - low memory usage - fast startup - a small native application - a UI designed around modern developer workflows - direct control over the entire rendering pipeline So instead of building on Electron or a web rendering stack, Metalterm is built around: Rust + native macOS APIs + Metal The terminal state, parsing and application logic live primarily in Rust, while the renderer talks directly to Apple's GPU stack through Metal. That gives me control over the entire frame. 0.22 ms GPU frame time One of the numbers I'm happiest with so far is GPU time. Metalterm currently takes approximately: 0.22 ms of GPU time per frame For comparison, a 120 Hz display gives you: 1000 ms / 120 = 8.33 ms per frame So the terminal renderer consumes around: 0.22 / 8.33 โ‰ˆ 2.6% of the available GPU frame budget. That leaves a lot of headroom. And that headroom matters. It means I can experiment with things like richer materials, background shaders, subtle visual effects and more advanced terminal UI without immediately sacrificing smoothness. The goal isn't just to hit 120 FPS. The goal is to make scrolling and interaction feel native to a high-refresh-rate display. How does it compare? I also started benchmarking Metalterm against other GPU-accelerated terminals. These are measurements from my current benchmark setup, so they shouldn't be interpreted as universal rankings. Hardware, configuration, shell environment and methodology obviously matter. But under the same test conditions, this is what I measured: | Metric | Ghostty 1.3.1 | Rio 0.5.24 | Metalterm | |---|---|---|---| | App Size | 40.0 MiB | 41.2 MiB | 9.8 MiB | | Cold Start | 409.1 ms | 334.5 ms | 331.6 ms | | Idle Memory | 119.7 MiB | 105.2 MiB | 96.3 MiB | | Memory After 10K Lines | 135.9 MiB | 128.4 MiB | 109.9 MiB | | Memory Increase | 16.2 MiB | 23.2 MiB | 13.6 MiB | | PTY Throughput | 106.3 ms | 116.2 ms | 69.0 ms | The app itself is currently only about 9.8 MiB. More interesting to me, though, is that Metalterm remains relatively small in memory after pushing additional terminal history through it. In this test, going from idle to 10,000 lines increased memory usage by about 13.6 MiB. Why Metal? A terminal is actually a pretty nice workload for a GPU. Most frames consist of relatively simple primitives: - backgrounds - glyphs - selections - cursors - decorations - UI surfaces The interesting part isn't making the GPU draw them. The interesting part is making sure the CPU does as little unnecessary work as possible before the GPU ever sees the frame. I try to avoid treating every refresh as: something changed, redraw everything. Instead, the architecture is designed around doing work when state actually changes and keeping the hot rendering path small. Metal also gives me direct control over pipelines, buffers, textures and synchronization, which makes profiling the renderer much less mysterious. If a frame costs 0.22 ms, I can actually investigate where those 0.22 ms went. Performance isn't just FPS One thing I've learned while building Metalterm is that terminal performance is surprisingly multidimensional. A terminal can render at 120 FPS and still feel slow. There are several independent paths that matter: PTY โ†“ read โ†“ ANSI / VT parser โ†“ terminal state โ†“ scrollback โ†“ layout โ†“ glyph preparation โ†“ GPU submission โ†“ Metal โ†“ display A bottleneck anywhere in that chain can destroy the experience. A fast renderer doesn't help if parsing a huge stream of ANSI sequences stalls the application. A fast parser doesn't help if scrollback causes constant allocations. And 120 FPS doesn't help much if input-to-photon latency is bad. So I'm now benchmarking individual parts of the pipeline rather than treating "terminal performance" as one number. A terminal is a surprisingly deep project I originally expected rendering to be one of the hardest parts. It turns out terminal compatibility is arguably harder. Once you go beyond basic command output, you quickly enter a world of: - CSI / ESC / SGR sequences - alternate screen buffers - scroll regions - Unicode and grapheme clusters - combining characters - IME - mouse reporting - bracketed paste - OSC sequences - shell integration - tmux - TUIs - resize and reflow behavior - enormous amounts of historical terminal behavior You can make a terminal that looks correct in a few days. Making one that behaves correctly when someone launches their weird 10-year-old shell configuration inside tmux inside SSH is another story entirely :) That's probably the part of the project I'm enjoying the most. Where Metalterm is going Metalterm is still young. Right now I'm focusing heavily on three areas: Compatibility I want existing shells, TUIs and developer tools to behave exactly as users expect. Performance I'm profiling parsing, PTY throughput, allocations, scrollback, input latency and rendering independently. UI I don't want Metalterm to just become another rectangle that happens to render text quickly. Metal gives me enough rendering headroom to experiment with terminal interfaces and visual styles that would normally be expensive or awkward to implement. There's a lot more I want to explore here. Try it Metalterm is available for macOS: If you work with terminals, Rust, Metal, GPU rendering or low-level macOS development, I'd especially love to hear what breaks. Real-world terminal configurations are proving to be much more creative than any test suite :) And if you're building something similar, I'd also be interested in comparing notes on terminal parsing, rendering architecture and performance benchmarking. Metalterm Rust + Metal ~10 MB app 0.22 ms GPU time/frame Built for macOS Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.