1macro_rules! shader {
2 (normal, $body:literal) => {
3 shader!(
4 r#"
5@group(0) @binding(4) var normal_tex: texture_2d<f32>;
6
7fn normal_of(x: i32, y: i32) -> vec3<f32> {
8 let dim = vec2<i32>(min(textureDimensions(normal_tex), post.extent.xy));
9 if (dim.x <= 0 || dim.y <= 0) { return vec3<f32>(0.0, 0.0, 1.0); }
10 let c = clamp(vec2<i32>(x, y), vec2<i32>(0), dim - vec2<i32>(1));
11 return textureLoad(normal_tex, c, 0).xyz;
12}
13"#,
14 $body
15 )
16 };
17 (depth, $body:literal) => {
18 shader!(
19 r#"
20@group(0) @binding(3) var<storage, read> depth_buf: array<f32>;
21
22fn depth_of(x: i32, y: i32) -> f32 {
23 let dim = vec2<i32>(src_dim());
24 if (dim.x <= 0 || dim.y <= 0) { return 0.0; }
25 let c = clamp(vec2<i32>(x, y), vec2<i32>(0), dim - vec2<i32>(1));
26 return depth_buf[u32(c.y) * u32(dim.x) + u32(c.x)];
27}
28"#,
29 $body
30 )
31 };
32 (geometry, $body:literal) => {
33 shader!(
34 r#"
35@group(0) @binding(3) var<storage, read> depth_buf: array<f32>;
36@group(0) @binding(4) var normal_tex: texture_2d<f32>;
37
38fn depth_of(x: i32, y: i32) -> f32 {
39 let dim = vec2<i32>(src_dim());
40 if (dim.x <= 0 || dim.y <= 0) { return 0.0; }
41 let c = clamp(vec2<i32>(x, y), vec2<i32>(0), dim - vec2<i32>(1));
42 return depth_buf[u32(c.y) * u32(dim.x) + u32(c.x)];
43}
44
45fn normal_of(x: i32, y: i32) -> vec3<f32> {
46 let dim = vec2<i32>(min(textureDimensions(normal_tex), post.extent.xy));
47 if (dim.x <= 0 || dim.y <= 0) { return vec3<f32>(0.0, 0.0, 1.0); }
48 let c = clamp(vec2<i32>(x, y), vec2<i32>(0), dim - vec2<i32>(1));
49 return textureLoad(normal_tex, c, 0).xyz;
50}
51"#,
52 $body
53 )
54 };
55 (bloomtex, $body:expr) => {
56 shader!(bloom_tex!(), $body)
57 };
58 (grade, $name:literal) => {
59 shader!(
60 grade_ops!(),
61 concat!(
62 " store(p, grade_",
63 $name,
64 "(src, p, post.p0, post.p1, post.p2, post.p3));\n"
65 )
66 )
67 };
68 ($extra:expr, $body:expr) => {
69 concat!(
70 post_head!(),
71 $extra,
72 post_common!(),
73 post_main!(),
74 $body,
75 "\n}\n"
76 )
77 };
78}
79
80macro_rules! post_head {
81 () => {
82 r#"
83struct Post {
84 size: vec2<u32>,
85 frame: u32,
86 seed: u32,
87 p0: vec4<f32>,
88 p1: vec4<f32>,
89 p2: vec4<f32>,
90 p3: vec4<f32>,
91 region: vec4<u32>,
92 extent: vec4<u32>,
93}
94
95@group(0) @binding(0) var<uniform> post: Post;
96@group(0) @binding(1) var src_tex: texture_2d<f32>;
97@group(0) @binding(2) var dst_tex: texture_storage_2d<rgba16float, write>;
98"#
99 };
100}
101
102macro_rules! bloom_tex {
103 () => {
104 r#"
105@group(0) @binding(3) var bloom_tex: texture_2d<f32>;
106
107fn bloom_dim() -> vec2<u32> {
108 return min(textureDimensions(bloom_tex), post.extent.zw);
109}
110
111fn load_bloom(p: vec2<i32>) -> vec4<f32> {
112 let dim = vec2<i32>(bloom_dim());
113 if (dim.x <= 0 || dim.y <= 0) { return vec4<f32>(0.0); }
114 let c = clamp(p, vec2<i32>(0), dim - vec2<i32>(1));
115 return textureLoad(bloom_tex, c, 0);
116}
117
118fn sample_bloom(x: f32, y: f32) -> vec4<f32> {
119 let dim = vec2<f32>(bloom_dim());
120 if (dim.x < 1.0 || dim.y < 1.0) { return vec4<f32>(0.0); }
121 let xc = clamp(x, 0.0, dim.x - 1.0);
122 let yc = clamp(y, 0.0, dim.y - 1.0);
123 let x0 = i32(floor(xc));
124 let y0 = i32(floor(yc));
125 let tx = xc - floor(xc);
126 let ty = yc - floor(yc);
127 let a = load_bloom(vec2<i32>(x0, y0));
128 let b = load_bloom(vec2<i32>(x0 + 1, y0));
129 let c = load_bloom(vec2<i32>(x0, y0 + 1));
130 let d = load_bloom(vec2<i32>(x0 + 1, y0 + 1));
131 return mix(mix(a, b, tx), mix(c, d, tx), ty);
132}
133"#
134 };
135}
136
137macro_rules! post_common {
138 () => {
139 r#"
140fn src_dim() -> vec2<u32> {
141 return min(textureDimensions(src_tex), post.extent.xy);
142}
143
144fn load_src(p: vec2<i32>) -> vec4<f32> {
145 let dim = vec2<i32>(src_dim());
146 if (dim.x <= 0 || dim.y <= 0) { return vec4<f32>(0.0); }
147 let c = clamp(p, vec2<i32>(0), dim - vec2<i32>(1));
148 return textureLoad(src_tex, c, 0);
149}
150
151fn sample_src(x: f32, y: f32) -> vec4<f32> {
152 let dim = vec2<f32>(src_dim());
153 if (dim.x < 1.0 || dim.y < 1.0) { return vec4<f32>(0.0); }
154 let xc = clamp(x, 0.0, dim.x - 1.0);
155 let yc = clamp(y, 0.0, dim.y - 1.0);
156 let x0 = i32(floor(xc));
157 let y0 = i32(floor(yc));
158 let tx = xc - floor(xc);
159 let ty = yc - floor(yc);
160 let a = load_src(vec2<i32>(x0, y0));
161 let b = load_src(vec2<i32>(x0 + 1, y0));
162 let c = load_src(vec2<i32>(x0, y0 + 1));
163 let d = load_src(vec2<i32>(x0 + 1, y0 + 1));
164 return mix(mix(a, b, tx), mix(c, d, tx), ty);
165}
166
167fn store(p: vec2<u32>, c: vec4<f32>) {
168 textureStore(dst_tex, p, c);
169}
170
171fn luma(c: vec3<f32>) -> f32 {
172 return dot(c, vec3<f32>(0.2126, 0.7152, 0.0722));
173}
174
175fn luma_at(x: i32, y: i32) -> f32 {
176 return luma(load_src(vec2<i32>(x, y)).rgb);
177}
178
179fn sobel(x: i32, y: i32) -> f32 {
180 let gx = -luma_at(x - 1, y - 1) + luma_at(x + 1, y - 1) - 2.0 * luma_at(x - 1, y) + 2.0 * luma_at(x + 1, y) - luma_at(x - 1, y + 1) + luma_at(x + 1, y + 1);
181 let gy = -luma_at(x - 1, y - 1) - 2.0 * luma_at(x, y - 1) - luma_at(x + 1, y - 1) + luma_at(x - 1, y + 1) + 2.0 * luma_at(x, y + 1) + luma_at(x + 1, y + 1);
182 return sqrt(gx * gx + gy * gy);
183}
184
185fn normalize3(v: vec3<f32>) -> vec3<f32> {
186 let len = length(v);
187 if (len < 1e-8) { return vec3<f32>(0.0, 0.0, 1.0); }
188 return v / len;
189}
190
191fn safe_pow(a: f32, b: f32) -> f32 {
192 if (a <= 0.0) { return 0.0; }
193 return pow(a, b);
194}
195
196fn edge_step(edge0: f32, edge1: f32, x: f32) -> f32 {
197 let span = edge1 - edge0;
198 if (abs(span) < 1e-8) {
199 if (x < edge0) { return 0.0; }
200 return 1.0;
201 }
202 let t = clamp((x - edge0) / span, 0.0, 1.0);
203 return t * t * (3.0 - 2.0 * t);
204}
205
206fn grain_hash(x: u32, y: u32, seed: u32) -> f32 {
207 var h = x * 0x8da6b343u ^ y * 0xd8163841u ^ seed * 0xcb1ab31fu;
208 h = h ^ (h >> 15u);
209 h = h * 0x2c1b3c6du;
210 h = h ^ (h >> 12u);
211 return f32(h >> 8u) / 16777216.0;
212}
213
214fn mixed_seed() -> u32 {
215 return post.seed ^ (post.frame * 0x9e3779b9u);
216}
217
218fn hash21(x: f32, y: f32) -> f32 {
219 var qx = fract(x * 123.34);
220 var qy = fract(y * 456.21);
221 let d = qx * (qx + 45.32) + qy * (qy + 45.32);
222 qx = qx + d;
223 qy = qy + d;
224 return fract(qx * qy);
225}
226
227fn grain_hash_i(x: i32, y: i32, seed: u32) -> f32 {
228 return grain_hash(u32(x), u32(y), seed);
229}
230
231fn value_noise(x: f32, y: f32, seed: u32) -> f32 {
232 let x0 = floor(x);
233 let y0 = floor(y);
234 let tx = x - x0;
235 let ty = y - y0;
236 let sx = tx * tx * (3.0 - 2.0 * tx);
237 let sy = ty * ty * (3.0 - 2.0 * ty);
238 let xi = i32(x0);
239 let yi = i32(y0);
240 let a = grain_hash_i(xi, yi, seed);
241 let b = grain_hash_i(xi + 1, yi, seed);
242 let c = grain_hash_i(xi, yi + 1, seed);
243 let d = grain_hash_i(xi + 1, yi + 1, seed);
244 let ab = a + (b - a) * sx;
245 let cd = c + (d - c) * sx;
246 return ab + (cd - ab) * sy;
247}
248
249fn bayer(x: u32, y: u32) -> f32 {
250 var v = 0u;
251 var xx = x & 7u;
252 var yy = y & 7u;
253 var half = 4u;
254 var mul = 1u;
255 for (var i = 0u; i < 3u; i = i + 1u) {
256 let cx = xx >= half;
257 let cy = yy >= half;
258 if (xx >= half) { xx = xx - half; }
259 if (yy >= half) { yy = yy - half; }
260 var add = 0u;
261 if (cx && !cy) { add = 2u; }
262 else if (!cx && cy) { add = 3u; }
263 else if (cx && cy) { add = 1u; }
264 v = v + add * mul;
265 mul = mul * 4u;
266 half = half / 2u;
267 }
268 return (f32(v) + 0.5) / 64.0;
269}
270
271fn saturate3(c: vec3<f32>, amount: f32) -> vec3<f32> {
272 let l = luma(c);
273 return max(vec3<f32>(l) + (c - vec3<f32>(l)) * amount, vec3<f32>(0.0));
274}
275
276fn quantize(v: f32, levels: f32) -> f32 {
277 let t = clamp(v, 0.0, 1.0);
278 let q = min(floor(t * levels), levels - 1.0);
279 return q / (levels - 1.0);
280}
281
282fn keep_rgb(rgb: vec3<f32>, threshold: f32, knee: f32, knee_width: f32, soft: f32) -> vec3<f32> {
283 let l = max(rgb.r, max(rgb.g, rgb.b));
284 var hard = 0.0;
285 if (l > threshold) { hard = max(l - threshold, 0.0) / max(l, 1e-4); }
286 var knee_t = 0.0;
287 if (knee_width > 1e-6) { knee_t = clamp((l - knee) / knee_width, 0.0, 1.0); }
288 return rgb * (hard + knee_t * knee_t * soft);
289}
290
291fn aces_channel(x: f32) -> f32 {
292 let a = x * (x * 2.51 + 0.03);
293 let b = x * (x * 2.43 + 0.59) + 0.14;
294 if (abs(b) < 1e-8) { return 0.0; }
295 return clamp(a / b, 0.0, 1.0);
296}
297
298fn agx_contrast(x: f32) -> f32 {
299 let x2 = x * x;
300 let x4 = x2 * x2;
301 return 15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232;
302}
303
304fn agx(c: vec3<f32>) -> vec3<f32> {
305 let r0 = vec3<f32>(0.842479062253094, 0.0784335999999992, 0.0792237451477643);
306 let r1 = vec3<f32>(0.0423282422610123, 0.878468636469772, 0.0791661274605434);
307 let r2 = vec3<f32>(0.0423756549057051, 0.0784336, 0.879142973793104);
308 let v = vec3<f32>(dot(r0, c), dot(r1, c), dot(r2, c));
309 let min_ev = -12.47393;
310 let max_ev = 4.026069;
311 let span = max_ev - min_ev;
312 let t0 = clamp((log2(max(v.r, 1e-10)) - min_ev) / span, 0.0, 1.0);
313 let t1 = clamp((log2(max(v.g, 1e-10)) - min_ev) / span, 0.0, 1.0);
314 let t2 = clamp((log2(max(v.b, 1e-10)) - min_ev) / span, 0.0, 1.0);
315 return clamp(vec3<f32>(agx_contrast(t0), agx_contrast(t1), agx_contrast(t2)), vec3<f32>(0.0), vec3<f32>(1.0));
316}
317
318fn neutral_map(c: vec3<f32>, start: f32, toe: f32, desat: f32, clamp_hi: bool) -> vec3<f32> {
319 let low = min(c.r, min(c.g, c.b));
320 var offset = toe;
321 if (low < 0.08) { offset = low - 6.25 * low * low; }
322 var v = c - vec3<f32>(offset);
323 let peak = max(v.r, max(v.g, v.b));
324 if (peak >= start && peak > 1e-8) {
325 let d = 1.0 - start;
326 let denom = peak + d - start;
327 var mapped = start;
328 if (abs(denom) >= 1e-8) { mapped = 1.0 - d * d / denom; }
329 v = v * (mapped / peak);
330 let g = 1.0 - 1.0 / (desat * (peak - mapped) + 1.0);
331 v = mix(v, vec3<f32>(mapped), g);
332 }
333 if (clamp_hi) { v = clamp(v, vec3<f32>(0.0), vec3<f32>(1.0)); }
334 return v;
335}
336
337fn band_shade(light: f32, bands: f32, softness: f32, threshold: f32, shadow: f32) -> f32 {
338 let bands_f = max(bands, 1.0);
339 let light_c = clamp(light, 0.0, 1.0);
340 let x = min(light_c * bands_f, bands_f - 1e-4);
341 var w = 0.0;
342 if (softness > 0.0) { w = edge_step(0.5 - softness * 0.5, 0.5 + softness * 0.5, fract(x)); }
343 let idx = floor(x) + w;
344 var q = light_c;
345 if (bands_f > 1.0) { q = clamp(idx / (bands_f - 1.0), 0.0, 1.0); }
346 var gate = 1.0;
347 if (softness <= 1e-5) {
348 if (light_c < threshold) { gate = 0.0; }
349 } else {
350 gate = edge_step(threshold - softness, threshold + softness, light_c);
351 }
352 return q * (shadow + (1.0 - shadow) * gate);
353}
354
355fn palette(i: u32) -> vec3<f32> {
356 if (i == 0u) { return vec3<f32>(0.0, 0.0, 0.0); }
357 if (i == 1u) { return vec3<f32>(0.0122865, 0.0241576, 0.0865005); }
358 if (i == 2u) { return vec3<f32>(0.208637, 0.0185002, 0.0865005); }
359 if (i == 3u) { return vec3<f32>(0.0, 0.242281, 0.0822827); }
360 if (i == 4u) { return vec3<f32>(0.40724, 0.0843762, 0.0368895); }
361 if (i == 5u) { return vec3<f32>(0.114435, 0.0953075, 0.0781874); }
362 if (i == 6u) { return vec3<f32>(0.539479, 0.545724, 0.571125); }
363 if (i == 7u) { return vec3<f32>(1.0, 0.879622, 0.806952); }
364 if (i == 8u) { return vec3<f32>(1.0, 0.0, 0.0742136); }
365 if (i == 9u) { return vec3<f32>(1.0, 0.366253, 0.0); }
366 if (i == 10u) { return vec3<f32>(1.0, 0.838799, 0.0202886); }
367 if (i == 11u) { return vec3<f32>(0.0, 0.775822, 0.0368895); }
368 if (i == 12u) { return vec3<f32>(0.0221739, 0.417885, 1.0); }
369 if (i == 13u) { return vec3<f32>(0.226966, 0.181164, 0.332452); }
370 if (i == 14u) { return vec3<f32>(1.0, 0.184475, 0.391572); }
371 return vec3<f32>(1.0, 0.603827, 0.401978);
372}
373
374fn nearest_palette(c: vec3<f32>) -> vec3<f32> {
375 var best = palette(0u);
376 var best_d = 1e30;
377 for (var i = 0u; i < 16u; i = i + 1u) {
378 let swatch = palette(i);
379 let d = dot(c - swatch, c - swatch);
380 if (d < best_d) { best_d = d; best = swatch; }
381 }
382 return best;
383}
384
385fn encode_channel(c: f32) -> f32 {
386 if (c <= 0.0031308) { return c * 12.92; }
387 return 1.055 * safe_pow(max(c, 0.0), 1.0 / 2.4) - 0.055;
388}
389
390fn gaussian(p: vec2<u32>, dirx: i32, diry: i32, radius: u32, spread: f32) -> vec4<f32> {
391 var acc = vec4<f32>(0.0);
392 var total = 0.0;
393 var s = spread;
394 if (s <= 1e-4) { s = 1.0; }
395 let r = i32(min(radius, 64u));
396 for (var k = -r; k <= r; k = k + 1) {
397 let w = exp(-f32(k * k) / s);
398 acc = acc + load_src(vec2<i32>(i32(p.x) + k * dirx, i32(p.y) + k * diry)) * w;
399 total = total + w;
400 }
401 return acc / max(total, 1e-8);
402}
403
404fn ring_radius(ring: u32) -> f32 {
405 if (ring == 0u) { return post.p1.x; }
406 if (ring == 1u) { return post.p1.y; }
407 if (ring == 2u) { return post.p1.z; }
408 return post.p1.w;
409}
410
411fn ring_gain(ring: u32) -> f32 {
412 if (ring == 0u) { return post.p2.x; }
413 if (ring == 1u) { return post.p2.y; }
414 if (ring == 2u) { return post.p2.z; }
415 return post.p2.w;
416}
417
418fn ring_channel(c: f32, threshold: f32, clamp_hi: f32) -> f32 {
419 var v = max(c - threshold, 0.0);
420 if (clamp_hi > 0.0) { v = min(v, clamp_hi); }
421 return v;
422}
423"#
424 };
425}
426
427macro_rules! post_main {
428 () => {
429 concat!(
430 "\n@compute @workgroup_size(8, 8)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n",
431 " let dst_dim = textureDimensions(dst_tex);\n",
432 " let scissor = post.region.z != 0u;\n",
433 " let p = select(id.xy, id.xy + post.region.xy, scissor);\n",
434 " if (scissor && (p.x >= post.region.z || p.y >= post.region.w)) { return; }\n",
435 " if (p.x >= dst_dim.x || p.y >= dst_dim.y) { return; }\n",
436 " if (post.size.x != 0u && (p.x >= post.size.x || p.y >= post.size.y)) { return; }\n",
437 " let src = load_src(vec2<i32>(p));\n"
438 )
439 };
440}
441
442macro_rules! grade_ops {
443 () => {
444 r#"
445fn as_half(c: vec4<f32>) -> vec4<f32> {
446 return vec4<f32>(unpack2x16float(pack2x16float(c.xy)), unpack2x16float(pack2x16float(c.zw)));
447}
448
449fn grade_exposure(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
450 return vec4<f32>(src.rgb * q0.x, src.a);
451}
452
453fn grade_black(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
454 let denom = 1.0 - q0.x;
455 if (abs(denom) < 1e-6) { return vec4<f32>(0.0, 0.0, 0.0, src.a); }
456 return vec4<f32>(max((src.rgb - vec3<f32>(q0.x)) / denom, vec3<f32>(0.0)), src.a);
457}
458
459fn grade_saturation(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
460 let l = dot(src.rgb, vec3<f32>(q0.y, q0.z, q0.w));
461 return vec4<f32>(max(vec3<f32>(l) + (src.rgb - vec3<f32>(l)) * q0.x, vec3<f32>(0.0)), src.a);
462}
463
464fn grade_contrast(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
465 if (q0.z > 0.5) {
466 return vec4<f32>(clamp(vec3<f32>(
467 safe_pow(max(src.r, 0.0), q0.x),
468 safe_pow(max(src.g, 0.0), q0.x),
469 safe_pow(max(src.b, 0.0), q0.x)
470 ) * q0.y, vec3<f32>(0.0), vec3<f32>(1.0)), src.a);
471 }
472 return vec4<f32>((src.rgb - vec3<f32>(q0.y)) * q0.x + vec3<f32>(q0.y), src.a);
473}
474
475fn grade_warmth(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
476 if (q0.y > 0.5) {
477 let l = clamp(luma(src.rgb), 0.0, 1.0);
478 let t = edge_step(q0.z, q0.w, l);
479 let warm = mix(q1.xy, q1.zw, t);
480 let scale = mix(vec2<f32>(1.0), warm, q0.x);
481 return vec4<f32>(max(vec3<f32>(src.r * scale.x, src.g, src.b * scale.y), vec3<f32>(0.0)), src.a);
482 }
483 return vec4<f32>(src.r * (1.0 + q0.x), src.g, src.b * (1.0 - q0.x), src.a);
484}
485
486fn grade_tone(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
487 if (q0.x < 0.5) {
488 return vec4<f32>(aces_channel(src.r * q0.y), aces_channel(src.g * q0.y), aces_channel(src.b * q0.y), src.a);
489 }
490 if (q0.x < 1.5) {
491 return vec4<f32>(neutral_map(src.rgb, q0.y, q0.z, q0.w, q1.x > 0.5), src.a);
492 }
493 return vec4<f32>(agx(src.rgb), src.a);
494}
495
496fn grade_vignette(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
497 if (abs(q0.x) < 1e-8) { return src; }
498 let dim = max(vec2<f32>(src_dim()), vec2<f32>(1.0));
499 let st = (vec2<f32>(p) + vec2<f32>(0.5)) / dim;
500 var factor = 1.0;
501 if (q0.y > 0.5) {
502 let d = vec2<f32>(st.x - 0.5, (st.y - 0.5) * q0.w);
503 factor = 1.0 - q0.x * edge_step(q1.y, q1.z, length(d));
504 } else {
505 let uv = st * 2.0 - vec2<f32>(1.0);
506 let r = length(vec2<f32>(uv.x, uv.y * q0.w)) / max(q1.x, 1e-4);
507 factor = 1.0 - q0.x * safe_pow(max(r, 0.0), q0.z);
508 }
509 return vec4<f32>(src.rgb * factor, src.a);
510}
511
512fn grade_grain(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
513 let mixed = mixed_seed();
514 let n = grain_hash(p.x, p.y, mixed ^ 7u) + grain_hash(p.x, p.y, mixed ^ 19u) - 1.0;
515 let add = n * q0.x * (1.0 - q0.y * luma(src.rgb));
516 var out = src.rgb + vec3<f32>(add);
517 if (q0.z > 0.5) { out = max(out, vec3<f32>(0.0)); }
518 return vec4<f32>(out, src.a);
519}
520
521fn grade_dither(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
522 if (abs(q0.x) < 1e-8) { return src; }
523 let h = hash21(f32(p.x) + f32(post.seed % 1024u), f32(p.y) + f32(post.frame % 1024u));
524 let d = (h - 0.5) * q0.x / 255.0;
525 return vec4<f32>(src.rgb + vec3<f32>(d), src.a);
526}
527
528fn grade_encode(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
529 return vec4<f32>(encode_channel(src.r), encode_channel(src.g), encode_channel(src.b), src.a);
530}
531
532fn grade_bw(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
533 let l = dot(src.rgb, vec3<f32>(q0.x, q0.y, q0.z));
534 return vec4<f32>(l, l, l, src.a);
535}
536
537fn grade_noir(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
538 let l = luma(src.rgb);
539 var out = vec3<f32>(l);
540 if (q0.z > 0.5) {
541 let keep = vec3<f32>(q1.x, q1.y, q1.z);
542 let kdir = keep - vec3<f32>(luma(keep));
543 let chroma = src.rgb - vec3<f32>(l);
544 let kn = length(kdir);
545 let cn = length(chroma);
546 if (kn > 1e-4 && cn > 1e-4) {
547 let gate = edge_step(q0.w, 1.0, dot(chroma, kdir) / (kn * cn));
548 out = mix(vec3<f32>(l), src.rgb, gate);
549 }
550 }
551 out = (out - vec3<f32>(0.5)) * q0.x + vec3<f32>(0.5);
552 if (q0.y < 0.999) {
553 out = max((out - vec3<f32>(q0.y)) / max(1.0 - q0.y, 1e-4), vec3<f32>(0.0));
554 }
555 return vec4<f32>(max(out, vec3<f32>(0.0)), src.a);
556}
557
558fn grade_sepia(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
559 let tint = vec3<f32>(
560 dot(src.rgb, vec3<f32>(0.393, 0.769, 0.189)),
561 dot(src.rgb, vec3<f32>(0.349, 0.686, 0.168)),
562 dot(src.rgb, vec3<f32>(0.272, 0.534, 0.131))
563 );
564 return vec4<f32>(mix(src.rgb, tint, clamp(q0.x, 0.0, 1.0)), src.a);
565}
566
567fn grade_posterize(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
568 let levels = max(q0.x, 2.0);
569 return vec4<f32>(quantize(src.r, levels), quantize(src.g, levels), quantize(src.b, levels), src.a);
570}
571
572fn grade_flicker(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
573 let h = grain_hash(post.frame, 0u, post.seed ^ 0xF11CE001u);
574 let scale = 1.0 + (h - 0.5) * q0.x;
575 return vec4<f32>(src.rgb * scale, src.a);
576}
577
578fn grade_scanlines(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
579 if (abs(q0.x) < 1e-8) { return src; }
580 let period = max(u32(q0.y), 2u);
581 var out = src.rgb;
582 if (p.y % period == period - 1u) { out = src.rgb * max(1.0 - q0.x, 0.0); }
583 return vec4<f32>(out, src.a);
584}
585
586fn grade_aperture(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
587 if (abs(q0.x) < 1e-8) { return src; }
588 let s = clamp(q0.x, 0.0, 1.0);
589 var out = src.rgb;
590 let column = p.x % 3u;
591 if (column == 0u) { out = vec3<f32>(src.r, src.g * (1.0 - s), src.b * (1.0 - s)); }
592 else if (column == 1u) { out = vec3<f32>(src.r * (1.0 - s), src.g, src.b * (1.0 - s)); }
593 else { out = vec3<f32>(src.r * (1.0 - s), src.g * (1.0 - s), src.b); }
594 return vec4<f32>(out, src.a);
595}
596
597fn grade_duotone(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
598 let l = clamp(luma(src.rgb), 0.0, 1.0);
599 let shadow = vec3<f32>(q0.x, q0.y, q0.z);
600 let highlight = vec3<f32>(q1.x, q1.y, q1.z);
601 return vec4<f32>(mix(shadow, highlight, l), src.a);
602}
603
604fn grade_gradient_map(src: vec4<f32>, p: vec2<u32>, q0: vec4<f32>, q1: vec4<f32>, q2: vec4<f32>, q3: vec4<f32>) -> vec4<f32> {
605 let l = clamp(luma(src.rgb), 0.0, 1.0);
606 var lower = q0;
607 var upper = q3;
608 if (q0.w <= l) { lower = q0; }
609 if (q1.w <= l) { lower = q1; }
610 if (q2.w <= l) { lower = q2; }
611 if (q3.w <= l) { lower = q3; }
612 if (q0.w >= l) { upper = q0; }
613 else if (q1.w >= l) { upper = q1; }
614 else if (q2.w >= l) { upper = q2; }
615 else { upper = q3; }
616 let span = upper.w - lower.w;
617 var t = 0.0;
618 if (abs(span) >= 1e-6) { t = clamp((l - lower.w) / span, 0.0, 1.0); }
619 return vec4<f32>(mix(lower.xyz, upper.xyz, t), src.a);
620}
621"#
622 };
623}
624
625pub const HEAD: &str = post_head!();
626pub const BLOOM_TEX: &str = bloom_tex!();
627pub const COMMON: &str = post_common!();
628pub const MAIN: &str = post_main!();
629pub const GRADE_OPS: &str = grade_ops!();
630
631pub const EXPOSURE: &str = shader!(grade, "exposure");
632
633pub const BLACK: &str = shader!(grade, "black");
634
635pub const SATURATION: &str = shader!(grade, "saturation");
636
637pub const CONTRAST: &str = shader!(grade, "contrast");
638
639pub const WARMTH: &str = shader!(grade, "warmth");
640
641pub const LUT: &str = shader!(
642 r#"
643@group(0) @binding(3) var<storage, read> lut_buf: array<f32>;
644
645fn lut_at(i: u32) -> vec3<f32> {
646 let b = i * 3u;
647 return vec3<f32>(lut_buf[b], lut_buf[b + 1u], lut_buf[b + 2u]);
648}
649"#,
650 r#"
651 let n = u32(post.p0.x);
652 if (n < 2u) { store(p, src); return; }
653 let scale = f32(n - 1u);
654 let xr = clamp(src.r, 0.0, 1.0) * scale;
655 let yg = clamp(src.g, 0.0, 1.0) * scale;
656 let zb = clamp(src.b, 0.0, 1.0) * scale;
657 let x0 = min(u32(floor(xr)), n - 1u);
658 let y0 = min(u32(floor(yg)), n - 1u);
659 let z0 = min(u32(floor(zb)), n - 1u);
660 let x1 = min(x0 + 1u, n - 1u);
661 let y1 = min(y0 + 1u, n - 1u);
662 let z1 = min(z0 + 1u, n - 1u);
663 let tx = xr - f32(x0);
664 let ty = yg - f32(y0);
665 let tz = zb - f32(z0);
666 let c00 = mix(lut_at(x0 + n * (y0 + n * z0)), lut_at(x1 + n * (y0 + n * z0)), tx);
667 let c10 = mix(lut_at(x0 + n * (y1 + n * z0)), lut_at(x1 + n * (y1 + n * z0)), tx);
668 let c01 = mix(lut_at(x0 + n * (y0 + n * z1)), lut_at(x1 + n * (y0 + n * z1)), tx);
669 let c11 = mix(lut_at(x0 + n * (y1 + n * z1)), lut_at(x1 + n * (y1 + n * z1)), tx);
670 let c0 = mix(c00, c10, ty);
671 let c1 = mix(c01, c11, ty);
672 store(p, vec4<f32>(mix(c0, c1, tz), src.a));
673"#
674);
675
676pub const TONE: &str = shader!(grade, "tone");
677
678pub const VIGNETTE: &str = shader!(grade, "vignette");
679
680pub const GRAIN: &str = shader!(grade, "grain");
681
682pub const DITHER: &str = shader!(grade, "dither");
683
684pub const ENCODE: &str = shader!(grade, "encode");
685
686pub const CEL: &str = shader!(
687 normal,
688 r#"
689 let l = max(luma(src.rgb), 0.0);
690 var light = clamp(l, 0.0, 1.0);
691 var from_normal = false;
692 if (post.p1.w > 0.5) {
693 light = clamp(dot(normalize3(normal_of(i32(p.x), i32(p.y))), normalize3(post.p2.xyz)), 0.0, 1.0);
694 from_normal = true;
695 }
696 let shade = max(band_shade(light, post.p0.x, post.p0.w, post.p0.z, post.p0.y), select(0.18, 0.55, from_normal));
697 let gray = src.r == src.g && src.g == src.b;
698 var out = vec3<f32>(shade);
699 if (from_normal) {
700 out = src.rgb * shade;
701 } else if (!gray && l > 1e-5) {
702 out = src.rgb * (shade / l);
703 }
704 if (post.p1.x > 0.0) {
705 let rough = max(post.p1.y, 0.04);
706 let denom = max(1.0 - post.p1.z, 1e-3);
707 let t = clamp((light - post.p1.z) / denom, 0.0, 1.0);
708 let spec = safe_pow(t, 1.0 / rough) * post.p1.x;
709 out = out + vec3<f32>(spec);
710 }
711 if (from_normal && light < 0.55) {
712 let cool = (0.55 - light) * 0.11;
713 out = out * vec3<f32>(1.0 - cool, 1.0, 1.0) + vec3<f32>(0.0, cool * 0.35, cool);
714 }
715 out = saturate3(out, 1.12);
716 store(p, vec4<f32>(out, src.a));
717"#
718);
719
720pub const OUTLINE: &str = shader!(
721 geometry,
722 r#"
723 if (post.p1.w < 0.5) { store(p, src); return; }
724 let radius = clamp(post.p0.x, 1.0, 8.0);
725 let rad = select(i32(ceil(radius)), 1, radius <= 1.4142135);
726 let has_depth = post.p2.x > 0.5;
727 let has_normal = post.p2.y > 0.5;
728 var edge = 0.0;
729 if (!has_depth && !has_normal) {
730 edge = max(edge, clamp(sobel(i32(p.x), i32(p.y)), 0.0, 1.0));
731 }
732 var center_depth = 0.0;
733 var center_normal = vec3<f32>(0.0, 0.0, 1.0);
734 if (has_depth) { center_depth = depth_of(i32(p.x), i32(p.y)); }
735 if (has_normal) { center_normal = normalize3(normal_of(i32(p.x), i32(p.y))); }
736 if (has_depth || has_normal || post.p2.z > 0.5) {
737 for (var dy = -rad; dy <= rad; dy = dy + 1) {
738 for (var dx = -rad; dx <= rad; dx = dx + 1) {
739 if (dx == 0 && dy == 0) { continue; }
740 let dist = sqrt(f32(dx * dx + dy * dy));
741 if (dist > radius) { continue; }
742 if (post.p2.z > 0.5) {
743 let diff = abs(src.rgb - load_src(vec2<i32>(i32(p.x) + dx, i32(p.y) + dy)).rgb);
744 edge = max(edge, edge_step(0.08, 0.22, max(diff.r, max(diff.g, diff.b))));
745 }
746 if (has_depth) {
747 let gap = abs(center_depth - depth_of(i32(p.x) + dx, i32(p.y) + dy));
748 if (gap > post.p0.z) {
749 edge = max(edge, clamp((gap - post.p0.z) / max(post.p0.z, 1e-4), 0.0, 1.0));
750 }
751 }
752 if (has_normal) {
753 let nn = normalize3(normal_of(i32(p.x) + dx, i32(p.y) + dy));
754 let dots = clamp(dot(center_normal, nn), -1.0, 1.0);
755 if (dots < post.p0.w) {
756 edge = max(edge, clamp((post.p0.w - dots) / max(1.0 + post.p0.w, 0.1), 0.0, 1.0));
757 }
758 }
759 }
760 }
761 }
762 let a = clamp(edge * post.p0.y, 0.0, 1.0);
763 var ink = post.p1.xyz;
764 if (post.p2.z > 0.5) {
765 ink = max(src.rgb * 0.15, vec3<f32>(0.035));
766 }
767 store(p, vec4<f32>(mix(src.rgb, ink, a), src.a));
768"#
769);
770
771pub const CAVITY: &str = shader!(
772 normal,
773 r#"
774 if (post.p0.z < 0.5 || abs(post.p0.x) < 1e-8) { store(p, src); return; }
775 let d = i32(floor(max(post.p0.y, 1.0) + 0.5));
776 let n = normalize3(normal_of(i32(p.x), i32(p.y)));
777 var bend = 0.0;
778 bend = bend + max(1.0 - dot(n, normalize3(normal_of(i32(p.x) + d, i32(p.y)))), 0.0);
779 bend = bend + max(1.0 - dot(n, normalize3(normal_of(i32(p.x) - d, i32(p.y)))), 0.0);
780 bend = bend + max(1.0 - dot(n, normalize3(normal_of(i32(p.x), i32(p.y) + d))), 0.0);
781 bend = bend + max(1.0 - dot(n, normalize3(normal_of(i32(p.x), i32(p.y) - d))), 0.0);
782 let k = max(1.0 - post.p0.x * clamp(bend * 0.25, 0.0, 1.0), 0.0);
783 store(p, vec4<f32>(src.rgb * k, src.a));
784"#
785);
786
787pub const RIM: &str = shader!(
788 normal,
789 r#"
790 if (post.p1.y < 0.5 || abs(post.p0.x) < 1e-8) { store(p, src); return; }
791 let power = clamp(1.0 / max(post.p0.y, 0.05), 0.05, 32.0);
792 let n = normalize3(normal_of(i32(p.x), i32(p.y)));
793 let fres = safe_pow(clamp(1.0 - clamp(abs(n.z), 0.0, 1.0), 0.0, 1.0), power);
794 let color = vec3<f32>(post.p0.z, post.p0.w, post.p1.x);
795 store(p, vec4<f32>(src.rgb + color * fres * post.p0.x, src.a));
796"#
797);
798
799pub const FUSED_CAVITY_RIM: &str = shader!(
800 normal,
801 r#"
802 if (post.p1.w < 0.5) { store(p, src); return; }
803 let x = i32(p.x);
804 let y = i32(p.y);
805 let n = normalize3(normal_of(x, y));
806 let d = i32(floor(max(post.p0.y, 1.0) + 0.5));
807 var bend = 0.0;
808 bend += max(1.0 - dot(n, normalize3(normal_of(x + d, y))), 0.0);
809 bend += max(1.0 - dot(n, normalize3(normal_of(x - d, y))), 0.0);
810 bend += max(1.0 - dot(n, normalize3(normal_of(x, y + d))), 0.0);
811 bend += max(1.0 - dot(n, normalize3(normal_of(x, y - d))), 0.0);
812 let shade = max(1.0 - post.p0.x * clamp(bend * 0.25, 0.0, 1.0), 0.0);
813 let power = clamp(1.0 / max(post.p0.w, 0.05), 0.05, 32.0);
814 let fres = safe_pow(clamp(1.0 - clamp(abs(n.z), 0.0, 1.0), 0.0, 1.0), power);
815 let rim = vec3<f32>(post.p1.x, post.p1.y, post.p1.z) * fres * post.p0.z;
816 store(p, vec4<f32>(src.rgb * shade + rim, src.a));
817"#
818);
819
820pub const BW: &str = shader!(grade, "bw");
821
822pub const NOIR: &str = shader!(grade, "noir");
823
824pub const SEPIA: &str = shader!(grade, "sepia");
825
826pub const POSTERIZE: &str = shader!(grade, "posterize");
827
828pub const NEON: &str = shader!(
829 "",
830 r#"
831 let center = saturate3(src.rgb, post.p0.x);
832 let radius_full = u32(max(post.p0.z, 0.0));
833 let radius = min(radius_full, 8u);
834 if (radius == 0u || abs(post.p0.y) < 1e-8) { store(p, vec4<f32>(center, src.a)); return; }
835 let spread = f32(max(radius_full, 1u)) * 6.0;
836 var acc = vec3<f32>(0.0);
837 var weight = 0.0;
838 for (var dy = -8; dy <= 8; dy = dy + 1) {
839 for (var dx = -8; dx <= 8; dx = dx + 1) {
840 if (u32(abs(dx)) > radius || u32(abs(dy)) > radius) { continue; }
841 let e = clamp(sobel(i32(p.x) + dx, i32(p.y) + dy), 0.0, 4.0);
842 let s = saturate3(load_src(vec2<i32>(i32(p.x) + dx, i32(p.y) + dy)).rgb, post.p0.x);
843 let w = exp(-f32(dx * dx + dy * dy) / spread);
844 acc = acc + s * e * w;
845 weight = weight + w;
846 }
847 }
848 var glow = vec3<f32>(0.0);
849 if (weight > 1e-8) { glow = acc / weight; }
850 store(p, vec4<f32>(center + glow * post.p0.y, src.a));
851"#
852);
853
854pub const NEON_EDGE: &str = shader!(
855 "",
856 r#"
857 let x = i32(p.x * 2u + 1u);
858 let y = i32(p.y * 2u + 1u);
859 let e = clamp(sobel(x, y), 0.0, 4.0);
860 let c = saturate3(load_src(vec2<i32>(x, y)).rgb, post.p0.x);
861 store(p, vec4<f32>(c * e, 1.0));
862"#
863);
864
865pub const NEON_ADD: &str = shader!(
866 bloomtex,
867 r#"
868 let full = max(vec2<f32>(src_dim()), vec2<f32>(1.0));
869 let small = vec2<f32>(bloom_dim());
870 let x = (f32(p.x) + 0.5) / full.x * small.x - 0.5;
871 let y = (f32(p.y) + 0.5) / full.y * small.y - 0.5;
872 let glow = sample_bloom(x, y).rgb;
873 let center = saturate3(src.rgb, post.p0.x);
874 store(p, vec4<f32>(center + glow * post.p0.y, src.a));
875"#
876);
877
878pub const FLICKER: &str = shader!(grade, "flicker");
879
880pub const WEAVE: &str = shader!(
881 "",
882 r#"
883 let hx = grain_hash(post.frame, 1u, post.seed ^ 0x11A00001u);
884 let hy = grain_hash(post.frame, 2u, post.seed ^ 0x22B00002u);
885 let dx = (hx - 0.5) * 2.0 * post.p0.x;
886 let dy = (hy - 0.5) * 4.0 * post.p0.x;
887 store(p, sample_src(f32(p.x) - dx, f32(p.y) - dy));
888"#
889);
890
891pub const DUST: &str = shader!(
892 "",
893 r#"
894 let mixed = mixed_seed() ^ 0xD0570003u;
895 let h = grain_hash(p.x, p.y, mixed);
896 var out = src.rgb;
897 if (post.p0.x > 0.0 && h < post.p0.x) {
898 let a = 0.55 + 0.45 * grain_hash(p.x, p.y, mixed ^ 0x51u);
899 out = out + vec3<f32>(a);
900 }
901 store(p, vec4<f32>(out, src.a));
902"#
903);
904
905pub const SCRATCHES: &str = shader!(
906 "",
907 r#"
908 let count = u32(max(post.p0.x, 0.0));
909 let dim = src_dim();
910 var out = src.rgb;
911 if (post.p0.y != 0.0 && count > 0u && dim.x > 0u) {
912 for (var i = 0u; i < count; i = i + 1u) {
913 let x0 = grain_hash(i, post.frame, post.seed ^ 0x5C8A0005u) * f32(dim.x);
914 let wobble = (grain_hash(i, p.y / 8u, post.seed ^ 0x5C8A0004u) - 0.5) * 3.0;
915 let dist = abs(f32(p.x) - x0 - wobble);
916 if (dist < 0.8) {
917 let gap = grain_hash(i * 97u + p.x, p.y, post.seed ^ post.frame);
918 if (gap > 0.4) {
919 out = out + vec3<f32>(post.p0.y * (1.0 - dist / 0.8));
920 }
921 }
922 }
923 }
924 store(p, vec4<f32>(out, src.a));
925"#
926);
927
928pub const ABERRATION: &str = shader!(
929 "",
930 r#"
931 if (abs(post.p0.x) < 1e-6) { store(p, src); return; }
932 let dim = src_dim();
933 let cx = f32(dim.x - 1u) * 0.5;
934 let cy = f32(dim.y - 1u) * 0.5;
935 let dx = f32(p.x) - cx;
936 let dy = f32(p.y) - cy;
937 let len = sqrt(dx * dx + dy * dy);
938 var ux = 0.0;
939 var uy = 0.0;
940 if (len >= 1e-4) { ux = dx / len; uy = dy / len; }
941 let r = sample_src(f32(p.x) + ux * post.p0.x, f32(p.y) + uy * post.p0.x);
942 let b = sample_src(f32(p.x) - ux * post.p0.x, f32(p.y) - uy * post.p0.x);
943 store(p, vec4<f32>(r.r, src.g, b.b, src.a));
944"#
945);
946
947pub const DISTORTION: &str = shader!(
948 "",
949 r#"
950 if (abs(post.p0.x) < 1e-8) { store(p, src); return; }
951 let dim = max(vec2<f32>(src_dim()), vec2<f32>(1.0));
952 let nx = (f32(p.x) + 0.5) / dim.x * 2.0 - 1.0;
953 let ny = (f32(p.y) + 0.5) / dim.y * 2.0 - 1.0;
954 let r2 = nx * nx + ny * ny;
955 let f = 1.0 + post.p0.x * r2;
956 let sx = (nx * f * 0.5 + 0.5) * dim.x - 0.5;
957 let sy = (ny * f * 0.5 + 0.5) * dim.y - 0.5;
958 store(p, sample_src(sx, sy));
959"#
960);
961
962pub const SCANLINES: &str = shader!(grade, "scanlines");
963
964pub const APERTURE: &str = shader!(grade, "aperture");
965
966pub const ONE_BIT: &str = shader!(
967 "",
968 r#"
969 let v = select(0.0, 1.0, clamp(luma(src.rgb), 0.0, 1.0) >= bayer(p.x, p.y));
970 store(p, vec4<f32>(v, v, v, src.a));
971"#
972);
973
974pub const ONE_BIT_BLUE: &str = shader!(
975 r#"
976@group(0) @binding(3) var<storage, read> blue_buf: array<u32>;
977
978fn blue_at(x: u32, y: u32) -> f32 {
979 let n = 64u;
980 return f32(blue_buf[(y % n) * n + (x % n)] & 255u) / 255.0;
981}
982"#,
983 r#"
984 let v = select(0.0, 1.0, clamp(luma(src.rgb), 0.0, 1.0) >= blue_at(p.x, p.y));
985 store(p, vec4<f32>(v, v, v, src.a));
986"#
987);
988
989pub const HALFTONE: &str = shader!(
990 "",
991 r#"
992 let rad = radians(post.p0.y);
993 let c = cos(rad);
994 let s = sin(rad);
995 let cell = max(post.p0.x, 1.0);
996 let fx = f32(p.x) * c + f32(p.y) * s;
997 let fy = -f32(p.x) * s + f32(p.y) * c;
998 let lx = fract(fx / cell) - 0.5;
999 let ly = fract(fy / cell) - 0.5;
1000 let dist = sqrt(lx * lx + ly * ly);
1001 let radius = sqrt(1.0 - clamp(luma(src.rgb), 0.0, 1.0)) * 0.5;
1002 var out = src.rgb;
1003 if (dist < radius) { out = vec3<f32>(post.p0.z, post.p0.w, post.p1.x); }
1004 store(p, vec4<f32>(out, src.a));
1005"#
1006);
1007
1008pub const COMIC: &str = shader!(
1009 geometry,
1010 r#"
1011 let light = clamp(luma(src.rgb), 0.0, 1.0);
1012 let peak = max(src.r, max(src.g, src.b));
1013 var base = vec3<f32>(0.97, 0.93, 0.78);
1014 if (peak - min(src.r, min(src.g, src.b)) >= 0.12) {
1015 if (src.r >= src.g && src.r >= src.b) {
1016 if (src.g > src.r * 0.75) { base = vec3<f32>(0.98, 0.77, 0.12); }
1017 else { base = vec3<f32>(0.91, 0.12, 0.16); }
1018 } else if (src.g >= src.b) { base = vec3<f32>(0.11, 0.68, 0.39); }
1019 else { base = vec3<f32>(0.12, 0.38, 0.82); }
1020 }
1021 let angle = radians(15.0);
1022 let s = sin(angle);
1023 let c = cos(angle);
1024 let fx = f32(p.x) * c + f32(p.y) * s;
1025 let fy = -f32(p.x) * s + f32(p.y) * c;
1026 let cell = max(post.p0.x, 2.0) + 2.0;
1027 let dx = fract(fx / cell) - 0.5;
1028 let dy = fract(fy / cell) - 0.5;
1029 let radius = sqrt(1.0 - light) * 0.34;
1030 let dist = sqrt(dx * dx + dy * dy);
1031 let dot_ink = 1.0 - edge_step(radius - 0.5 / cell, radius + 0.5 / cell, dist);
1032 let x = i32(p.x);
1033 let y = i32(p.y);
1034 var ink = edge_step(0.12, 0.30, sobel(x, y)) * 0.65;
1035 for (var k = 0; k < 4; k = k + 1) {
1036 var dx = 0;
1037 var dy = 0;
1038 if (k == 0) { dx = -2; }
1039 if (k == 1) { dx = 2; }
1040 if (k == 2) { dy = -2; }
1041 if (k == 3) { dy = 2; }
1042 let diff = abs(src.rgb - load_src(vec2<i32>(x + dx, y + dy)).rgb);
1043 ink = max(ink, edge_step(0.08, 0.22, max(diff.r, max(diff.g, diff.b))));
1044 }
1045 if (post.p0.z > 0.5) {
1046 let z = depth_of(x, y);
1047 ink = max(ink, edge_step(0.002, 0.012, abs(z - depth_of(x - 2, y))));
1048 ink = max(ink, edge_step(0.002, 0.012, abs(z - depth_of(x + 2, y))));
1049 ink = max(ink, edge_step(0.002, 0.012, abs(z - depth_of(x, y - 2))));
1050 ink = max(ink, edge_step(0.002, 0.012, abs(z - depth_of(x, y + 2))));
1051 }
1052 if (post.p0.w > 0.5) {
1053 let n = normalize3(normal_of(x, y));
1054 ink = max(ink, edge_step(0.3, 0.6, 1.0 - dot(n, normalize3(normal_of(x - 1, y)))) * 0.55);
1055 ink = max(ink, edge_step(0.3, 0.6, 1.0 - dot(n, normalize3(normal_of(x + 1, y)))) * 0.55);
1056 ink = max(ink, edge_step(0.3, 0.6, 1.0 - dot(n, normalize3(normal_of(x, y - 1)))) * 0.55);
1057 ink = max(ink, edge_step(0.3, 0.6, 1.0 - dot(n, normalize3(normal_of(x, y + 1)))) * 0.55);
1058 }
1059 let covered = max(ink, dot_ink * 0.55) * clamp(post.p0.y, 0.0, 1.0);
1060 store(p, vec4<f32>(mix(base, vec3<f32>(0.004, 0.003, 0.008), covered), src.a));
1061"#
1062);
1063
1064pub const RUBBER_HOSE: &str = shader!(
1065 geometry,
1066 r#"
1067 let x = i32(p.x);
1068 let y = i32(p.y);
1069 let mixed = mixed_seed();
1070 let wobble = grain_hash(p.x / 32u, p.y / 32u, mixed);
1071 var shift = 0;
1072 if (wobble < 0.33) { shift = -1; }
1073 else if (wobble > 0.66) { shift = 1; }
1074 let radius = 5 + select(0, 1, grain_hash(p.x / 48u, p.y / 48u, mixed ^ 0x12ab9921u) > 0.5);
1075 var edge = 0.0;
1076 if (post.p0.x > 0.5) {
1077 let z = depth_of(x + shift, y);
1078 edge = max(edge, edge_step(0.002, 0.012, abs(z - depth_of(x + shift + radius, y))));
1079 edge = max(edge, edge_step(0.002, 0.012, abs(z - depth_of(x + shift - radius, y))));
1080 edge = max(edge, edge_step(0.002, 0.012, abs(z - depth_of(x + shift, y + radius))));
1081 edge = max(edge, edge_step(0.002, 0.012, abs(z - depth_of(x + shift, y - radius))));
1082 }
1083 if (post.p0.y > 0.5) {
1084 let n = normalize3(normal_of(x + shift, y));
1085 edge = max(edge, edge_step(0.3, 0.65, 1.0 - dot(n, normalize3(normal_of(x + shift + 2, y)))));
1086 edge = max(edge, edge_step(0.3, 0.65, 1.0 - dot(n, normalize3(normal_of(x + shift - 2, y)))));
1087 edge = max(edge, edge_step(0.3, 0.65, 1.0 - dot(n, normalize3(normal_of(x + shift, y + 2)))));
1088 edge = max(edge, edge_step(0.3, 0.65, 1.0 - dot(n, normalize3(normal_of(x + shift, y - 2)))));
1089 }
1090 edge = max(edge, edge_step(0.12, 0.32, sobel(x + shift, y)) * 0.7);
1091 let center = load_src(vec2<i32>(x + shift, y)).rgb;
1092 let dx0 = abs(center - load_src(vec2<i32>(x + shift + radius, y)).rgb);
1093 let dx1 = abs(center - load_src(vec2<i32>(x + shift - radius, y)).rgb);
1094 let dy0 = abs(center - load_src(vec2<i32>(x + shift, y + radius)).rgb);
1095 let dy1 = abs(center - load_src(vec2<i32>(x + shift, y - radius)).rgb);
1096 edge = max(edge, edge_step(0.08, 0.22, max(dx0.r, max(dx0.g, dx0.b))));
1097 edge = max(edge, edge_step(0.08, 0.22, max(dx1.r, max(dx1.g, dx1.b))));
1098 edge = max(edge, edge_step(0.08, 0.22, max(dy0.r, max(dy0.g, dy0.b))));
1099 edge = max(edge, edge_step(0.08, 0.22, max(dy1.r, max(dy1.g, dy1.b))));
1100 let l = clamp(luma(src.rgb), 0.0, 1.0);
1101 var dark = l < 0.18;
1102 if (post.p0.y > 0.5) {
1103 let n = normalize3(normal_of(x, y));
1104 dark = dark || (n.z > 0.1 && dot(n, normalize3(vec3<f32>(-0.45, 0.65, 0.61))) < 0.1);
1105 }
1106 dark = dark && (post.p0.x < 0.5 || depth_of(x, y) < 0.999);
1107 let fill = select(0.77, 0.91, l > 0.56);
1108 let grain = (grain_hash(p.x, p.y, mixed) - 0.5) * 0.07;
1109 let flicker = (grain_hash(0u, 0u, mixed ^ 0x55aa10efu) - 0.5) * 0.08;
1110 let u = (f32(p.x) / max(f32(post.size.x), 1.0) - 0.5) * 2.0;
1111 let v = (f32(p.y) / max(f32(post.size.y), 1.0) - 0.5) * 2.0;
1112 let vignette = 1.0 - 0.25 * min(u * u + v * v, 1.0);
1113 let dust = grain_hash(p.x / 3u, p.y / 3u, mixed ^ 0x29837781u) < 0.0008;
1114 let light = clamp((fill + grain + flicker) * vignette, 0.0, 1.0);
1115 let ink = select(light, 0.003, edge > 0.5 || dust || dark);
1116 store(p, vec4<f32>(ink * vec3<f32>(1.0, 0.91, 0.73), src.a));
1117"#
1118);
1119
1120pub const MODERN_COMIC: &str = shader!(
1121 geometry,
1122 r#"
1123 let x = i32(p.x);
1124 let y = i32(p.y);
1125 let left_color = load_src(vec2<i32>(x - 1, y)).rgb;
1126 let right_color = load_src(vec2<i32>(x + 1, y)).rgb;
1127 let up_color = load_src(vec2<i32>(x, y - 1)).rgb;
1128 let down_color = load_src(vec2<i32>(x, y + 1)).rgb;
1129 let filtered = (src.rgb * 4.0 + left_color + right_color + up_color + down_color) * 0.125;
1130 let light = clamp(luma(filtered), 0.0, 1.0);
1131 let left = luma(left_color);
1132 let right = luma(right_color);
1133 let up = luma(up_color);
1134 let down = luma(down_color);
1135 let gradient = max(abs(left - right), abs(up - down)) * 0.5;
1136 let steps = clamp(post.p0.x, 3.0, 5.0);
1137 let scaled = light * steps;
1138 let base = min(floor(scaled), steps - 1.0);
1139 let aa = clamp(gradient * steps * 0.5, 0.002, 0.18);
1140 let blend = edge_step(1.0 - aa, 1.0, scaled - base);
1141 let low = (base + 0.5) / steps;
1142 var high = (base + 1.5) / steps;
1143 if (base >= steps - 2.0) { high = 1.0; }
1144 var tone = mix(low, high, blend);
1145 if (base >= steps - 1.0 || (light >= post.p1.y && base >= steps - 2.0)) { tone = 1.0; }
1146 let scale = tone / max(light, 0.025);
1147 let black_aa = max(gradient * 0.5, 0.001);
1148 let black_gate = edge_step(post.p0.y - black_aa, post.p0.y + black_aa, light);
1149 var out = clamp(saturate3(clamp(filtered * scale, vec3<f32>(0.0), vec3<f32>(1.0)), post.p1.x) * black_gate, vec3<f32>(0.0), vec3<f32>(1.0));
1150 let surround = (clamp(luma_at(x - 2, y), 0.0, 1.0) + clamp(luma_at(x + 2, y), 0.0, 1.0) + clamp(luma_at(x, y - 2), 0.0, 1.0) + clamp(luma_at(x, y + 2), 0.0, 1.0)) * 0.25;
1151 let detail = max(surround - luma(src.rgb), gradient * 0.35);
1152 let feature = edge_step(0.12, 0.26, detail) * clamp(post.p0.w, 0.0, 1.0);
1153 var silhouette = 0.0;
1154 var crease = 0.0;
1155 var distance = 1;
1156 if (post.p1.z > 0.5) { distance = select(2, 3, depth_of(x, y) < 0.55); }
1157 for (var k = 0; k < 4; k = k + 1) {
1158 var dx = 0;
1159 var dy = 0;
1160 if (k == 0) { dx = distance; }
1161 if (k == 1) { dx = -distance; }
1162 if (k == 2) { dy = distance; }
1163 if (k == 3) { dy = -distance; }
1164 if (post.p1.z > 0.5) {
1165 let diff = abs(src.rgb - load_src(vec2<i32>(x + dx, y + dy)).rgb);
1166 silhouette = max(silhouette, edge_step(0.10, 0.28, max(diff.r, max(diff.g, diff.b))));
1167 let gap = abs(depth_of(x, y) - depth_of(x + dx, y + dy));
1168 silhouette = max(silhouette, edge_step(0.002, 0.015, gap));
1169 }
1170 if (post.p1.w > 0.5) {
1171 let a = normalize3(normal_of(x, y));
1172 let b = normalize3(normal_of(x + sign(dx), y + sign(dy)));
1173 crease = max(crease, edge_step(0.16, 0.48, 1.0 - dot(a, b)));
1174 }
1175 }
1176 let weight = clamp(post.p0.z, 0.0, 1.0);
1177 let ink = clamp(max(max(silhouette * weight, crease * weight * 0.78), feature), 0.0, 1.0);
1178 out = mix(out, vec3<f32>(0.005, 0.004, 0.008), ink);
1179 store(p, vec4<f32>(out, src.a));
1180"#
1181);
1182
1183pub const WATERCOLOR: &str = shader!(
1184 "",
1185 r#"
1186 let scale = max(post.p0.z, 1.0);
1187 let e = clamp(sobel(i32(p.x), i32(p.y)), 0.0, 1.0);
1188 let dark = max(1.0 - post.p0.x * e, 0.0);
1189 let n = value_noise(f32(p.x) / scale, f32(p.y) / scale, mixed_seed() ^ 0xA9E10006u);
1190 let grain = 1.0 + (n - 0.5) * post.p0.y;
1191 store(p, vec4<f32>(max(src.rgb * dark * grain, vec3<f32>(0.0)), src.a));
1192"#
1193);
1194
1195pub const PAPER_GRAIN: &str = shader!(
1196 "",
1197 r#"
1198 let scale = max(post.p0.y, 1.0);
1199 let n = value_noise(f32(p.x) / scale, f32(p.y) / scale, mixed_seed() ^ 0xA9E10006u);
1200 let grain = 1.0 + (n - 0.5) * post.p0.x;
1201 store(p, vec4<f32>(max(src.rgb * grain, vec3<f32>(0.0)), src.a));
1202"#
1203);
1204
1205pub const PIXEL: &str = shader!(
1206 "",
1207 r#"
1208 let dim = src_dim();
1209 let size = max(u32(post.p0.x), 1u);
1210 let x0 = (p.x / size) * size;
1211 let y0 = (p.y / size) * size;
1212 let x1 = min(x0 + size, dim.x);
1213 let y1 = min(y0 + size, dim.y);
1214 var acc = vec3<f32>(0.0);
1215 var n = 0.0;
1216 for (var y = y0; y < y1; y = y + 1u) {
1217 for (var x = x0; x < x1; x = x + 1u) {
1218 acc = acc + load_src(vec2<i32>(i32(x), i32(y))).rgb;
1219 n = n + 1.0;
1220 }
1221 }
1222 var mean = acc / max(n, 1.0);
1223 if (post.p0.z > 0.5) {
1224 mean = nearest_palette(mean);
1225 } else if (post.p0.y >= 2.0) {
1226 mean = vec3<f32>(quantize(mean.r, post.p0.y), quantize(mean.g, post.p0.y), quantize(mean.b, post.p0.y));
1227 }
1228 store(p, vec4<f32>(mean, src.a));
1229"#
1230);
1231
1232pub const DUOTONE: &str = shader!(grade, "duotone");
1233
1234pub const GRADIENT: &str = shader!(grade, "gradient_map");
1235
1236pub const KUWAHARA: &str = shader!(
1237 "",
1238 r#"
1239 let r = i32(min(u32(max(post.p0.x, 0.0)), 8u));
1240 if (r <= 0) { store(p, src); return; }
1241 var best = vec3<f32>(0.0);
1242 var best_var = 1e30;
1243 for (var q = 0; q < 4; q = q + 1) {
1244 var x0 = -r;
1245 var x1 = 0;
1246 var y0 = -r;
1247 var y1 = 0;
1248 if (q == 1 || q == 3) { x0 = 0; x1 = r; }
1249 if (q == 2 || q == 3) { y0 = 0; y1 = r; }
1250 var sum = vec3<f32>(0.0);
1251 var sq = 0.0;
1252 var n = 0.0;
1253 for (var dy = y0; dy <= y1; dy = dy + 1) {
1254 for (var dx = x0; dx <= x1; dx = dx + 1) {
1255 let s = load_src(vec2<i32>(i32(p.x) + dx, i32(p.y) + dy));
1256 sum = sum + s.rgb;
1257 let l = luma(s.rgb);
1258 sq = sq + l * l;
1259 n = n + 1.0;
1260 }
1261 }
1262 let mean = sum / n;
1263 let ml = luma(mean);
1264 let var_ = max(sq / n - ml * ml, 0.0);
1265 if (var_ < best_var) { best_var = var_; best = mean; }
1266 }
1267 store(p, vec4<f32>(best, src.a));
1268"#
1269);
1270
1271pub const TILT_SHIFT: &str = shader!(
1272 depth,
1273 r#"
1274 if (post.p0.w < 0.5 || post.p0.z < 0.35 || abs(post.p0.y) < 1e-6) { store(p, src); return; }
1275 let z = depth_of(i32(p.x), i32(p.y));
1276 let coc = clamp(abs(z - post.p0.x) / post.p0.y, 0.0, 1.0) * post.p0.z;
1277 if (coc < 0.35) { store(p, src); return; }
1278 var acc = src;
1279 var weight = 1.0;
1280 for (var k = 0u; k < 8u; k = k + 1u) {
1281 let a = f32(k) * 6.283185307179586 / 8.0;
1282 let d = coc * sqrt((f32(k) + 0.5) / 8.0);
1283 acc = acc + sample_src(f32(p.x) + cos(a) * d, f32(p.y) + sin(a) * d);
1284 weight = weight + 1.0;
1285 }
1286 store(p, acc / weight);
1287"#
1288);
1289
1290pub const BLOOM_DOWN: &str = shader!(
1291 "",
1292 r#"
1293 let factor_f = max(post.p0.x, 1.0);
1294 let factor = max(u32(factor_f), 1u);
1295 var acc = vec3<f32>(0.0);
1296 if (post.p1.y > 0.5) {
1297 let base_x = f32(p.x) * factor_f + factor_f * 0.5 - 0.5;
1298 let base_y = f32(p.y) * factor_f + factor_f * 0.5 - 0.5;
1299 let s00 = sample_src(base_x - 1.0, base_y - 1.0);
1300 let s10 = sample_src(base_x + 1.0, base_y - 1.0);
1301 let s01 = sample_src(base_x - 1.0, base_y + 1.0);
1302 let s11 = sample_src(base_x + 1.0, base_y + 1.0);
1303 acc = (s00.rgb + s10.rgb + s01.rgb + s11.rgb) * 0.25;
1304 } else {
1305 let span = min(factor, 64u);
1306 let base_x = i32(p.x) * i32(factor);
1307 let base_y = i32(p.y) * i32(factor);
1308 var n = 0.0;
1309 for (var dy = 0u; dy < span; dy = dy + 1u) {
1310 for (var dx = 0u; dx < span; dx = dx + 1u) {
1311 acc = acc + load_src(vec2<i32>(base_x + i32(dx), base_y + i32(dy))).rgb;
1312 n = n + 1.0;
1313 }
1314 }
1315 acc = acc / max(n, 1.0);
1316 }
1317 let kept = keep_rgb(acc * post.p1.z, post.p0.y, post.p0.z, post.p0.w, post.p1.x);
1318 store(p, vec4<f32>(kept, 1.0));
1319"#
1320);
1321
1322pub const BLOOM_DOWN_BOX2: &str = shader!(
1323 "",
1324 r#"
1325 let base = vec2<i32>(p) * 2;
1326 let acc = (
1327 load_src(base).rgb
1328 + load_src(base + vec2<i32>(1, 0)).rgb
1329 + load_src(base + vec2<i32>(0, 1)).rgb
1330 + load_src(base + vec2<i32>(1, 1)).rgb
1331 ) * 0.25;
1332 let kept = keep_rgb(acc * post.p1.z, post.p0.y, post.p0.z, post.p0.w, post.p1.x);
1333 store(p, vec4<f32>(kept, 1.0));
1334"#
1335);
1336
1337pub const BLOOM_BLUR: &str = shader!(
1338 "",
1339 r#"
1340 let radius = u32(max(post.p0.z, 0.0));
1341 store(p, gaussian(p, i32(post.p0.x), i32(post.p0.y), radius, post.p0.w));
1342"#
1343);
1344
1345pub fn bloom_blur_linear(radius: u32, spread: f32) -> String {
1346 let (center, taps) = crate::bloom::linear_taps(radius, spread);
1347 let list = |part: fn(&(f64, f64)) -> f64| {
1348 if taps.is_empty() {
1349 "0.0".to_string()
1350 } else {
1351 taps.iter()
1352 .map(|tap| format!("{:.10}", part(tap)))
1353 .collect::<Vec<_>>()
1354 .join(", ")
1355 }
1356 };
1357 let body = format!(
1358 " let weights = array<f32, {slots}>({weights});\n let offsets = array<f32, {slots}>({offsets});\n let size = vec2<f32>(textureDimensions(src_tex));\n let edge = select(vec2<f32>(1e30), vec2<f32>(src_dim()) - vec2<f32>(0.5), src_dim() < textureDimensions(src_tex));\n let center = vec2<f32>(p) + vec2<f32>(0.5);\n let direction = vec2<f32>(post.p0.x, post.p0.y);\n var acc = load_src(vec2<i32>(p)) * {center:.10};\n for (var k = 0u; k < {count}u; k = k + 1u) {{\n let offset = direction * offsets[k];\n let left = textureSampleLevel(src_tex, blur_sampler, min(center - offset, edge) / size, 0.0);\n let right = textureSampleLevel(src_tex, blur_sampler, min(center + offset, edge) / size, 0.0);\n acc = acc + (left + right) * weights[k];\n }}\n store(p, acc);\n",
1359 slots = taps.len().max(1),
1360 weights = list(|tap| tap.0),
1361 offsets = list(|tap| tap.1),
1362 count = taps.len(),
1363 );
1364 format!(
1365 "{}{}{}{}{}\n}}\n",
1366 post_head!(),
1367 "@group(0) @binding(3) var blur_sampler: sampler;\n",
1368 post_common!(),
1369 post_main!(),
1370 body
1371 )
1372}
1373
1374pub const BLOOM_ADD: &str = shader!(
1375 bloomtex,
1376 r#"
1377 if (abs(post.p0.x) < 1e-8) { store(p, src); return; }
1378 let full = max(vec2<f32>(src_dim()), vec2<f32>(1.0));
1379 let small = vec2<f32>(bloom_dim());
1380 let u = (f32(p.x) + 0.5) / full.x;
1381 let v = (f32(p.y) + 0.5) / full.y;
1382 let s = sample_bloom(u * small.x - 0.5, v * small.y - 0.5);
1383 store(p, vec4<f32>(src.rgb + s.rgb * post.p0.x, src.a));
1384"#
1385);
1386
1387macro_rules! bloom_linear {
1388 () => {
1389 r#"
1390@group(0) @binding(3) var bloom_tex: texture_2d<f32>;
1391@group(0) @binding(4) var bloom_sampler: sampler;
1392
1393fn bloom_uv(p: vec2<u32>) -> vec2<f32> {
1394 let size = textureDimensions(bloom_tex);
1395 let small = min(size, post.extent.zw);
1396 let ratio = select(vec2<f32>(small) / vec2<f32>(size), vec2<f32>(1.0), small == size);
1397 let edge = select(vec2<f32>(1e30), (vec2<f32>(small) - vec2<f32>(0.5)) / vec2<f32>(size), small < size);
1398 return min((vec2<f32>(p) + vec2<f32>(0.5)) / vec2<f32>(src_dim()) * ratio, edge);
1399}
1400"#
1401 };
1402}
1403
1404pub const BLOOM_LINEAR: &str = bloom_linear!();
1405
1406pub const BLOOM_ADD_LINEAR: &str = shader!(
1407 bloom_linear!(),
1408 r#"
1409 let uv = bloom_uv(p);
1410 let bloom = textureSampleLevel(bloom_tex, bloom_sampler, uv, 0.0);
1411 store(p, vec4<f32>(src.rgb + bloom.rgb * post.p0.x, src.a));
1412"#
1413);
1414
1415pub const BLOOM_RINGS: &str = shader!(
1416 "",
1417 r#"
1418 var acc = vec3<f32>(0.0);
1419 var weight = 0.0;
1420 for (var ring = 0u; ring < 4u; ring = ring + 1u) {
1421 let radius = ring_radius(ring) * post.p0.w;
1422 let gain = ring_gain(ring);
1423 for (var i = 0u; i < 20u; i = i + 1u) {
1424 let a = f32(i) * 6.283185307179586 / 20.0 + f32(ring) * 0.37;
1425 let s = sample_src(f32(p.x) + cos(a) * radius, f32(p.y) + sin(a) * radius);
1426 acc = acc + s.rgb * gain;
1427 }
1428 weight = weight + 20.0 * gain;
1429 }
1430 if (weight > 1e-8) {
1431 store(p, vec4<f32>(acc / weight, 1.0));
1432 } else {
1433 store(p, src);
1434 }
1435"#
1436);
1437
1438pub const BLOOM_RING_DOWN: &str = shader!(
1439 "",
1440 r#"
1441 let factor = max(post.p0.x, 1.0);
1442 let x = f32(p.x) * factor + factor * 0.5 - 0.5;
1443 let y = f32(p.y) * factor + factor * 0.5 - 0.5;
1444 let c = (sample_src(x - 1.0, y - 1.0).rgb + sample_src(x + 1.0, y - 1.0).rgb + sample_src(x - 1.0, y + 1.0).rgb + sample_src(x + 1.0, y + 1.0).rgb) * 0.25;
1445 var v = max(c * post.p0.w - vec3<f32>(post.p0.y), vec3<f32>(0.0));
1446 if (post.p0.z > 0.0) { v = min(v, vec3<f32>(post.p0.z)); }
1447 store(p, vec4<f32>(v, 1.0));
1448"#
1449);
1450
1451pub const HALATION_HOT: &str = shader!(
1452 "",
1453 r#"
1454 let factor = post.p0.y;
1455 let x = f32(p.x) * factor + factor * 0.5 - 0.5;
1456 let y = f32(p.y) * factor + factor * 0.5 - 0.5;
1457 let c = (sample_src(x - 1.0, y - 1.0).rgb + sample_src(x + 1.0, y - 1.0).rgb + sample_src(x - 1.0, y + 1.0).rgb + sample_src(x + 1.0, y + 1.0).rgb) * 0.25;
1458 let h = max(luma(c) - post.p0.x, 0.0);
1459 store(p, vec4<f32>(h, h * 0.25, h * 0.05, src.a));
1460"#
1461);
1462
1463pub const HALATION_ADD: &str = shader!(
1464 bloomtex,
1465 r#"
1466 if (abs(post.p0.x) < 1e-8) { store(p, src); return; }
1467 let full = max(vec2<f32>(src_dim()), vec2<f32>(1.0));
1468 let small = vec2<f32>(bloom_dim());
1469 let u = (f32(p.x) + 0.5) / full.x;
1470 let v = (f32(p.y) + 0.5) / full.y;
1471 let s = sample_bloom(u * small.x - 0.5, v * small.y - 0.5);
1472 store(p, vec4<f32>(src.rgb + s.rgb * post.p0.x, src.a));
1473"#
1474);