Skip to content

perf(webgl): skip SDF buffer upload when text content is unchanged#91

Open
chiefcll wants to merge 1 commit into
mainfrom
perf/skip-static-sdf-upload
Open

perf(webgl): skip SDF buffer upload when text content is unchanged#91
chiefcll wants to merge 1 commit into
mainfrom
perf/skip-static-sdf-upload

Conversation

@chiefcll

Copy link
Copy Markdown
Contributor

What

Skips the per-frame bufferData upload of the shared SDF vertex buffer in render() when the buffer's content is guaranteed identical to what the GPU already holds.

Why

render() re-uploaded the entire used SDF buffer on every frame where any text existed — on a TV screen, that's every frame. A screen with a few hundred visible glyphs re-uploads tens-to-hundreds of KB at 60fps even when every text node is static, and bufferData (orphan-and-reallocate semantics) is the expensive call on the embedded driver stacks this engine targets. Any frame where images fade, focus rings animate, or colors pulse — but text holds still — paid this for nothing.

How

The dirty signal is derived inside the renderer, with no scene-graph invalidation hooks: the cache-hit text path (addSdfCachedQuads) writes byte-identical data at identical offsets as long as visit order and glyph counts are unchanged. A new sdfBufferChanged flag forces upload when any of these occur:

  • a cache-miss write ran (addSdfQuads — layout/transform/color/alpha changed)
  • the render list was rebuilt (invalidateQuadBuffer — write order changed)
  • an RTT pass ran (RTT glyphs occupy the front of the shared buffer)
  • the backing buffer grew (storage replaced)
  • the used size differs from the last upload (contributor set changed — catches text nodes appearing/disappearing even when every individual write was a cache hit)

The decision is isolated in shouldUploadSdfBuffer() for unit testing. Context loss needs no handling: the engine's documented recovery is a full app reload (no webglcontextrestored path exists).

The per-frame memcpys and SdfRenderOp rebuilds are unchanged — only the GL transfer is elided, which is where the cost lives. Canvas2D backend untouched (no SDF path).

Testing

  • New unit tests (WebGlRenderer.sdfUpload.test.ts): first-frame upload, skip on unchanged content + matching size, forced upload on cache-miss write / size mismatch, growth and render-list invalidation hooks (tested via Object.create(WebGlRenderer.prototype) — no GL context needed).
  • pnpm build clean; webgl + CoreNode suites 83/83.
  • Browser-verified in the examples app: static text-align renders crisp SDF paragraphs (skip path); stress-single-level-text animates text continuously with intact glyphs (cache-miss re-upload path).
  • CI visual regression should be pixel-identical — the GPU receives the same bytes either way.

🤖 Generated with Claude Code

render() re-uploaded the entire used SDF vertex buffer via bufferData on
every frame where any text existed, even when every text node took the
cache-hit path and rewrote byte-identical data at identical offsets. On a
text-heavy TV screen that is a guaranteed multi-hundred-KB driver copy per
frame paid during every animation that doesn't touch text.

The skip is safe because the dirty signal is derivable inside the renderer:
the cache-hit path (addSdfCachedQuads) produces identical bytes as long as
visit order and glyph counts are unchanged. Upload is forced when:
- any cache-miss write ran (addSdfQuads: layout/transform/color/alpha change)
- the render list was rebuilt (write order changed)
- an RTT pass ran (RTT glyphs occupy the front of the shared buffer)
- the backing buffer grew (storage replaced)
- the used size differs from the last upload (contributor set changed)

Context loss needs no handling: the engine's documented recovery is a full
app reload (no webglcontextrestored path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant