// Source history on a boundary shader: previous: true retains the prior // rasterization of the subtree as uPrevious, rotated when the content // actually changes - not per frame. That makes it transition material: on // each content change uPrevious holds the old look and uSource the new, and // uMix sweeps a cross-dissolve between them. For a static panel uPrevious // equals uSource (the previous rasterization IS the same content), so // feedback/accumulation is not what this is for - that stays with manual // targets. // // Click the panel to cycle its colors: the old palette dissolves into the // new one instead of snapping. import { render, onFrame, createSignal } from "@solidrt/core" import { compileShader, destroyShader, glsl, linkProgram } from "@solidrt/core/gpu" let VERTEX = glsl` out vec2 vUV; void main() { vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2)); vUV = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); } ` let DISSOLVE = glsl` uniform sampler2D uSource; uniform sampler2D uPrevious; uniform float uMix; in vec2 vUV; void main() { fragColor = mix(texture(uPrevious, vUV), texture(uSource, vUV), uMix); } ` const PALETTES = [ ["#0077ff", "#ff6a00", "#00c46a"], ["#e63946", "#ffb703", "#8338ec"], ["#00b4d8", "#ef476f", "#ffd166"], ] as const function App() { let vs = compileShader("vertex", VERTEX, { header: true }) let fs = compileShader("fragment", DISSOLVE, { header: true }) let dissolve = linkProgram(vs, fs, { label: "dissolve" }) destroyShader(vs) destroyShader(fs) let [palette, setPalette] = createSignal(0) let colors = () => PALETTES[palette() % PALETTES.length]! let [mixv, setMix] = createSignal(1) // Sweep uMix back to 1 after each change (~250ms). At 1 the signal stops // changing, so the pass stops re-running. onFrame((_tick, _frame, rate) => setMix(m => Math.min(1, m + 4 / rate))) let cycle = () => { setPalette(p => (p + 1) % PALETTES.length) setMix(0) } return ( Boundary history Click: colors cross-dissolve ) } render(() => )