FL flatland

A three-flatland experiment

Slug in uikit

Below are two demos running the same Ladder and Grid workloads. One is upstream uikit with MSDF text and SVG-mesh icons; the other is a fork using Slug for both, which also carries a few uikit CPU optimizations we intend to contribute upstream. This page reports what the benchmarks show, and where each approach fits, without claiming more than the measurements support.

three-flatland · uikit fork Slug Text and icons drawn from their outline curves on the GPU. No atlas. Open the live demo → pmndrs/uikit · upstream MSDF Text drawn from a precomputed multi-channel distance-field atlas. Open the live demo →

Both demos are the benchmark-suite apps themselves, so DPR does not follow your display automatically. Set it manually in the navbar, along with scene (Ladder / Grid), view angle, backend, and language.

SetupWhat's being compared

Both demos render the same two scenes:

Each demo renders that text and those icons a different way:

Slug: evaluate the outline curves

Slug sends each glyph's quadratic Bézier outline to the GPU and estimates fractional coverage per fragment from the curves and their winding. Nothing is baked, so there is no atlas resolution: the outline is evaluated at the requested size.

MSDF: sample a baked atlas

Ahead of time, each glyph is baked into a small multi-channel distance-field texture. At draw time the shader samples the atlas, takes the median of three channels, scales the stored distance into screen pixels, and computes coverage. That is light work, but the atlas is baked at a fixed resolution.

QualitySmall text: a known MSDF limitation

A finite-resolution MSDF can lose small glyph features when it is minified (drawn much smaller than it was baked). Its shader scales the baked distance range into screen pixels, and msdfgen's author warns that anti-aliasing is likely to fail once that screen-space range becomes too narrow. Standard reusable atlases also lack the size-specific hinting that conventional rasterizers use to snap thin stems onto the pixel grid. How visible this is depends on atlas resolution, the baked distance range (pxrange), filtering, and the glyph itself. In this demo you can see it directly: at 1× the small MSDF rows soften while Slug stays sharp.

Slug small text at 1x, magnified: crisp edges
Slug1× · magnified 4×
MSDF small text at 1x, magnified: soft edges
MSDF1× · magnified 4×
The same 8–12px rows, captured at 1× and magnified 4×. In these rows Slug keeps sharper edges; the MSDF rows are softer, then recover as size grows.

At 2× DPR the glyph covers more screen pixels, and the softness seen here is reduced. Slug has no atlas resolution to fall below, so its outline does not need a higher-resolution re-bake as scale changes, and in the tested rows it stays sharp at 1×. (Very small Slug text has its own anti-aliasing considerations, which Slug mitigates with dilation; resolution-independence is not a guarantee of perfect rasterization at every size.) MSDF was designed to preserve sharp corners better than single-channel SDF; Chlumský's msdfgen is the reference for the multi-channel technique.

GPU costFor text, MSDF uses much less GPU time

Slug's analytic coverage does more fragment work than an atlas sample, so it costs more. Measured on the Ladder scene, p50 GPU-milliseconds per frame from real timestamp queries (lower is better):

Single device; p50 GPU timestamp over a settled ~12s window. See Method for environment.

Text · Ladder GPU @ 1× GPU @ 2× draws
MSDF · WebGL 0.19 ms 0.53 ms 29
Slug · WebGPU 1.11 ms 2.69 ms 5
Slug · WebGL2 1.82 ms 3.63 ms 5

On this Ladder scene, MSDF took about one-fifth to one-tenth of Slug's GPU time (roughly 5–10×), depending on backend and DPR. Note this compares complete implementations across different renderers, not the fragment algorithms in isolation. Both cost more at 2× DPR: here Slug rose 2.42× on WebGPU and 1.99× on WebGL2, while MSDF rose 2.79× but stayed much cheaper in absolute time.

Reducing that 2× cost is unfinished work; see Open questions below.

Draw callsIcon batching changes the draw-call result

In this uikit build, each SVG icon path is turned into an unbatched mesh, so a grid of a few thousand labels produces a few thousand draw calls. Slug includes icon curves in the same shared batches as its glyphs. On the Grid scene:

Single device; Slug = WebGPU, uikit = WebGL. p50 GPU timestamp; draws read from the live renderer.

Icons · Grid (thousands of cells) backend draw calls GPU
Slug WebGPU 6 2.49 ms
uikit (mesh per SVG path) WebGL 3,907 2.21 ms

6 draw calls versus 3,907. The total GPU timestamps are similar (2.49 vs 2.21 ms) despite that gap, so fragment time is not the main measured difference. The draw count points to more CPU and driver submission overhead for uikit, but this page does not include a CPU profile that isolates it.

Scope of the icon result. The draw-call gap comes from this uikit build converting SVG paths to unbatched meshes, not from MSDF itself. A batched or atlas-based MSDF icon path would narrow it. Icon representation and text representation are separable decisions: an engine can use MSDF for both, MSDF text with instanced icon geometry, or Slug for both. Slug's advantage here is that text and icons share one batched pipeline; the six measured draws include the icons, but the demo does not measure the marginal cost of adding Slug text on top.

Frame-rate confound. The Slug demo (the fork) also carries CPU-side layout and frame-loop changes that upstream uikit lacks, so frame rate cannot be used to compare Slug with MSDF; the tables above report GPU time and draw calls instead. Those changes are the subject of the next section.

CJKChinese, Japanese, Korean

Both render Simplified Chinese, and both scale to any display size. What grows with the character set is storage. Slug's curve data fits one GPU page well past the common sets:

Character set glyphs Slug curves
Common3,50012 MB · 1 page
GB23126,76324 MB · 1 page
GBK21,00074 MB · 1 page
Full (Noto SC)65,535229 MB · paged

An MSDF atlas grows on its own model (glyph count and cell size, not outline complexity). We have not measured the two head-to-head; the shared point is that the full repertoire exceeds a single GPU resource and needs paging either way.

Contributing backFixes and performance improvements discovered along the way

Building the fork surfaced a number of issues in upstream uikit, and in its React-Three-Fiber v10 binding, that we intend to contribute back. Two are the per-frame CPU costs that make frame rate unusable for comparing Slug with MSDF here, which is why the tables above report GPU time and draw calls instead. The rest are correctness fixes.

Performance

Correctness

All of these are already fixed in the fork, apart from the paint-order tie, which has a deterministic repro but a fix still being designed. The upstream PRs are not yet filed; details and repros live in the fork's upstream-bug ledger and PR #179.

UnresolvedOpen questions

This is a work in progress, and several things here are genuinely unsettled. We would rather name them than pretend they are solved.

The takeawayWhich fits your case?

Lean MSDF when…

  • Your workload is mostly text with few icons, or your icon path already batches.
  • You want lower GPU time for text and render on retina or at known sizes.
  • You can tune the atlas distance range for your smallest real render size.

Lean Slug when…

  • Your current icon path does not batch and you have many SVG icons.
  • You want text and icons in one batched pipeline.
  • You need outline data that renders at changing scales without generating extra atlas resolutions (3D, free zoom, AR/VR).