Skip to content

fix(webgl): respect clipping rect in reuseRenderOp's default-shader path#32

Merged
chiefcll merged 3 commits into
mainfrom
fix/reuse-render-op-clip-bypass
May 23, 2026
Merged

fix(webgl): respect clipping rect in reuseRenderOp's default-shader path#32
chiefcll merged 3 commits into
mainfrom
fix/reuse-render-op-clip-bypass

Conversation

@chiefcll
Copy link
Copy Markdown
Contributor

Summary

WebGlRenderer.reuseRenderOp had a fast path for "both nodes use the default shader" that returned true without checking the clipping rect or RTT state. Two clipping containers with different scissor rects but the same default shader were getting batched into one render op, so every quad in the batch was drawn under the first node's scissor.

In practice this meant: in a row of side-by-side clipping: true boxes, only the first box rendered its background — every subsequent box's quad was clipped out by the first box's scissor rect. Per-box children (reference lines, small markers) were silently culled too.

How it surfaced

The text-vertical-center example renders a row of colored clipping boxes per fontSize, each with a horizontal red center-guide line. After the 'optical' baseline default landed, re-ordering the children of each box (text before line) created a render op layout that triggered the bug — boxes past the first lost their color, and one row's red line vanished.

The bug existed all along; the chain-break from SDF text ops (which sets curRenderOp = null) was masking it. Any pattern that puts two clipping: true colored siblings adjacent in render order would hit it.

Fix

Move the RTT and clipping-rect checks above the default-shader fast path in reuseRenderOp:

// Correctness first — RTT and clipping must match regardless of shader.
if (USE_RTT && (rtt-mismatch)) return false;
if (compareRect(curRenderOp.clippingRect, node.clippingRect) === false) return false;

// Then the fast path.
if (curShader.shaderKey === 'default' && shader.shaderKey === 'default') {
  return true;
}

Performance impact is negligible — compareRect is four number compares.

Bonus: example test sub-pixel fix

While diagnosing this I also found that text-vertical-center's fontSize 60 row was missing its red center-guide line. Separate issue, but worth shipping together for review context:

At the default examples config (resolution=720, appHeight=1080), the logical→physical scale is 720/1080 = 0.667. A 1-logical-px line at certain Y positions covers no pixel center after rasterization and disappears. fontSize 60's row at logical y=430 lands at physical Y range [286.67, 287.33] — neither pixel 286 (center 286.5) nor pixel 287 (center 287.5) is inside. Bumping the line to h: 2 (1.33 physical px) guarantees rasterization. Added a comment explaining it.

Test plan

  • pnpm build clean.
  • vitest run — 182/182 pass.
  • Manually verified in pnpm start: with the fix, text-vertical-center shows all boxes with their background colors and all rows' red center-guide lines.

Files

🤖 Generated with Claude Code

chiefcll and others added 3 commits May 22, 2026 20:22
The "both shaders are 'default'" fast path in `reuseRenderOp` returned
true without checking the clipping rect (or RTT state). Two clipping
containers with different clipping rects but the same default shader
were getting batched into one render op, so every quad in the batch
was drawn under the FIRST node's scissor rect — meaning containers
past the first in a row lost their background (drawn outside the
batched scissor) and per-container guides like reference lines were
silently culled.

The text-vertical-center example surfaced this: with the optical-mode
default and a re-ordered tree, all but the first box per row lost their
color, and one row's reference line disappeared on top of the sub-pixel
rasterization issue described below.

Move the RTT and clipping-rect checks above the default-shader fast
path so correctness can't be bypassed. Performance impact is negligible
— compareRect is 4 number compares.

Also: bump the center-guide line in `text-vertical-center` from h=1 to
h=2. At the default examples config (resolution=720, appHeight=1080)
the logical→physical scale is 0.667, so a 1-logical-px line at certain
Y values covers no pixel center and disappears — separate, well-known
sub-pixel rasterization behavior, not a renderer bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chiefcll chiefcll merged commit c554939 into main May 23, 2026
1 check passed
@chiefcll chiefcll deleted the fix/reuse-render-op-clip-bypass branch May 23, 2026 00:45
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