1pub const COLOUR_WGSL: &str = r#"
2fn look_decode(c: vec3f) -> vec3f {
3 return select(pow((c + 0.055) / 1.055, vec3f(2.4)), c / 12.92, c <= vec3f(0.04045));
4}
5
6fn look_encode(c: vec3f) -> vec3f {
7 let x = clamp(c, vec3f(0.0), vec3f(1.0));
8 return select(1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, x * 12.92, x <= vec3f(0.0031308));
9}
10
11fn look_cbrt(x: f32) -> f32 {
12 return sign(x) * pow(abs(x), 1.0 / 3.0);
13}
14
15fn look_oklab(encoded: vec3f) -> vec3f {
16 let c = look_decode(encoded);
17 let l = look_cbrt(0.41222146 * c.r + 0.53633255 * c.g + 0.051445995 * c.b);
18 let m = look_cbrt(0.2119035 * c.r + 0.6806995 * c.g + 0.10739696 * c.b);
19 let s = look_cbrt(0.08830246 * c.r + 0.28171885 * c.g + 0.6299787 * c.b);
20 return vec3f(
21 0.21045426 * l + 0.7936178 * m - 0.004072047 * s,
22 1.9779985 * l - 2.4285922 * m + 0.4505937 * s,
23 0.025904037 * l + 0.78277177 * m - 0.80867577 * s,
24 );
25}
26
27fn look_srgb(lab: vec3f) -> vec3f {
28 let l_ = lab.x + 0.39633778 * lab.y + 0.21580376 * lab.z;
29 let m_ = lab.x - 0.105561346 * lab.y - 0.06385417 * lab.z;
30 let s_ = lab.x - 0.08948418 * lab.y - 1.2914855 * lab.z;
31 let l = l_ * l_ * l_;
32 let m = m_ * m_ * m_;
33 let s = s_ * s_ * s_;
34 return look_encode(vec3f(
35 4.0767417 * l - 3.3077116 * m + 0.23096994 * s,
36 -1.268438 * l + 2.6097574 * m - 0.34131938 * s,
37 -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s,
38 ));
39}
40
41fn look_stop_mix(a: vec4f, b: vec4f, f: f32, oklab: bool) -> vec4f {
42 if (oklab) {
43 let lab = mix(look_oklab(a.rgb), look_oklab(b.rgb), f);
44 return vec4f(look_srgb(lab), mix(a.a, b.a, f));
45 }
46 return mix(a, b, f);
47}
48
49fn look_gradient_position(kind: f32, geometry: vec4f, p: vec2f) -> f32 {
50 if (kind < 0.5) {
51 let d = geometry.zw - geometry.xy;
52 return clamp(dot(p - geometry.xy, d) / max(dot(d, d), 1e-12), 0.0, 1.0);
53 }
54 return clamp(length((p - geometry.xy) / max(geometry.zw, vec2f(1e-6))), 0.0, 1.0);
55}
56"#;
57
58pub const LOOK_WGSL: &str = r#"
59struct LookUniform {
60 size: vec4f,
61 place: vec4f,
62 frame: vec4f,
63 p: array<vec4f, 29>,
64}
65
66struct LookPalette {
67 entries: array<vec4f, 512>,
68}
69
70@group(0) @binding(0) var<uniform> look: LookUniform;
71@group(0) @binding(1) var look_source: texture_2d<f32>;
72@group(0) @binding(2) var look_aux: texture_2d<f32>;
73@group(0) @binding(3) var look_sampler: sampler;
74@group(0) @binding(4) var<uniform> look_palette: LookPalette;
75
76@vertex
77fn look_vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
78 let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
79 return vec4f(xy[index], 0.0, 1.0);
80}
81
82fn look_layout_at(pixel: vec2f) -> vec2f {
83 return (pixel - look.place.yz) / look.place.x;
84}
85
86fn look_premultiply(c: vec4f) -> vec4f {
87 return vec4f(c.rgb * c.a, c.a);
88}
89
90fn look_over(top: vec4f, under: vec4f) -> vec4f {
91 return top + under * (1.0 - top.a);
92}
93
94fn look_gradient(base: u32, p: vec2f) -> vec4f {
95 let header = look.p[base + 11u];
96 let count = u32(header.x);
97 if (count == 0u) {
98 return vec4f(0.0);
99 }
100 let t = look_gradient_position(header.y, look.p[base + 10u], p);
101 let oklab = header.z > 0.5;
102 let first = look.p[base + 8u].x;
103 if (t <= first) {
104 return look_premultiply(look.p[base]);
105 }
106 for (var i = 0u; i + 1u < count; i += 1u) {
107 let a = look.p[base + 8u + i / 4u][i % 4u];
108 let j = i + 1u;
109 let b = look.p[base + 8u + j / 4u][j % 4u];
110 if (t < b) {
111 return look_premultiply(look_stop_mix(look.p[base + i], look.p[base + j], (t - a) / (b - a), oklab));
112 }
113 }
114 return look_premultiply(look.p[base + count - 1u]);
115}
116
117fn look_grid_cover(distance: f32, width: f32, scale: f32) -> f32 {
118 return clamp(width * scale * 0.5 - distance * scale + 0.5, 0.0, 1.0);
119}
120
121fn look_grid(p: vec2f) -> vec4f {
122 let spacing = look.p[13].xy;
123 let origin = look.p[13].zw;
124 let line = look.p[14];
125 let every = line.y;
126 var minor = 0.0;
127 var major = 0.0;
128 for (var axis = 0u; axis < 2u; axis += 1u) {
129 let s = spacing[axis];
130 if (s <= 0.0) {
131 continue;
132 }
133 let u = (p[axis] - origin[axis]) / s;
134 minor = max(minor, look_grid_cover(abs(u - floor(u + 0.5)) * s, line.x, look.place.x));
135 if (every > 0.0) {
136 let period = s * every;
137 let v = (p[axis] - origin[axis]) / period;
138 major = max(major, look_grid_cover(abs(v - floor(v + 0.5)) * period, line.z, look.place.x));
139 }
140 }
141 let paint = look_premultiply(look.p[15]) * minor;
142 if (every > 0.0) {
143 return look_over(look_premultiply(look.p[16]) * major, paint);
144 }
145 return paint;
146}
147
148@fragment
149fn look_backdrop(@builtin(position) position: vec4f) -> @location(0) vec4f {
150 let p = look_layout_at(position.xy);
151 let flags = look.p[12];
152 var base = look_premultiply(look.p[17]) * flags.x;
153 base = look_over(look_gradient(0u, p), base);
154 if (flags.y > 0.5) {
155 base = look_over(look_grid(p), base);
156 }
157 let top = textureLoad(look_source, vec2i(position.xy), 0);
158 return look_over(top, base);
159}
160
161fn look_bayer(p: vec2u, bits: u32) -> u32 {
162 var value = 0u;
163 for (var i = 0u; i < bits; i += 1u) {
164 let xb = (p.x >> i) & 1u;
165 let yb = (p.y >> i) & 1u;
166 value += ((xb ^ yb) * 2u + yb) << (2u * (bits - 1u - i));
167 }
168 return value;
169}
170
171@fragment
172fn look_quantise(@builtin(position) position: vec4f) -> @location(0) vec4f {
173 let c = textureLoad(look_source, vec2i(position.xy), 0);
174 if (c.a <= 0.0) {
175 return vec4f(0.0);
176 }
177 let header = look.p[0];
178 let count = u32(header.x);
179 let order = u32(header.y);
180 var lab = look_oklab(c.rgb / c.a);
181 if (order > 1u) {
182 let bits = u32(header.w);
183 let at = vec2u(position.xy) % vec2u(order);
184 let t = (f32(look_bayer(at, bits)) + 0.5) / f32(order * order);
185 lab.x += (t - 0.5) * header.z;
186 }
187 var low = 0u;
188 var high = count;
189 while (low < high) {
190 let middle = (low + high) / 2u;
191 if (look_palette.entries[middle].x < lab.x) {
192 low = middle + 1u;
193 } else {
194 high = middle;
195 }
196 }
197 var best = 0u;
198 var best_distance = 3.4e38;
199 var best_index = 1e9;
200 for (var i = low; i < count; i += 1u) {
201 let entry = look_palette.entries[i];
202 let dl = entry.x - lab.x;
203 if (dl * dl > best_distance) {
204 break;
205 }
206 let d = entry.xyz - lab;
207 let distance = dot(d, d);
208 if (distance < best_distance || (distance == best_distance && entry.w < best_index)) {
209 best_distance = distance;
210 best = i;
211 best_index = entry.w;
212 }
213 }
214 for (var j = low; j > 0u; j -= 1u) {
215 let entry = look_palette.entries[j - 1u];
216 let dl = entry.x - lab.x;
217 if (dl * dl > best_distance) {
218 break;
219 }
220 let d = entry.xyz - lab;
221 let distance = dot(d, d);
222 if (distance < best_distance || (distance == best_distance && entry.w < best_index)) {
223 best_distance = distance;
224 best = j - 1u;
225 best_index = entry.w;
226 }
227 }
228 return vec4f(look_palette.entries[256u + best].rgb * c.a, c.a);
229}
230
231@fragment
232fn look_upscale(@builtin(position) position: vec4f) -> @location(0) vec4f {
233 let p = vec2u(position.xy);
234 let offset = vec2u(look.p[0].xy);
235 let extent = vec2u(look.p[0].zw);
236 let source = vec2u(look.frame.zw);
237 if (any(p < offset) || any(p >= offset + extent)) {
238 return look.p[1];
239 }
240 let at = (p - offset) * source / extent;
241 return textureLoad(look_source, vec2i(at), 0);
242}
243
244@fragment
245fn look_vignette(@builtin(position) position: vec4f) -> @location(0) vec4f {
246 let c = textureLoad(look_source, vec2i(position.xy), 0);
247 let p = look_layout_at(position.xy);
248 let q = (p - look.p[0].xy) / max(look.p[0].zw, vec2f(1e-6));
249 let t = clamp((length(q) - 1.0) / max(look.p[1].x, 1e-6), 0.0, 1.0);
250 let s = t * t * (3.0 - 2.0 * t);
251 let shade = look.p[2];
252 let light = 1.0 - (1.0 - clamp(look.p[1].y, 0.0, 1.0)) * s * clamp(shade.a, 0.0, 1.0);
253 return vec4f(c.rgb * light + shade.rgb * c.a * (1.0 - light), c.a);
254}
255
256@fragment
257fn look_mix(@builtin(position) position: vec4f) -> @location(0) vec4f {
258 let a = textureLoad(look_source, vec2i(position.xy), 0);
259 let b = textureLoad(look_aux, vec2i(position.xy), 0);
260 let blend = look.p[0];
261 var w = blend.x;
262 if (blend.y > 0.5) {
263 let p = look_layout_at(position.xy);
264 let wipe = look.p[1];
265 w = clamp((wipe.z - dot(p, wipe.xy)) / max(blend.z, 1e-4) + 0.5, 0.0, 1.0);
266 }
267 return mix(a, b, w);
268}
269
270@fragment
271fn look_copy(@builtin(position) position: vec4f) -> @location(0) vec4f {
272 return textureLoad(look_source, vec2i(position.xy), 0);
273}
274
275@fragment
276fn look_bright(@builtin(position) position: vec4f) -> @location(0) vec4f {
277 let factor = u32(look.p[0].x);
278 let threshold = look.p[0].y;
279 let limit = vec2i(look.frame.zw) - vec2i(1);
280 let start = vec2i(vec2u(position.xy) * factor);
281 var sum = vec4f(0.0);
282 for (var y = 0u; y < factor; y += 1u) {
283 for (var x = 0u; x < factor; x += 1u) {
284 let c = textureLoad(look_source, min(start + vec2i(i32(x), i32(y)), limit), 0);
285 let luminance = dot(c.rgb, vec3f(0.2126, 0.7152, 0.0722));
286 sum += c * (max(luminance - threshold, 0.0) / max(luminance, 1e-4));
287 }
288 }
289 return sum / f32(factor * factor);
290}
291
292@fragment
293fn look_blur(@builtin(position) position: vec4f) -> @location(0) vec4f {
294 let direction = vec2i(look.p[0].xy);
295 let sigma = max(look.p[0].z, 1e-3);
296 let reach = i32(look.p[0].w);
297 let limit = vec2i(look.size.xy) - vec2i(1);
298 let centre = vec2i(position.xy);
299 var sum = vec4f(0.0);
300 var total = 0.0;
301 for (var i = -reach; i <= reach; i += 1) {
302 let w = exp(-f32(i * i) / (2.0 * sigma * sigma));
303 sum += textureLoad(look_source, clamp(centre + direction * i, vec2i(0), limit), 0) * w;
304 total += w;
305 }
306 return sum / total;
307}
308
309@fragment
310fn look_crt(@builtin(position) position: vec4f) -> @location(0) vec4f {
311 let size = look.size.xy;
312 let lines = look.p[0];
313 let curve = look.p[1];
314 let bezel = look_premultiply(look.p[2]);
315 let glow = look.p[3];
316 var q = position.xy;
317 var edge = 1.0;
318 let warped = curve.z > 0.5;
319 if (warped) {
320 let uv = q / size * 2.0 - 1.0;
321 let w = uv * (1.0 + curve.x * uv.yx * uv.yx);
322 if (any(abs(w) > vec2f(1.0))) {
323 return bezel;
324 }
325 let e = clamp((vec2f(1.0) - abs(w)) / max(curve.y, 1e-5), vec2f(0.0), vec2f(1.0));
326 let s = e * e * (vec2f(3.0) - 2.0 * e);
327 edge = s.x * s.y;
328 q = (w * 0.5 + 0.5) * size;
329 }
330 var colour = vec4f(0.0);
331 if (curve.w > 0.0) {
332 let centre = size * 0.5;
333 let along = (q - centre) / length(centre) * curve.w;
334 let r = textureSampleLevel(look_source, look_sampler, (q + along) / size, 0.0);
335 let g = textureSampleLevel(look_source, look_sampler, q / size, 0.0);
336 let b = textureSampleLevel(look_source, look_sampler, (q - along) / size, 0.0);
337 colour = vec4f(r.r, g.g, b.b, g.a);
338 } else if (warped) {
339 colour = textureSampleLevel(look_source, look_sampler, q / size, 0.0);
340 } else {
341 colour = textureLoad(look_source, vec2i(position.xy), 0);
342 }
343 if (glow.w > 0.5) {
344 let light = textureSampleLevel(look_aux, look_sampler, q / size, 0.0);
345 colour = vec4f(colour.rgb + light.rgb * glow.rgb, colour.a);
346 }
347 if (lines.z > 0.5) {
348 let y = (q.y - look.place.z) / look.place.x;
349 let gain = 1.0 - clamp(lines.y, 0.0, 1.0) * (0.5 + 0.5 * cos(6.283185307179586 * y / max(lines.x, 1e-6)));
350 colour = vec4f(colour.rgb * gain, colour.a);
351 }
352 if (warped) {
353 colour = colour * edge + bezel * (1.0 - edge);
354 }
355 return colour;
356}
357"#;
358
359pub fn source() -> String {
360 format!("{COLOUR_WGSL}\n{LOOK_WGSL}")
361}