Skip to content
dream-soft-render
guide

Fast frames

What the rasterizer recognizes and draws as spans, what costs the most, how to keep an egui frame cheap, and the benchmarks.

Most of this crate is not a triangle rasterizer. It is the code that avoids needing one. A UI is a pile of rectangles, rounded panels and glyphs, and each of those arrives as anonymous triangles. Treated as triangles, a 5-pixel glyph costs two triangle setups, three edge functions per pixel and an interpolated texture coordinate per pixel. Recognized as a rectangle, it costs a row of texel copies.

What the rasterizer recognizes#

A mesh is read front to back. At each position the rasterizer tries, in order:

  1. A rectangle. The next six indices name four distinct vertices forming an axis-aligned rectangle, all with one color. A solid one fills spans. A textured one copies texels along each row, if its UVs map linearly across it; glyph quads, images and everything fill_rect and textured_rect draw qualify.
  2. A fan. Four or more consecutive triangles share a center vertex, walk its outline in one direction and have one color. The whole polygon fills spans, row by row. egui draws rounded rectangles, circles and filled shapes this way.
  3. One triangle. A triangle with one color, or whose three corners sample the same texel, fills spans with that color. Anything else, a gradient, a feathered edge, a rotated or distorted texture, is covered and colored pixel by pixel.

All three give exactly the pixels general triangle rasterization would; the tests hold each fast path to it. What they change is the cost.

What costs the most#

  • Per-pixel triangles. Mixed vertex colors send a quad or fan to the last case. That is every gradient, every shadow, and every one of egui’s feathered edges.
  • Feathering. egui anti-aliases shape edges with a one-pixel strip of translucent triangles along each of them. They are thin, numerous and all per-pixel. Counted in AArch64 instructions per steady frame of the benchmark scenes, turning feathering off removes 44% of the work for a form, 17% for a page of text, and 35% for a window with a shadow. Hosting egui shows how, and what it looks like.
  • Pixels. Every covered pixel costs a blend or a write. A full-surface clear, or a panel that covers the screen, touches every pixel once more.
  • Translucency. An opaque span is a copy. A translucent one reads, blends and writes each pixel.

Keeping an egui frame cheap#

  • Let unchanged frames skip. It is on by default, and a frame that matches the last one costs one comparison instead of a rasterization. Hosting egui lists what counts as unchanged.
  • Turn feathering off on slow CPUs. Text keeps its anti-aliasing.
  • Present only frames whose surface_changed is true. The copy to the screen touches every pixel, changed or not.
  • Render at the screen’s own size. Scaling afterwards is one more pass over every pixel.

Drawing with Frame yourself, draw rectangles with fill_rect and textured_rect, give each rectangle one color, and prefer a fan of one color to separate triangles.

On AArch64#

The hot loops run NEON code on AArch64: translucent spans, textured and glyph rows, gradient rows, and the coverage scans that find where each triangle row starts and ends. It writes the same bytes as the portable code. Platforms lists each kernel.

Benchmarks#

cargo bench --bench draw
cargo bench --features egui --bench frame
cargo bench --features egui --bench frame -- --no-feathering --scene form

Both report microseconds per frame at 640x480, as the median (p50) with the 10th and 90th percentiles. Neither has --help; the options are at the top of each file under benches/.

draw uses the drawing API alone, without egui:

WorkloadDraws
glyphs3,200 7x9 textured rectangles, cut from a coverage atlas and tinted: a page of text
panels48 overlapping 120x60 rectangles, a third opaque and the rest translucent
mesha 256-triangle fan with a different color at every outer vertex, translucent

--frames N sets the number of frames and --workload NAME runs one.

frame renders the three egui scenes the golden tests pin, form, preview and window, and reports the rasterization and the whole call separately:

OptionDoes
--frames NFrames measured per scene
--scene NAMEOne scene only
--no-featheringTurns egui’s feathering off, as dream-ini’s PortMaster build does
--hashPrints each scene’s golden hash
--dump DIRWrites each scene’s surface to DIR/<scene>.rgba
--statsPrints the renderer’s deep statistics for one frame
--settle NFrames rendered before measuring; shorter, for tracing under qemu

Run without --bench, as cargo test --all-targets does, each draws once, which keeps them compiling and working.

Numbers#

Every release runs cargo bench in CI and attaches the output to its GitHub release as BENCHMARKS.md. For 1.0.0, on GitHub’s ubuntu-22.04 x86-64 runner:

Workloadp50p10p90
glyphs3089 µs3083 µs3117 µs
panels354 µs350 µs363 µs
mesh4791 µs4775 µs4820 µs

A desktop x86-64 CPU tells you about a desktop x86-64 CPU. For the handhelds this crate is for, compare AArch64 instruction counts, as the numbers above for feathering do, or measure on the device.