Running Ghostty on a 2012 GPU
I tried Ghostty because of ghostel.el, a terminal emulator for Emacs powered by libghostty-vt, the VT engine extracted from the Ghostty terminal.
Why I was trying ghostel in the first place
I run coding agents like Claude Code and OpenCode from the terminal, and I live in Emacs, so the obvious move is to run them in eshell. That does not work.
Eshell is not a terminal emulator. It is a shell written in Emacs Lisp that runs a command and inserts its output into a buffer. Modern coding agents are full-screen TUI applications — Claude Code, for instance, is built on Ink, a React renderer for the terminal.1 Programs like that assume a real terminal: a PTY, raw-mode keyboard input, ANSI cursor addressing, the alternate screen buffer, and a constant stream of escape sequences repainting the screen many times a second.2
Eshell provides almost none of that. It interprets basic color codes through
ansi-color and little else; it advertises TERM=dumb; its input model is
line-oriented, so the agent never sees individual keystrokes; and there is no
cursor positioning or alternate screen. So the agent’s UI arrives as garbled
escape sequences, redraws pile up in the buffer instead of replacing each
other, and interactive prompts do not respond to keys. Eshell’s escape hatch
for this — listing the program in eshell-visual-commands so it runs under
term.el — helps, but term.el is slow and its emulation is incomplete enough
that the agents still misrender.3
Ghostel fixes this the same way vterm does: by embedding a real VT engine. A native module (written in Zig) links libghostty-vt and handles terminal state, rendering, and PTY I/O, while Elisp handles buffers and keymaps — the same two-layer design as emacs-libvterm, but with Ghostty’s engine instead of libvterm. That brings the Kitty keyboard protocol, synchronized output (DEC 2026), and OSC 8 hyperlinks, none of which libvterm supports.4 Claude Code and OpenCode render and respond correctly inside it.
Ghostel only needs libghostty-vt, not the Ghostty application. But using it made me curious about the terminal itself, so I installed Ghostty on my ThinkPad T430 — the same 2012 machine from my WezTerm post.
It would not open
Launching Ghostty from the desktop, the window flashed and closed. Running
ghostty from a terminal showed why:
info: ghostty version=1.3.1
info(gtk): GTK version build=4.14.5 runtime=4.14.5
info(opengl): loaded OpenGL 4.2
warning(opengl): OpenGL version is too old. Ghostty requires OpenGL 4.3
warning(gtk_ghostty_surface): failed to initialize surface err=error.OpenGLOutdated
warning(gtk_ghostty_surface): surface failed to initialize err=error.SurfaceError
The system provides OpenGL 4.2; Ghostty’s renderer requires 4.3. The desktop launch fails the same way — there is just no terminal attached to show the log, so the window appears and dies.
I checked which GPU this machine has. glxinfo was not installed, but
lspci was enough:
$ lspci -nn | grep -Ei 'vga|3d'
00:02.0 VGA compatible controller [0300]: Intel Corporation 3rd Gen Core processor Graphics Controller [8086:0166] (rev 09)
That is the Intel HD 4000, the integrated GPU on Ivy Bridge, the third generation of Core processors, released in 2012.5
Mesa’s drivers
On Linux, OpenGL is implemented by Mesa: one userspace project containing many hardware drivers, with the right one selected at runtime for the GPU present. This machine runs Mesa 25.2.8 on Ubuntu 24.04.
Intel GPUs are split across three Mesa drivers by hardware generation:
i915 covers gen2–3, crocus covers gen4–7 (it replaced the older
i965 driver), and iris covers gen8 (Broadwell)
and newer. Ivy Bridge is gen7, so it is served by crocus —
crocus_dri.so sits in /usr/lib/x86_64-linux-gnu/dri/ on this
machine.6
Crocus exposes at most OpenGL 4.2 on Ivy Bridge, and GLSL 4.20 to match. GLSL is the language shaders are written in — shaders being the small programs a renderer runs on the GPU — and its version tracks OpenGL’s. OpenGL 4.3 is an umbrella over a bundle of features — compute shaders (GPU programs for general computation rather than drawing), shader storage buffer objects (blocks of GPU memory those programs can read and write), and others — and this generation never got the complete set.7 Haswell, one generation later, gets OpenGL 4.6. The ceiling is per GPU generation inside the driver, so no Mesa upgrade will move it.
Mesa also ships software rasterizers — renderers that do all of the GPU’s
drawing work on the CPU instead. The main one is llvmpipe, which uses LLVM
to generate fast machine code at runtime and implements
OpenGL 4.5.8 That becomes relevant below.
Advertising a version the hardware does not have
The version an application sees comes from Mesa: glGetString(GL_VERSION)
and glGetIntegerv(GL_MAJOR_VERSION, ...) on the context it created. Mesa
has an environment variable, MESA_GL_VERSION_OVERRIDE, that makes it report
a different version than the driver’s real one — 4.3FC means version 4.3,
forward-compatible core profile, the variant of OpenGL with the old
deprecated functions removed. It enables no functionality; it only changes
the advertised number. The variable exists because hardware sometimes supports
every extension an application actually uses while the driver stops short of
exposing the version umbrella, and an application that only gates on the
number will then run fine.9
$ MESA_GL_VERSION_OVERRIDE=4.3FC ghostty
info(opengl): loaded OpenGL 4.3
error(opengl): shader compilation failure id=2 message=0:1(10): error:
GLSL 4.30 is not supported. Supported versions are: 1.10, 1.20, 1.30,
1.40, 1.50, 3.30, 4.00, 4.10, 4.20, 1.00 ES, and 3.00 ES
warning(gtk_ghostty_surface): failed to initialize surface err=error.CompileFailed
In order: Ghostty’s version check now passes (“loaded OpenGL 4.3”). It
proceeds to compile its shaders, which begin with #version 430. Mesa’s GLSL
compiler, which still truthfully supports only up to 4.20 on this driver,
rejects the directive. Surface initialization fails with CompileFailed
instead of OpenGLOutdated. GTK’s own GSK renderer printed the same GLSL
errors once the context claimed 4.3.
There is a companion variable, MESA_GLSL_VERSION_OVERRIDE, that would make
the compiler accept a #version 430 directive too — but it also adds no
functionality, so it would only move the failure into whichever 4.30 features
the shaders actually use. The override moved the failure from the version
check into the compiler, which settles the question: the limit is in the
driver and hardware, not in Ghostty being overly strict.
Software rendering works
Mesa’s llvmpipe implements OpenGL 4.5, comfortably above Ghostty’s requirement:
$ LIBGL_ALWAYS_SOFTWARE=1 ghostty
info(opengl): loaded OpenGL 4.5
The surface initializes, the window opens, and Ghostty runs normally. Making
this permanent takes two small changes: a user-level desktop entry at
~/.local/share/applications/com.mitchellh.ghostty.desktop that overrides
the system one with
Exec=env LIBGL_ALWAYS_SOFTWARE=1 /usr/bin/ghostty --gtk-single-instance=true
and a shell alias for terminal launches:
alias ghostty="LIBGL_ALWAYS_SOFTWARE=1 ghostty"
Both work. But every frame is now rendered by the CPU.
Removing it
Ghostty’s selling points are its GPU renderer and the platform-native UI. On this machine the first one is gone — llvmpipe on an i5-3320M is functional but it is the opposite of what the terminal is designed around. I already use WezTerm, which runs hardware-accelerated on this same GPU (its OpenGL front end targets a version Ivy Bridge can provide), and I documented that setup earlier today. Running Ghostty in software-rendering mode gives me nothing WezTerm does not already do better here, so I uninstalled it and removed the desktop override and the alias.
Ghostel is unaffected by this. It links libghostty-vt directly and renders through Emacs, so it does not depend on the GPU. The part of Ghostty I actually needed — its terminal emulation — runs fine on this laptop, and I will keep using it through ghostel.
References
-
Ink, a React renderer for the terminal; its README lists Claude Code among the projects built on it. ↩
-
There is no single document that defines “a real terminal”; the pieces are specified separately. Pseudoterminals: pty(7). Raw versus canonical input: termios(3). Cursor addressing and the other control functions: ECMA-48 (the standard behind “ANSI escape sequences”). The de facto extensions terminals actually implement, including the alternate screen buffer (modes 1047/1049): XTerm Control Sequences. Linus Åkesson’s “The TTY demystified” explains how these fit together. ↩
-
GNU Emacs manual, Eshell — see the Input/Output and Visual Commands sections for the
TERMhandling and theeshell-visual-commandsmechanism. ↩ -
Kitty keyboard protocol; synchronized output (DEC mode 2026); OSC 8 hyperlinks. ↩
-
The identification comes from the PCI ID in brackets.
lspciprints the device name from the PCI ID database, where vendor8086is Intel and device0166is the “3rd Gen Core processor Graphics Controller” — the third Core generation is Ivy Bridge, launched in 2012. The marketing name is in Mesa’s driver table,crocus_pci_ids.h, which maps0x0166toivb_gt2, "Intel(R) HD Graphics 4000". The CPU agrees: this machine’s i5-3320M ships with HD Graphics 4000. ↩ -
Mesa documentation for the driver architecture; mesamatrix.net tracks which OpenGL version each Mesa driver supports. ↩
-
OpenGL 4.3 core specification (PDF), Khronos registry — its change-log appendix lists the features new in 4.3 (2012), including compute shaders and shader storage buffer objects. ↩
-
Mesa documentation, Environment Variables — documents
MESA_GL_VERSION_OVERRIDE, theFCsuffix, andMESA_GLSL_VERSION_OVERRIDE. ↩
Comments