Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What a frame costs

A native window is up in a tenth of a second, a settled frame costs microseconds, and this page says where the rest of the time goes.

Read this page to know which numbers to expect from a Goldberry window and where each one was measured. The three chapters after it say how to start fast, how to keep a frame cheap, and how to measure your own application rather than trust these figures.

519msnative image, from exec to the first frame; the window is open at 116.2 ms
1980msthe same application on a cold JVM; 1340 ms with a JDK 25 AOT cache
3.13msmedian frame at 960×640 with a wrapped paragraph, paced to the display
2.3msa full 3840×2160 raster across four paint workers
117µsrepainting one small change, drawing only the damage
9.1µsa frame in which nothing changed: layout and the walk on the retained tree

Every number above comes from a record in the decision log, and the table below says which.

NumberWhat it measuresRecord
519 ms, 116 msThe showcase’s native image, median of 7 runs, timed from exec. The window is open at 116.2 ms in the run the record printsADR-0506
1980 ms, 1340 msThe showcase on the JVM, cold and with an AOT cache, median of 5 runsADR-0506
3.13 ms, 4.28 msMedian and 95th percentile of a 960×640 frame with text, paced to the displayStatus, M1, from the pacing in ADR-0047
2.3 msA 3840×2160 paint in PaintBenchmark, down from 6.0 ms on one threadADR-0042
117 µsOne small box changed at 960×640, repainted inside its damage, down from 367 µsADR-0072
9.1 µsLayout and the walk on a showcase-shaped tree when nothing changed, down from 190 µsADR-0069

The frame loop

One UI thread runs the loop. Blend2D’s workers rasterize the bands. This is the pipeline from docs/ARCHITECTURE.md §5, in the order a frame runs it:

input events → dispatch (hit-test on render tree)
→ rebuild dirty widgets → diff → update elements/render objects
→ style resolution (invalidated nodes) → Yoga layout (incremental)
→ paint recording (dirty layers only) → Blend2D raster (banded)
→ present(buffer, damage) / GPU composite

Three trees take part. Widgets are immutable records that describe. Elements persist and hold state. Render objects own the Yoga nodes and are kept between frames, so a frame diffs a new description against a retained tree rather than building one. The architecture overview has the full picture.

Where the time goes at 960×640

Everything before rasterization is now a rounding error. Rasterization and present are the frame.

StageCostRecord
Everything but rasterization, nothing changed3.5 µs, down from 354 µsADR-0070
Rasterization on one threadabout 320 µsADR-0070
Paint in a live window, four workers2.146 ms, from 2.856 ms synchronousADR-0042
Present, unpacedabout 6.4 ms, of which 4.8 ms is blocking on the swapchain and 43 µs is this repository’s codeADR-0046
Present, paced to the display1.20 ms, with paint falling to 1.61 ms beside itADR-0047

Read the paint row with care. The same scene paints in 0.34 ms in a benchmark loop and in 2.15 ms in a running window, because present leaves the next paint’s caches cold. Only a figure from a live window says what a frame costs. Measuring explains the gap.

Note

Every number on this page was measured on one Linux machine. The run that would repeat the frame benchmarks on Linux, macOS and Windows is open, and the status page says what each platform’s CI leg has reported so far. See the frame evidence.

The three chapters