perf: sort main-pass SDF text writes for fewer draw calls - #865
Open
wouterlucas wants to merge 3 commits into
Open
perf: sort main-pass SDF text writes for fewer draw calls#865wouterlucas wants to merge 3 commits into
wouterlucas wants to merge 3 commits into
Conversation
Render SDF glyphs into renderer-owned shared vertex buffers (one plain 6f + one rich 7f per-vertex layout) backed by per-node CPU vertex caches. Consecutive text nodes that share a layout, font atlas, and clip merge into a single draw call, and unchanged nodes skip GPU uploads entirely via a cache-hit mem-copy path. - add SdfBuffer (cursor/growth/change tracking) and SdfRenderOp (contiguous-range drawElements with scissor-only clipping) - rewrite SdfShader to feed pre-transformed position, per-vertex color, distance range, and style as vertex attributes; remove the per-node u_transform/u_size/u_color/u_distanceRange uniforms - extend WebGlShaderProgram.bindRenderOp to bind SdfRenderOps through the normal uniform path; drop the legacy onSdfBind hook and isSdfRenderOp flag - move CoreTextNode onto a shared SdfVertexCache; track lastStartQuad and lastWriteDirty so a render-list reorder marks the shared buffer dirty and re-uploads instead of leaving stale GPU bytes at shifted offsets - add the text-stress-sdf example (interleaved vs grouped render order) and unit tests for the buffer, bind path, and offset-tracking fix
Add certified visual baselines for the SDF text stress example.
wouterlucas
force-pushed
the
feat/sdf-text-sorted-writes
branch
from
August 7, 2026 15:44
b726054 to
d5cbd76
Compare
Contributor
Author
|
Rebased onto latest |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Defer main-pass SDF text rendering into a scene-wide sorted list instead of writing interleaved glyph runs inline during the traversal.
SDF text already batches into a shared GPU buffer, but only consecutive writes sharing a vertex layout (plain 6f vs rich 7f), glyph atlas (per font family) and scissor clip rect merge into one
drawElements. Apps that interleave plain/rich text (cards, rails) forced a draw call per region. This sorts the visible SDF text nodes by batch key at render-list build time so each (layout, atlas, clip) group becomes one contiguous range and merges into a single draw call, with zero app-side grouping.Changes
lib/utils.ts— newSdfBatchKeytype andcompareSdfBatchKeystotal ordering (matches the merge key inWebGlRenderer.finalizeSdfBatch: layout, atlas, clip rect incl. radius).CoreNode.getSdfBatchKey()— virtual returningnull;CoreTextNodeoverrides it to return the key for main-pass SDF text,nullfor canvas text, un-laid-out text, and text inside render-to-texture subtrees.Stage—buildRenderListcollects main-pass SDF text nodes (scene order) instead of writing inline;sortRenderListSdfTextNodessorts them with a stable sort (equal keys keep scene order / relative z), cached across frames;writeRenderListSdfTextemits them after the main list on every frame. RTT traversal and all non-SDF nodes keep the inline path.getSdfBatchKeytests inCoreTextNode.test.ts; 12 Stage tests covering comparator order, deferral, sorted emission, inline paths (images, rounded clips, RTT), visibility skips, stable equal-keys, and cached replay.Correctness
The sort is best-effort:
finalizeSdfBatchstill keys off actual write-time values, so a stale cached order (e.g. afontFamily/clip change without a structural rebuild) only costs extra draw calls, never wrong output.Verification
pnpm test272/272,tsc --build, prettier, eslint clean.chromium-local).