Skip to main content

LOD Properties — Reference Sheet

A catalog of node and subtree properties where a zoom-aware Level-of-Detail (LOD) decision can reduce per-frame work. Pairs with item 51 (Subpixel LOD Culling) in optimization.md, which drops entire leaves whose projected bounds collapse below a threshold.

This document defines what is LOD-able, what the decision metric is, and where in the pipeline the decision applies. It does NOT prescribe specific thresholds or promise specific wins — both require empirical verification per backend.

Principles

  1. LOD decisions are camera-zoom-indexed. A node's visual significance depends on how it projects to device pixels.
  2. Two kinds of LOD:
    • Skip work — eliminate draw dispatches entirely (safe, portable)
    • Replace with cheaper primitive — swap a complex draw for a simpler one (requires per-backend validation; modern GPUs may already short-circuit)
  3. The only trustworthy reason to implement a rule is a measured win. Categories that "look" cheap on paper may already be handled by the underlying graphics backend.
  4. Threshold policy is pluggable, not hard-coded. Per-property thresholds live in a runtime config so they can be tuned per backend / per fixture / per workload.

Notation

  • z — camera zoom (device pixels per world unit)
  • px(x) = x · z — project a world-space length to device pixels
  • A property is "subpixel" when its projection falls below a threshold matching the backend's AA resolution (typically 0.5 px for coverage, 1.0 px for structural features)

Pipeline Stages

Each LOD decision applies at one of three stages:

StageWork avoidedConstraint
Frame planskip node / subtree entirelyneeds zoom + bounds at plan time
Picture recordemit cheaper primitives into cached SkPictureper-node pictures must become zoom-variant
Draw timedynamic per-frame decision against current zoomcheap decision; compatible with cached pictures

Catalog

A. Geometric node / bounds

IDPropertyMetricAction
A1render boundsboth axes projected < εcull leaf ✅ item 51
A2render bounds areaarea·z² < ε²cull leaf
A3render bounds diagonaldiag·z < εcull leaf
A4stroke-only contributionstroke_w·z < εdrop stroke paint, keep fill
A5subtree cumulative areaΣ child area·z² < ε²cull subtree

B. Corner & rounding

IDPropertyMetricAction
B1corner radius (rect)r·z < εRRect → Rect
B2corner radius (path)r·z < εdrop corner arcs → polyline
B3stroke join mitermiter·z < εforce bevel fallback

C. Stroke & outline

IDPropertyMetricAction
C1stroke width (thin)width·z < εskip stroke draw
C2stroke width (hairline)width·z ≈ 1 pxclamp to width=0 hairline path
C3dash segment lengthdash·z < εreplace with solid stroke
C4dash gap lengthgap·z < εreplace with solid stroke
C5variable-width amplitudeamp·z < εcollapse to constant stroke
C6marker sizemarker·z < εomit marker

D. Path / vector complexity

IDPropertyMetricAction
D1segment chord lengthchord·z < εdrop consecutive near-coincident pt
D2bezier flattening toltolerance = 1/zcoarser curve tessellation
D3sub-path bbox areabbox·z² < ε²drop sub-path
D4near-coincident pointsd·z < εmerge points

E. Effects (save_layer / filter avoidance)

IDPropertyMetricAction
E1drop-shadow blur radiusr·z < εskip shadow
E2drop-shadow offset|offset|·z < εfold color into fill
E3inner-shadow radiusr·z < εskip
E4layer blur sigmaσ·z < εskip blur
E5backdrop blur sigmaσ·z < εskip backdrop blur
E6glass displacementd·z < εskip
E7noise grain scalegrain·z < εskip

F. Opacity & blend

IDPropertyMetricAction
F1alpha near zeroopacity < 1/255cull node
F2opacity × areaα·w·h·z² < εcull node
F3blend on tiny subtreesubtree area·z² < ε²force Normal blend

G. Fills

IDPropertyMetricAction
G1gradient projected spanspan·z < εaveraged solid
G2gradient stop densitystops > pixel spancollapse to average
G3image fill sizeimg_display_px < εcenter-pixel solid
G4pattern tile sizetile·z < εtile-averaged solid
G5occluded paintopaque paint aboveskip occluded paints

H. Text

IDPropertyMetricAction
H1font size (cull)font·z < ε_cullskip text entirely ✅ item 52
H2font size (greek)ε_cull ≤ font·z < ε_greekrender as SkRect(s) ✅ item 52
H3line heightlh·z < εcollapse to thin rect
H4glyph advanceadv·z < εmerge adjacent glyphs
H5attributed run spanrun·z < εmerge runs
H6decoration thicknessthickness·z < εskip decoration
H7text-shadow blurr·z < εskip

I. Clip & mask

IDPropertyMetricAction
I1clip path areabbox·z² < ε²drop clipped subtree
I2clip complexitymany segments, low zreplace with bbox clip
I3mask areabbox·z² < ε²drop masked subtree

J. Container / subtree

IDPropertyMetricAction
J1subtree cumulative areaΣ children·z² < ε²rasterize once as snapshot
J2container vs sparse childrenchildren « containerskip container paint
J3nested container depthdepth > N at low zflatten subtree to image

K. Render-surface backing

IDPropertyMetricAction
K1surface backing resolutionbounds·zallocate at projected size
K2filter qualitysurface_px smallnearest sampling
K3compositor promotioncost estimate at zdon't promote if blit ≥ live

L. Devtools overlays

IDPropertyMetricAction
L1frame title labelnode_w·z < label_whide label
L2selection handlesnode_area·z² < ε²hide handles
L3hit badgesdensity at zcluster badges

Verification

Each property must be verified before implementation. Two checks:

  1. Skia cost probe — measure the raw per-primitive cost of the operation to be avoided OR of the replacement primitive. If the backend already short-circuits the condition, the LOD rule is moot or regressive. See examples/skia_bench/* for the probe pattern.
  2. Scene-level bench-report diff — run with/without the LOD rule across a diverse fixture set, compare per-stage timings.

Two independent sources of possible redundancy:

  • Skia's existing fast paths (e.g. SkRRect::isRect() for r=0)
  • GPU driver's analytic-coverage shaders that early-exit on sub-pixel inputs (varies per backend — Metal, Ganesh GL, Graphite, WebGL, …)

Rules that skip work entirely (A, E, H1/H2, F1, G5) are generally safe to implement without per-backend validation: they remove draw dispatches the backend would otherwise execute.

Rules that replace with a cheaper primitive (B, C, D, G1–G4) need per-backend measurement because modern analytic-AA shaders may already handle the sub-pixel case efficiently.

Applied Findings

Findings are tracked inline in optimization.md (numbered items) and in per-property verification notes alongside their benchmarks.

  • Item 51 (A1) — implemented. Subpixel leaf-bounds culling.
  • Item 52 (H1) — implemented. Text font-size-below-threshold cull.
  • B1 (RRect → Rect) — measured via skia_bench_rrect_vs_rect. Needs per-backend decision; on some backends the analytic rrect shader is already cheaper than drawRect at sub-pixel radii.