summaryrefslogtreecommitdiff
path: root/shaders/post/grain.glsl
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 05:44:11 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit4452a8f467765cbfa0dd0444a9bf416ac8bd0bf5 (patch)
tree0806bb947d2a0d17dcb784d9fc017d66859186db /shaders/post/grain.glsl
parentfde030a699b3d6667d1fa21ae192b5011e3f373f (diff)
downloadpardes-4452a8f467765cbfa0dd0444a9bf416ac8bd0bf5.tar.gz
pardes-4452a8f467765cbfa0dd0444a9bf416ac8bd0bf5.zip
G4: bloom, vignette and grain in the post chain
Three bundled Shadertoy passes beside Crt, each `Bloom|Vignette|Grain 0..3` (off by default, nothing drawn while off), in shaders/post/: - Bloom: what is brighter than the page (a channel's linear peak over 0.75/0.7/0.6 and over the page's own + 0.15, so a light page does not bloom) glows, 2/3.5/5% (ยง8.2's budget). The glow is a dual Kawase blur in RGBA16F textures at half size and down, 3/4/5 steps by level (shaders/bloom-down, bloom-up; Post.bloomChain), composited in linear light and dithered. - Vignette: round, from halfway to a corner, 10/18/28% at the far corners, in linear light, dithered. - Grain: a still, triangular-spread grain of 1.5/3/5 of 255 on page-coloured pixels only, a logical pixel big; still, so it asks for no frames. Focus rule: the shell passes every bundled pass the rectangles of the tags with their grips, the column and workspace tags, the notices and the cursor (pardesSpare, at most 32, neighbours on one row merged), and a bundled pass leaves them as they are; and a per-theme ceiling (pardesCap, Post.capsOf) keeps the page's text and the selection's text at min(their contrast, 4.5) under each pass at its strongest. User files get no ceiling and no spared rectangles. Fix found on the way: SDL GPU binds at most 4 KiB of a uniform block, so every Shadertoy colour after iPalette (iBackgroundColor, iForegroundColor, the cursor and selection colours, iPalette[>=4]) read zero. The prefix's block is cut in three (Globals, Palette, Colors) with ghostty's names and order, each pushed as a slice of the one struct; pardes's own block moves to binding 3. Tests: the caps hold over every native theme; each uniform slice fits 4 KiB; the prefix's members in ghostty's order across its blocks. Gates: tty/gui unit-test and core-test pass; snap fails only nested-optout; GUI goldens 01-08 byte-identical (09-18 wait on wkzlsqvy, as in somtrmsz); web builds. Feel review material: .scratch/render/{bloom,vignette,grain}/ (levels on forge, dusk, acme, crops, a levels mp4).
Diffstat (limited to 'shaders/post/grain.glsl')
-rw-r--r--shaders/post/grain.glsl32
1 files changed, 32 insertions, 0 deletions
diff --git a/shaders/post/grain.glsl b/shaders/post/grain.glsl
new file mode 100644
index 00000000..61c51492
--- /dev/null
+++ b/shaders/post/grain.glsl
@@ -0,0 +1,32 @@
+// Grain: the page's own ground takes a fine, still grain, like paper. Only
+// pixels of the page's colour (iBackgroundColor) are grained: no glyph, no
+// selection, no tag or rule, and nothing in pardesSpare. The grain averages
+// to the page, is a logical pixel big (pardesScale), and never moves, so it
+// costs no frames. At most 1.5, 3 and 5 of 255 at levels 1 to 3, and never
+// more than pardesCap, which keeps the text on it at its own contrast or 4.5.
+// It is a Shadertoy file: pardes puts its prefix before it.
+
+float amplitude() { return min(pardesByLevel(1.5, 3.0, 5.0) / 255.0, pardesCap); }
+
+// An integer hash of the grain cell: white, with no pattern to see.
+float hash(uvec2 p) {
+ uint h = p.x * 1664525u + p.y * 1013904223u;
+ h ^= h >> 16;
+ h *= 2246822519u;
+ h ^= h >> 13;
+ h *= 3266489917u;
+ h ^= h >> 16;
+ return float(h) / 4294967295.0;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord / iResolution.xy;
+ vec4 frame = texture(iChannel0, uv);
+ fragColor = frame;
+ vec3 page = iBackgroundColor.rgb;
+ if (any(greaterThan(abs(frame.rgb - page), vec3(0.75 / 255.0))) || pardesSpared(fragCoord)) return;
+ uvec2 cell = uvec2(fragCoord / max(1.0, pardesScale));
+ // Two hashes summed: a triangular spread, softer than a flat one.
+ float n = hash(cell) + hash(cell + uvec2(7919u, 104729u)) - 1.0;
+ fragColor = vec4(clamp(page + n * amplitude(), 0.0, 1.0), frame.a);
+}