feat(canvas): curated named GPU shader effects for scripted-UI canvas#6422
Merged
Merged
Conversation
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
force-pushed
the
feature/canvas-named-shader-effects
branch
from
July 21, 2026 05:15
a1ce5e5 to
7a36d1c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Effects compile once and cache for process lifetime (RuntimeEffectCache),
keyed by source; uniforms clamped; unknown name / SkSL compile failure
both return a null filter.
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 CTM-scaled resolution — vignette centering / CRT curvature / bloom
radius stay correct under HiDPI (proven by a 2x-scale test).
(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
claudem5~/Code/pulp-shader-fx14ef2f39-9113-4e61-bd8b-8faab97cab00Resume
Jump to this tab
Relaunch (any agent)
Restore URL — https://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV
stamped 2026-07-21 03:08 UTC