# glslop rendering: guarantees, diagnostics and workarounds This focused reference describes the current canonical WebGL2 renderer, not universal ISF-host support. Authoring workflow: https://glslop.com/authoring.txt. Agent doc index: https://glslop.com/llms.txt (everything in one file: https://glslop.com/llms-full.txt). ## Renderer contract - ISF bodies use the familiar GLSL ES 1.00 syntax in a WebGL2 context. Declared INPUT names become uniforms; don't reuse those names for local GLSL variables. - Every pass target has the full render dimensions. `PASSES.WIDTH` and `HEIGHT` are accepted but **ignored**. Diagnostics flag their presence. Don't rely on a small noise atlas allocating less memory. - Normal targets are RGBA8. `FLOAT: true` requests RGBA16F when EXT_color_buffer_float is available; otherwise the runtime falls back to RGBA8, which can change simulation precision. Diagnostics report the effective format and whether this fallback happened. - RGBA8 and RGBA16F default to LINEAR filtering. RGBA16F linear sampling is core WebGL2 functionality; it must not depend on the WebGL1-only OES_texture_half_float_linear extension. The previous check incorrectly chose NEAREST on tested WebGL2 browsers. - A pass may declare `"FILTER": "NEAREST"` or `"FILTER": "LINEAR"`. **FILTER is a glslop extension**, not a promise other ISF hosts will honor it. Conflicting filters on the same named target are rejected. Use NEAREST for algorithms that need exact cells; choose LINEAR deliberately for interpolated fields. - Persistent targets ping-pong across draws and initialize to transparent black. Resize or deterministic seek reallocates/clears that history. Live clock reset alone does not erase buffers. Changing the old accidental FLOAT/NEAREST behavior can affect existing work that samples between texel centers. Check representative simulations visually; explicit NEAREST preserves cell sampling. This change does not regenerate stored posters/clips or rewrite submitted source. ## Deterministic capture is not a simulation timeline For single-pass and nonpersistent multipass shaders, a still is one draw at the requested TIME. For any persistent target, the poster path starts with clean buffers and performs **exactly 120 draws**. TIME ramps from span/120 to span, where span is the requested positive time (or 2 seconds at time 0). Increasing `--time` does not increase the number of simulation steps. The current poster implementation supplies TIMEDELTA=1/60 and FRAMEINDEX=floor(TIME*60), not the loop counter. Earlier prose claimed TIMEDELTA=span/120; that was inaccurate. Video-export drivers may explicitly override these uniforms. Do not assume the first draw has TIME=0 or FRAMEINDEX=0. The workbench's seek and captures use this same algorithm, clearing previous history before each capture. Live playback is exploratory history; capture is deliberately reset/replayed. Pixel identity also depends on source, inputs, resolution, textures, browser/driver, and any use of the DATE uniform. Sharing an engine is not a universal cross-device byte-identity guarantee. ## Stepped timeline capture `render --frames` (and `capture: { fps, frames }` on `/render` and `/render/bridge`) is the simulation timeline the poster is not. History is cleared, then the shader is stepped from frame 0 on the same timeline the video exporter uses: frame *i* is drawn at TIME=i/fps with TIMEDELTA=1/fps and FRAMEINDEX=i, so a persistent target advances exactly once per frame. The listed frames are returned in ascending order, each as a PNG with its average hash; the last of them doubles as the still. A shader with no persistent target carries no state between frames, so only the listed frames are drawn. DATE is pinned to 2000-01-01 00:00:00 for the whole capture. Limits: 60 fps, frame indices to 18000, 120 frames per call. The exporter and the capture share one implementation of this timeline, so a captured frame is the frame the downloaded clip would contain. ## Persistent cache initialization example (workaround, not a new host guarantee) Use a cleared-channel sentinel when a generated cache must initialize even if its first TIME is 6: ```glsl /*{ "PASSES": [ {"TARGET":"cache", "PERSISTENT":true, "FLOAT":true, "FILTER":"NEAREST"}, {} ] }*/ void main() { if (PASSINDEX == 0) { vec4 old = IMG_THIS_PIXEL(cache); if (old.a < 0.5) { vec2 cell = floor(gl_FragCoord.xy); float n = fract(sin(dot(cell, vec2(127.1,311.7))) * 43758.5453); gl_FragColor = vec4(n, n, n, 1.0); } else gl_FragColor = old; } else { gl_FragColor = IMG_THIS_PIXEL(cache); } } ``` This is a small initialization example, not a high-quality noise function or a complete 3D atlas. The sentinel must be a channel your algorithm reserves; if later operations overwrite it, initialization will run again. A cache dependent on input settings must also invalidate itself when those settings change. For packed noise atlases, filter choice alone cannot prevent bleeding across adjacent tiles. Pad tile borders or explicitly interpolate neighboring lattice samples in GLSL. Nearest vs linear and RGBA8 vs RGBA16F are separate choices; test derivatives/materials, not just broad image similarity. ## Performance measurements `compile_ms` is observed wall time for compile/link readiness, separate from draw cost. It may include polling overhead and cache effects. A fresh browser process is not proof the driver shader cache is cold. Workbench benchmarking and CLI `render --benchmark` warm three draws, then collects twelve draw + synchronized 1-pixel readback wall-time samples at the selected size/time/inputs. Readback provides a synchronization point missing from CPU command-submission timings. The result includes median/p95 and the method label. It is **not GPU-only time**, and 1000/median is not a general FPS prediction. A future GPU-only measurement needs EXT_disjoint_timer_query_webgl2 and must discard disjoint/invalid samples; this tool does not claim that. Worker rendering protects page JavaScript, not the browser-wide GPU process. In particular, terminating a native compiler in Chrome can stall animation frames even while JS timers remain responsive. The workbench serializes operations and does not terminate workers on file saves. Closing the page still ends its worker; no claim of driver cancellation or universal freeze immunity is made. ## Artistic advice (not renderer constraints) Inspect full frames, crops, motion and matched A/B captures. Compile success and a good benchmark do not establish visual polish. Material response, raymarch bounds, camera placement and scale cues can be wrong in a technically successful image. Ask a human to review before calling an ambitious work done.