summaryrefslogtreecommitdiff
path: root/shaders/prebuilt/post-glitch.frag.glsl
blob: 7b28823763d2432de6a433f912e643a2a7bdd82b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#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);
}