Gophics is one idea carried all the way down: a pipeline. You describe the UI as immutable values; the framework diffs them into a retained tree, lays it out once, and composites it on the GPU — every frame, in pure Go.
Everything below rests on one split, so it is worth getting first. A widget is an immutable description — a plain struct value, cheap to make, thrown away every frame. An element is the retained node the framework keeps behind it: it holds your state, and it persists across rebuilds.
// The widget: a value. Rebuilt constantly, owns nothing.
type Counter struct{ Start int }
// The state: kept alive by the element behind that widget.
type counterState struct {
widget.StateBase[Counter]
n int
}
func (s *counterState) Build(ctx widget.Ctx) widget.Widget {
return theme.Button{
Label: fmt.Sprintf("%d", s.n),
OnTap: func() { s.SetState(func() { s.n++ }) },
}
}
So Build is not a draw call and describes nothing about pixels. It returns a fresh description; the framework diffs it against the element tree and touches only what actually differs. That is what makes rebuilding the whole subtree on every tap cheap, and it is why widgets can be compared, serialized, and asserted on in a test without a window.
Those two trees are what the frame runs over. Each frame flows through the same phases (mirroring the model that made own-rendering UIs fast), and every phase is independently instrumentable and testable:
input gestures and callbacks mark widgets dirty
tickers animations advance, marking more
↓
build rebuild dirty widgets, reconcile against the element tree
↓
layout constraints down, sizes up — one pass, with relayout boundaries
↓
paint dirty nodes record display lists into a layer tree
↓
composite layer tree → GPU commands → present
Input and tickers are the two things that start a frame; the four phases below them are the frame itself.
Only what changed is redone, and that is the point of the whole arrangement rather than an optimisation on top of it. Each phase has its own boundary: a rebuild stops where the description is unchanged, a relayout stops at a node whose constraints did not move, and a repaint stops at the layer whose damage rectangle covers it. Typing a character in a text field does not re-lay-out the page around it, and a one-widget change costs about one widget's worth of work.
Each concern is a small Go package; dependencies point downward. You build apps against the top; everything below is the engine.
widget Widget/Element trees, State (StateBase[W]), keys, focus, reconciler, gestures (Handler) theme design tokens & styled components (opt-in) anim tickers, curves, controllers ────────────────────────────────────────────────────────────────────────────────────────── layout the box protocol — BoxConstraints down, sizes up; flex, stack, padding, viewports ────────────────────────────────────────────────────────────────────────────────────────── scene retained display lists, diffed for damage paint Canvas: paths, gradients, layers, text text shaping · bidi · line-breaking (go-text) geom points, rects, rrects, affine transforms ────────────────────────────────────────────────────────────────────────────────────────── app the runtime: app.Run (a window) · app.Headless (tests) shell per-platform window / input / present internal/gfx the vendored, pure-Go WebGPU + shader-compiler + 2D-renderer substrate (zero CGo)
Gophics draws every pixel itself rather than wrapping native widgets or a webview — that's why one widget tree looks the same on every platform. The rendering substrate is vendored in-tree and is entirely pure Go (no CGo): a WebGPU implementation, a WGSL shader compiler, and an analytic-AA 2D renderer. The shell layer presents that output per platform:
Because the renderer has an offscreen path, the whole UI is a rendering library: app.Headless mounts a widget tree, drives taps/keys/frames, and returns an image — no window, no emulator. Layout, painting, gestures, and full widget trees all run under go test, and golden-image tests are a first-class feature. A server can render a widget tree to a PNG or a report with no display at all.