Skip to content

feat(canvas): curated named GPU shader effects for scripted-UI canvas#6422

Merged
shipyard-local[bot] merged 1 commit into
mainfrom
feature/canvas-named-shader-effects
Jul 21, 2026
Merged

feat(canvas): curated named GPU shader effects for scripted-UI canvas#6422
shipyard-local[bot] merged 1 commit into
mainfrom
feature/canvas-named-shader-effects

Conversation

@shipyard-local

@shipyard-local shipyard-local Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Add a SAFE, curated named-effect capability to the scripted-UI canvas so
generated / scripted plugin UIs (Forge) can apply signature GPU post-effects
by NAME — never by injecting arbitrary SkSL. Closes the retro/neon/tube/CRT
look gap that the CSS-filter path (blur/brightness/…) cannot cover.

New JS bridge entry, distinct from the CSS-filter path:
canvasSetShaderEffect(id, name, intensity=1.0)
Curated effects (each: single clamped intensity in [0,1]):
crt — scanlines + barrel curvature + vignette + aperture mask
grain — static film grain
vignette — radial edge darkening
noise — isotropic procedural value-noise overlay
brushed — horizontal brushed-metal streaks
bloom — threshold + small-kernel glow (neon/tube)
"none"/"" disables; an unknown name is a graceful no-op (never rejects a
build). intensity is clamped host-side.

Implementation:

  • core/canvas/src/named_shader_effects.{hpp,cpp}: vetted SkSL library.
    Effects compile once and cache for process lifetime (RuntimeEffectCache),
    keyed by source; uniforms clamped; unknown name / SkSL compile failure
    both return a null filter.
  • Canvas::save_layer_with_shader_effect(): new virtual. Default (non-Skia /
    CoreGraphics / RecordingCanvas) falls back to a plain save_layer so the
    effect is skipped and headless CPU renders never crash. SkiaCanvas builds
    an SkImageFilters::RuntimeShader over the compositing layer via push_layer.
  • The RuntimeShader image filter runs in DEVICE space, so the effect gets
    the CTM-scaled resolution — vignette centering / CRT curvature / bloom
    radius stay correct under HiDPI (proven by a 2x-scale test).
  • CanvasWidget::set_shader_effect() stores sticky, declarative effect state
    (persists across command-list rebuilds — no per-frame re-issue, no
    animation loop needed for a static overlay) and opens the existing
    per-canvas layer through the effect path when active — no extra layer.

Tests (Skia raster, headless): each named effect measurably changes pixels
vs. baseline; unknown name is a byte-identical no-op; intensity 0 is identity;
vignette stays device-centered under 2x scale; bloom bleeds light past bright
edges. Bridge API manifest + generated TS/docs regenerated.

Hotspot-Grow: core/view/src/widget_bridge/canvas2d_api.cpp reason="one bridge registration for the new canvasSetShaderEffect capability"
Hotspot-Grow: core/canvas/include/pulp/canvas/canvas.hpp reason="one virtual (save_layer_with_shader_effect) + its doc for the curated effect capability"
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV


🔎 Provenance

Agent claude
Machine m5
Tab Analyze Pulp for AI plugin builder capabilities
Directory ~/Code/pulp-shader-fx
Session 14ef2f39-9113-4e61-bd8b-8faab97cab00

Resume

claude --resume 14ef2f39-9113-4e61-bd8b-8faab97cab00

Jump to this tab

cmux surface focus BD0A704A-23E0-4F9B-88CC-25BD1ACDAFEA

Relaunch (any agent)

cmux surface resume get --surface BD0A704A-23E0-4F9B-88CC-25BD1ACDAFEA

Restore URLhttps://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV

stamped 2026-07-21 03:08 UTC

Add a SAFE, curated named-effect capability to the scripted-UI canvas so
generated / scripted plugin UIs (Forge) can apply signature GPU post-effects
by NAME — never by injecting arbitrary SkSL. Closes the retro/neon/tube/CRT
look gap that the CSS-filter path (blur/brightness/…) cannot cover.

New JS bridge entry, distinct from the CSS-filter path:
  canvasSetShaderEffect(id, name, intensity=1.0)
Curated effects (each: single clamped intensity in [0,1]):
  crt      — scanlines + barrel curvature + vignette + aperture mask
  grain    — static film grain
  vignette — radial edge darkening
  noise    — isotropic procedural value-noise overlay
  brushed  — horizontal brushed-metal streaks
  bloom    — threshold + small-kernel glow (neon/tube)
"none"/"" disables; an unknown name is a graceful no-op (never rejects a
build). intensity is clamped host-side.

Implementation:
  - core/canvas/src/named_shader_effects.{hpp,cpp}: vetted SkSL library.
    Effects compile once and cache for process lifetime (RuntimeEffectCache),
    keyed by source; uniforms clamped; unknown name / SkSL compile failure
    both return a null filter.
  - Canvas::save_layer_with_shader_effect(): new virtual. Default (non-Skia /
    CoreGraphics / RecordingCanvas) falls back to a plain save_layer so the
    effect is skipped and headless CPU renders never crash. SkiaCanvas builds
    an SkImageFilters::RuntimeShader over the compositing layer via push_layer.
  - The RuntimeShader image filter runs in DEVICE space, so the effect gets
    the CTM-scaled resolution — vignette centering / CRT curvature / bloom
    radius stay correct under HiDPI (proven by a 2x-scale test).
  - CanvasWidget::set_shader_effect() stores sticky, declarative effect state
    (persists across command-list rebuilds — no per-frame re-issue, no
    animation loop needed for a static overlay) and opens the existing
    per-canvas layer through the effect path when active — no extra layer.

Tests (Skia raster, headless): each named effect measurably changes pixels
vs. baseline; unknown name is a byte-identical no-op; intensity 0 is identity;
vignette stays device-centered under 2x scale; bloom bleeds light past bright
edges. Bridge API manifest + generated TS/docs regenerated.

Hotspot-Grow: core/view/src/widget_bridge/canvas2d_api.cpp reason="one bridge registration for the new canvasSetShaderEffect capability"
Hotspot-Grow: core/canvas/include/pulp/canvas/canvas.hpp reason="one virtual (save_layer_with_shader_effect) + its doc for the curated effect capability"
Skill-Update: skip skill=web-plugins reason="mechanical PulpWebUi.cmake source-list mirror for the new named_shader_effects.cpp TU; the mirror-new-core-TUs-or-wasm-ld-breaks rule is already documented in the web-plugins SKILL.md, no new durable knowledge"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV
@danielraffel
danielraffel force-pushed the feature/canvas-named-shader-effects branch from a1ce5e5 to 7a36d1c Compare July 21, 2026 05:15
@shipyard-local
shipyard-local Bot merged commit 3fde63c into main Jul 21, 2026
25 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant