emacs-gpu/README.md
Andros Fenollosa b766212695 Metal: coalesce presents and composite in one command buffer
A redisplay pass runs several update cycles back-to-back (buffer window
plus echo area), and with display sync every present blocked on a
drawable: two blocking presents per keystroke halved typing throughput,
and the present itself paid for a second command buffer.

Presents now coalesce: one landing within ~8 ms of the previous commits
to the static texture and is flushed by a one-shot main-queue block
(or absorbed by the next cycle's present), and the composite pass is
encoded on the same command buffer as the cycle's draws.  The policy's
flush presents through the same coalescing path.

Benchmarks (M1 Pro, font-locked xdisp.c, 120x45): machine-paced typing
goes from 30 to 106 chars/s and sustained scroll from 116 to 475
redisplays/s with vsync off, matching the stock backend's throughput
and total CPU; with vsync on (default) the same visible 60 fps now
costs ~35% less CPU than stock.  README gains the numbers.

Pixel parity, GIF/video playback, the first-tab-switch deferral and the
echo-area tests are all unchanged.
2026-06-04 17:55:53 +02:00

4.6 KiB

emacs-gpu

GNU Emacs with a GPU-accelerated display backend.

On macOS it renders with native Apple Metal: text goes through a GPU glyph atlas, images and inline video are textures, and the whole frame is composited by the GPU instead of CoreGraphics. The output is pixel-accurate against the stock Cocoa backend.

OpenGL support for GNU/Linux and Windows is planned but not implemented yet. The drawing logic is already platform-neutral (src/gfxterm.c) behind a small driver interface (src/gfxdrv.h); an OpenGL driver only needs to implement that interface (src/glterm.c is the documented skeleton). Contributions welcome.

Why a GPU backend?

Beyond raw rendering, it enables things the stock backend cannot do:

  • Inline video playback: AVFoundation decodes straight into Metal textures (zero copies) and the frames are composited inside the buffer, following scrolling and clipped to the window. No xwidgets, no embedded browser.
  • GPU cursor effects (opt-in): expanding rings, comet trails and friends are drawn as a compositor overlay, without ever touching the buffer content underneath.
  • A path to cheap visual effects: buffer transitions, smooth scrolling or any future eye candy is one more shader pass over the composited frame, not a rewrite of the display engine.

Text is rasterized once into a GPU glyph atlas and drawn as textured quads; scrolling moves already-rendered pixels with a texture blit.

Performance

Measured on an Apple M1 Pro (Emacs 32 development build, 120x45 frame, font-locked xdisp.c, same binary with and without the GPU backend; /usr/bin/time -l over scripted workloads):

Workload Stock (Cocoa) GPU, vsync on (default) GPU, vsync off
Sustained scroll, redisplays/s 481 324 475
CPU for 15 s of that scroll 16.0 s 10.5 s 15.5 s
Typing throughput (chars/s, machine-paced) 108 52 106
Idle (8 s) CPU 1.21 s 1.19 s same
Peak RSS ~140 MB ~144 MB same

Honest reading:

  • Machine-paced throughput and CPU cost match the stock backend (vsync off). There is no GPU tax.
  • With vsync on (the default), presents wait for the display refresh: the screen shows the same 60 fps either way, but Emacs burns ~35% less CPU under flat-out scrolling because it stops rendering frames nobody can see. Human-paced input is unaffected (the cap is ~52 machine-paced updates/s; keyboard auto-repeat tops out well below that). (mtl-vsync nil) switches to uncapped, stock-like behavior.
  • Idle cost is identical and the GPU resources add ~4 MB of RSS.

Status: experimental, under active development.

Note: I am not answering issues for now. Feel free to open them as a public record (they will be read eventually), but do not expect a reply at this stage.

Demos

Inline video playing inside a buffer, decoded by AVFoundation straight into Metal textures (mtl-video-insert):

Inline video

An animated GIF playing next to font-locked code scrolling, all composited by the GPU:

Animated GIF and code

GPU cursor effects (mtl-animations), here the sonicboom mode:

Sonicboom cursor

Building on macOS

Requires Xcode (or the Command Line Tools) and the usual Emacs build dependencies (brew install autoconf automake gnutls texinfo pkg-config).

./autogen.sh

SDK=$(xcrun --sdk macosx --show-sdk-path)
CC="xcrun clang" OBJC="xcrun clang" \
CFLAGS="-isysroot $SDK" CPPFLAGS="-isysroot $SDK" OBJCFLAGS="-isysroot $SDK" \
./configure --with-ns --with-mtl

make -j$(sysctl -n hw.ncpu)

The binary is src/emacs (or install the app bundle from nextstep/).

Enabling the GPU backend

Emacs starts with the regular Cocoa backend; switch a frame to Metal with:

(add-to-list 'load-path "/path/to/emacs-gpu/lisp")
(require 'mtl)
(mtl-enable)

Extras once enabled:

(mtl-video-insert "video.mp4" 480 270 t)  ; inline video, follows scrolling
(mtl-animations t)                        ; GPU cursor effects (experimental)
(mtl-draw-stats)                          ; renderer counters

How it works

redisplay engine (xdisp.c, untouched)
        ↓
src/gfxterm.c   platform-neutral drawing policy
        ↓
src/gfxdrv.h    driver interface (~25 ops)
        ↓
src/mtlterm.m   Metal driver: glyph atlas (CoreText → R8 texture),
                render cycle on a persistent texture, AVFoundation
                video through CVMetalTextureCache

License

GNU General Public License v3 or later, same as GNU Emacs.