Add Radiant Gallery screensaver: 87 Metal shaders cycling natively on macOS#2
Open
boxabirds wants to merge 51 commits intopbakaus:mainfrom
Open
Add Radiant Gallery screensaver: 87 Metal shaders cycling natively on macOS#2boxabirds wants to merge 51 commits intopbakaus:mainfrom
boxabirds wants to merge 51 commits intopbakaus:mainfrom
Conversation
Three new fullscreen immersive pages: - /zoom: Cycles through gallery shaders in iframes with WebGL noise-dissolve morph transitions, mouse-guided zoom, and ultra-slow hue cycling. - /morph: Single WebGL uber-shader with ~25 interpolatable parameters expressing noise fields, domain warping, orbs, fabric folds, and lighting. Presets morph continuously via parameter interpolation. - /morph-webgpu: WebGPU rewrite of the morph engine. Single WGSL uber-shader with uniform buffer uploaded as Float32Array. Parameters driven by three correlated master oscillators (sine-sum, no grid discontinuities) controlling warp, orbs, and fold/lighting phases. Zero-allocation render loop. Distinct color palettes per mode (teal/cyan, violet/blue, magenta/rose). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- FBM loop cap 3→5 octaves, matching original fluid-amber's detail level - Power-4 winner-take-all (was power-2) with wider drift speed spread for stronger attractor dominance and less mid-blend "mixed paint" - Hold integer uniforms constant across all presets (fbm_octaves=5, orb_count=7) to eliminate discrete pops at blend thresholds; control detail/visibility via continuous params (fbm_decay, orb_intensity) - Preset params matched to original shaders: fluid-amber warp strengths, chromatic-bloom zero-noise pure orbs, silk-cascade full fold+specular, bioluminescence strong wave interference - Color palettes unified to warm-amber family so linear blending produces coherent tones instead of gray; hue_shift handles variety Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 new techniques: kaleidoscope fold, spiral warp, metaball orbs, moiré interference, burn frontier. ~90 WGSL lines, 10 new uniforms. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MP3 plays through a 4-pole lowpass filter chain (2x BiquadFilterNode) with slow LFO modulation (~0.08Hz) tracking visual sharpness. Keyboard only: spacebar toggles, -/= adjusts volume. Lazy-loads on first press. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ral, kaleidoscope 5 new pipeline stages added incrementally, each perf-tested: - Metaball orbs: mix(gaussian, inverse-distance, orb_sharpness) in existing orb fn - Moiré interference: 4-center multiplicative ring beats (freq=55, hard gate) - Burn frontier: threshold sweep on noise field with bright edge glow - Spiral arms: log-spiral distance field with floor-based modulo - Kaleidoscope fold: binary select (not mix) to avoid seam artifacts 9 presets total (4 original + 5 new). Uniform buffer 52→60 floats. All new features use smoothstep(0.3, 0.6) gates to prevent bleed at low blend weights. Tranche-3 plan saved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ixes New techniques: - Chladni cymatics: 6 standing-wave eigenfunctions (cos(nπx)·cos(mπy)) with mode interpolation. Preset 9. - Chromatic aberration: radial RGB split post-processing filter. Subtle values on orb/metaball/moiré presets. Debug HUD (press 'd'): - GPU fps via onSubmittedWorkDone (measures actual GPU completion, not RAF) - Dominant preset name + weight percentage Performance: - Reverted kaleidoscope to binary select (double warped_field saturated GPU) - Halved animation speed (time divisor 2000→4000) Uniform buffer: 60→64 floats (256 bytes). 10 presets total. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Aurora curtain: 8 vertical sine-displaced line SDFs with per-line phase variation, tapered width, additive glow. Preset 10. - Power-8 winner-take-all (was power-4): ~80% dominance with 11 presets, matching the separation power-4 gave with 4 presets. - Fix WGSL let→var for mutable curtain center binding. - Remove close button overlay. - 11 presets, 64-float uniform buffer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cache wave_field(p,t) result in wave_raw; reuse at wave crest highlight site — eliminates one full wave_field() evaluation per pixel - Gate voronoi_cracks() 3×3 nested loop behind u.voronoi_str > 0.01; voronoi_str is always 0 across all presets so the loop was running unconditionally every pixel with a ×0 result - Gate fabric_fold() (2× fbm2 + 4 sines) behind u.fold_str > 0.01; declare fold_grad before the gate so downstream normal/aniso code compiles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…BM loop unroll
- Gate curtain_field(), chladni_field(), spiral_field(), moire_field() each
behind their respective uniform > 0.01 checks; declare result vars before
gate so downstream color-pass code still compiles
- Gate burn frontier behind u.burn_str > 0.01; declare burn_gate/burn_edge
before gate (used in additive color pass) defaulting to 0.0
- Gate compute_orbs() behind u.orb_intensity > 0.01; restructure envelope to
default to 1.0 (no field modulation) when orbs are off — fixes a latent
correctness issue where envelope could collapse to 0 at low orb_intensity
- Gate wave_field() call itself behind u.wave_str > 0.01 (result was cached
in phase 1; now the function isn't called at all when wave is off)
- Add override FBM_MAX_OCTAVES: i32 = 3 constant to WGSL; change fbm() loop
bound from literal 5 to FBM_MAX_OCTAVES — enables driver loop unrolling
- Pass constants: { FBM_MAX_OCTAVES: 3 } in engine.ts pipeline descriptor
for both vertex and fragment stages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cale Render the main fragment shader to a ⌊w/2⌋×⌊h/2⌋ intermediate texture, then blit to the swap chain with bilinear upscaling. At ¼-speed animation over smooth noise fields with no geometric hard edges, the quality loss is imperceptible while GPU fragment work drops by ~4×. engine.ts changes: - Add halfResTexture: GPUTexture created at canvas.width/2 × canvas.height/2 with RENDER_ATTACHMENT | TEXTURE_BINDING usage - Add blit pipeline: separate WGSL module (BLIT_WGSL constant inline in engine.ts) with vs_blit fullscreen triangle + fs_blit linear sampler - Extract _buildHalfResResources() static helper: creates half-res texture, blit bind group (linear sampler + texture view), and main render bundle targeting the half-res view; called at init and on every resize - Add public resize(w, h) method: destroys old half-res texture, calls _buildHalfResResources to rebuild texture + bind group + render bundle - render(): two-pass submission — Pass 1 executes the (bundled) main draw to halfResTexture; Pass 2 runs the blit pipeline directly (no bundle, since swap chain view changes each frame) - destroy(): also destroys halfResTexture +page.svelte: call engine?.resize(w, h) in the onResize handler so the half-res texture is rebuilt to match new canvas dimensions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… trails, clusters)
…t by morph content
…ing original realism
- Colour variation: slow-drifting hue-rotation patches oscillate in/out on a 20s sine cycle (monotone for half, colourful for half) - Audio VCF ceiling raised from 8kHz to 20kHz (fully open at max sharpness) - 'f' key fades visuals + audio in/out over 10s (CSS transition + gain ramp) - Cursor auto-hides after 3s of inactivity, restores on mouse move Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replaced close button with "By Julian Harris | Based on Radiant" links - Attribution fades out when cursor auto-hides after 3s inactivity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixed-position children of a fixed parent don't escape stacking context. Moved attribution + key-guide to top level, z-index 10003. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
svelte:head hides all <nav> elements to suppress gallery nav. Changed attribution from <nav> to <div>. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mobile: credits show 15s then fade; sound hint pulses in centre for 15min; double-tap toggles audio with "Sound on/off" feedback (5s fade) - Kaleido: suppressed for first 60s, faster drift speed to cycle out sooner - Audio LFO: half speed (25s cycle), square-wave shape (3rd+5th harmonics) so filter lingers at 20kHz for ~half the cycle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Native macOS screensaver (.saver bundle) using Metal. Foundation layer: - Simplex noise, FBM, dual domain warping, ridged noise - Gaussian/metaball orbs, wave interference, fabric fold - Voronoi cracks, two-light Blinn-Phong + Fresnel lighting - 4-color palette, ACES tonemap, grain, vignette, hue rotation - 4 presets: flowing-warp, orb-field, silk-folds, ocean-waves - Power-8 winner-take-all blending with incommensurate drift - Swift + Metal, universal binary (arm64/x86_64), make build/install Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Metal shader gains: kaleido_fold, spiral_field, moire_field, burn frontier (phase/threshold/mask/edge). All gated with smoothstep. Swift view expanded to 9 presets with kaleido startup ramp. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chladni field: 6 mode pairs with smooth interpolation. Chromatic aberration: radial RGB split post-tonemap. 10th preset: chladni-resonance. Final preset count reached. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spatially-varying hue rotation creates organic colour islands that drift across the frame. Monotone for half the cycle, colourful for the other half (sin wave * 0.9 strength). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gate voronoi, fold, chladni, spiral, moire, burn behind uniform threshold checks (warp-coherent, zero divergence cost). Cache wave_field result to eliminate duplicate evaluation. Add README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace ScreenSaverView timer with CVDisplayLink for vsync'd rendering - Render at half resolution (RESOLUTION_SCALE=0.5), CAMetalLayer upscales - Timestamp in CFBundleName for cache-busting during development Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Native Metal screensaver framework for cycling through all 87 shaders: - CVDisplayLink for vsync'd rendering, half-res CAMetalLayer - Dual offscreen textures for A/B compositing - Noise dissolve transition shader (ports zoom route's GLSL) - Cycling state machine: 12s dwell + 3s morph, shuffled order - Zoom drift during dwell (1.0→1.35), hue cycling (300s) - ShaderRegistry for adding shaders incrementally - Multi-file metallib build (Common.metal shared via #include) - fluid-amber as first ported shader (proof of concept) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Batch GLSL→MSL translation of fragment shaders: burning-film, silk-cascade, bioluminescence, chromatic-bloom, vortex, chladni-resonance, moire-interference, golden-throne, kaleidoscope-runway, neon-drip, eclipse-glow, aurora-veil, moonlit-ripple, diamond-caustics, smolder, stardust-veil, shifting-veils, painted-strata, liquid-gold All 20 shaders cycle with 12s dwell + 3s noise dissolve transitions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All WebGL fragment shaders now ported to Metal: artpop-iridescence, aurora-curtain, bass-ripple, crystal-lattice, dither-gradient, edge-of-chaos, event-horizon, feedback-loop, gilded-fracture, gilt-mosaic, gilt-thread, gothic-filigree, hologram-glitch, ink-dissolve, laser-labyrinth, lens-whisper, lipstick-smear, magma-core, magnetic-field, metamorphosis, neon-drive, neon-revival, polaroid-burn, radiant-geometry, sacred-strange, scream-wave, sequin-wave, shattered-plains, signal-decay, silk-groove, strobe-geometry, sugar-glass, thunder-sermon, torn-paper, tropical-heat, vertigo, vinyl-grooves, voltage-arc, rain-on-glass, rain-umbrella 60 shaders cycling with noise dissolve transitions at 60fps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 87 shaders now ported. Canvas 2D effects reimagined as fragment shaders: particle systems via analytical per-pixel evaluation, trail effects via noise approximation, geometric drawing via SDF. analog-drift, champagne-fizz, clockwork-mind, digital-rain, flow-field, generative-tree, glitter-storm, ink-calligraphy, jazz-chaos, kinetic-grid, laser-precision, luminous-silt, magnetic-sand, murmuration, pendulum-wave, phase-transition, phyllotaxis, resonant-strings, rubber-reality, spark-chamber, strange-attractor, synth-ribbon, tesseract-shadow, topographic, velvet-spotlight, vintage-static, woven-radiance 87 shaders cycling with noise dissolve transitions at 60fps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Swap textureA/B pointers when morph completes so dwell starts with the same pixels that were fully revealed, eliminating the 1-frame re-render discontinuity - Remove WebGL-era Y-flip from transition shader (Metal is top-down) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All render passes (shader A, shader B, composite) now encoded into one MTLCommandBuffer. Eliminates two waitUntilCompleted() CPU stalls per frame during morph transitions. 60fps maintained throughout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
During dwell phase, render shader in a single pass directly to the drawable — no offscreen texture, no composite. Only use the 3-pass offscreen approach during the 3-second morph transitions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CAMetalLayer defaults to .bottomLeft gravity, so half-res drawables sit in the corner instead of stretching. Explicit .resize ensures the smaller drawable fills the full view on all displays. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cross-fade uses setBlendColor alpha with src*blendAlpha + dst*one blend mode. During morph: draw outgoing at alpha=(1-progress), then incoming at alpha=progress, both directly to drawable. No offscreen textures, no composite shader. 60fps maintained during transitions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incoming shader during morph is now the same as current during the subsequent dwell. No shader identity change at the boundary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Individual shaders have their own speed constants (TIME_SCALE etc). The morph-webgpu quarter-speed divisor was making them 4x too slow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add docs/ and music/ to .gitignore (local planning files, source MP3) - Delete screensaver-webgpu-poc/ (superseded by morph-screensaver/) - Add morph-webgpu README documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Click/drag (desktop) or touch/drag (mobile) wipes raindrops off glass - Replace independent drift signals with sequential A→B crossfade - Increase blur canvas from 1/8 to 1/2 scale for sharper frosted glass - Decouple mouse from morph shader (no more swirl on click) - Preserve double-tap audio toggle on mobile Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep rain-overlay branch versions: colour_var_str (not curtain), UI elements (attribution, key-guide, sound hint), rain canvas styles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts keeping rain-overlay's sequential crossfade (not exponential smoothing), colour_var_str shader (not curtain), and all rain overlay UI elements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The struct members were already replaced with colour_var_str in the merge resolution, but the function body and usage were left behind, causing WGSL parse errors that poisoned the entire WebGPU pipeline. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
A native macOS screensaver that cycles through all 87 Radiant shaders, ported to Metal Shading Language. No WKWebView — pure GPU rendering via CAMetalLayer.
Build & test
Requires Xcode Command Line Tools. Metal Toolchain recommended for shader precompilation (
xcodebuild -downloadComponent MetalToolchain), falls back to runtime compilation.Architecture
Disclosure: AI-assisted development
This code was generated with Claude Code (Anthropic), then iteratively tested, debugged, and refined over multiple sessions. Specific areas of human quality review:
contentsGravity = .resize)mod()vsfmod()sign differences,constexprvsconstantaddress space, variable name collisions with Metal builtinsThe AI performed the bulk mechanical GLSL→MSL translation (87 shaders), while the human directed architecture, tested every build on real hardware, and caught issues the AI missed.
Test plan
make buildsucceedsmake installplaces .saver in ~/Library/Screen Savers/🤖 Generated with Claude Code