Skip to content

perf(shaders): move RoundedWithBorder border math from varyings to CPU uniforms#89

Closed
chiefcll wants to merge 1 commit into
mainfrom
perf/rounded-with-border-uniforms
Closed

perf(shaders): move RoundedWithBorder border math from varyings to CPU uniforms#89
chiefcll wants to merge 1 commit into
mainfrom
perf/rounded-with-border-uniforms

Conversation

@chiefcll

Copy link
Copy Markdown
Contributor

What

Rewrites the WebGL RoundedWithBorder shader so all border geometry is computed once on the CPU and uploaded as uniforms, instead of being recomputed in the vertex shader and shipped to the fragment stage through varyings.

Why

All nine border varyings (v_innerSize, v_outerSize, v_outerBorderUv, v_innerBorderUv, v_innerBorderRadius, v_outerBorderRadius, v_halfDimensions — 18 interpolated floats) were derived purely from uniforms, so they were constant across the quad yet paid per-fragment interpolation cost on every pixel. On the embedded GPUs this engine targets, interpolator pressure is real fragment-pipeline cost.

This matters most for the common TV-app pattern of border: {w: 0} on every card until focus: with a zero-width border, the fragment shader now carries the same 3 varyings as the plain Rounded shader, making the per-pixel cost of an unfocused card identical to Rounded. The full border path only runs its math when border props or node dimensions actually change (cached by the existing shader value-key cache), i.e. once per focus change rather than per frame.

How

  • calcBorderShaderValues() mirrors the previous vertex-shader expressions exactly (adjusted widths, gap handling, inner/outer sizes, UV offsets, inner/outer radii, edge offset) and writes into a reused scratch buffer.
  • borderZero is precomputed and uploaded as a uniform instead of re-derived per fragment via dot/step.
  • The vertex shader's border branch is replaced with arithmetic on that flag; the quad-expansion logic (edge + edge offset) is preserved.
  • With a zero border, update() skips computing/uploading the border uniforms entirely (both shader stages early-out before reading them).

GLSL output is unchanged by construction. No prop/API changes; the Canvas2D backend is untouched.

Testing

  • pnpm build passes.
  • New co-located unit tests (RoundedWithBorder.test.ts) verify the CPU math against hand-computed cases: uniform/asymmetric borders, gap, align inside/center/outside, sub-pixel width collapse, and negative inner-radius clamping.
  • Locally loaded ?test=shader-border in the examples app: shaders compile and render with no GL errors.
  • Visual certification deferred to CI's visual regression run (snapshots should be pixel-identical).

🤖 Generated with Claude Code

…U uniforms

All nine border varyings (inner/outer size, border UVs, inner/outer radii,
half dimensions) were derived purely from uniforms, so they were constant
across the quad yet interpolated per fragment. Compute them once on the CPU
in update() (cached by the shader value-key, so they only recompute when
border props or node dimensions change) and upload them as uniforms.

The fragment shader now carries the same 3 varyings as the plain Rounded
shader, and with a zero-width border its per-pixel cost is identical to
Rounded - the common TV-app pattern of border: {w: 0} until focus no longer
pays the full border pipeline on every unfocused card.

borderZero is also precomputed on the CPU and uploaded as a uniform instead
of being re-derived per fragment, and the vertex shader's branch is replaced
with arithmetic on that flag. GLSL output is unchanged by construction; the
CPU math mirrors the previous vertex shader expressions exactly and is unit
tested against hand-computed cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chiefcll chiefcll closed this Jun 10, 2026
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