Concepts — the shared vocabulary of this survey
The thirty-four subjects surveyed here do not agree on what their words mean. "Display list", "scene", "render node" and "command buffer" each name at least three different artifacts across the catalog; "backend", "device", "renderer", "painter" and "canvas" are used interchangeably by some projects and as a careful hierarchy by others; and "degrade", "emulate", "lower" and "fall back" name the same act performed in four different places. This page pins each term to one meaning for the rest of this tree, grounded in at least two subjects that use it that way, and says what each is not.
Where a subject's own name for a thing disagrees with the definition here, the subject file keeps the subject's spelling and this page supplies the translation. Nothing here overrides a deep-dive: if the digest and a subject file disagree, the subject file wins.
Last reviewed: August 23, 2026
NOTE
This is a vocabulary page, not a synthesis. The cross-subject conclusions live in comparison.md; the questions Q1–Q8 that every subject answers are defined in the umbrella.
1. The reified-work cluster
This is where the disagreement is worst, and it is worst because the field uses one set of words for two independent choices. Separate them and the mess resolves.
| Axis | The question it asks | Poles |
|---|---|---|
| Instructions vs result | Does the artifact say what to do, or hold what was produced? | instruction stream ⟷ produced raster/grid |
| Tree vs flat | Does the artifact nest, so a node's meaning depends on its ancestors? | recursive tree ⟷ flat, position-independent sequence |
Two axes, four quadrants, and every reifying subject in this survey sits in one of them:
| Artifact | Instructions or result | Tree or flat |
|---|---|---|
WebRender BuiltDisplayList (webrender) | instructions | flat (byte stream) |
Chromium PaintOpBuffer (chromium) | instructions | flat (variable-stride arena) |
Flutter DisplayList (flutter) | instructions | flat (byte arena + offset table) |
Skia SkRecord (skia) | instructions | flat (tag array + arena) |
| Cairo recording surface (cairo/D2D) | instructions | flat (union of six payload structs) |
SDL SDL_RenderCommand queue (SDL) | instructions | flat (11 tags over 4 union arms) |
Vello Encoding (vello) | instructions | flat (six parallel streams) |
sparkles:ui DrawOp[] (canvas.d) | instructions | flat (closed sum, uniform stride) |
GSK GskRenderNode (GTK4/GSK) | instructions | tree |
Qt Quick QSGNode (Qt Quick) | instructions | tree |
Avalonia IRenderDataItem (Avalonia) | instructions | tree (RenderDataPushNode) |
Vg Data.image (OCaml Vg) | instructions | tree |
Gloss Picture (Gloss) | instructions | tree |
notty I.t (Notty) | instructions | tree |
diagrams RNode (diagrams) | instructions | tree |
Ratatui Buffer (Ratatui) | result | flat (cell grid) |
Textual Strip/Segment (Textual) | result | flat (row of segments) |
Mosaic TextSurface (Mosaic) | result | flat (cell grid) |
libvaxis Screen (libvaxis) | result | flat (cell grid) |
Vty DisplayOps (Vty) | result | flat (row-major span ops) |
display list
Definition used here: a flat, ordered sequence of drawing instructions whose elements are values — inspectable, comparable and replayable without running a painter.
Grounded in WebRender, where BuiltDisplayList is a variable-width tagged byte stream of DisplayItems written by peek_poke-style serialization (display_item.rs, display_list.rs), and in Chromium, where a PaintOpBuffer is a variable-stride arena of PaintOp subclasses (paint_op_buffer.h) and a DisplayItemList wraps one with producer-declared visual rects (display_item_list.h). Flutter and Skia use the same shape by different storage (display_list.h, SkRecord.h).
Do not confuse with: a render tree (§ render tree) — a display list here is flat by definition, so pushClip/popClip is a bracket convention rather than a structural parent. Nor with a result buffer: a Ratatui Buffer is not a display list, even though both are flat arrays.
IMPORTANT
"Display list" in a browser context sometimes means the whole retained scene including its clip and spatial trees. WebRender explicitly does not: every item carries a clip_chain_id and spatial_id into out-of-band trees, and CLIPPING_AND_POSITIONING.md records that hierarchical clipping was abandoned. This tree follows WebRender: the display list is the flat part.
scene
Definition used here: the whole reified drawing work for one frame, irrespective of shape — the noun for "everything to be painted", used when the distinction between tree, stream and encoding is not the point.
Every project that ships a type literally called Scene means something different by it, which is exactly why this tree treats the word as a role rather than a structure: GPUI's Scene is struct-of-arrays over eight Primitive variants (scene.rs); Vello's Scene wraps an Encoding of six parallel byte streams (encoding.rs); Masonry's record::Scene is a Command tag plus an id into typed side arenas; and Flutter's SceneBuilder builds a layer tree, one tier above its display list (compositing.dart).
Do not confuse with: "scene graph" (§ terms this tree avoids).
render tree / render node
Definition used here: a recursive tree of instruction nodes, where a node's effect depends on its ancestors (transform, clip, opacity, style scope) and the backend walks rather than iterates.
Grounded in GSK, where GskRenderNode is an open GType hierarchy of 37 GskRenderNodeType kinds (gskenums.h, gskrendernode.h) each carrying a graphene_rect_t bounds on the shared base struct; and in Qt Quick, whose QSGNode tree carries a seven-value NodeType and is retained across frames for batching. Vg, Gloss, Notty and diagrams are the same shape expressed as an algebraic data type instead of a class hierarchy.
A render node is one element of such a tree. The distinguishing property is not "it is semantic" — GSK's nodes are semantic and Gloss's are not — it is that scope is structural: Color c p in Gloss colours a whole subtree, and a flat list must repeat that value per element.
Do not confuse with: a widget tree. GSK's node tree sits below GTK's widgets; gtk_css_style_snapshot_border resolves widget → CSS → node above the seam. Mosaic's MosaicNode tree is the opposite: a widget tree above a painter with no reified drawing artifact at all.
command buffer
Definition used here: a mutable, short-lived queue of instructions accumulated and then submitted, whose lifetime is a frame or a flush and which is not intended to be retained, diffed or compared.
Grounded in SDL_Renderer, whose SDL_RenderCommand queue is drained by RunCommandQueue several times per frame and whose vertex arena is reset on every flush (SDL_sysrender.h); and in Direct2D's ID2D1CommandList, which is deliberately opaque — there is no public value type, and it is read only by streaming into an ID2D1CommandSink (ID2D1CommandList, ID2D1CommandSink).
Do not confuse with: a display list. The difference is retention and inspectability, not encoding: SDL's queue and WebRender's list are both flat tagged sequences, but only one is a value you can keep. sparkles:ui's DrawOp[] is a display list by this definition, not a command buffer.
recording
Definition used here: capturing a call sequence made against a normal drawing API into a replayable artifact, where the recorder is itself a conforming implementation of the drawing seam.
Grounded in Skia's SkPictureRecorder/SkPicture — a canvas that appends to an SkRecord and hands back an immutable picture (SkPicture.h) — and in Cairo's recording surface, a cairo_surface_t that stores each call as a cairo_command_t, copying payloads at record time (cairo-recording-surface.c). Racket's record-dc% is the mechanised version: a macro generates the recorder from the dc<%> method list, yielding both a replay closure and a write-able datum (Racket).
The load-bearing property is that a recording is produced by the same seam the real backends implement. RecordingCanvas in canvas.d is a recording in exactly this sense.
Do not confuse with: encoding — a recording preserves the call vocabulary; an encoding does not have to.
encoding
Definition used here: a buffer layout designed for one consumer's access pattern, where entries are variable-width, positionally recovered, and not individually addressable by kind.
Grounded in Vello: Encoding is six parallel append-only streams (path_tags, path_data, draw_tags, draw_data, transforms, styles) whose entries are recovered by prefix sum over a tag byte, because the consumer is a data-parallel GPU pipeline (encoding.rs); the same repository chose a plain Rust enum for vello::recording::Command because that consumer dispatches sequentially. WebRender's byte stream is an encoding by the same logic — the shape follows the IPC boundary.
Vello's pathseg.md records abandoning a fixed 36-byte element record for exactly the reason friction §4 gives about DrawOp.
Do not confuse with: serialization. Chromium serializes its PaintOpBuffer across a process boundary and the op vocabulary changes on the way (DrawTextBlobOp becomes DrawSlugOp; two ops refuse to serialize at all) — so the encoding and the recording are different artifacts there.
result buffer
Definition used here: the produced pixels or cells reified as a comparable value, rather than the instructions that produced them.
Grounded in Ratatui (Buffer { area, content: Vec<Cell> }, whose diff against the previous frame is the rendering algorithm — buffer.rs) and Textual (Strip, "like an immutable list of Segments", strip.py). Mosaic, libvaxis and Vty all reify results too.
This is the quadrant sparkles:ui does not occupy, and the reason its cell backend and its GPU backend can share a seam at all: a result buffer buys diffing, read-back composition and value-comparable goldens, and cannot reach Skia.
2. The who-draws cluster
Ten words, roughly three roles. The roles are what this tree names; the words are what the subjects call them.
| Role | This tree's word | Subjects' words |
|---|---|---|
| The framework-side object an application draws against, which lowers before dispatch | painter | Qt QPainter, Java2D Graphics2D, imaging Painter, Skia SkCanvas, Cairo cairo_t, Racket dc<%> |
| The implementation a toolkit swaps to change targets | backend | Qt QPaintEngine, Skia SkDevice, Slint ItemRenderer, Ratatui Backend, iced Renderer, imaging PaintSink, Avalonia IDrawingContextImpl |
| The thing drawn into, which owns pixels/cells and declares its own size | surface | Cairo cairo_surface_t, Skia SkSurface, wgpu Surface, SDL render target, Ratatui Buffer, libvaxis Screen |
backend
Definition used here: the swappable implementation of the drawing seam — the party that turns instructions into a target's native effect, and the party a survey question like Q2 is asking about.
Grounded in Ratatui (Backend, one trait, ten methods of which one draws — backend.rs) and Slint (ItemRenderer, whose implementations span a software MCU renderer and Skia — item_rendering.rs).
Do not confuse with: device. Several subjects' "backend" is not the party that owns pixels: Cairo's cairo_surface_backend_t is a private vtable (cairo-surface-backend-private.h) on an object that is also the surface, while Skia separates SkDevice (backend) from SkSurface (surface) explicitly.
device
Definition used here: the physical or logical output whose properties (resolution, colour depth, unit, hinting regime) parameterise what drawing and measurement mean — as distinct from the code that draws.
Grounded in Qt (QPaintDevice, which declares extent while QPaintEngine does the drawing) and in Java2D (SurfaceData declares getBounds() and getDeviceConfiguration(), and the FontRenderContext is what makes measurement device-parameterised).
Do not confuse with: backend. The distinction earns its keep in § measurement: Pango's metrics depend on the device (hint_metrics) while its PangoRenderer — the backend — never measures.
renderer
Definition used here: the object that consumes a whole reified scene and produces output, as opposed to one that receives calls.
Grounded in GSK (GskRenderer is four methods with no drawing primitives — the entire vocabulary lives in the node tree it is handed) and Vello (Renderer takes a Scene plus RenderParams). Pango's PangoRenderer is a counter-example naming: it is call-driven, so this tree calls it a backend.
painter
Definition used here: the framework-side drawing facade an application calls, which lowers, brackets, defaults and emulates before anything reaches a backend.
Grounded in Qt (QPainter emulates a feature the engine declines and hands the engine an image of the result) and in imaging/Masonry (Painter's ~40 convenience methods all lower to PaintSink's ten, so backends never implement them). SDL is the same split with the seam moved: nineteen public draw calls collapse into six queue functions, and SDL_RenderTexture9Grid — a nine-patch — never reaches a driver.
Do not confuse with: paint engine (below) or canvas. A painter is the half a backend author does not implement.
paint engine
Definition used here: Qt's specific name for its backend (QPaintEngine), retained because it is the survey's canonical declared-capability model — PaintEngineFeature plus hasFeature (Qt, QPaintEngine).
Used in this tree only when talking about Qt. Note that Qt itself abandoned it: Qt Quick's scene graph deleted PaintEngineFeature and did not replace it.
canvas
Definition used here: an object that is both painter and dispatcher — it accepts drawing calls and either records them or forwards them to a backend.
Grounded in Skia (SkCanvas: 33 public draw*, 26 onDraw* virtuals, forwarding to a 10-pure-virtual SkDevice — SkCanvas.h, SkDevice.h) and in Chromium (PaintCanvas, 45 pure virtuals, with RecordPaintCanvas as the recording implementation — paint_canvas.h).
This is the word sparkles:ui uses, and it uses it in the narrower sense — see § what these words mean in sparkles:ui today.
surface / target
Definition used here: the artifact that owns the output storage and declares its own extent. "Target" is used when the emphasis is on selection (which surface a painter is currently aimed at).
Grounded in Cairo (cairo_surface_t, with get_extents; a surface whose slot is NULL or returns FALSE "is considered to be boundless") and in wgpu (Surface plus SurfaceCapabilities, a preference-ordered list with a guaranteed floor element — surface.rs).
Q8 in this survey is precisely the question of whether extent belongs here or to the scene; the subjects disagree, so the word must not smuggle the answer.
A sink is the narrow variant of a backend that cannot refuse: every method returns nothing and failure surfaces later, out of band. imaging's PaintSink records set_error_once and reports from finish() -> Result (Parley and Xilem); Direct2D's ID2D1CommandSink does the same and additionally accepts a narrower vocabulary than the drawing API (ID2D1CommandSink, Cairo and Direct2D).
3. Primitive, semantic, and the four words for degrading
primitive operation
An operation whose meaning is fully determined by its geometry and appearance: every backend that can draw it at all draws the same thing. Rects, paths, positioned glyph runs. egui is the limit case — the backend receives triangles — and Notty is the other limit, three constructors with no geometry at all.
semantic operation
An operation that names an intent the backend must interpret, because different targets legitimately realise it differently. Slint's draw_box_shadow, GSK's GskBorderNode, Skia's DrawShadowRec (a lighting model, not a blurred rect), Vello's draw_blurred_rounded_rect, Flutter's drawShadow, and sparkles:ui's scrollbar (friction §3).
Three subjects state an admission test for the seam, and they agree:
- GSK — do the backends disagree about how to draw it? (GTK4/GSK)
- Vello — would lowering it require the caller to know something only the backend knows? (Vello)
- Skia — could a backend do something genuinely different with it, and does the default lowering ship in the framework? (Skia)
Do not confuse with: a widget operation. GSK stops hard at the CSS box model; no surveyed subject has a scrollbar node.
lowering
Rewriting a higher-level operation into lower-level ones before the backend sees it, unconditionally and for every target. SDL's SDL_RenderRect becomes SDL_RenderLines over five points; Godot's RendererCanvasCull turns canvas_item_add_line into a feathered quad (SDL, Godot). Lowering is not degradation: nothing is lost, and no target was consulted.
emulation
The framework implements a capability the backend declined, then hands the backend the result. Qt's QPainter is the canonical case: it rasterises the missing feature and passes an image (Qt). Java2D reaches the same place structurally — MTLSurfaceData.validatePipe calls super.validatePipe for every state it cannot accelerate, so the floor is the superclass (Java2D).
degradation
The backend substitutes a lower-fidelity realisation of a semantic operation, on its own authority. Slint's backends decide; GridCanvas fills a cell where SkiaCanvas draws one device pixel. GSK adds a third location the axis did not have: degradation lives in the node kind, because every GskRenderNodeClass ships a Cairo draw vmethod, so the fallback travels with the kind rather than with the framework or the backend (GTK4/GSK).
WARNING
Qt "emulates" in the framework and Slint "degrades" in the backend, and both projects would call the other's act by their own word. This tree uses emulation for framework-side and degradation for backend-side, always, regardless of what the subject calls it.
fallback
A named alternative realisation published for whoever wants it — neither automatically applied nor owned by one party. piet is the clean case: piet::util exports size_for_blurred_rect and compute_blurred_rect, and each backend chooses whether to call them. ruleEndpoints and scrollbarCell in canvas.d are fallbacks in exactly this sense — published helpers that every backend is currently required to call.
refusal
Declining an operation and saying so, rather than approximating it. SDL's SDL_SetRenderDrawBlendMode returns SDL_Unsupported() rather than approximating (SDL); Notcurses' NCVISUAL_OPTION_NODEGRADE asks for failure instead of a lower blitter (Notcurses); wgpu refuses device creation and names whose fault it is (wgpu).
4. Measurement vocabulary
Pango is the anchor for this cluster because it is the only surveyed subject that keeps every distinction separate and names all of them.
shaping
Turning a run of characters plus a resolved font into positioned glyph ids. HarfBuzz's hb_shape takes a hb_buffer_t in with Unicode and out with glyphs (hb-shape.cc); Avalonia's ITextShaperImpl.ShapeText returns a ShapedBuffer (ITextShaperImpl.cs).
Several seams accept only shaped text: Flutter's drawText takes a std::shared_ptr<DlText>, WebRender's text item carries an array of GlyphInstance { index, point }, and Vello's Glyph is { id, x, y } (Flutter, WebRender, Vello).
layout
Breaking shaped runs into lines against a constraint, and the object that result lives in. Flutter's Paragraph must be layout(ParagraphConstraints)-ed before it may be measured or drawn; Parley's Layout is built by a ranged_builder and read for width/height (Flutter, Parley/Xilem).
The transferable rule from both: the measured artifact must be the painted artifact. Masonry caches built Layouts keyed by the constraint and has measure, layout and paint all read the same cached value.
extents — ink vs logical
The ink extent is the rectangle the marks actually cover; the logical extent is the box the text nominally occupies, including leading and whitespace advance. Pango returns both from every extents call, at four nested levels, and accumulates them by deliberately different rules — a zero-ink glyph contributes nothing to the ink rect and still reserves advance width (glyphstring.c, pango-layout.c).
Cairo's cairo_text_extents_t makes the same split as an ink rect beside x_advance/y_advance (cairo-scaled-font.c), and DirectWrite's DWRITE_TEXT_METRICS carries width beside widthIncludingTrailingWhitespace (DWRITE_TEXT_METRICS).
IMPORTANT
A single Size cannot express this in any unit. That is a separate defect from friction §1's complaint about the unit, and it is why buildDisplayList's reliance on a textRun's rect.width bounding its ink is a coincidence rather than an invariant.
advance
How far the pen moves — a property of the run, not of its marks. Vg makes it the caller's input outright (I.cut_glyphs takes ?advances:v2 list, OCaml Vg); WebRender and Vello carry positions rather than advances because the caller already applied them.
cell width
The number of terminal columns a string occupies. Distinguished from advance because it is a Unicode property resolved above any font, and because the oracle is contested:
- Ratatui ships one unit and two disagreeing width functions —
Line::widthuses rawUnicodeWidthStr::widthwhileBuffer::set_stringngoes throughCellWidth::cell_width, which adds back the halfwidth katakana sound marks (cell_width.rs). - libvaxis makes the oracle a negotiated capability:
gwidth(str, Method)withMethod = { unicode, wcwidth, no_zwj }, and its own tests pin a ZWJ sequence at 2 under one and 4 under the others (gwidth.zig). - Vty puts the oracle in a process-global C table generated by interrogating the actual terminal, precisely so toolkit and device cannot disagree (Vty).
cellsOf in canvas.d is a cell-width oracle in this sense.
grapheme
The user-perceived character a cell holds. Notty computes it once, at construction, by running a segmenter inside Text.of_string and caching the sum (Notty); libvaxis's Cell.Character carries the cluster plus a caller-supplied width override it explicitly declines to verify (Cell.zig).
measuring regime
The named device-dependent mode under which a measurement was taken. Microsoft names it (DWRITE_MEASURING_MODE = NATURAL/GDI_CLASSIC/GDI_NATURAL, DWRITE_MEASURING_MODE); Java2D bundles it into an immutable FontRenderContext used as a cache key (Java2D); Racket assigns it a small integer, get-font-metrics-key, where 0 means "not cacheable" (Racket); Cairo passes it one-way to the font layer via get_font_options and CAIRO_HINT_METRICS_ON (Cairo/D2D).
sparkles:ui has exactly one regime — cells — and no name for it.
5. Capability vocabulary
wgpu is the anchor here because it is the only surveyed subject that types the kinds apart instead of flattening them into one bitmask (features.rs, limits.rs, device.rs).
| Term | Definition used here | Grounded in |
|---|---|---|
| feature | A discrete capability a caller may request, and whose absence is a typed refusal | wgpu Features; Qt PaintEngineFeature + hasFeature; SDL SupportsBlendMode |
| limit | A continuous quantity a caller may request up to, refused with the requested and allowed values | wgpu Limits (~60 fields, tagless); SDL max-texture-size |
| capability | An observable fact about the target that cannot be requested at all | wgpu DownlevelFlags (no required_downlevel_flags field exists); libvaxis Capabilities; Mosaic Terminal.Capabilities |
| floor | The set every conforming implementation must supply, stated as a named value or a language construct | wgpu DownlevelFlags::compliant(); Skia's ten pure-virtual SkDevice draws; SDL's seven guaranteed blend modes; Java2D's super.validatePipe |
| profile | A named, ordered tier that quantises the space so the set of behaviours to test stays finite | wgpu ShaderModel::{Sm2,Sm4,Sm5} (Ord) and its ten limit buckets; Direct2D D2D1_FEATURE_LEVEL; Notcurses' blitter ladder |
| downlevel | A target that is below the floor and says so, without pretending otherwise | wgpu DownlevelFlags; Notcurses' auto-degrade |
Two properties of wgpu's arrangement have no counterpart elsewhere in the survey and are worth naming:
- The grant is closed above as well as below.
DeviceDescriptor::required_featuresdocuments that "Exactly the specified set of features, and no more or less, will be allowed" — not requesting a capability forbids it on hardware that has it. - Refusable and observable capabilities are different types, so demanding something unrequestable is unrepresentable rather than an error case.
sparkles:ui today has only the third row (capability), and has it implicitly, via __traits(compiles) at each interpreter call site — which is friction §2.
6. Payload ownership
Six answers, all shipped somewhere, to "who owns a command's text, image or vertex data, and may the command outlive the frame".
| Term | Definition used here | Grounded in |
|---|---|---|
| borrowed | The command holds a slice into memory it does not own; validity ends with the frame | ImGui's ImDrawData::Valid (imtui); libvaxis Cell.grapheme; imaging's *Ref<'a> payloads |
| owned | The command copies the payload into storage it controls | Skia SkRecord::alloc<T> and SkPath by value; Cairo's recording surface memcpys glyphs and clusters; Ratatui's CompactString per cell |
| arena | Payloads are copied into one contiguous per-artifact allocation; commands hold offsets or slices into it | Flutter DisplayListStorage; Chromium PaintOpBuffer; Godot's 4 KiB CommandBlocks; SDL's SDL_AllocateRenderVertices with (first, count); DrawOp.text, which CmdBuffer.textRun copies into a FrameArena and holds as a slice (canvas.d) |
| refcounted | The payload is shared by an atomic or non-atomic count and may outlive any one holder | GSK's gatomicrefcount (which is what makes the deferred Cairo fallback legal); Flutter's sk_sp/shared_ptr; egui's Arc; Avalonia's IRef (Ref.cs) |
| interned | Repeated payloads are stored once in a side table and referenced by a small id | Masonry's record::Scene (labels and file names interned once); Chromium's mirrored ClientPaintCache/ServicePaintCache keyed by PaintCacheId (paint_cache.h) |
| shared handle | The command carries a key; the payload lives in a store owned by the consumer | WebRender's ImageKey/FontInstanceKey with bytes on a separate ResourceUpdate channel; Slint's draw_cached_pixmap; GPUI's AtlasTile into a PlatformAtlas |
Three refinements the survey adds that the six labels do not capture:
- Offset pair versus slice. Both forms name bytes an arena owns, and they are not equally strong. A command holding
(first, count)— SDL's vertex ranges, WebRender's byte-stream cursors, Flutter's offset table — is trivially copyable and transferable to another thread, because it names a position and not an address. A command holding a slice, asDrawOp.textdoes, is bound to the arena's address for as long as it lives: the rule an operation is valid while the buffer that built it is alive and unreset is stated on the type, andUI-O4is open on where the retain boundary should sit. - Weak plus inline geometry. iced's recorded layer stores a
paragraph::Weakcarryingmin_bounds,align_xandalign_ybeside the weak pointer, so a dropped payload degrades to "nothing drawn" while damage tracking still has the rectangle (iced). - A declared sharing property. Flutter's
DisplayList::isUIThreadSafe()is conjoined from each payload's own answer as ops are recorded, so the finished list answers "may this cross a thread" for itself (Flutter).
Terms this tree avoids, and why
| Term | Why it is avoided |
|---|---|
| scene graph | Qt Quick means a retained node tree with batching and a thread rendezvous; most other uses mean any hierarchy at all. Say render tree (structure) or scene (role). |
| immediate mode / retained mode | Cuts across the axes that matter. egui is "immediate mode" and reifies a sum type; Qt's QPaintEngine is "retained"-adjacent and reifies nothing. Say what is reified and whether it survives the frame. |
| context | Means a painter (cairo_t), a device selector (ID2D1DeviceContext), a measurement service (FontContext, LayoutContext), and a measuring regime (FontRenderContext). Always qualify it. |
| draw call | Means a seam method invocation to a toolkit author and a GPU submission to a graphics programmer. Say operation, instruction or submission. |
| layer | Flutter's layer tree, GPUI's StartLayer/EndLayer, iced's Layer struct-of-arrays and imaging's VisualLayerKind are four different things. Qualify or name the type. |
| 2D API | piet's post-mortem is precisely that "one 2D API over many backends" defines a vocabulary as the intersection of its backends. The phrase smuggles the design piet abandoned. |
What these words mean in sparkles:ui today
The toolkit already uses three of the contested words. Fixing their meaning here is half the point of the page.
DrawOp— one element ofsparkles:ui's display list: astructwrapping a closedSumTypeover eight per-kind payloads (FillRect,TextRun,Glyph,Line,Rule,Scrollbar,PushClip,PopClip), withalias payload this, dispatched byop.match!(…)(canvas.d). Each arm carries only the fields its own primitive paints from, and every operation is as wide as the widest arm —static assert(DrawOp.sizeof <= 64), a budget rather than an equality, bounded byTextRun.OpKindis derived through an eight-armmatch!rather than stored, so the tag and the arm cannot disagree. It is an instruction, not a result; flat, not a tree; and — by the definitions above — the elements of a display list, not of a command buffer, becauseRecordingCanvaskeeps them.- display list — the
DrawOp[]thatbuildDisplayListemits and a painter walks once. Flat and position-independent except for thepushClip/popClipbracket convention, which is a stream convention rather than structure — precisely the property Vg and WebRender give up in opposite directions. - canvas — an
isCanvas!T: a backend in this page's vocabulary, not a painter and not a canvas in Skia's sense. It receives already-lowered instructions, owns no framework-side emulation, and is discovered structurally rather than declared. RecordingCanvas— a recording in the Skia/Cairo/Racket sense: a conforming backend that captures instead of drawing.SlotandVisual— the semantic role and the resolved appearance. Each payload stores the resolved fields its own primitive paints from: anInkfor the four content primitives, andFillRect's own colour fields beside aconst(BoxChrome)*that is null unless the box has a border, shadow, radius or arrow.DrawOp.visualreconstructs aVisualon demand throughvisualOf, lossy by design, so the seam speaksVisualend to end while the payloads store the split. ASlotis stored, on six of the eight payloads;PushClipandPopClipcarry none, andDrawOp.slotreportsSlot.inheritfor them (friction §6). Note the vocabulary collision: imaging'sContextKindRef::Slotis also calledSlot, and is in the channel explicitly documented as not reaching the rasterizer (Parley/Xilem).RuleEdge— a position enumeration, which is what this survey's §5 subjects consistently replace with either a named fidelity (Notcurses, Flutter's hairlinestrokeWidth = 0), a snapping policy (GTK'sGskSnapDirection, Pango'spango_quantize_line_geometry, GPUI'ssnap_stroke), or a query for the smallest addressable unit (iced's1.0 / hint_factor()).
Sources
Every term above is grounded in the subject deep-dives of this tree, which carry the pinned-SHA citations; the external links below are the specific declarations quoted on this page, at the same revisions the subject files pin.
| Cluster | Subjects it is grounded in |
|---|---|
| Reified work (§1) | WebRender, Chromium, Flutter, Skia, Vello, GTK4/GSK, Qt Quick, Cairo and Direct2D, SDL, Avalonia, Ratatui, Textual, Mosaic, libvaxis, Vty, Notty, OCaml Vg, Gloss, diagrams, Racket, Parley and Xilem, Monomer, elm-canvas |
| Who draws (§2) | Qt, Slint, Java2D, iced, Doodle, piet, wgpu, egui |
| Semantics and degrading (§3) | GTK4/GSK, Vello, Skia, Qt, Java2D, SDL, Godot, piet, Notcurses, Notty |
| Measurement (§4) | Pango and HarfBuzz, Flutter, Parley and Xilem, Cairo and Direct2D, Ratatui, libvaxis, Vty, Notty, Godot TextServer, GPUI, Racket, OCaml Vg, elm-ui, functional images |
| Capability (§5) | wgpu, Qt, SDL, Notcurses, libvaxis, Mosaic, Skia, Java2D, imtui, Scala Doodle, Haskell diagrams |
| Ownership (§6) | Skia, Cairo and Direct2D, Flutter, Chromium, GTK4/GSK, Avalonia, egui, iced, Slint, GPUI, WebRender, Godot, SDL, imtui |
The sparkles:ui side of every entry is libs/ui/src/sparkles/ui/canvas.d and canvas-seam-friction.md.