Skip to content

fix(shaders): don't re-convert raw canvas shaders#40

Merged
chiefcll merged 1 commit into
mainfrom
fix/canvas-raw-shader-access
Jul 2, 2026
Merged

fix(shaders): don't re-convert raw canvas shaders#40
chiefcll merged 1 commit into
mainfrom
fix/canvas-raw-shader-access

Conversation

@chiefcll

Copy link
Copy Markdown
Contributor

Problem

Raw Canvas shaders still go through convertToShader when set on each node, even though a raw shader already carries everything it needs to render. WebGL handles this correctly; Canvas does not.

The guard in ElementNode._render was:

if (SHADERS_ENABLED && props.shader && !props.shader.program) {
  props.shader = Config.convertToShader(node, props.shader);
}

A CanvasShaderNode exposes render, not program, so it falls through the !props.shader.program check and gets wrongly re-run through convertToShader. WebGL only works because WebGlShaderNode happens to have a program field.

Solution

This is an alternative to #34. Instead of widening the check to 'program' in shader || 'render' in shader (which enumerates backend-specific fields and needs a new clause per backend), use the single field that every built shader shares and a raw StyleEffects props object never has: shaderType.

  • WebGlShaderNode and CanvasShaderNode both extend CoreShaderNode, which declares readonly shaderType.
  • The DOM-renderer test fake returns { shaderType, props, program: {} }.
  • StyleEffects (border*/shadow/rounded/gradients/holePunch) has no shaderType.

So !('shaderType' in props.shader) cleanly means "this is a raw style-props object, build a shader from it" and covers WebGL, Canvas, and the DOM test path uniformly.

Behavior parity

props.shader has shaderType? result
WebGL node yes skip (same as before)
Canvas node yes skip (fixes the bug)
DOM test fake yes skip
StyleEffects props no convert

Verification

  • npm run tsc — clean
  • npm test — 134 passed
  • npm run lint — 0 errors

🤖 Generated with Claude Code

Raw Canvas shaders (CanvasShaderNode) expose `render`, not `program`, so
the `!props.shader.program` guard let them fall through and re-run through
`convertToShader`, corrupting an already-built shader. WebGL worked only
because WebGlShaderNode happens to have `program`.

Both backends' shader nodes extend CoreShaderNode, which always carries a
`shaderType`, as does the DOM-renderer test fake. A raw StyleEffects props
object never does. Switch the guard to a single `'shaderType' in shader`
check that covers WebGL, Canvas, and the DOM test path uniformly — and
won't need another `|| '...' in shader` clause when a new backend lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chiefcll chiefcll merged commit 84f44e1 into main Jul 2, 2026
1 check passed
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