vercel-labs / vercel-labs/native

The reference rasteriser does per-pixel work that is loop-invariant or arithmetically absent

Open
#442 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
7.7k
Forks
314
Avg merge
5h
Merged PRs (30d)
13

Description

Measured with the toolkit's own frame profiler on Ubuntu 26.04 aarch64, four cores, ReleaseFast, scrolling a real feed in a Zig app: present is 79% of a scroll frame.

stage p50
rebuild 0.4 ms
layout 2.5 ms
reconcile 1.0 ms
emit 0.9 ms
plan 2.0 ms
present 26.4 ms

On Linux present is the reference rasteriser plus the GTK blit, because there is no packet presenter to take. So the raster is the frame.

Two reductions took present p50 from 29.3ms to 16.0ms, and the frame interval from 43ms to 28ms (23fps to 36fps). Minimum of three runs each, because that machine only ever reads high: consecutive runs of an identical binary spread 35%.

1. referencePointInRoundedRect is dead weight for a square corner

fillRoundedRect has a binary path for radius 0, and it calls that predicate per pixel. With every radius zero the function reduces to the containsPoint at its top: each of the four corner branches needs a point either left of minX or at/past maxX, and containsPoint has already excluded both, so it always falls through to return true.

What it was costing per pixel: a rect.normalized() construction, assertNormalized, isEmpty, four clamps and eight comparisons, behind a call.

I hoisted the normalize out of the loop and call containsPoint directly. Deliberately not a span fill: deriving the covered pixel span from the geometric rect means reproducing the pixel-center comparisons exactly, and getting that subtly wrong moves golden bytes. This form cannot, being the same predicate evaluated the same number of times with the loop-invariant part lifted.

This matters more than it sounds because apps emit full-size backgrounds as radius-0 fill_rounded_rect, so it is most of the pixels on screen.

2. An opaque fill is a store, not a read-modify-write

Two things compound here.

A solid colour samples the same at every pixel, so referenceSampleFill and its switch are loop-invariant and were being called per pixel.

And when that colour is opaque the destination is arithmetically absent from the result, not merely unlikely to matter. With src_a == 1, out_a is 1 + dst_a * 0 = 1, and each channel is (clamp(src.c) * 1 + dst_c * dst_a * 0) / 1. Multiplying and dividing by exactly 1.0 is identity in IEEE754, so every output byte is a function of the source alone. The four-byte read before the blend buys nothing.

So the inner loop of a background fill becomes four byte writes: no sample, no blend, no read. It applies to fill_rect and to the square path of fill_rounded_rect.

Gradients and translucent fills are untouched and take the path they always did.

3. A smaller one alongside

blendPixelCoverage was called for every pixel of a rounded fill including the interior, where coverage is exactly 1. blendRgba8Coverage already routes cov >= 1 to blendRgba8, and referenceScaleColorAlpha(src, 1) scales alpha by exactly 1.0, so hoisting that test to the call site is identity. I could not measure this one above the noise and I am carrying it on being provably free rather than on a number.

What I did not do, and why it may matter more to you than to me

Text is 13-18% of the frame, not the dominant cost, which surprised me: drawGlyphOutline re-parses and re-flattens each glyph outline from the font bytes every frame with no cache at any level, and fillGlyphPath has no active edge table. It is real work and it is the obvious thing to optimise, but on this content it is aimed at the smaller share. I mention it because the scanline fill's O(rows x subsamples x edges) shape is the sort of thing that looks worse on other content than it did on mine.

Golden pixel suite green throughout.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating referencePointInRoundedRect, fillRoundedRect, fill_rect, blendPixelCoverage, and their related sampling and blending functions in the reference rasteriser. Compare the radius-zero and opaque-fill paths with the described invariants, then run the golden pixel suite and the toolkit frame profiler; done means the suite remains green and the measured present path improves without changing translucent or gradient behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
zig
Domain
computer-graphics, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.