summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig5
-rw-r--r--docs/config.md15
-rw-r--r--docs/design.typ5
-rw-r--r--docs/macos.md21
-rw-r--r--docs/render-pipeline.md19
-rw-r--r--docs/web.md2
-rw-r--r--shaders/crt.ci.metal75
-rw-r--r--shaders/post/glitch.glsl64
-rw-r--r--shaders/post/ripple.glsl52
-rw-r--r--shaders/prebuilt/post-glitch.frag.glsl138
-rw-r--r--shaders/prebuilt/post-glitch.frag.spvbin10764 -> 0 bytes
-rw-r--r--shaders/prebuilt/post-ripple.frag.glsl126
-rw-r--r--shaders/prebuilt/post-ripple.frag.spvbin9096 -> 0 bytes
-rw-r--r--src/config.zig22
-rw-r--r--src/effect_sources.zig8
-rw-r--r--src/gui/Post.zig8
-rw-r--r--src/macos.zig17
-rw-r--r--src/macos/Sources/PardesView.swift2
-rw-r--r--src/macos/Sources/ScenePostprocessor.swift48
-rw-r--r--src/macos/pardes.h5
-rw-r--r--test/output.zig2
21 files changed, 61 insertions, 573 deletions
diff --git a/build.zig b/build.zig
index c1117593..97d51a51 100644
--- a/build.zig
+++ b/build.zig
@@ -51,10 +51,9 @@ const gui_shaders = [_][]const u8{
"overlay.vert", "overlay.frag",
"image.vert", "image.frag",
"post.vert",
- // The bundled post passes: shaders/post/<name>.glsl behind the Shadertoy
+ // The bundled post pass: shaders/post/crt.glsl behind the Shadertoy
// prefix (shaderSource).
- "post-crt.frag", "post-ripple.frag",
- "post-glitch.frag",
+ "post-crt.frag",
};
pub fn build(b: *std.Build) void {
diff --git a/docs/config.md b/docs/config.md
index 9c51f329..16fd59e5 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -513,18 +513,19 @@ immediately and remains the one authoritative geometry. DOM web intentionally
does not expose these builtins: its renderer is selectable HTML/CSS and has no
canvas or shader stage.
-The scene effects are independent switches and can be combined:
+The scene effect is `Crt`, at a level from 0 (off) to 3; `on` is 2:
```text
Crt
-Ripple
-Glitch
+Crt 3
```
-They share one full-scene shader pass in SDL and macOS. With all three off the
-pass is bypassed. CRT works in linear light with restrained scanlines, mask,
-bloom, curvature, and noise rather than remapping the theme to a strong fixed
-palette; Ripple and Glitch primarily perturb sample coordinates.
+The SDL GUI runs it as the bundled pass of its post chain, which also takes
+Shadertoy files written for ghostty (`Shader ~/crt.glsl`, `Shader off`), and
+`ShaderAnimation off|on|always` says when the chain animates on its own. With
+the chain empty the pass is bypassed. CRT works in linear light with restrained
+scanlines, mask, bloom and vignette, and no curvature, so clicks land where
+they are drawn.
`EffectCode PanelAscii` or `EffectCode Crt` lists the current backend's
build-embedded source paths under `/virtual`. Look opens each full file;
diff --git a/docs/design.typ b/docs/design.typ
index 234c3692..caa35b37 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -913,8 +913,9 @@ while its geometry moves and placed again on the last active sample.
SDL supplies old/new glyph data, diff flags, final/presented boxes, and effect
parameters to the glyph and native-image shaders. Pixel attachments bypass ASCII
because they have no character byte. macOS passes the same records across its
-plain C ABI and composites old/new panel images in Metal and Core Image. Scene
-`Crt`, `Ripple` and `Glitch` bits share one full-window pass in each native GUI.
+plain C ABI and composites old/new panel images in Metal and Core Image. The
+scene `Crt` is one full-window pass in each native GUI; the SDL GUI's post
+chain also runs Shadertoy files (`Shader`).
DOM web is a separate platform, not a shader GUI: retaining selectable HTML and
CSS is more important than duplicating the renderer in canvas, so it exposes
diff --git a/docs/macos.md b/docs/macos.md
index e104d3a6..5166e2d5 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -102,8 +102,8 @@ asking for a thin insert caret instead of a block. `Surface.images` crosses
separately as `pardes_frame_images` / `pardes_frame_image_list` — see "Pixel
attachments" below.
-`pardes_scene` is the matching full-window snapshot: a bit each for CRT,
-ripple, and glitch, plus the 60 Hz time/frame that animates them. It is returned
+`pardes_scene` is the matching full-window snapshot: a bit for CRT, plus the
+60 Hz time/frame that animates it. It is returned
by value, so the renderer never holds a pointer into live core state. A zero
flags word is also the fast-path contract: draw the CoreText frame directly.
@@ -662,8 +662,7 @@ longer leaves a dark titlebar over a cream grid.
## Scene effects
-`Crt`, `Ripple`, and `Glitch` are three switches over one postprocess, not three
-stacked filters. Their canonical macOS source is `shaders/crt.ci.metal`, which
+`Crt` is one full-window postprocess. Its canonical macOS source is `shaders/crt.ci.metal`, which
the build installs as `pardes.app/Contents/Resources/crt.ci.metal`. The Swift
shell loads that exact asset with `CIKernel.kernels(withMetalString:)` and runs
it through a `CIContext` created from the system Metal device. The source is
@@ -673,14 +672,12 @@ The ordinary CoreText/attachment/cursor pass is one function. With any scene
bit or panel track it targets a retained, backing-scale bitmap; kernels then
sample the complete frame into the flipped view. PDFs, image panes, taglines,
rules, and the caret therefore receive the same effect. With every bit off and
-no panel track the bitmap and Core Image context are bypassed entirely. Ripple
-and glitch alter sample coordinates only, preserving theme colors; CRT works
+no panel track the bitmap and Core Image context are bypassed entirely. CRT works
in linear light with restrained bloom, scan/mask, vignette, hum, and noise
instead of applying a broad color remap.
The scene kernel alone receives a `clampedToExtent` image and the final output
-is cropped back to the original finite extent. Coordinate tears and chroma
-samples therefore clamp to the edge exactly like SDL's scene sampler instead
+is cropped back to the original finite extent. Chroma samples therefore clamp to the edge exactly like SDL's scene sampler instead
of acquiring transparent-black seams from Core Image's finite source image.
The Zig side owns the clock. `pardes_animation_tick` increments its wrapped
@@ -689,8 +686,8 @@ from that integer. Input bursts cannot accelerate the shader.
Pointer input follows the same destination-to-source transform as the last
presented scene frame before it is divided by the cell metrics. The view keeps
-that exact `pardes_scene_s` snapshot and mirrors the Metal barrel, ripple and
-glitch sampling arithmetic in `ScenePostprocessor.sourcePoint`; pixels outside
+that exact `pardes_scene_s` snapshot and mirrors the Metal barrel arithmetic
+in `ScenePostprocessor.sourcePoint`; pixels outside
the CRT tube have no cell. The resulting displayed-grid cell then reaches the
core, whose panel-track mapping resolves it to canonical pane content.
@@ -716,8 +713,8 @@ while `pardesComposedInCore` effects only clip the already-composed core cells;
pixel attachments have no character value and pass through unchanged.
Because the input is the finished bitmap rather than a glyph-only
batch, backgrounds, glyphs, taglines, rules, the caret, PDF pages, and image
-panes move and dissolve together. Scene CRT/ripple/glitch runs once after the
-panel composition.
+panes move and dissolve together. The scene CRT runs once after the panel
+composition.
With no scene bit and no panel track the retained bitmap, Core Image context,
and Metal passes are bypassed. `EffectCode Panel*` links to this Metal
diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md
index 2fd6045b..7c94fbea 100644
--- a/docs/render-pipeline.md
+++ b/docs/render-pipeline.md
@@ -505,16 +505,14 @@ Mirror ghostty 1.3.2 (`zig-pkg/ghostty-*/src/renderer/shadertoy.zig`,
`custom-shader-animation`): off = redraw only when content changes; on =
continuous while the window is focused; always = continuous. Continuous means
the shell redraws its retained Surface (3.1), never a core tick or render.
-- **Existing crt/ripple/glitch** become bundled Shadertoy files in
- `shaders/post/` selected by the same builtins (decision 2: kept, polished);
- `scene_effects` and `crt.frag.glsl`/`crt.zig` are deleted. Each bundled pass
- takes a level, `Crt 0..3` (0 off, `on` the default 2, `off` and bare still
- work), passed as `pardesLevel`: 2 is the polished rewrite, 3 at least as
- strong as the old effects (pixels changed by more than 8 in a channel,
- against a plain frame: Crt 18.3% vs 17.4% old, Ripple 11.1% vs 9.5%, Glitch
- 9.3% a frame vs 8.1%); a Glitch level raises how often it slips as well as
- how hard. The chain is
- `Crt`, `Ripple`, `Glitch` and `Shader <path>` in the order they were put in
+- **Existing crt** becomes a bundled Shadertoy file in `shaders/post/` under
+ the same builtin; `scene_effects` and `crt.frag.glsl`/`crt.zig` are deleted.
+ It takes a level, `Crt 0..3` (0 off, `on` the default 2, `off` and bare
+ still work), passed as `pardesLevel`: 2 is the polished rewrite, 3 at least
+ as strong as the old CRT (pixels changed by more than 8 in a channel against
+ a plain frame: 18.3% vs 17.4%). Ripple and Glitch were rewritten too, then
+ removed at the user's word after a live look (decision 8). The chain is
+ `Crt` and `Shader <path>` in the order they were put in
(`Shader off` empties it of files), `ShaderAnimation off|on|always`
(default on). The core keeps no clock for it: level A redraws are the
shell's.
@@ -905,3 +903,4 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach.
5. The animation pacing fix goes into the no-visual-change phase.
6. Cursor blink is on by default.
7. The bundled CRT has no barrel distortion, so clicks stay exact.
+8. (After trying them live) Ripple and Glitch are removed entirely; Crt stays.
diff --git a/docs/web.md b/docs/web.md
index 3a68f958..bbbed8a3 100644
--- a/docs/web.md
+++ b/docs/web.md
@@ -42,7 +42,7 @@ shells, and two comptime capability flags say so once each. It exposes no
`PanelAscii`, `PanelVertical`, `PanelEdges`, `PanelFall`, `PanelWave`,
`PanelCurtain`, `PanelScramble`, `PanelType`) because
`capabilities.panel_transitions` is `pardes.hosted`, and no
-`Crt`/`Ripple`/`Glitch` because `capabilities.scene_shaders` is
+`Crt` or `Shader` because `capabilities.scene_shaders` is
`platform == .gui or platform == .macos` (`builtins.capabilities`; the words
themselves carry those availabilities in `config.Runtime.settings`).
Applying either faithfully would require a second canvas renderer and give up
diff --git a/shaders/crt.ci.metal b/shaders/crt.ci.metal
index c7e91a7e..9159af18 100644
--- a/shaders/crt.ci.metal
+++ b/shaders/crt.ci.metal
@@ -1,7 +1,6 @@
// Native macOS panel and scene postprocess. Panel kernels consume the core's
-// plain tracks; the final full-window kernel ports crt.frag.glsl and combines
-// CRT, ripple and glitch. Scene constants and sampling geometry stay in step
-// with GLSL, while panel easing/noise stays in step with panel_animation.zig.
+// plain tracks; the final full-window kernel is the CRT. Panel easing/noise
+// stays in step with panel_animation.zig.
//
// The three entry points are `extern "C" [[stitchable]]`. Both halves are
// load-bearing: this source is compiled at runtime by
@@ -38,24 +37,6 @@ float pardesCellNoise(float2 serialParts, float colValue, float rowValue) {
return float(x & 0xffffu) / 65535.0;
}
-float2 pardesSceneUV(float2 uv, float timeSeconds, float rippleOn, float glitchOn) {
- if (rippleOn > 0.5) {
- float2 centered = uv - 0.5;
- float radius = length(centered);
- // Reversed smoothstep edges are undefined in GLSL and Metal alike.
- float envelope = 1.0 - smoothstep(0.04, 0.72, radius);
- float wave = sin(radius * 42.0 - timeSeconds * 5.2);
- uv += centered / max(radius, 0.00001) * wave * envelope * 0.0032;
- }
- if (glitchOn > 0.5) {
- float band = floor(uv.y * 96.0);
- float seed = pardesNoise(float2(band, floor(timeSeconds * 13.0)));
- float tear = smoothstep(0.955, 1.0, seed);
- uv.x += (pardesNoise(float2(seed, band)) - 0.5) * tear * 0.020;
- }
- return uv;
-}
-
float4 pardesSceneSample(
coreimage::sampler source,
float2 origin,
@@ -66,42 +47,13 @@ float4 pardesSceneSample(
source.sample(source.transform(origin + uv * size)));
}
-// Keep this ordering identical to crt.frag.glsl's sceneSrgb: every caller
-// supplies its own pre-warp coordinate, then ripple/glitch move that sample,
-// then glitch splits its red/blue channels around the moved center.
-float4 pardesSceneSrgb(
- coreimage::sampler source,
- float2 origin,
- float2 size,
- float2 uv,
- float timeSeconds,
- float rippleOn,
- float glitchOn
-) {
- float2 warped = pardesSceneUV(uv, timeSeconds, rippleOn, glitchOn);
- float4 center = pardesSceneSample(source, origin, size, warped);
- if (glitchOn <= 0.5) return center;
-
- float band = floor(warped.y * 96.0);
- float seed = pardesNoise(float2(band, floor(timeSeconds * 13.0)));
- float tear = smoothstep(0.955, 1.0, seed);
- float2 chroma = float2((0.35 + 1.25 * tear) / size.x, 0.0);
- float4 red = pardesSceneSample(source, origin, size, warped + chroma);
- float4 blue = pardesSceneSample(source, origin, size, warped - chroma);
- return float4(red.r, center.g, blue.b, center.a);
-}
-
float3 pardesSceneLinear(
coreimage::sampler source,
float2 origin,
float2 size,
- float2 uv,
- float timeSeconds,
- float rippleOn,
- float glitchOn
+ float2 uv
) {
- return coreimage::srgb_to_linear(pardesSceneSrgb(
- source, origin, size, uv, timeSeconds, rippleOn, glitchOn).rgb);
+ return coreimage::srgb_to_linear(pardesSceneSample(source, origin, size, uv).rgb);
}
bool pardesInside(float2 p, float4 box) {
@@ -242,8 +194,6 @@ extern "C" [[stitchable]] float4 pardesScene(
float timeSeconds,
float frame,
float crtOn,
- float rippleOn,
- float glitchOn,
float2 sceneSize,
float2 sceneOrigin,
coreimage::destination destination
@@ -277,24 +227,18 @@ extern "C" [[stitchable]] float4 pardesScene(
// transparent theme.
if (crtOn > 0.5 && edge <= 0.0) return float4(0.0, 0.0, 0.0, 1.0);
- float4 center = pardesSceneSrgb(
- source, origin, size, uv, timeSeconds, rippleOn, glitchOn);
+ float4 center = pardesSceneSample(source, origin, size, uv);
float3 scene = center.rgb;
- // Ripple/glitch alone are coordinate effects. Returning the sampled sRGB
- // values directly keeps exact theme colors exact.
+ // Without the CRT the scene passes through, exact.
if (crtOn <= 0.5) {
return coreimage::premultiply(float4(clamp(scene, 0.0, 1.0), center.a));
}
float3 centerLinear = coreimage::srgb_to_linear(scene);
float2 radialChroma = cc * (chromaPx * r2) * texel;
- float3 redSample = pardesSceneLinear(
- source, origin, size, uv + radialChroma,
- timeSeconds, rippleOn, glitchOn);
- float3 blueSample = pardesSceneLinear(
- source, origin, size, uv - radialChroma,
- timeSeconds, rippleOn, glitchOn);
+ float3 redSample = pardesSceneLinear(source, origin, size, uv + radialChroma);
+ float3 blueSample = pardesSceneLinear(source, origin, size, uv - radialChroma);
float3 col = float3(
redSample.r,
centerLinear.g,
@@ -308,8 +252,7 @@ extern "C" [[stitchable]] float4 pardesScene(
float2(0.0, bloom.y), float2(0.0, -bloom.y)
};
for (uint i = 0; i < 4; i++) {
- // Deliberately raw, like GLSL brightPass: bloom belongs to the source
- // image and does not recursively warp through the scene effects.
+ // Deliberately raw, like GLSL brightPass.
float4 sampleValue = pardesSceneSample(
source, origin, size, uv + bloomOffsets[i]);
float3 linearValue = coreimage::srgb_to_linear(sampleValue.rgb);
diff --git a/shaders/post/glitch.glsl b/shaders/post/glitch.glsl
deleted file mode 100644
index 8f420d9c..00000000
--- a/shaders/post/glitch.glsl
+++ /dev/null
@@ -1,64 +0,0 @@
-// Glitch: now and then the signal slips. At the default level, every couple
-// of seconds a short
-// burst tears a few horizontal bands sideways and pulls their red and blue
-// apart; the bands are sized in pixels and the burst eases in and out over
-// a fraction of a second, so it reads as one event rather than a flicker,
-// and it looks the same at 60 or 144 Hz because every step is keyed to
-// iTime, not to frames. Between bursts the page is left exactly as it is.
-// A torn band's brightness dips in linear light. It is a Shadertoy file:
-// pardes puts its prefix before it.
-
-// Each at levels 1, 2 and 3 (`Glitch 1`..`Glitch 3`); 2 is the default. A
-// higher level slips more often and harder.
-float period() { return pardesByLevel(4.5, 2.4, 0.55); } // seconds between bursts, on average
-float burst() { return pardesByLevel(0.2, 0.28, 0.5); } // seconds a burst lasts
-float stepHz() { return pardesByLevel(20.0, 24.0, 30.0); } // how often a burst re-tears
-float shiftPx() { return pardesByLevel(8.0, 14.0, 34.0); }
-float splitPx() { return pardesByLevel(1.5, 2.5, 6.0); }
-float dip() { return pardesByLevel(0.08, 0.12, 0.22); }
-float tearFrom() { return pardesByLevel(0.8, 0.72, 0.35); } // how few bands tear
-
-float hash(vec2 p) {
- vec3 q = fract(vec3(p.xyx) * 0.1031);
- q += dot(q, q.yzx + 33.33);
- return fract((q.x + q.y) * q.z);
-}
-
-vec3 toLinear(vec3 c) {
- return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045)));
-}
-
-vec3 toSrgb(vec3 c) {
- c = max(c, vec3(0.0));
- return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308)));
-}
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord) {
- vec2 size = iResolution.xy;
- vec2 uv = fragCoord / size;
- // Which burst window this is, when in it the burst starts, and how far
- // into the burst we are: 0 outside one.
- float window = floor(iTime / period());
- float start = window * period() + hash(vec2(window, 7.0)) * (period() - burst());
- float t = (iTime - start) / burst();
- float live = step(0.0, t) * step(t, 1.0);
- float envelope = live * sin(3.14159265 * clamp(t, 0.0, 1.0));
- if (envelope <= 0.0) {
- fragColor = texture(iChannel0, uv);
- return;
- }
- // Bands of 6 to 40 px, re-cut at stepHz() during the burst; about one
- // band in four tears.
- float tick = floor(iTime * stepHz());
- float band_px = mix(6.0, 40.0, hash(vec2(tick, 3.0)));
- float band = floor(fragCoord.y / band_px);
- float roll = hash(vec2(band, tick));
- float tear = smoothstep(tearFrom(), tearFrom() + 0.18, roll) * envelope;
- float shift = (hash(vec2(tick, band + 11.0)) - 0.5) * 2.0 * shiftPx() * tear;
- vec2 at = vec2(fragCoord.x + shift, fragCoord.y);
- vec2 split = vec2(splitPx() * tear, 0.0);
- vec4 center = texture(iChannel0, at / size);
- vec3 col = vec3(texture(iChannel0, (at + split) / size).r, center.g, texture(iChannel0, (at - split) / size).b);
- col = toSrgb(toLinear(col) * (1.0 - dip() * tear));
- fragColor = vec4(col, center.a);
-}
diff --git a/shaders/post/ripple.glsl b/shaders/post/ripple.glsl
deleted file mode 100644
index 3236d339..00000000
--- a/shaders/post/ripple.glsl
+++ /dev/null
@@ -1,52 +0,0 @@
-// Ripple: the page as a still pool a slow swell crosses. Rings travel out
-// from the middle of the window at a fixed speed in pixels, so they look
-// the same at any size and frame rate; at the default level the displacement
-// is at most a couple of pixels, so text stays readable, and it eases in over its first half
-// second instead of snapping on. Each ring catches the light a little on its
-// rising side, in linear light, and the result is dithered so the shading
-// never bands. It is a Shadertoy file: pardes puts its prefix before it.
-
-const float TAU = 6.28318530718;
-const float EASE_IN = 0.5; // seconds
-
-// Each at levels 1, 2 and 3 (`Ripple 1`..`Ripple 3`); 2 is the default.
-float wavelengthPx() { return pardesByLevel(170.0, 150.0, 120.0); }
-float speedPx() { return pardesByLevel(40.0, 55.0, 80.0); } // outward, per second
-float amplitudePx() { return pardesByLevel(1.2, 2.2, 5.0); }
-float shade() { return pardesByLevel(0.02, 0.035, 0.09); } // light on a slope, linear
-float reachEnd() { return pardesByLevel(0.85, 0.95, 1.3); } // how far out it dies away
-
-vec3 toLinear(vec3 c) {
- return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045)));
-}
-
-vec3 toSrgb(vec3 c) {
- c = max(c, vec3(0.0));
- return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308)));
-}
-
-float ign(vec2 p) {
- return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715))));
-}
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord) {
- vec2 size = iResolution.xy;
- vec2 center = 0.5 * size;
- vec2 offset = fragCoord - center;
- float radius = length(offset);
- float reach = 0.5 * length(size);
- // Strongest a third of the way out, fading to nothing at the middle
- // and at the edges, so the window's frame never wobbles.
- float envelope = smoothstep(0.0, 0.18, radius / reach) * (1.0 - smoothstep(0.35, reachEnd(), radius / reach));
- float ease = smoothstep(0.0, EASE_IN, iTime);
- float phase = (radius - iTime * speedPx()) * TAU / wavelengthPx();
- float height = sin(phase);
- float slope = cos(phase);
- vec2 dir = offset / max(radius, 1e-3);
- vec2 moved = fragCoord + dir * height * amplitudePx() * envelope * ease;
-
- vec4 s = texture(iChannel0, moved / size);
- vec3 col = toLinear(s.rgb) * (1.0 + shade() * slope * envelope * ease);
- vec3 srgb = toSrgb(col) + (ign(fragCoord + float(iFrame % 64) * 5.588238) - 0.5) / 255.0;
- fragColor = vec4(clamp(srgb, 0.0, 1.0), s.a);
-}
diff --git a/shaders/prebuilt/post-glitch.frag.glsl b/shaders/prebuilt/post-glitch.frag.glsl
deleted file mode 100644
index 7b288237..00000000
--- a/shaders/prebuilt/post-glitch.frag.glsl
+++ /dev/null
@@ -1,138 +0,0 @@
-#version 450
-// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every
-// post pass: a bundled one in shaders/post/ at build time, a user's file at
-// run time. The uniform names, types and std140 order are ghostty 1.3.2's
-// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for
-// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's
-// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as
-// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor
-// rectangles share one top-left space, and iCurrentCursor is (left x,
-// BOTTOM-edge y, width, height) in framebuffer pixels.
-
-layout(set = 3, binding = 0, std140) uniform Globals {
- uniform vec3 iResolution;
- uniform float iTime;
- uniform float iTimeDelta;
- uniform float iFrameRate;
- uniform int iFrame;
- uniform float iChannelTime[4];
- uniform vec3 iChannelResolution[4];
- uniform vec4 iMouse;
- uniform vec4 iDate;
- uniform float iSampleRate;
- uniform vec4 iCurrentCursor;
- uniform vec4 iPreviousCursor;
- uniform vec4 iCurrentCursorColor;
- uniform vec4 iPreviousCursorColor;
- uniform int iCurrentCursorStyle;
- uniform int iPreviousCursorStyle;
- uniform int iCursorVisible;
- uniform float iTimeCursorChange;
- uniform float iTimeFocus;
- uniform int iFocus;
- uniform vec3 iPalette[256];
- uniform vec3 iBackgroundColor;
- uniform vec3 iForegroundColor;
- uniform vec3 iCursorColor;
- uniform vec3 iCursorText;
- uniform vec3 iSelectionForegroundColor;
- uniform vec3 iSelectionBackgroundColor;
-};
-
-// pardes's own: pardesOpaque is 1 when the window is opaque, so a pass
-// cannot punch holes in it through its alpha; pardesLevel is a bundled
-// pass's strength, 1 to 3 (`Crt 3`), and 0 for a user's file.
-layout(set = 3, binding = 1, std140) uniform PardesPost {
- float pardesOpaque;
- float pardesLevel;
-};
-
-// A bundled pass's constant at levels 1, 2 and 3.
-float pardesByLevel(float one, float two, float three) {
- return pardesLevel < 1.5 ? one : (pardesLevel < 2.5 ? two : three);
-}
-
-#define CURSORSTYLE_BLOCK 0
-#define CURSORSTYLE_BLOCK_HOLLOW 1
-#define CURSORSTYLE_BAR 2
-#define CURSORSTYLE_UNDERLINE 3
-#define CURSORSTYLE_LOCK 4
-
-layout(set = 2, binding = 0) uniform sampler2D iChannel0;
-
-layout(location = 0) out vec4 _fragColor;
-
-#define texture2D texture
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord);
-void main() {
- mainImage(_fragColor, gl_FragCoord.xy);
- if (pardesOpaque > 0.5) _fragColor.a = 1.0;
-}
-
-// The shader's own lines count from 1, so glslc's errors name them.
-#line 1
-// Glitch: now and then the signal slips. At the default level, every couple
-// of seconds a short
-// burst tears a few horizontal bands sideways and pulls their red and blue
-// apart; the bands are sized in pixels and the burst eases in and out over
-// a fraction of a second, so it reads as one event rather than a flicker,
-// and it looks the same at 60 or 144 Hz because every step is keyed to
-// iTime, not to frames. Between bursts the page is left exactly as it is.
-// A torn band's brightness dips in linear light. It is a Shadertoy file:
-// pardes puts its prefix before it.
-
-// Each at levels 1, 2 and 3 (`Glitch 1`..`Glitch 3`); 2 is the default. A
-// higher level slips more often and harder.
-float period() { return pardesByLevel(4.5, 2.4, 0.55); } // seconds between bursts, on average
-float burst() { return pardesByLevel(0.2, 0.28, 0.5); } // seconds a burst lasts
-float stepHz() { return pardesByLevel(20.0, 24.0, 30.0); } // how often a burst re-tears
-float shiftPx() { return pardesByLevel(8.0, 14.0, 34.0); }
-float splitPx() { return pardesByLevel(1.5, 2.5, 6.0); }
-float dip() { return pardesByLevel(0.08, 0.12, 0.22); }
-float tearFrom() { return pardesByLevel(0.8, 0.72, 0.35); } // how few bands tear
-
-float hash(vec2 p) {
- vec3 q = fract(vec3(p.xyx) * 0.1031);
- q += dot(q, q.yzx + 33.33);
- return fract((q.x + q.y) * q.z);
-}
-
-vec3 toLinear(vec3 c) {
- return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045)));
-}
-
-vec3 toSrgb(vec3 c) {
- c = max(c, vec3(0.0));
- return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308)));
-}
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord) {
- vec2 size = iResolution.xy;
- vec2 uv = fragCoord / size;
- // Which burst window this is, when in it the burst starts, and how far
- // into the burst we are: 0 outside one.
- float window = floor(iTime / period());
- float start = window * period() + hash(vec2(window, 7.0)) * (period() - burst());
- float t = (iTime - start) / burst();
- float live = step(0.0, t) * step(t, 1.0);
- float envelope = live * sin(3.14159265 * clamp(t, 0.0, 1.0));
- if (envelope <= 0.0) {
- fragColor = texture(iChannel0, uv);
- return;
- }
- // Bands of 6 to 40 px, re-cut at stepHz() during the burst; about one
- // band in four tears.
- float tick = floor(iTime * stepHz());
- float band_px = mix(6.0, 40.0, hash(vec2(tick, 3.0)));
- float band = floor(fragCoord.y / band_px);
- float roll = hash(vec2(band, tick));
- float tear = smoothstep(tearFrom(), tearFrom() + 0.18, roll) * envelope;
- float shift = (hash(vec2(tick, band + 11.0)) - 0.5) * 2.0 * shiftPx() * tear;
- vec2 at = vec2(fragCoord.x + shift, fragCoord.y);
- vec2 split = vec2(splitPx() * tear, 0.0);
- vec4 center = texture(iChannel0, at / size);
- vec3 col = vec3(texture(iChannel0, (at + split) / size).r, center.g, texture(iChannel0, (at - split) / size).b);
- col = toSrgb(toLinear(col) * (1.0 - dip() * tear));
- fragColor = vec4(col, center.a);
-}
diff --git a/shaders/prebuilt/post-glitch.frag.spv b/shaders/prebuilt/post-glitch.frag.spv
deleted file mode 100644
index 619616a8..00000000
--- a/shaders/prebuilt/post-glitch.frag.spv
+++ /dev/null
Binary files differ
diff --git a/shaders/prebuilt/post-ripple.frag.glsl b/shaders/prebuilt/post-ripple.frag.glsl
deleted file mode 100644
index d29a4c22..00000000
--- a/shaders/prebuilt/post-ripple.frag.glsl
+++ /dev/null
@@ -1,126 +0,0 @@
-#version 450
-// pardes's Shadertoy prefix (docs/render-pipeline.md §6), put before every
-// post pass: a bundled one in shaders/post/ at build time, a user's file at
-// run time. The uniform names, types and std140 order are ghostty 1.3.2's
-// (src/renderer/shaders/shadertoy_prefix.glsl), so a shader written for
-// ghostty runs here; only the bindings are SDL GPU's (a fragment shader's
-// textures in set 2, its uniform blocks in set 3). Y is down everywhere, as
-// on ghostty's Metal renderer: fragCoord, iChannel0's uv and the cursor
-// rectangles share one top-left space, and iCurrentCursor is (left x,
-// BOTTOM-edge y, width, height) in framebuffer pixels.
-
-layout(set = 3, binding = 0, std140) uniform Globals {
- uniform vec3 iResolution;
- uniform float iTime;
- uniform float iTimeDelta;
- uniform float iFrameRate;
- uniform int iFrame;
- uniform float iChannelTime[4];
- uniform vec3 iChannelResolution[4];
- uniform vec4 iMouse;
- uniform vec4 iDate;
- uniform float iSampleRate;
- uniform vec4 iCurrentCursor;
- uniform vec4 iPreviousCursor;
- uniform vec4 iCurrentCursorColor;
- uniform vec4 iPreviousCursorColor;
- uniform int iCurrentCursorStyle;
- uniform int iPreviousCursorStyle;
- uniform int iCursorVisible;
- uniform float iTimeCursorChange;
- uniform float iTimeFocus;
- uniform int iFocus;
- uniform vec3 iPalette[256];
- uniform vec3 iBackgroundColor;
- uniform vec3 iForegroundColor;
- uniform vec3 iCursorColor;
- uniform vec3 iCursorText;
- uniform vec3 iSelectionForegroundColor;
- uniform vec3 iSelectionBackgroundColor;
-};
-
-// pardes's own: pardesOpaque is 1 when the window is opaque, so a pass
-// cannot punch holes in it through its alpha; pardesLevel is a bundled
-// pass's strength, 1 to 3 (`Crt 3`), and 0 for a user's file.
-layout(set = 3, binding = 1, std140) uniform PardesPost {
- float pardesOpaque;
- float pardesLevel;
-};
-
-// A bundled pass's constant at levels 1, 2 and 3.
-float pardesByLevel(float one, float two, float three) {
- return pardesLevel < 1.5 ? one : (pardesLevel < 2.5 ? two : three);
-}
-
-#define CURSORSTYLE_BLOCK 0
-#define CURSORSTYLE_BLOCK_HOLLOW 1
-#define CURSORSTYLE_BAR 2
-#define CURSORSTYLE_UNDERLINE 3
-#define CURSORSTYLE_LOCK 4
-
-layout(set = 2, binding = 0) uniform sampler2D iChannel0;
-
-layout(location = 0) out vec4 _fragColor;
-
-#define texture2D texture
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord);
-void main() {
- mainImage(_fragColor, gl_FragCoord.xy);
- if (pardesOpaque > 0.5) _fragColor.a = 1.0;
-}
-
-// The shader's own lines count from 1, so glslc's errors name them.
-#line 1
-// Ripple: the page as a still pool a slow swell crosses. Rings travel out
-// from the middle of the window at a fixed speed in pixels, so they look
-// the same at any size and frame rate; at the default level the displacement
-// is at most a couple of pixels, so text stays readable, and it eases in over its first half
-// second instead of snapping on. Each ring catches the light a little on its
-// rising side, in linear light, and the result is dithered so the shading
-// never bands. It is a Shadertoy file: pardes puts its prefix before it.
-
-const float TAU = 6.28318530718;
-const float EASE_IN = 0.5; // seconds
-
-// Each at levels 1, 2 and 3 (`Ripple 1`..`Ripple 3`); 2 is the default.
-float wavelengthPx() { return pardesByLevel(170.0, 150.0, 120.0); }
-float speedPx() { return pardesByLevel(40.0, 55.0, 80.0); } // outward, per second
-float amplitudePx() { return pardesByLevel(1.2, 2.2, 5.0); }
-float shade() { return pardesByLevel(0.02, 0.035, 0.09); } // light on a slope, linear
-float reachEnd() { return pardesByLevel(0.85, 0.95, 1.3); } // how far out it dies away
-
-vec3 toLinear(vec3 c) {
- return mix(pow((c + 0.055) / 1.055, vec3(2.4)), c / 12.92, lessThanEqual(c, vec3(0.04045)));
-}
-
-vec3 toSrgb(vec3 c) {
- c = max(c, vec3(0.0));
- return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, c * 12.92, lessThanEqual(c, vec3(0.0031308)));
-}
-
-float ign(vec2 p) {
- return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715))));
-}
-
-void mainImage(out vec4 fragColor, in vec2 fragCoord) {
- vec2 size = iResolution.xy;
- vec2 center = 0.5 * size;
- vec2 offset = fragCoord - center;
- float radius = length(offset);
- float reach = 0.5 * length(size);
- // Strongest a third of the way out, fading to nothing at the middle
- // and at the edges, so the window's frame never wobbles.
- float envelope = smoothstep(0.0, 0.18, radius / reach) * (1.0 - smoothstep(0.35, reachEnd(), radius / reach));
- float ease = smoothstep(0.0, EASE_IN, iTime);
- float phase = (radius - iTime * speedPx()) * TAU / wavelengthPx();
- float height = sin(phase);
- float slope = cos(phase);
- vec2 dir = offset / max(radius, 1e-3);
- vec2 moved = fragCoord + dir * height * amplitudePx() * envelope * ease;
-
- vec4 s = texture(iChannel0, moved / size);
- vec3 col = toLinear(s.rgb) * (1.0 + shade() * slope * envelope * ease);
- vec3 srgb = toSrgb(col) + (ign(fragCoord + float(iFrame % 64) * 5.588238) - 0.5) / 255.0;
- fragColor = vec4(clamp(srgb, 0.0, 1.0), s.a);
-}
diff --git a/shaders/prebuilt/post-ripple.frag.spv b/shaders/prebuilt/post-ripple.frag.spv
deleted file mode 100644
index 5f05f9f3..00000000
--- a/shaders/prebuilt/post-ripple.frag.spv
+++ /dev/null
Binary files differ
diff --git a/src/config.zig b/src/config.zig
index e8c5a705..d1e5f23d 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -129,8 +129,6 @@ pub const leader_path = paths: {
}
if (builtins.capabilities.scene_shaders) {
table.set(.Crt, "tr");
- table.set(.Ripple, "tR");
- table.set(.Glitch, "tg");
table.set(.Shader, null);
table.set(.ShaderAnimation, null);
}
@@ -622,7 +620,7 @@ pub const Runtime = struct {
pub const ShaderAnimation = enum { off, on, always };
- /// Each pass a bundled effect (Crt, Ripple, Glitch) or a Shadertoy file.
+ /// Each pass the bundled Crt or a Shadertoy file.
pub const Post = struct {
pub const max = 8;
pub const Entry = struct {
@@ -774,8 +772,8 @@ pub const Runtime = struct {
pub const MessageTime = enum { linger, fall, dissolve };
pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold, verbose, message_animation, ligatures };
- /// The bundled post passes, each a Shadertoy file in shaders/post/.
- pub const Scene = enum { crt, ripple, glitch };
+ /// The bundled post pass, a Shadertoy file in shaders/post/.
+ pub const Scene = enum { crt };
pub const Action = union(enum) {
toggle: Toggle,
@@ -843,8 +841,6 @@ pub const Runtime = struct {
.{ .word = "PanelScramble", .action = .{ .transition = .scramble }, .availability = .panel_transitions },
.{ .word = "PanelType", .action = .{ .transition = .typewriter }, .availability = .panel_transitions },
.{ .word = "Crt", .action = .{ .scene = .crt }, .availability = .scene_shaders },
- .{ .word = "Ripple", .action = .{ .scene = .ripple }, .availability = .scene_shaders },
- .{ .word = "Glitch", .action = .{ .scene = .glitch }, .availability = .scene_shaders },
.{ .word = "Shader", .action = .shader, .availability = .scene_shaders },
.{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders },
};
@@ -1263,18 +1259,18 @@ pub const Runtime = struct {
test "the post chain runs in the order its passes were put in, and Shader off empties it of files" {
var state: Runtime = .{};
try std.testing.expect(apply(&state, find("Shader").?, " ~/a.glsl\n"));
- try std.testing.expect(apply(&state, find("Glitch").?, "on"));
+ try std.testing.expect(apply(&state, find("Crt").?, "on"));
try std.testing.expect(apply(&state, find("Shader").?, "/b.glsl"));
const chain = state.post.list();
try std.testing.expectEqual(@as(usize, 3), chain.len);
try std.testing.expectEqualStrings("~/a.glsl", chain[0].path.get());
- try std.testing.expectEqual(Scene.glitch, chain[1].scene.?);
+ try std.testing.expectEqual(Scene.crt, chain[1].scene.?);
try std.testing.expectEqualStrings("/b.glsl", chain[2].path.get());
const moved = state.post.generation;
// A file named again leaves; the others keep their order.
try std.testing.expect(apply(&state, find("Shader").?, "~/a.glsl"));
try std.testing.expect(state.post.generation != moved);
- try std.testing.expectEqual(Scene.glitch, state.post.list()[0].scene.?);
+ try std.testing.expectEqual(Scene.crt, state.post.list()[0].scene.?);
try std.testing.expect(apply(&state, find("Shader").?, "off"));
try std.testing.expectEqual(@as(usize, 1), state.post.len);
try std.testing.expect(!apply(&state, find("Shader").?, null));
@@ -1288,7 +1284,7 @@ pub const Runtime = struct {
const crt = find("Crt").?;
try std.testing.expect(apply(&state, crt, "on"));
try std.testing.expectEqual(@as(u8, Post.default_level), state.post.level(.crt));
- try std.testing.expect(apply(&state, find("Glitch").?, "1"));
+ try std.testing.expect(apply(&state, find("Shader").?, "/after.glsl"));
const moved = state.post.generation;
try std.testing.expect(apply(&state, crt, "3"));
try std.testing.expectEqual(@as(u8, 3), state.post.level(.crt));
@@ -1472,8 +1468,6 @@ pub const Runtime = struct {
},
.scene => |effect| switch (effect) {
.crt => "Crt 2\n",
- .ripple => "Ripple 2\n",
- .glitch => "Glitch 2\n",
},
.shader => "Shader ~/crt.glsl\n",
.shader_animation => "ShaderAnimation always\n",
@@ -1544,8 +1538,6 @@ pub const Runtime = struct {
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font requested") == null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Panel transition off\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Crt unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ripple unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Glitch unsupported\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "TaglineSize unsupported\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Look hover delay: off\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Native images: off\n") != null);
diff --git a/src/effect_sources.zig b/src/effect_sources.zig
index 6ce11160..83bd1313 100644
--- a/src/effect_sources.zig
+++ b/src/effect_sources.zig
@@ -12,10 +12,8 @@ const ui_fragment_path = shader_dir ++ "ui.frag.glsl";
const image_vertex_path = shader_dir ++ "image.vert.glsl";
const image_fragment_path = shader_dir ++ "image.frag.glsl";
const post_vertex_path = shader_dir ++ "post.vert.glsl";
-// The bundled post passes as compiled: the prefix and the pass together.
+// The bundled post pass as compiled: the prefix and the pass together.
const post_crt_path = shader_dir ++ "post-crt.frag.glsl";
-const post_ripple_path = shader_dir ++ "post-ripple.frag.glsl";
-const post_glitch_path = shader_dir ++ "post-glitch.frag.glsl";
pub const Backend = @TypeOf(build_config.platform);
pub const backend: Backend = build_config.platform;
@@ -55,8 +53,6 @@ pub const files = switch (backend) {
.{ .path = image_fragment_path, .contents = @embedFile("effect-source-image.frag.glsl") },
.{ .path = post_vertex_path, .contents = @embedFile("effect-source-post.vert.glsl") },
.{ .path = post_crt_path, .contents = @embedFile("effect-source-post-crt.frag.glsl") },
- .{ .path = post_ripple_path, .contents = @embedFile("effect-source-post-ripple.frag.glsl") },
- .{ .path = post_glitch_path, .contents = @embedFile("effect-source-post-glitch.frag.glsl") },
},
.macos => [_]filesystem.Source{
.{ .path = "src/macos/Sources/PardesView.swift", .contents = @embedFile("macos/Sources/PardesView.swift") },
@@ -77,8 +73,6 @@ pub fn forSetting(setting: config.Runtime.Setting) ?[]const []const u8 {
.scene => |effect| switch (backend) {
.gui => switch (effect) {
.crt => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_crt_path },
- .ripple => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_ripple_path },
- .glitch => &.{ "src/gui/Post.zig", "src/gui/gui.zig", post_vertex_path, post_glitch_path },
},
.macos => &.{ "src/macos/Sources/PardesView.swift", "src/macos/Sources/ScenePostprocessor.swift", "shaders/crt.ci.metal" },
.tty, .web, .esp32p4 => null,
diff --git a/src/gui/Post.zig b/src/gui/Post.zig
index d9d3d53e..f715965f 100644
--- a/src/gui/Post.zig
+++ b/src/gui/Post.zig
@@ -1,7 +1,7 @@
//! The post chain (docs/render-pipeline.md §6): Shadertoy passes over the
//! finished frame, each reading the one before it (the frame first) and the
-//! last one writing the window. A pass is bundled (Crt, Ripple, Glitch:
-//! shaders/post/, compiled with the build) or a user's file (Shader <path>),
+//! last one writing the window. A pass is the bundled Crt (shaders/post/,
+//! compiled with the build) or a user's file (Shader <path>),
//! compiled when it joins the chain by the build's own glslc invocation on a
//! thread of its own, never on the frame path. A file whose compile fails
//! keeps its last good pipeline, and glslc missing is said once.
@@ -28,8 +28,6 @@ const prefix = @embedFile("post-prefix.glsl");
const vert_spv = @embedFile("post.vert.spv");
const bundled_spv = std.EnumArray(Scene, []const u8).init(.{
.crt = @embedFile("post-crt.frag.spv"),
- .ripple = @embedFile("post-ripple.frag.spv"),
- .glitch = @embedFile("post-glitch.frag.spv"),
});
/// ghostty's Shadertoy uniform block, member for member in the GLSL block's
@@ -170,7 +168,7 @@ fn collect(post: *Post, gpa: std.mem.Allocator, device: *c.SDL_GPUDevice, format
.none => {},
.missing => if (!post.missing_said) {
post.missing_said = true;
- core.setMessage(core.active, "Shader: glslc is not on PATH, so Shadertoy files do not run (Crt, Ripple and Glitch do)");
+ core.setMessage(core.active, "Shader: glslc is not on PATH, so Shadertoy files do not run (Crt does)");
},
.failed => |text| {
defer gpa.free(text);
diff --git a/src/macos.zig b/src/macos.zig
index 0bad86ee..6cd7838f 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -112,8 +112,6 @@ const cell_flag_default: u8 = 1;
const cell_flag_tagline: u8 = 2;
const cell_flag_hover: u8 = 4;
const scene_flag_crt: u32 = 1 << 0;
-const scene_flag_ripple: u32 = 1 << 1;
-const scene_flag_glitch: u32 = 1 << 2;
const scene_frame_hz: u32 = 60;
const scene_wrap_ns: u64 = 4096 * std.time.ns_per_s;
@@ -850,13 +848,11 @@ export fn pardes_should_quit() bool {
return st.core.quit;
}
-// The Metal shell keeps its own CRT, ripple and glitch kernel: the bundled
-// passes of the GUI's post chain, as flags.
+// The Metal shell keeps its own CRT kernel: the GUI's bundled post pass, as
+// a flag.
fn encodeSceneEffects(post: *const pardes.config.Runtime.Post) u32 {
var flags: u32 = 0;
if (post.has(.crt)) flags |= scene_flag_crt;
- if (post.has(.ripple)) flags |= scene_flag_ripple;
- if (post.has(.glitch)) flags |= scene_flag_glitch;
return flags;
}
@@ -2842,8 +2838,6 @@ test "pardes.h matches the Zig boundary" {
try expectEqual(@as(u8, c.PARDES_CELL_HOVER), cell_flag_hover);
try expectEqual(@as(u16, c.PARDES_TOPBAR_H), pardes.TOPBAR_H);
try expectEqual(@as(u32, c.PARDES_SCENE_CRT), scene_flag_crt);
- try expectEqual(@as(u32, c.PARDES_SCENE_RIPPLE), scene_flag_ripple);
- try expectEqual(@as(u32, c.PARDES_SCENE_GLITCH), scene_flag_glitch);
try expectEqual(@as(u8, c.PARDES_PANEL_OPENING), @intFromEnum(animation.Phase.opening));
try expectEqual(@as(u8, c.PARDES_PANEL_MOVING), @intFromEnum(animation.Phase.moving));
try expectEqual(@as(u8, c.PARDES_PANEL_CLOSING), @intFromEnum(animation.Phase.closing));
@@ -2918,13 +2912,6 @@ test "scene effect flags and display clock are compact and independent" {
try expectEqual(@as(u32, 0), encodeSceneEffects(&post));
_ = post.set(.crt, "", true);
try expectEqual(scene_flag_crt, encodeSceneEffects(&post));
- _ = post.set(.ripple, "", true);
- _ = post.set(.glitch, "", true);
- try expectEqual(scene_flag_crt | scene_flag_ripple | scene_flag_glitch, encodeSceneEffects(&post));
- try expectEqual(
- scene_flag_crt | scene_flag_ripple | scene_flag_glitch,
- encodeSceneEffects(.{ .crt = true, .ripple = true, .glitch = true }),
- );
var st: State = undefined;
st.scene_ns = scene_wrap_ns - (std.time.ns_per_ms * 5);
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index 70f467e3..8ba9c84b 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -684,7 +684,7 @@ final class PardesView: NSView {
private var latchedButton: pardes_mouse_button_e?
private var latchedCell: GridPoint?
/// Last physical point delivered by AppKit and its mapping through the
- /// accepted scene. Ripple/glitch can move a source cell under a stationary
+ /// accepted scene. The CRT's barrel moves a source cell under a stationary
/// hand, so successful scene presentations reconcile this point again.
private var pointerPoint: CGPoint?
private var pointerCell: GridPoint?
diff --git a/src/macos/Sources/ScenePostprocessor.swift b/src/macos/Sources/ScenePostprocessor.swift
index cf7892b3..41d54e34 100644
--- a/src/macos/Sources/ScenePostprocessor.swift
+++ b/src/macos/Sources/ScenePostprocessor.swift
@@ -205,11 +205,9 @@ final class ScenePostprocessor {
if scene.flags != 0 {
let crt: Float = scene.flags & UInt32(PARDES_SCENE_CRT) != 0 ? 1 : 0
- let ripple: Float = scene.flags & UInt32(PARDES_SCENE_RIPPLE) != 0 ? 1 : 0
- let glitch: Float = scene.flags & UInt32(PARDES_SCENE_GLITCH) != 0 ? 1 : 0
// SDL samples the scene with CLAMP_TO_EDGE. A finite Core Image
// instead supplies transparent black outside its extent, which
- // would make ripple/glitch tear dark seams at the window edge.
+ // would tear dark seams at the window edge.
// Clamp the input only; the kernel still emits true black before
// sampling when a destination lies outside the CRT tube.
let sceneInput = filtered.clampedToExtent()
@@ -218,7 +216,7 @@ final class ScenePostprocessor {
roiCallback: { _, _ in extent },
arguments: [
sceneInput, scene.time_seconds, Float(scene.frame),
- crt, ripple, glitch,
+ crt,
CIVector(x: extent.width, y: extent.height),
CIVector(x: extent.minX, y: extent.minY),
])
@@ -291,7 +289,7 @@ final class ScenePostprocessor {
/// The scene kernel is a destination-to-source mapping. Pointer input must
/// apply that same mapping before the grid and its panel tracks see a cell,
- /// otherwise a visible word under ripple/glitch is not the word Look gets.
+ /// otherwise a visible word under the barrel is not the word Look gets.
static func sourcePoint(
_ point: CGPoint,
size: CGSize,
@@ -311,27 +309,8 @@ final class ScenePostprocessor {
ccY *= barrel
uvX = ccX * 0.5 + 0.5
uvY = ccY * 0.5 + 0.5
- // Only CRT has a black tube. Ripple/glitch without CRT sample through
- // the texture's clamp-to-edge policy and remain clickable there.
+ // The CRT's black tube has no cell under it.
if crt > 0.5 && min(min(uvX, uvY), min(1 - uvX, 1 - uvY)) <= 0 { return nil }
-
- let time = scene.time_seconds
- if scene.flags & UInt32(PARDES_SCENE_RIPPLE) != 0 {
- let centeredX = uvX - 0.5
- let centeredY = uvY - 0.5
- let radius = sqrtf(centeredX * centeredX + centeredY * centeredY)
- let envelope = 1 - smoothstep(0.04, 0.72, radius)
- let wave = sinf(radius * 42 - time * 5.2)
- let travel = wave * envelope * 0.0032 / max(radius, 0.00001)
- uvX += centeredX * travel
- uvY += centeredY * travel
- }
- if scene.flags & UInt32(PARDES_SCENE_GLITCH) != 0 {
- let band = floorf(uvY * 96)
- let seed = noise(band, floorf(time * 13))
- let tear = smoothstep(0.955, 1, seed)
- uvX += (noise(seed, band) - 0.5) * tear * 0.020
- }
uvX = min(max(uvX, 0), max(0, 1 - 1 / Float(size.width)))
uvY = min(max(uvY, 0), max(0, 1 - 1 / Float(size.height)))
return CGPoint(
@@ -339,23 +318,4 @@ final class ScenePostprocessor {
y: CGFloat(1 - uvY) * size.height)
}
- private static func smoothstep(_ edge0: Float, _ edge1: Float, _ x: Float) -> Float {
- let t = min(max((x - edge0) / (edge1 - edge0), 0), 1)
- return t * t * (3 - 2 * t)
- }
-
- /// Scalar spelling of pardesNoise in crt.ci.metal. Float arithmetic is
- /// deliberate: the shader's band decision must not move at a Double round.
- private static func noise(_ x: Float, _ y: Float) -> Float {
- var qx = fract(x * 0.1031)
- var qy = fract(y * 0.1031)
- var qz = fract(x * 0.1031)
- let d = qx * (qy + 33.33) + qy * (qz + 33.33) + qz * (qx + 33.33)
- qx += d
- qy += d
- qz += d
- return fract((qx + qy) * qz)
- }
-
- private static func fract(_ x: Float) -> Float { x - floorf(x) }
}
diff --git a/src/macos/pardes.h b/src/macos/pardes.h
index f74330e5..29f8a37d 100644
--- a/src/macos/pardes.h
+++ b/src/macos/pardes.h
@@ -82,8 +82,7 @@ typedef struct {
// from the background colour, which a real selection shares.
#define PARDES_CELL_HOVER 0x04u
-// One coherent snapshot for the full-window shader pass. The bitmask lets the
-// host run CRT, ripple and glitch in one kernel instead of stacking filters;
+// One coherent snapshot for the full-window shader pass (CRT);
// time/frame advance only on pardes_animation_tick's display clock.
typedef struct {
uint32_t flags;
@@ -92,8 +91,6 @@ typedef struct {
} pardes_scene_s;
#define PARDES_SCENE_CRT 0x01u
-#define PARDES_SCENE_RIPPLE 0x02u
-#define PARDES_SCENE_GLITCH 0x04u
// One backend-neutral pane transition. Boxes are logical cell rectangles;
// the native shader converts them to backing pixels using the same measured
diff --git a/test/output.zig b/test/output.zig
index 0fdd9f4d..e49ec4ff 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -669,7 +669,7 @@ test "every enabled setting builtin mutates the State Config reports" {
try std.testing.expect(std.mem.indexOf(u8, report, transition) != null);
// A bare word turns a bundled pass on at its default level.
const scene_status = if (builtins.capabilities.scene_shaders) "2" else "unsupported";
- for ([_][]const u8{ "Crt", "Ripple", "Glitch" }) |name| {
+ for ([_][]const u8{"Crt"}) |name| {
const line = try std.fmt.bufPrint(&buf, "{s} {s}\n", .{ name, scene_status });
try std.testing.expect(std.mem.indexOf(u8, report, line) != null);
}