What should win a rendering race: a UI framework architected around the GPU from its first commit, or a widget toolkit transplanted out of a 3D content creation suite that draws on the CPU by default? A week ago that was a pub question. On November 6, a project called BUI reached the Hacker News front page with 532 points and a published set of benchmarks against GPUI, the GPU-accelerated Rust UI framework that powers the Zed editor, and the pub question suddenly had numbers attached.
BUI is Blender's user interface, decoupled from the Blender codebase into a standalone library. The README reports that the same gpui-syntax source file, compiled unchanged against BUI, GPUI, and the performance fork gpui-fast, shows its first frame roughly five times sooner under BUI, and that on a scene with 3,000 animated cells BUI holds 59.9 frames per second where GPUI manages 19.9. Claims this strong deserve a skeptical read rather than applause, so this article goes through the published material the way you would review a benchmark in a pull request: what was measured, on what hardware, with which caveats, and what the mechanism behind the numbers actually is.
What BUI actually is
The core of BUI is what its author calls the kit: a small runtime written in C that holds every widget in one retained tree, lays the widgets out, handles mouse and keyboard input, and draws them the way Blender does. The drawing code is a C port of Blender's own interface_widgets.cc, and the interaction rules follow Blender's conventions, down to the number fields you can scrub horizontally and the expression evaluator that accepts 2*pi in a text box. Thin binding layers on top expose the same kit to Python, Rust, JavaScript, C++, and Dear ImGui.
The README is unusually explicit about what the project is not. It is not Blender: it contains only the UI parts it needs, and you do not have Blender installed to use it. It is not a browser: the Rust and JavaScript sides accept HTML and CSS as a UI description language, but there is no browser engine, no webview, and no network stack. And it is not a re-imagining: the Dear ImGui support runs the real Dear ImGui 1.93, and the widget drawing is ported rather than rewritten.
The most striking demo of the port's fidelity is that an unmodified tkinter program runs on it:
import blenderui.tksyntax as tk from blenderui.tksyntax import ttk, messagebox root = tk.Tk() name = tk.StringVar(value="Cube") ttk.Entry(root, textvariable=name).pack(fill="x", padx=8, pady=8) ttk.Button(root, text="Greet", command=lambda: messagebox.showinfo("Hi", "Hello " + name.get())).pack() root.mainloop() Every public tkinter and ttk name, all 7,846 of them, is claimed as covered. The repository was created on August 26 of this year and already carries the GPL, a detail that matters for adoption and that we will come back to.
The fairest comparison the author could build
Benchmark disputes in UI frameworks usually die on the same hill: the two sides did not run the same program. BUI's author preempts that dispute with an unusual move. One source file, settings.rs, written in gpui syntax, is compiled unchanged against three targets: blenderui, GPUI itself, and gpui-fast. The window is a small settings panel with a label, a text field, a checkbox, a slider, and a button. Because GPUI ships no form widgets, its version renders the same controls as styled divs, which the author flags openly as a limitation of the comparison rather than hiding it.
The measurement setup is similarly declared up front: a Ryzen 7 5800U laptop on Windows 11, on AC power, median of ten launches per configuration, timing from process start until the first frame is on screen. The author also warns that the numbers predate the project's newest features and come from a single machine. That is the correct posture, and it makes the results more credible, not less.
Startup: where the gap opens
The startup table is where the contrast is sharpest. With BUI's default CPU renderer, the Rust version of the settings window reaches its first frame in 38 milliseconds, spends 27 milliseconds of CPU getting there, idles at 13 MB of memory, and burns zero milliseconds of idle CPU per minute. The gpui-syntax file compiled against blenderui tells the same story: 40 milliseconds to first frame, 27 milliseconds of CPU, 15 MB, zero idle.
The same file compiled against GPUI reports 231 milliseconds to first frame, 226 milliseconds of CPU consumed during startup, 29 MB at idle, and 743 milliseconds of idle CPU per minute, which works out to about 1.2 percent of one core keeping the window alive while nothing happens. The gpui-fast fork lands at 226 milliseconds and 217 milliseconds of CPU. In the author's summary: about five times sooner to first frame, about eight times less CPU spent starting, and half the memory on the CPU renderer.
That idle line is easy to skip and should not be skipped. A settings panel spends essentially its entire life idle. A framework that keeps 1.2 percent of a core busy doing nothing is paying a battery tax on behalf of every open window, forever.
Sustained frames: 3,000 animated cells
The heavy scene is a window filled with 3,000 rounded, numbered cells, every cell changing every frame, which the author labels the worst case. All four apps ran in the same session, with the GPU rows on Direct3D 11. BUI on its GPU renderer holds 59.9 frames per second at 11.6 milliseconds of CPU per frame. BUI on its software raster holds 57.6 frames per second at 22 milliseconds of CPU. gpui-fast reaches 31.5, and GPUI reaches 19.9 at 50.5 milliseconds of CPU per frame plus its GPU time. Memory runs 40, 30, 65, and 77 MB respectively, and first-frame latency is roughly 5 milliseconds for both BUI renderers against about 200 and 220 milliseconds for the GPUI pair.
Two honest wrinkles accompany those numbers. First, gpui-fast's frame rate varied between 30 and 60 across sessions on the test laptop, so the author instructs readers to compare only within one session. Second, uncapped figures, 1,491 frames per second for 100 cells on BUI's GPU renderer falling to 94 at 3,000 cells, describe throughput headroom rather than anything a display can show. The interesting comparison at display rates is the CPU cost per frame, because that is what competes with the rest of your application.
The mechanism: damage rendering, not brute force
Why does a software raster hold up against a GPU pipeline? The README's answer is damage rendering, and the discipline is worth quoting in full because it is the entire performance philosophy in six steps: a change marks only its widget's rectangle dirty; dirty rectangles merge, up to 24 per window; if nothing is dirty the loop does not render at all and sleeps until the next input or timer; only dirty regions redraw, clipped to their rectangles; the raster then compares the new pixels against the previous frame in 64-by-32 tiles, so a redraw that changed nothing reports nothing; and only damaged tiles reach the screen.
The consequence is that a hover frame, the frame a settings panel actually produces, costs about 0.05 milliseconds on the component demo, while a complete redraw of everything costs about 0.7 milliseconds. A framework that repaints its tree every frame is doing the full-tree cost hundreds of times per second of interaction. A damage-tracked framework pays the 0.05 millisecond price for the common case and the 0.7 millisecond price only when the whole window genuinely changes. The GPU is excellent at the expensive frame, but the trick is arranging never to draw it.
Two renderers, one pixel stream
BUI's GPU renderer is opt-in rather than default, and the engineering around it is telling. Every primitive is one 128-byte instance fed to a single uber-shader, so a frame is a few instanced draw calls on Direct3D 11 under Windows or OpenGL 3.3 core under Linux. Text and images live in cached atlases, frames render into a persistent target so only damaged regions redraw, and presentation on Windows uses a flip-model swap chain with dirty rectangles. On Linux the GL libraries load at run time, and where no device can start, BUI quietly stays on the CPU.
The parity claim is the part most projects would skip: the GPU shaders evaluate the software raster's formulas, and a parity test finds 10 pixels out of 172,800 differing by more than 3 out of 255. Turning the GPU on is one line in any binding:
import blenderui as bui bui.set_gpu(True) # before creating windows; tkinter syntax: tk.Tk(gpu=True) let app = Application::new().gpu(true); The author's own guidance is measured: the CPU renderer for tool windows, forms, settings panels, servers, and tests, because it idles at zero and uses the least memory; the GPU renderer for large HiDPI windows with heavy animation, where it costs about 10 MB more plus GPU memory and saves roughly half the CPU per frame. macOS gets the CPU renderer for now because no Metal backend exists yet.
The caveats that travel with the numbers
The README's limitations section reads like it was written by someone who has been burned by benchmark posts before. All numbers come from one Windows 11 laptop; Linux and macOS have not been measured. There is no accessibility support at all: BUI draws its own widgets and does not talk to UI Automation, AT-SPI, or NSAccessibility, so screen readers cannot see a BUI window, and the author says not to ship it in software that must be accessible. Text is Blender's bundled Inter font only, with no per-glyph fallback, no input methods, and no OpenType features. The toolkit looks like Blender, not like your operating system. Qt-syntax coverage is about 24 percent, and 261 of 745 catalogued CSS properties render.
Then there is the license. BUI is GPL-3.0-or-later, and a program that includes it is distributed under the GPL as well. For proprietary desktop tools, that is not a footnote; it is a decision point that arrives before any benchmark does.
Run the benchmarks yourself
Every number cited here comes from a tool inside the repository, and the author publishes the method alongside the results. The Rust comparison suite builds all four apps and measures its own CPU cycles, frames, and memory, with GPU utilization pulled from Windows performance counters:
cd bindings/rust/bench cargo build --release py run.py --reps 3 target\release\bui-bench --cells 3000 --mode anim --uncapped Setting BUI_PROFILE=1 on any BUI app prints per-phase frame times, layout, paint, raster, and present, every 60 frames, which is the right way to check whether the claims hold on your own hardware and your own widget mix.
What this changes if you ship desktop tools
The BUI numbers do not prove that GPU-accelerated UI frameworks are a mistake. They prove something narrower and more useful: that the largest UI performance wins on typical tool windows come from architecture rather than from the rendering device. Damage tracking, sleeping at idle, a retained tree that only recomputes what changed, and a startup path that does not pay for a GPU device before the first pixel, these are decisions available to any framework. The 5-millisecond first frame beats the 60-frames-per-second figure as the story of this benchmark, because startup and idle are where real tool windows live.
GPUI remains pre-1.0, carries over 445,000 crate downloads, and powers one of the fastest editors in daily use; a single community benchmark will not change its trajectory, and the HN commenters most excited about BUI said so explicitly. But the next time someone asserts that a modern UI must be GPU-rendered to feel fast, there is now a public, re-runnable counterexample drawn from the most battle-tested widget set in open source. Measure the first frame, measure the idle minute, and only then argue about shaders.
Originally published on Dispatch.
