BUI Blender's user interface, as a library you can use from Python, Rust, JavaScript/React, C, C++ and Dear ImGui. People spent decades building and optimizing Blender's GUI. It's battery and memory efficient, and super duper fast. We decoupled it, to give everyone a chance to build their own products with it. It supports multiple languages and frameworks, so you won't miss a beat! Website and full documentation: bui.preetham.top This README is a small book. Read it front to back for the whole story, or jump to the chapter you need. Every chapter links to a longer guide in docs/ or on the website when there's more to say. Contents Part I: Welcome What BUI is Why BUI exists Who it's for The many ways in Features at a glance A gallery Part II: Getting started Install First app: tkinter First app: Blender components First app: Qt syntax First app: Rust, in rsx and gpui builders First app: JavaScript and React First app: Dear ImGui First app: C Part III: Concepts How it works Blender's widgets and interactions Three looks: HTML, styled and Blender components Rendering on the CPU and the GPU Damage rendering Window hosts Text and fonts Themes: dark and light Part IV: Guides by language Python Rust JavaScript and React Dear ImGui C and embedding Where the docs live Part V: Performance Before you read the numbers Start time and battery Frame times JavaScript and React: startup and animation Run the benchmarks yourself Part VI: Reference Platforms Environment variables Building from source and CMake options Coverage Repository layout Examples Part VII: The project Status and roadmap FAQ Contributing and publishing License and credits Part I: Welcome 1. What BUI is BUI takes the user interface of Blender (its widget drawing, its interaction rules and its look) and turns it into a standalone UI library with a small C core. Your code builds the widget tree and handles callbacks. Layout, input, drawing and presenting all happen in fast native code, on the CPU or the GPU. At the heart of BUI is the kit: a small runtime written in C. The kit holds every widget in one tree, lays them out, handles the mouse and keyboard, and draws them the way Blender does. The drawing code is a C port of Blender's own widget code, and the interaction follows Blender's rules. On top of the kit sit bindings: thin layers that let Python, Rust, JavaScript, C++ and Dear ImGui talk to it. What BUI is not: Not Blender. It contains the parts of Blender's UI code it needs and builds on its own. You don't need Blender installed, and you don't need to know Blender to use it. Not a browser. The Rust and JavaScript sides use HTML and CSS as a UI language, but there's no browser engine, no webview and no network. Not a re-imagining. The widget drawing is ported from Blender's interface_widgets.cc, and the Dear ImGui support runs the real Dear ImGui. 2. Why BUI exists Blender's interface is famous for how it feels. You press a number field and drag: the cursor vanishes, the value glides under your hand, and you never hit the edge of the screen. You type 2*pi into a field and it just works. You type "ad cu" into a menu and it finds "Add Cube". Millions of artists have these habits in their hands. That interface is also fast and frugal. It redraws only what changed, sleeps when nothing does, and starts in milliseconds. BUI exists so that any app, in any language, can have both: Blender's feel, and Blender's efficiency. 3. Who it's for You have a tkinter app and wish it looked less like 1998. Run it on BUI, unchanged. You're building a tool for artists who already know Blender. Give them the panels, sliders and shortcuts their hands expect. You write Rust or React and want a native desktop UI without shipping a browser. You use Dear ImGui and want it to look and feel like Blender, with one build flag. You're curious how a UI toolkit works. The source is readable, and the concepts are explained in plain words. 4. The many ways in You can drive BUI many ways, and all of them share the same C runtime, so they all get the same widgets and interactions: Not sure which to pick? If you already have code, use the row that matches it. If you're starting fresh in Python, the Blender components are the friendliest. 5. Features at a glance Blender's widgets and interactions Number fields you drag with a hidden, continuous cursor: Shift for precision, Ctrl to snap. Math in number fields, with Blender's own expression evaluator: 2*pi, min(4, 3), round(1.7), x if c else y. Ctrl+Wheel steps values, Ctrl+C / Ctrl+V copy values, Backspace resets. Drag across checkboxes to set them all. Press-drag-release menus, pie menus, and Blender's fuzzy menu search: "ad cu" finds "Add Cube", typos are forgiven, recent picks rank first. Ctrl+Z / Ctrl+Shift+Z on every UI edit, and inside a text field while you type; Tab between fields. Blender's float display precision, multi-field vertical drags, and panels that collapse with Blender's animation. The HSV color wheel, color ramps, curves, trees, lists, text areas, canvases, a node editor and Blender's File Browser. Blender's Dark and Light themes, switchable at run time. Python, four ways tkinter: all 7,846 public names of tkinter and ttk. Nine real programs behave identically on the standard library and on BUI. Qt syntax: widgets, layouts, signals, QPainter, menus and timers. Components: 45 Blender widgets, editors and templates. Dear ImGui: from blenderui import imgui, pyimgui-style. One pip install, no Qt or Tk widgets drawing anything. Rust: the web platform, natively All 113 HTML elements, drawn like a browser draws them; styled gives any subtree Blender's look, and Blender components (
,
{/* a Blender component */} ) } render(
, { title: "Counter", width: 360, height: 200, gpu: true }) bun app.jsx # run it (Bun reads JSX directly) npx bui build app.jsx # or ship it: app.exe, one 7 MB file Without React, @blender-ui/native gives you a DOM-like API: const { App, signal } = require("@blender-ui/native") const app = new App({ gpu: true }) const win = app.window({ title: "Hello", width: 360, height: 200 }) win.body.className = "m-0 p-6 flex flex-col gap-3 bg-[#2b2b2b] text-[#e6e6e6]" const clicks = signal(0) const label = win.createElement("span") label.textContent = clicks // follows the signal const button = win.createElement("button") button.textContent = "Add one" button.addEventListener("click", () => clicks.value++) win.body.appendChild(label) win.body.appendChild(button) A task list in React (examples/site/js/todo_react.js); the DOM-like version draws the same Node.js 18+ or Bun. Prebuilt for Windows x64; on Linux, build the addon from source (npm run build in bindings/node) and windows are native. Guide: docs/javascript.md. 13. First app: Dear ImGui The real Dear ImGui 1.93 runs on BUI: BUI's windows, input, CPU/GPU renderer, Blender's theme and the Inter font. Existing ImGui code compiles unchanged. C++: #include "imgui.h" #include "imgui_impl_bui.h" // see examples/imgui_demo.cpp static void frame(void*) { ImGui::ShowDemoWindow(); ImGui::Begin("Hello"); static float v = 0.5f; ImGui::SliderFloat("Value", &v, 0.0f, 1.0f); ImGui::End(); } int main() { ImGui::CreateContext(); ImGui_ImplBUI_App* app = ImGui_ImplBUI_CreateApp("Demo", 1280, 800, frame, nullptr); ImGui_ImplBUI_RunApp(app); ImGui_ImplBUI_DestroyApp(app); ImGui::DestroyContext(); } Python: import blenderui.imgui as imgui # C: cimgui.h · Rust: the blenderui-imgui crate state = {"value": 0.5} def frame(): imgui.show_demo_window() imgui.begin("Hello") _, state["value"] = imgui.slider_float("Value", state["value"], 0.0, 1.0) imgui.end() imgui.run(frame, title="Hello", size=(1280, 800)) C (cimgui.h, cimgui conventions: ImGui_Button, ImGui_ButtonEx, and the backend in cimgui_impl_bui.h) and Rust (blenderui-imgui) work the same way: use blenderui_imgui as imgui; fn main() { let mut value = 0.5f32; imgui::run("Hello", [1280, 800], move || { imgui::show_demo_window(); imgui::begin("Hello"); imgui::slider_float("Value", &mut value, 0.0, 1.0); imgui::end(); }); } Build with -DBUI_IMGUI_BLENDER_WIDGETS=ON (or the Rust feature blender-widgets) and ImGui's widgets become Blender's own: DragFloat is Blender's drag-to-scrub number field (Shift for precision, Ctrl to snap), SliderFloat Blender's number slider, checkboxes drag-toggle, menus and combos get Blender's menus. The rest of the ImGui API is unchanged. (The PyPI wheel is built without it, so from Python you get ImGui's own widgets in Blender's theme unless you build from source.) Dear ImGui's own demo with BUI_IMGUI_BLENDER_WIDGETS=ON: number fields, sliders, color rows and panel headers are Blender's The same ImGui code: ImGui's widgets (left) and the Blender-widgets build (right) (examples/site/imgui_inspector.cpp) Guide, coverage and the stock look: docs/imgui.md. 14. First app: C The kit's API is one header, kit/include/BUI_kit.h, and it doubles as the reference: every function has a one-line comment. This is a shortened examples/kit_hello.c: a slider and a button in a native window. #include
#include "BUI_kit.h" static void on_event(void *data, BKNode *node, int kind, void *ref, double a, double b) { if (kind == BK_EV_CLICK) printf("clicked %s\n", BK_get_str(node, "text")); else if (kind == BK_EV_CHANGE) printf("%s = %g\n", BK_get_str(node, "label"), BK_get_num(node, "value")); } int main(void) { BKNode *root = BK_node_new("frame"); /* everything is a node */ BK_set_num(root, "padding", 8); /* configured by property name */ BKNode *size = BK_node_new("slider"); BK_set_str(size, "label", "Size"); BK_set_num(size, "max", 10); BK_set_num(size, "value", 2.5); BK_node_add(root, size, -1); BK_geom_set_manager(size, BK_GEOM_PACK); /* Tk's packer, in C */ BK_geom_set(size, "fill", 1); BKNode *apply = BK_node_new("button"); BK_set_str(apply, "text", "Apply"); BK_node_add(root, apply, -1); BK_geom_set_manager(apply, BK_GEOM_PACK); BKContext *ctx = BK_context_new(root); /* input, focus, popups, undo */ BKHost host = {0}; host.emit = on_event; /* the window fills in the rest */ BK_context_set_host(ctx, &host); if (BK_window_supported()) { /* Win32, X11 or Cocoa */ BKWindow *win = BK_window_new(ctx, "BUI from C", 320, 120); BK_window_show(win); BK_window_run(); BK_window_free(win); } BK_context_free(ctx); BK_node_free(root); return 0; } cmake -S . -B build -DBUI_BUILD_EXAMPLES=ON cmake --build build --target bui_kit_hello A bigger C program: a modifier stack (examples/site/kit_modifiers.c) Without a window system, render into a raster instead (BK_render(ctx, raster, w, h, full)), which is also how tests and screenshots work. Guide: docs/c-api.md. Part III: Concepts 15. How it works your code: tkinter Qt components Dear ImGui (C++/C/Python/Rust) Rust: rsx! / HTML / CSS / Tailwind / hooks JS / React │ │ │ │ │ │ └──── Python: options, signals │ ImGui core + BUI backend └── style cascade + taffy layout ◄────┘ │ │ │ C kit (kit/): node tree · geometry · input & interaction · Blender widget drawing · undo · animation · themes · Blender search/expr │ renderer: damage-driven software raster (parallel) or GPU (Direct3D 11 / OpenGL 3.3, instanced, opt-in) │ window: native Win32 / X11 / Cocoa window · stdlib Tk window · headless (tests, screenshots) Your code, in any language above, creates widgets and says what happens when they change. The kit keeps them in one tree, lays them out and handles input, following Blender's rules. The renderer draws them, on the CPU by default or on the GPU if you ask. Damage rendering keeps it cheap: a change redraws only the rectangle it touched, and an idle window uses no CPU at all. What's under the hood: Blender's real UI code. Widget drawing is a C port of Blender's interface_widgets.cc (round_box_edges, widgetbase_draw, the shape tables), and interaction follows interface_handlers.cc. Color math, rects, text cursors, string search and expression evaluation are Blender's blenlib, copied in. A few interface files (string search, text-edit undo, float precision) are copied into kit/intern/blender/ and edited for the kit. The X11 and Cocoa hosts follow Blender's GHOST; the Win32 host is BUI's own. One node tree. Every widget is one kit node. The C kit owns layout, hit testing, hover, focus, drags, menus, tooltips, undo and animation. Damage rendering. A change marks only its widget's rectangle, and only changed tiles reach the screen. Thin bindings. Python, Rust and JavaScript keep only what their syntax layer needs. More: ARCHITECTURE.md and How it works. 16. Blender's widgets and interactions These rules live in the C kit, so they work the same everywhere Blender widgets are drawn: the Python components, tkinter and Qt syntax, the Rust and JavaScript Blender components and form controls inside styled, ImGui's Blender-widgets build, and every C kit widget. Drag to scrub. Press a number field and drag sideways. The cursor hides and wraps, so you never hit the screen edge, and comes back where the drag started. A float moves one click step per pixel; Shift moves at a tenth of the speed; Esc cancels and restores the value. Ctrl snaps with Blender's rules: unbounded floats to whole units, angles to 10°, sliders to 0.1, 1 or 10 depending on their range, integers to multiples of 10. Click to type, and type math: Blender's expression evaluator (expr_pylike_eval.cc) turns 2*pi, 90/4, min(4, 3) or x if c else y into numbers. This works in Blender number fields and sliders; plain text fields keep what you type as text. Shortcuts on any value: Backspace resets to the default, - negates, Ctrl+Wheel steps, Ctrl+C / Ctrl+V copy and paste, right-click opens Copy, Paste and Reset. Several fields at once. Drag down over a column of number fields to collect them, then sideways to change them together. Drag across checkboxes (or panel headers) to set them all in one stroke. Panels collapse with Blender's animation; Ctrl+click opens one and closes the others; drag the grip to reorder. Menus work press-drag-release or click; hover switches menu-bar menus; pie menus open around the pointer. Menu search is Blender's string search: start typing in any open menu. It matches word initials ("ad cu" finds "Add Cube"), forgives typos, and ranks your recent picks first. Undo everywhere. Ctrl+Z and Ctrl+Shift+Z work on every UI edit, and inside a text field while you type. A burst of typing or a multi-field drag is one step. Float precision follows Blender: small values show the decimals they need. Tooltips appear after Blender's half-second delay. Coming soon: typing values with units ("2 m"), Blender's search-as-you-type popups, and widget drag and drop. More: Blender's widgets. 17. Three looks: HTML, styled and Blender components In Rust and JavaScript you pick the look per element: The components are Button, Checkbox, Radio, NumberField, NumberSlider, TextField, TextArea, ColorSwatch, Dropdown and ProgressBar. Each one is the HTML element it stands for, so every attribute, style and event works on it: rsx! {
// browser look // Chrome's slider // Blender's slider
// Blender's look inside
} div().child(Checkbox().checked(true)).child(Button().child("Apply")) // gpui syntax import { render, NumberSlider, Button } from "@blender-ui/react" //
,
// DOM API: win.createElement("NumberSlider") cargo run --example forms: plain HTML, inside styled, and the Blender components The same markup, plain (left) and inside styled (right). Plain HTML looks the same in both themes. Blender components from Rust (site_components.rs) Python's Blender components (Panel, PieMenu, the node editor ...) are a separate, larger set. 18. Rendering on the CPU and the GPU BUI has two renderers, and they draw the same pixels. The software raster (gpu/intern/gpu_raster.c) is the default. It draws anti-aliased shapes with signed-distance coverage, like Blender's widget shaders, into one frame buffer, compares it with the last frame in 64×32 tiles, and reports only the tiles that changed. Big canvases draw in parallel bands on Windows (BUI_RASTER_THREADS). It needs nothing from your graphics card and uses the least memory. The GPU renderer is opt-in: Direct3D 11 on Windows (gpu/intern/gpu_d3d11.c) and OpenGL 3.3 core on Linux (gpu/intern/gpu_gl.c). It's modeled on Blender's instanced widget drawing: each primitive is one 128-byte instance of a single uber-shader, so a frame is a few instanced draw calls. Text and images are cached in atlases. Frames render into a persistent target, so only damaged regions redraw; on Windows, a flip-model swap chain presents dirty rectangles. On Linux, libGL, libEGL and libX11 are loaded at run time (GLX for windows, EGL for offscreen), so no GL headers are needed, and BUI falls back to the CPU where OpenGL is missing. The shaders evaluate the software raster's formulas: the parity test finds 10 of 172,800 pixels that differ by more than 3/255, on both backends. Turn the GPU on: BUI_GPU=1 overrides the app-wide setting in code; BUI_GPU=0 turns the app-wide defaults off, but a window that asks for the GPU itself still gets it. Where no device can start, BUI quietly stays on the CPU (BUI_GPU_DEBUG=1 says why). Which one? A tool window, form or settings panel: the CPU (lightest on memory, idles at zero). A large or HiDPI window, lots of animation, thousands of items: the GPU (about half the CPU per frame, or less, for about 10 MB more RAM plus GPU memory). Servers and tests: the CPU. macOS: the CPU, for now (a GPU renderer there is coming soon). More: Rendering. 19. Damage rendering Leave a BUI window open and walk away: it uses no CPU at all. Hover a button, and BUI redraws that button, not the window. A change marks only its widget's rectangle as dirty (popups get a margin for their shadow). Dirty rectangles merge (up to 24 per window). If nothing is dirty, the loop doesn't render; it sleeps until the next input or timer. Only the dirty regions are redrawn, clipped to their rectangles. The raster compares the new pixels with the last frame in 64×32 tiles; a redraw that changed nothing reports nothing. Only damaged tiles reach the screen (Win32 copies them, X11 sends each rectangle with XPutImage, Cocoa invalidates them, the GPU presents dirty rectangles). On the full component demo, that's about 0.7 ms for a complete frame and about 0.05 ms for a hover or number-scrub frame. Animations run only while something moves, paced to the display. More: Damage rendering. 20. Window hosts A window host puts a kit context in a top-level window and feeds it input. BUI_UI_SCALE=1.5 fixes the UI scale instead of following the display (1 is Blender's default, 20 px widgets). More: Window hosts. 21. Text and fonts Your app's text looks the same on Windows, Linux and macOS, and it looks like Blender's: Inter, Blender's UI font, is embedded (the variable version, with optical-size and weight axes). UI text is 11 pt at scale 1. Rendered like Blender's BLF: BUI vendors FreeType 2.13.3 and renders with light hinting, Blender's gamma-1.43 coverage table, Inter's optical size, a real bold instance, no kerning (as in Blender), and Blender's text shadows. With BUI_TEXT_FREETYPE=OFF, stb_truetype rasterizes instead. On the GPU, glyphs are drawn from their outlines with Slug by default, or from a per-glyph atlas like Blender's (BUI_SLUG=0, bui.set_slug(False), .slug(false), BUI_gpu_set_slug(0)). Other fonts: BUI_FONT=path.ttf replaces Inter, BUI_FONT_DIR adds a folder to search first, blenderui::add_font (Rust) and BUI_text_add_font (C) register faces from memory, and CSS @font-face loads local files. Windows' GDI text is opt-in (-DBUI_TEXT_GDI=ON, Rust feature gdi-text): ClearType-style hinting and system fonts, without Slug. Limits worth knowing: no per-glyph font fallback yet (missing characters show as boxes), no IME, right-to-left text is reordered but not shaped, no OpenType features. Font fallback and IME are coming soon. More: Text and fonts. 22. Themes: dark and light BUI looks like Blender because it uses Blender's theme. It ships two: Blender's Dark theme (the default) and its Light preset. Both are generated by tools/gen_theme.py from Blender's own Blender_Dark.xml and Blender_Light.xml presets, over Blender 5.x's built-in defaults. Switch at run time, and every open window relayouts and redraws: Widgets, panels, the region background, the styled HTML sheet and ImGui's Blender style all follow the theme. Plain HTML keeps a browser's look either way. Inside each theme you can still color things yourself: bg / fg in tkinter, setStyleSheet in Qt syntax, color= on components, full CSS on HTML in Rust and JavaScript, ImGui's own style colors. BUI also includes Blender's icons: all 781 of Blender's SVG icons, rasterized with Blender's NanoSVG code so they're sharp at any scale. Use them by Blender's name: Button("Add", icon="ADD"), EnumRow(..., icons=[...]). Coming soon: loading any Blender theme file, beyond Dark and Light. More: Theming. Part IV: Guides by language 23. Python pip install blenderui gives you four APIs in one package: tkinter syntax (blenderui.tksyntax): every public tkinter and ttk name (7,846 of 7,846). Run existing programs with blenderui-tk app.py, or call blenderui.tksyntax.install() first. messagebox and simpledialog are Blender-styled, colorchooser is Blender's color picker, and filedialog is Blender's File Browser (BUI_FILEDIALOG=native for the OS dialogs). Look-only Tk options (relief, ttk.Style ...) are accepted, but the look comes from Blender's theme. Guide: docs/tkinter.md. Qt syntax (blenderui.qtsyntax): widgets, layouts (box, grid, form, stacked), signals, QPainter, menus, timers and animations, QFileDialog as Blender's File Browser. Partial: model/view classes, docks, toolbars, splitters and Qt stylesheets are missing. Guide: docs/qt.md. Blender components (blenderui.components): 45 components, from NumberSlider to NodeEditor. Reference: docs/components.md. Dear ImGui (blenderui.imgui), pyimgui-style. Guide: docs/imgui.md. You can mix them in one process: windows, timers and the event loop are shared (bui.host). Every interaction runs in C, so tests can drive widgets headless. On the website: tkinter, Qt syntax, components, headless testing. 24. Rust The blenderui crate is a web-like framework drawn by BUI: every HTML element, CSS, Tailwind, HTML-like markup (rsx! / view!) or gpui-style builders, components and hooks. A taste: rsx! { // Tailwind as attributes, any variant, arbitrary values; typos fail to compile
// control flow right in the markup
{for item in items {
{item}
}}
{if dirty {
"Unsaved changes"
}} // real CSS, evaluated by the engine
} error: rsx!: `bg-sky-50O` is not a Tailwind utility; did you mean `bg-sky-500` or `bg-sky-50` or `bg-sky-100`? What's in the box: Layout by taffy: block flow and wrapping text, flexbox and grid (every property, including grid-template-areas and named lines), tables, relative / absolute / fixed positioning (fixed boxes are placed against the window), scrolling, and bidirectional text. CSS: selectors level 4, nesting, @layer / @media / @supports / @scope / @container / @font-face / @property, var() and env(), every math function, CSS Color 4 and 5. Effects and animations: @keyframes animations and transitions (every longhand, cubic-bezier(), steps(), linear(), interruptible transitions), filter and backdrop-filter, mix-blend-mode, clip-path, masks, 2D transforms, border-image. Frames render only while something moves, and headless windows step a virtual clock (App::advance_clock). Tailwind CSS v4, compiled on first use, with compile-time checks in rsx! and tw!. Components and hooks in zopra, React or Leptos style, with optional props and keys. Blender components, styled, and the theme. Headless testing: Application::headless(), screenshots, simulated input. Not yet: async resources (use_resource), generic components, 3D transforms, OpenType features, multi-column layout. Guide: docs/rust.md, crate README: bindings/rust/blenderui, and the website's Rust section (including effects and animations). 25. JavaScript and React @blender-ui/react: a React 19.3 renderer. HTML host components, CSS and Tailwind, events, Suspense, transitions, actions, and the Blender components. Hooks keep React's names but hold signals: pass one into JSX and a change updates that node without re-rendering. README: bindings/react. @blender-ui/native: a DOM-like API (createElement, appendChild, addEventListener, className with Tailwind, style) with signals, memo, effect and batch. README: bindings/node. How it's fast: BUI renders on its own UI thread, so it never blocks JavaScript; each tick sends one batch of changes; styles are parsed once. Standalone apps: npx bui build app.jsx makes a ~7 MB executable with an embedded QuickJS engine; --bun makes a Bun executable instead; npx bui run app.jsx runs on QuickJS without building. Tests: new App({ headless: true }) renders off-screen, and win.click(x, y) simulates input. Limits: npm packages are prebuilt for Windows x64 only; events reach JavaScript after BUI handled them (no native preventDefault), and event.target is the deepest listening element. Guide: docs/javascript.md; website: React, native API, signals, standalone executables. 26. Dear ImGui The C, Python and Rust bindings are generated from imgui.h. ImGui is idle-free on BUI: frames run only on input and while something animates. The Blender-widgets build (BUI_IMGUI_BLENDER_WIDGETS=ON, Rust feature blender-widgets) swaps in Blender's widgets, and can be switched at run time (ImGui_ImplBUI_SetBlenderWidgets, imgui.set_blender_widgets). In that build, window title bars, tooltips, plain popups, tables and the color picker keep ImGui's drawing. Guides: docs/imgui.md, bindings/imgui/README.md (the backend and the Blender style), bindings/imgui/blender/README.md (the widget mapping), and the website's Dear ImGui section. 27. C and embedding The kit's concepts: nodes made by type name (frame, slider, button, panel, tree, canvas, filebrowser ...), properties set by name, Tk's geometry managers (pack, grid, place) in C, a context that owns input, popups, focus and undo, a host that receives events, and windows. A custom node draws and handles input itself; Dear ImGui runs on BUI that way. Guide: docs/c-api.md. To embed BUI in your own renderer, render the kit into a BuiRaster and blit it (as bui.qt.BuiCanvas does inside Qt). A separate, lower-level layer, render sinks (api/bui_c_api.h), hands the triangles, lines and text of the immediate-mode GPU seam to your renderer; the Qt/QPainter backend and the SVG recorder use it. Guide: INTEGRATION.md. 28. Where the docs live Part V: Performance 29. Before you read the numbers These numbers were measured on a Ryzen 7 5800U laptop (Windows 11, on AC power) before the latest features landed: Dear ImGui, the Linux GPU renderer, the light theme, and the Blender search and expression code. They show the shape of things, not a promise for your machine. Rerun them with the tools in Run the benchmarks yourself. 30. Start time and battery The same small settings window (a label, text field, checkbox, slider and button) written each of the five ways, from bench/apps, on BUI's CPU renderer (the default) and its GPU renderer (BUI_GPU=1). The last rows are one gpui-syntax source file, settings.rs, compiled unchanged against blenderui, Zed's gpui and gpui-fast. gpui has no form widgets, so there the controls are styled divs. Median of 10 launches, from process start until the first frame is on screen, with Python 3.14 and Node 22: On the same code, blenderui shows its first frame about 5× sooner than gpui and gpui-fast, spends about 8× less CPU getting there, and on the CPU renderer uses half the memory. Idle, gpui keeps about 1.2 % of a core busy; blenderui uses none. Laptops can't report energy per app, so battery cost is measured by the CPU time an app causes: the CPU it spends starting up, and the CPU it uses over 20 seconds while the window sits idle (what drains a battery while an app is open). BUI redraws only when something changes and otherwise sleeps, so idle use is near zero on either renderer. The GPU renderer starts a little slower and uses more memory (the GPU device), and pays off on large, animated scenes (below). Node's event loop accounts for the JavaScript idle figure, Python's times include starting the interpreter, and memory doesn't count GPU memory. 31. Frame times Python and C, on the full component demo (520×900): Dear ImGui's demo window with every section open (27k triangles, 1280×800) is a full software redraw of about 5–8 ms. Rust, against Zed's gpui and the faster fork gpui-fast. The scene is 3000 animated cells, and all apps ran in the same session (GPU rows on Direct3D 11): The GPU renderer is off by default. Turn it on with Application::new().gpu(true), tk.Tk(gpu=True), bui.set_gpu(True) or BUI_GPU=1: Every primitive is one instance of a single shader, so a frame is a few instanced draw calls (Direct3D 11 on Windows, OpenGL 3.3 on Linux). Text and images are cached in atlases. Frames render into a persistent target, so only damaged regions redraw. On Windows, presentation uses a flip-model swap chain with dirty rects. Its shaders evaluate the software raster's formulas: the parity test finds 10 of 172,800 pixels that differ by more than 3/255, on both backends. gpui-fast's heavy-scene frame rate varies between sessions on this laptop (30–60 fps), so compare within one session. Method, raw data and how to rerun: bindings/rust/bench/README.md. 32. JavaScript and React: startup and animation Startup. These launch a small React app (the counter above) that quits once its first frame is on screen. Each figure is the median of 12 launches after a warm-up launch (node bench/startup.js). First frame is measured from the runtime's start. Launch to exit is wall time measured from outside, including process creation and teardown. Animation. The same scene as the Rust table above, in one mode: one cell changes per frame, GPU renderer, medians of 3 runs. CPU is the whole process, counted with the cycle counter as for Rust. Plain JS uses @blender-ui/native. React with signals passes the changing values as signals, so the grid renders once. React with state re-renders the grid every frame. React's production build is used throughout. What this means: JavaScript costs little over Rust: 0.4–2.4 ms per frame, for sending changes to BUI's UI thread. Signals make React as cheap as the plain API: the component doesn't re-render. QuickJS starts fastest and uses the least memory, from a 7 MB file. It has no JIT, so re-rendering a 3,000-element list every frame drops it to 20 FPS. Keep big lists on signals, or build with --bun. Method, the memory breakdown and how to rerun: docs/javascript.md. 33. Run the benchmarks yourself Every number above comes from a tool in this repo. Build in release mode, close other heavy apps, and plug the laptop in. Run one benchmark (and one build) at a time. Start time and battery: the five ways and gpui (bench/, Windows). The same settings window written each way, on the CPU and the GPU renderer, plus one gpui-syntax file compiled for blenderui, gpui and gpui-fast. It reports start time, CPU used to start, memory and idle CPU, and writes bench/results.json. cmake -S . -B build && cmake --build build --config Release --target buipy # Python extension cd bindings/node && npm run build && cd ../.. # Node addon cd bindings/rust && cargo build --release --example bench_app && cd ../.. # Rust app cd bindings/rust/bench && cargo build --release --bins && cd ../../.. # gpui comparison py -3 bench/run.py 10 20 # launches per app, seconds of idle measurement Frame cost: 100, 1,000 and 3,000 animated cells, against gpui and gpui-fast (bindings/rust/bench/, Windows). A 1000×800 window filled with N rounded, numbered cells, in three modes: anim: every cell changes every frame, the worst case. one: one cell changes per frame, like a hover or a ticking value. idle: nothing changes. Each app measures its own CPU cycles, frames and memory, and run.py adds GPU utilisation and GPU memory from Windows' performance counters. cd bindings/rust/bench set GPUI_FXC_PATH=C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\fxc.exe # gpui needs fxc.exe cargo build --release py run.py --reps 3 # all apps, 100 / 1000 / 3000 cells, about 12 minutes py run.py --cells 3000 --apps blenderui,blenderui-gpu --reps 1 target\release\bui-bench --cells 3000 --mode anim # one run by hand (--mode one / idle, --uncapped) set BUI_GPU=1 && target\release\bui-bench --cells 3000 --mode anim # on the GPU renderer Method and full results: bindings/rust/bench/README.md. JavaScript: the same cells, and startup (bindings/node/bench/): cd bindings/node node bench/cells.js 3000 # DOM-like API, one cell changing per frame node bench/cells.js 3000 --react # React re-rendering the grid node bench/cells.js 3000 --react --signals # React with signals: no re-render node bench/cells.js 3000 --gpu # on the GPU renderer bun bench/cells.js 3000 # on Bun node cli.js run bench/cells.js -- 3000 # on the 5 MB QuickJS runtime node bench/startup.js # launch to first frame: Node, Bun and both executables tkinter: canvas frame cost (headless, any platform): py bindings/python/bench/tk_canvas_bench.py 3000 all # move 3,000 canvas items every frame py bindings/python/bench/tk_canvas_bench.py 3000 one # move one Inside any app: run it with BUI_PROFILE=1 to print per-phase frame times (layout, paint, raster, present) every 60 frames; any value turns it on. Use BUI_GPU=1 / BUI_GPU=0 to pick the renderer. More: Profiling. Part VI: Reference 34. Platforms Works means it works and is tested. Experimental means it's written but has had little hands-on testing on real hardware (macOS: one Apple Silicon Mac so far, plus CI). macOS. The macOS window host is new: it's a port of Blender's Cocoa code (GHOST). So far it has been checked by CI and tried by hand on one Apple Silicon Mac by Krish Kalaria, who reported issue #3 and fixed it in pull request #4. If you have another one, please try it and tell us what happens: docs/macos-testing.md. Python can still use the stdlib Tk window there (BUI_HOST=tk; this needs Python's tkinter, for example from python.org or brew install python-tk). Node runs BUI on its own UI thread and macOS only allows windows on the main thread, so JavaScript stays headless on macOS for now. Linux. libX11 and libGL are loaded when the first window opens, so BUI also imports and renders headless on servers and in containers without them, and falls back to the CPU raster where OpenGL is missing. Building from source needs the X11 headers (libx11-dev / libX11-devel); no GL headers are needed. The C, Python, window and GPU parity tests pass on Linux, on Mesa's software OpenGL and on an NVIDIA GPU. Packages: 35. Environment variables Most are read by the C library, so they work the same from every language. On/off variables look at the first character: 1, y, Y, t or T is on. Where a variable and an app-wide API call set the same thing, the variable wins. Every variable, with defaults and where it's read: Environment variables and docs/getting-started.md. 36. Building from source and CMake options You need CMake 3.20+, a C11 / C++20 compiler (MSVC, GCC/MinGW or Clang) and Python 3.11+ with headers. On Linux, also the X11 headers. git clone https://github.com/ppmpreetham/BUI && cd BUI cmake -S . -B build && cmake --build build -j 2 ctest --test-dir build # C, C++, ImGui and Python test suites export PYTHONPATH=$PWD/bindings/python # Windows: set PYTHONPATH=%CD%\bindings\python python examples/components_demo.py cd bindings/rust && cargo test # Rust: CSS, Tailwind, rsx!, hooks, effects, headless windows Rebuild the docs screenshots with python tools/gen_doc_images.py (and the website's dark and light shots with --site). More: CMake options. 37. Coverage How each was counted: Coverage, SYNTAX_COVERAGE.md, docs/rust.md, docs/imgui.md. 38. Repository layout 39. Examples Part VII: The project 40. Status and roadmap Done Partial or in progress Planned (coming soon) Our roadmap has no dates on it. Like a good render, it's done when the noise goes away. 41. FAQ Electron ships Chromium with every app: on the order of 100 MB or more. Electrobun runs on Bun and renders in the system's webview. It's small to download, but the UI is still a browser page. GPUIX, like BUI, renders React natively: React drives Zed's GPUI. Star History 42. Contributing and publishing Contributions are welcome: bug reports, missing tkinter/Qt behavior, CSS features, platform hosts and docs. If you have a Mac, running the Cocoa host and reporting what happens is one of the most useful things you can do right now. Read CONTRIBUTING.md first, and please follow the Code of Conduct. Report security issues privately, as SECURITY.md describes. Releases go to PyPI (blenderui, wheels built by CI on a v* tag), crates.io (the eight Rust crates, in dependency order) and npm (@blender-ui/native, @blender-ui/react). The steps are in docs/publishing.md. 43. License and credits License. BUI is derived from Blender sources and is licensed GPL-2.0-or-later. The SPDX header is kept in every file. Third-party parts keep their own licenses: The Rust and JavaScript bindings also depend on crates and packages such as taffy, napi-rs, React and QuickJS, each under its own license. Acknowledgments. Thank you to: Blender and its community: the UI this project is built from, including its GHOST window code and blenlib. Dear ImGui by Omar Cornut: the immediate-mode GUI that runs on BUI. taffy: flexbox, grid and block layout for the Rust crate. Tailwind CSS: the v4 theme behind the Tailwind support. FreeType: glyph rendering, as in Blender. stb_truetype: font metrics and the fallback rasterizer. NanoSVG by Mikko Mononen: the icon rasterizer, via Blender's edited copy. Inter by Rasmus Andersson: the embedded UI font, as in Blender. Slug by Eric Lengyel: the GPU glyph coverage algorithm. gpui, zopra and Leptos: the APIs the Rust syntax follows. BUI is Blender's UI, minus the default cube. You're free to add your own.