shadertoy-cli
Agent-friendly ShaderToy project, rendering, debugging, and live-preview CLI.
The installed binary is named shadertoy.
The CLI embeds its project templates, JSON Schema, agent documentation, preview web UI, and Camoufox import helper, so the installed executable does not need adjacent data files. Built-in topics are available through shadertoy docs for agent, project, import, manifest, passes, glsl, assets, buffers, channels, state, sweep, blind, experiment, profile, test, trace, graph, and preview guidance. Use shadertoy docs glsl for the generated prelude/entry-point contract and shadertoy docs assets for cubemap and volume file layouts.
Import a public ShaderToy into a fully local editable project:
shadertoy import https://www.shadertoy.com/view/lsX3W4 -o mandelbrot
cd mandelbrot
shadertoy check
Import uses a Camoufox browser session to handle ShaderToy's browser/Cloudflare path. On first use it creates a private cached Python environment, installs the pinned Camoufox adapter, and fetches its browser. Python 3.10+ is required. On Linux, the Camoufox browser also needs the usual Firefox GTK runtime (for Debian/Ubuntu, libgtk-3-0 or its distro equivalent). Supported textures, cubemaps, volumes, file-backed video inputs, and Sound passes are materialized into the local project; the original ShaderToy response is retained under .shadertoy/import-response.json. Webcam inputs remain live-preview-only.
See the repository README for project format, state/debugging workflows, and the underlying C++/Rust library architecture.
The underlying C++ renderer was originally written by Yingwei Zheng (dtcxzyw/shadertoy), whose groundwork this CLI builds on.
On Linux, GL-backed CLI operations including check, render, render-frames, render-video, render-audio, inspect buffer, inspect storage, state capture, profile, test, replay, and native preview use a surfaceless EGL context and do not need DISPLAY or WAYLAND_DISPLAY. render-frames and render-video reuse one deterministic runtime across their requested timeline. Encoded video and file-backed video channels require ffmpeg/ffprobe.
Additional development tooling
GLSL sources support project-local quoted #include directives. Configure
shared include roots with [shader] include_dirs = ["shaders/lib"]; live
preview tracks the include dependency graph and recompiles only affected passes,
preserving feedback buffers when possible.
Projects can declare typed custom uniforms with [[uniform]]; defaults are applied
everywhere, deterministic rendering/state-capture/runtime-inspection commands accept
--set name=value, preview exposes matching controls, and [[test]] cases can override
them independently. Named [preset.NAME] sections keep quality tiers in the same
project root; render_scale adjusts default output size while
[preset.NAME.pass.PASS] can override fixed pass dimensions and compute
iterations/local size. check, build, preview, render, render-frames,
render-video, sweep, and profile accept --preset NAME.
Preview and profile deliberately do not count a preset's smaller final output as
an optimization. Preview holds the final Image at the base project output size
across preset switches (or at a human-selected review size), and
profile --preset NAME benchmarks at base project output size by default while
reporting the preset-requested scale separately. Internal pass reductions still
apply and remain measurable.
shadertoy sweep --set gain=0.8,1.0,1.2 renders parameter variants and a contact
sheet without temporary project copies. profile separates per-pass raw GPU execution
timestamps from CPU completion waits and the legacy post-completion attributed interval.
It marks timer samples invalid when asynchronous work clearly outruns the timer, retains
raw per-sample diagnostics, reports MAD outliers, and exposes an independent
non-intrusive frame GPU timestamp interval for cross-run comparisons.
--discard-outliers removes flagged samples from aggregates while
keeping them in JSON; --sync-per-pass retires each attribution boundary before the next
pass for maximum-isolation diagnostics. Blind sweeps anonymize parameter variants,
while blind create accepts existing images/render directories, project
directories, STTF builds, Git revisions, and project:PATH@preset=NAME sources.
blind judge records the preference and rationale before blind reveal exposes
the sealed mapping and writes a combined report. experiment turns the same
source forms into a reproducible baseline/candidate or N-way run with
deterministic frame sets, normalized RMSE, windowed SSIM, contact sheets,
provenance, project/git GPU profiles, and optional sealed blind labels.
graph renders the resolved pass/resource graph as text/JSON or Graphviz DOT.
check --pedantic additionally treats advisory graph/resource diagnostics as
failures, including unreachable passes, unused assets/uniforms, viewport-sized
feedback, and unordered shared SSBO users.
Buffer passes can opt into fixed width/height render targets for stable
simulation grids, while each iChannel can independently choose
filter = "nearest" | "linear" | "mipmap" and
wrap = "clamp" | "repeat".
For heavier pipelines, kind = "compute" adds fixed-size OpenGL 4.3 compute
dispatch, typed r32f/rg32f/rgba16f/rgba32f outputs, repeated dispatch
iterations via iIteration, writable iOutput image load/store, and persistent
named SSBOs shared across passes. Buffer/compute passes can also expose up to eight render targets with extra_outputs, and consumers select an attachment with output = N. inspect buffer --output-index N --raw ... reads any MRT attachment, while inspect storage can decode/dump a named SSBO. .ststate format 4 can optionally include SSBO bytes with state capture --include-storage. See shadertoy docs passes and
shadertoy docs channels for the exact semantics.
For automated visual/numeric validation, add [[test]] cases to
ShaderToy.toml and run shadertoy test. Visual cases compare
deterministic PNGs with an RMSE tolerance and emit actual/expected/diff artifacts
on failure; buffer cases can also assert no NaN/Inf values and finite-value mean
ranges. Tests can also expand across frames = [...] and resolutions = [[w,h], ...],
repeat each variant with assert_deterministic = true, and prove fixed GPU
simulation grids are independent of output size with
assert_resolution_independent = true. Tests can also compare
uniforms against reference_uniforms with minimum/maximum RMSE bounds,
enforce total/per-pass GPU budgets, compare exact SSBO binary fixtures, and set
assert_state_roundtrip = true to verify persistent pass/SSBO state survives
serialization into a fresh runtime. Use shadertoy test --ci for a non-mutating
automation path.
ShaderToy Sound passes (kind = "sound") are compiled by check and render to
deterministic stereo PCM WAV with shadertoy render-audio. File-backed video
assets update at deterministic iTime and expose iChannelTime; webcam channels
are available only in live preview and are intentionally rejected by headless
render/replay/recording.
Trace bundles
shadertoy trace capture -o target/bug.sttrace freezes a deterministic render
into a self-contained directory containing the STTF, final PNG, persistent
.ststate, resolved configuration, source/asset hashes, graph/synchronization
metadata, GPU pass timings, and optional per-pass PNG/RGBA32F readbacks.
trace inspect verifies artifact SHA-256 values, while trace replay renders
only from the bundled STTF and requires a bit-exact RGB match.