Skip to main content

pfx_post/
chain.rs

1use crate::bloom::{self, Bloom};
2use crate::comic::{self, ModernComic};
3use crate::fx::{self, NoiseKind};
4use crate::grade::{self, Contrast, WarmthKind};
5use crate::image::Image;
6use crate::tape::{self, Tape};
7use crate::tone::Tone;
8use crate::wgsl;
9use std::borrow::Cow;
10use std::fmt::Write;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct Buffers {
14    pub depth: bool,
15    pub normal: bool,
16}
17
18impl Buffers {
19    pub const NONE: Self = Self {
20        depth: false,
21        normal: false,
22    };
23
24    pub const GEOMETRY: Self = Self {
25        depth: true,
26        normal: true,
27    };
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum Aux {
32    None,
33    Depth,
34    Normal,
35    DepthNormal,
36    Bloom,
37    BloomB,
38    BloomLinear,
39    LinearSampler,
40    Lut,
41    Blue,
42    Grade,
43    GradeBloom,
44    GradeBloomB,
45    GradeBloomLinear,
46}
47
48#[derive(Clone, Debug)]
49pub struct GpuDispatch {
50    pub shader: Cow<'static, str>,
51    pub params: [f32; 16],
52    pub aux: Aux,
53}
54
55#[repr(C)]
56#[derive(Clone, Copy, Debug)]
57pub struct Uniforms {
58    pub size: [u32; 2],
59    pub frame: u32,
60    pub seed: u32,
61    pub params: [f32; 16],
62    pub region: [u32; 4],
63    pub extent: [u32; 4],
64}
65
66impl Uniforms {
67    pub fn new(width: u32, height: u32, frame: u32, seed: u32, params: [f32; 16]) -> Self {
68        Self {
69            size: [width, height],
70            frame,
71            seed,
72            params,
73            region: [0; 4],
74            extent: [width, height, width, height],
75        }
76    }
77
78    pub fn region(mut self, region: [u32; 4]) -> Self {
79        self.region = region;
80        self
81    }
82
83    pub fn extent(mut self, source: (u32, u32), aux: (u32, u32)) -> Self {
84        self.extent = [source.0, source.1, aux.0, aux.1];
85        self
86    }
87
88    pub fn bytes(&self) -> [u8; 112] {
89        let mut out = [0u8; 112];
90        out[0..4].copy_from_slice(&self.size[0].to_ne_bytes());
91        out[4..8].copy_from_slice(&self.size[1].to_ne_bytes());
92        out[8..12].copy_from_slice(&self.frame.to_ne_bytes());
93        out[12..16].copy_from_slice(&self.seed.to_ne_bytes());
94        for (i, value) in self.params.iter().enumerate() {
95            let at = 16 + i * 4;
96            out[at..at + 4].copy_from_slice(&value.to_ne_bytes());
97        }
98        for (i, value) in self.region.iter().enumerate() {
99            let at = 80 + i * 4;
100            out[at..at + 4].copy_from_slice(&value.to_ne_bytes());
101        }
102        for (i, value) in self.extent.iter().enumerate() {
103            let at = 96 + i * 4;
104            out[at..at + 4].copy_from_slice(&value.to_ne_bytes());
105        }
106        out
107    }
108}
109
110pub const BIND_UNIFORM: u32 = 0;
111pub const BIND_SRC: u32 = 1;
112pub const BIND_DST: u32 = 2;
113pub const BIND_AUX: u32 = 3;
114pub const BIND_NORMAL: u32 = 4;
115
116#[derive(Clone, Debug)]
117pub enum Pass {
118    Exposure(f32),
119    Black(f32),
120    Bloom(Bloom),
121    Saturation(grade::Saturation),
122    Contrast(Contrast),
123    Warmth(grade::Warmth),
124    Lut(grade::Lut),
125    Tone(Tone),
126    Vignette(grade::Vignette),
127    Grain(grade::Grain),
128    Dither(grade::Dither),
129    Encode,
130    Cel(fx::Cel),
131    Outline(fx::Outline),
132    Cavity(fx::Cavity),
133    Rim(fx::Rim),
134    Bw(fx::Bw),
135    Noir(fx::Noir),
136    Sepia(f32),
137    Posterize(f32),
138    Neon(fx::Neon),
139    Flicker(f32),
140    Weave(f32),
141    Dust(f32),
142    Scratches(fx::Scratches),
143    Halation(fx::Halation),
144    Aberration(f32),
145    Distortion(f32),
146    Curvature(f32),
147    Scanlines(fx::Scanlines),
148    Aperture(f32),
149    OneBit(fx::OneBit),
150    Halftone(fx::Halftone),
151    Comic(fx::Comic),
152    RubberHose,
153    ModernComic(ModernComic),
154    Watercolor(fx::Watercolor),
155    PaperGrain(fx::PaperGrain),
156    Pixel(fx::Pixelate),
157    Duotone(fx::Duotone),
158    GradientMap(fx::GradientMap),
159    Kuwahara(u32),
160    TiltShift(fx::TiltShift),
161    Tape(Tape),
162    Grade(Vec<Pass>),
163    FusedCavityRim(Vec<Pass>),
164}
165
166#[derive(Clone, Debug)]
167pub struct Chain {
168    pub passes: Vec<Pass>,
169    pub seed: u32,
170    pub frame: u32,
171}
172
173impl Chain {
174    pub fn new() -> Self {
175        Self {
176            passes: Vec::new(),
177            seed: 0,
178            frame: 0,
179        }
180    }
181
182    pub fn is_local(&self) -> bool {
183        self.passes.iter().all(Pass::is_local)
184    }
185
186    pub fn apply(
187        &self,
188        image: &Image,
189        depth: Option<&[f32]>,
190        normal: Option<&[[f32; 3]]>,
191    ) -> Image {
192        let depth = depth.filter(|d| d.len() == image.pixels.len());
193        let normal = normal.filter(|n| n.len() == image.pixels.len());
194        let mut current = image.clone();
195        for pass in &self.passes {
196            current = pass.run(&current, depth, normal, self.frame, self.seed);
197        }
198        current
199    }
200
201    pub fn split_after_bloom(&self) -> (Self, Self) {
202        let boundary = self
203            .passes
204            .iter()
205            .position(|pass| matches!(pass, Pass::Bloom(_)))
206            .map_or(0, |index| index + 1);
207        (
208            Self {
209                passes: self.passes[..boundary].to_vec(),
210                seed: self.seed,
211                frame: self.frame,
212            },
213            Self {
214                passes: self.passes[boundary..].to_vec(),
215                seed: self.seed,
216                frame: self.frame,
217            },
218        )
219    }
220}
221
222impl Default for Chain {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228impl Pass {
229    pub fn name(&self) -> &'static str {
230        match self {
231            Self::Exposure(_) => "exposure",
232            Self::Black(_) => "black",
233            Self::Bloom(_) => "bloom",
234            Self::Saturation(_) => "saturation",
235            Self::Contrast(_) => "contrast",
236            Self::Warmth(_) => "warmth",
237            Self::Lut(_) => "lut",
238            Self::Tone(_) => "tone",
239            Self::Vignette(_) => "vignette",
240            Self::Grain(_) => "grain",
241            Self::Dither(_) => "dither",
242            Self::Encode => "encode",
243            Self::Cel(_) => "cel",
244            Self::Outline(_) => "outline",
245            Self::Cavity(_) => "cavity",
246            Self::Rim(_) => "rim",
247            Self::Bw(_) => "bw",
248            Self::Noir(_) => "noir",
249            Self::Sepia(_) => "sepia",
250            Self::Posterize(_) => "posterize",
251            Self::Neon(_) => "neon",
252            Self::Flicker(_) => "flicker",
253            Self::Weave(_) => "weave",
254            Self::Dust(_) => "dust",
255            Self::Scratches(_) => "scratches",
256            Self::Halation(_) => "halation",
257            Self::Aberration(_) => "aberration",
258            Self::Distortion(_) => "distortion",
259            Self::Curvature(_) => "curvature",
260            Self::Scanlines(_) => "scanlines",
261            Self::Aperture(_) => "aperture",
262            Self::OneBit(_) => "one_bit",
263            Self::Halftone(_) => "halftone",
264            Self::Comic(_) => "comic",
265            Self::RubberHose => "rubber_hose",
266            Self::ModernComic(_) => "modern_comic",
267            Self::Watercolor(_) => "watercolor",
268            Self::PaperGrain(_) => "paper_grain",
269            Self::Pixel(_) => "pixel",
270            Self::Duotone(_) => "duotone",
271            Self::GradientMap(_) => "gradient_map",
272            Self::Kuwahara(_) => "kuwahara",
273            Self::TiltShift(_) => "tilt_shift",
274            Self::Tape(_) => "tape",
275            Self::Grade(_) => "grade",
276            Self::FusedCavityRim(_) => "fused_cavity_rim",
277        }
278    }
279
280    pub fn is_identity(&self) -> bool {
281        match self {
282            Self::Exposure(v) => (*v - 1.0).abs() < 1e-8,
283            Self::Bloom(v) => v.strength.abs() < 1e-8,
284            Self::Vignette(v) => v.strength.abs() < 1e-8,
285            Self::Saturation(v) => (v.amount - 1.0).abs() < 1e-8,
286            Self::Warmth(v) => v.amount.abs() < 1e-8,
287            Self::Halation(v) => v.strength.abs() < 1e-8,
288            _ => false,
289        }
290    }
291
292    pub fn run(
293        &self,
294        image: &Image,
295        depth: Option<&[f32]>,
296        normal: Option<&[[f32; 3]]>,
297        frame: u32,
298        seed: u32,
299    ) -> Image {
300        match self {
301            Self::Exposure(v) => grade::exposure(image, *v),
302            Self::Black(v) => grade::black(image, *v),
303            Self::Bloom(v) => bloom::apply(image, v),
304            Self::Saturation(v) => grade::saturation(image, v),
305            Self::Contrast(v) => grade::contrast(image, v),
306            Self::Warmth(v) => grade::warmth(image, v),
307            Self::Lut(v) => grade::lut(image, v),
308            Self::Tone(v) => grade::tone(image, v),
309            Self::Vignette(v) => grade::vignette(image, v),
310            Self::Grain(v) => grade::grain(image, v, frame, seed),
311            Self::Dither(v) => grade::dither(image, v, frame, seed),
312            Self::Encode => grade::encode(image),
313            Self::Cel(v) => fx::cel(image, v, normal),
314            Self::Outline(v) => fx::outline(image, v, depth, normal),
315            Self::Cavity(v) => fx::cavity(image, v, normal),
316            Self::Rim(v) => fx::rim(image, v, normal),
317            Self::Bw(v) => fx::bw(image, v),
318            Self::Noir(v) => fx::noir(image, v),
319            Self::Sepia(v) => fx::sepia(image, *v),
320            Self::Posterize(v) => fx::posterize(image, *v),
321            Self::Neon(v) => fx::neon(image, v),
322            Self::Flicker(v) => fx::flicker(image, *v, frame, seed),
323            Self::Weave(v) => fx::weave(image, *v, frame, seed),
324            Self::Dust(v) => fx::dust(image, *v, frame, seed),
325            Self::Scratches(v) => fx::scratches(image, v, frame, seed),
326            Self::Halation(v) => fx::halation(image, v),
327            Self::Aberration(v) => fx::aberration(image, *v),
328            Self::Distortion(v) | Self::Curvature(v) => fx::distort(image, *v),
329            Self::Scanlines(v) => fx::scanlines(image, v),
330            Self::Aperture(v) => fx::aperture(image, *v),
331            Self::OneBit(v) => fx::one_bit(image, v),
332            Self::Halftone(v) => fx::halftone(image, v),
333            Self::Comic(v) => fx::comic(image, v, depth, normal),
334            Self::RubberHose => fx::rubber_hose(image, depth, normal, frame, seed),
335            Self::ModernComic(v) => comic::apply(image, v, depth, normal),
336            Self::Watercolor(v) => fx::watercolor(image, v, frame, seed),
337            Self::PaperGrain(v) => fx::paper_grain(image, v, frame, seed),
338            Self::Pixel(v) => fx::pixelate(image, v),
339            Self::Duotone(v) => fx::duotone(image, v),
340            Self::GradientMap(v) => fx::gradient_map(image, v),
341            Self::Kuwahara(v) => fx::kuwahara(image, *v),
342            Self::TiltShift(v) => fx::tilt_shift(image, v, depth),
343            Self::Tape(v) => tape::apply(image, v, frame, seed),
344            Self::Grade(passes) | Self::FusedCavityRim(passes) => {
345                passes.iter().fold(image.clone(), |current, pass| {
346                    pass.run(&current, depth, normal, frame, seed)
347                })
348            }
349        }
350    }
351
352    pub fn region_reach(&self) -> Option<u32> {
353        match self {
354            Self::Bloom(_)
355            | Self::Neon(_)
356            | Self::Halation(_)
357            | Self::Outline(_)
358            | Self::Comic(_)
359            | Self::RubberHose
360            | Self::ModernComic(_)
361            | Self::TiltShift(_)
362            | Self::Tape(_)
363            | Self::Distortion(_)
364            | Self::Curvature(_)
365            | Self::Pixel(_) => None,
366            Self::Grade(passes) => passes
367                .iter()
368                .try_fold(0u32, |reach, pass| Some(reach + pass.region_reach()?)),
369            Self::Kuwahara(radius) => Some((*radius).min(8)),
370            Self::Watercolor(_) => Some(1),
371            Self::Weave(amount) => Some((amount.abs() * 2.0).ceil() as u32 + 1),
372            Self::Aberration(amount) => Some(amount.abs().ceil() as u32 + 1),
373            _ => Some(0),
374        }
375    }
376
377    pub fn is_local(&self) -> bool {
378        match self {
379            Self::Exposure(_)
380            | Self::Black(_)
381            | Self::Saturation(_)
382            | Self::Contrast(_)
383            | Self::Warmth(_)
384            | Self::Lut(_)
385            | Self::Tone(_)
386            | Self::Vignette(_)
387            | Self::Grain(_)
388            | Self::Dither(_)
389            | Self::Encode
390            | Self::Bw(_)
391            | Self::Noir(_)
392            | Self::Sepia(_)
393            | Self::Posterize(_)
394            | Self::Duotone(_) => true,
395            Self::Bloom(bloom) => bloom.strength <= 0.0,
396            Self::Grade(passes) => passes.iter().all(Self::is_local),
397            _ => false,
398        }
399    }
400
401    pub fn is_per_pixel(&self) -> bool {
402        matches!(
403            self,
404            Self::Exposure(_)
405                | Self::Black(_)
406                | Self::Saturation(_)
407                | Self::Contrast(_)
408                | Self::Warmth(_)
409                | Self::Tone(_)
410                | Self::Vignette(_)
411                | Self::Grain(_)
412                | Self::Dither(_)
413                | Self::Encode
414                | Self::Bw(_)
415                | Self::Noir(_)
416                | Self::Sepia(_)
417                | Self::Posterize(_)
418                | Self::Flicker(_)
419                | Self::Scanlines(_)
420                | Self::Aperture(_)
421                | Self::Duotone(_)
422                | Self::GradientMap(_)
423        )
424    }
425
426    pub fn params(&self, buffers: Buffers) -> Vec<[f32; 16]> {
427        match self {
428            Self::Grade(passes) => grade_params(passes),
429            other => other
430                .dispatches(buffers)
431                .into_iter()
432                .map(|dispatch| dispatch.params)
433                .collect(),
434        }
435    }
436
437    pub fn dispatches(&self, buffers: Buffers) -> Vec<GpuDispatch> {
438        match self {
439            Self::Bloom(bloom) => bloom_dispatches(bloom, 1.0),
440            Self::Grade(passes) => grade_dispatches(passes),
441            Self::Halation(pass) => halation_dispatches(pass),
442            Self::Neon(pass) if pass.radius > 0 && pass.strength.abs() >= 1e-8 => vec![
443                GpuDispatch {
444                    shader: Cow::Borrowed(wgsl::NEON_EDGE),
445                    params: fill(&[(pass.saturation, 0)]),
446                    aux: Aux::None,
447                },
448                GpuDispatch {
449                    shader: Cow::Borrowed(wgsl::BLOOM_BLUR),
450                    params: fill(&[
451                        (1.0, 0),
452                        (0.0, 1),
453                        (pass.radius.min(8).div_ceil(2) as f32, 2),
454                        (pass.radius.max(1) as f32 * 1.5, 3),
455                    ]),
456                    aux: Aux::None,
457                },
458                GpuDispatch {
459                    shader: Cow::Borrowed(wgsl::BLOOM_BLUR),
460                    params: fill(&[
461                        (0.0, 0),
462                        (1.0, 1),
463                        (pass.radius.min(8).div_ceil(2) as f32, 2),
464                        (pass.radius.max(1) as f32 * 1.5, 3),
465                    ]),
466                    aux: Aux::None,
467                },
468                GpuDispatch {
469                    shader: Cow::Borrowed(wgsl::NEON_ADD),
470                    params: fill(&[(pass.saturation, 0), (pass.strength, 1)]),
471                    aux: Aux::Bloom,
472                },
473            ],
474            other => vec![GpuDispatch {
475                shader: Cow::Borrowed(other.shader()),
476                params: other.pack(buffers),
477                aux: other.aux(),
478            }],
479        }
480    }
481
482    fn shader(&self) -> &'static str {
483        match self {
484            Self::Exposure(_) => wgsl::EXPOSURE,
485            Self::Black(_) => wgsl::BLACK,
486            Self::Bloom(_) => wgsl::BLOOM_ADD,
487            Self::Saturation(_) => wgsl::SATURATION,
488            Self::Contrast(_) => wgsl::CONTRAST,
489            Self::Warmth(_) => wgsl::WARMTH,
490            Self::Lut(_) => wgsl::LUT,
491            Self::Tone(_) => wgsl::TONE,
492            Self::Vignette(_) => wgsl::VIGNETTE,
493            Self::Grain(_) => wgsl::GRAIN,
494            Self::Dither(_) => wgsl::DITHER,
495            Self::Encode => wgsl::ENCODE,
496            Self::Cel(_) => wgsl::CEL,
497            Self::Outline(_) => wgsl::OUTLINE,
498            Self::Cavity(_) => wgsl::CAVITY,
499            Self::Rim(_) => wgsl::RIM,
500            Self::Bw(_) => wgsl::BW,
501            Self::Noir(_) => wgsl::NOIR,
502            Self::Sepia(_) => wgsl::SEPIA,
503            Self::Posterize(_) => wgsl::POSTERIZE,
504            Self::Neon(_) => wgsl::NEON,
505            Self::Flicker(_) => wgsl::FLICKER,
506            Self::Weave(_) => wgsl::WEAVE,
507            Self::Dust(_) => wgsl::DUST,
508            Self::Scratches(_) => wgsl::SCRATCHES,
509            Self::Halation(_) => wgsl::HALATION_ADD,
510            Self::Aberration(_) => wgsl::ABERRATION,
511            Self::Distortion(_) | Self::Curvature(_) => wgsl::DISTORTION,
512            Self::Scanlines(_) => wgsl::SCANLINES,
513            Self::Aperture(_) => wgsl::APERTURE,
514            Self::OneBit(pass) => match pass.kind {
515                NoiseKind::Ordered => wgsl::ONE_BIT,
516                NoiseKind::Blue => wgsl::ONE_BIT_BLUE,
517            },
518            Self::Halftone(_) => wgsl::HALFTONE,
519            Self::Comic(_) => wgsl::COMIC,
520            Self::RubberHose => wgsl::RUBBER_HOSE,
521            Self::ModernComic(_) => wgsl::MODERN_COMIC,
522            Self::Watercolor(_) => wgsl::WATERCOLOR,
523            Self::PaperGrain(_) => wgsl::PAPER_GRAIN,
524            Self::Pixel(_) => wgsl::PIXEL,
525            Self::Duotone(_) => wgsl::DUOTONE,
526            Self::GradientMap(_) => wgsl::GRADIENT,
527            Self::Kuwahara(_) => wgsl::KUWAHARA,
528            Self::TiltShift(_) => wgsl::TILT_SHIFT,
529            Self::Tape(_) => tape::WGSL,
530            Self::Grade(_) => wgsl::EXPOSURE,
531            Self::FusedCavityRim(_) => wgsl::FUSED_CAVITY_RIM,
532        }
533    }
534
535    fn aux(&self) -> Aux {
536        match self {
537            Self::Lut(_) => Aux::Lut,
538            Self::Outline(_) | Self::ModernComic(_) | Self::Comic(_) | Self::RubberHose => {
539                Aux::DepthNormal
540            }
541            Self::Cel(_) | Self::Cavity(_) | Self::Rim(_) | Self::FusedCavityRim(_) => Aux::Normal,
542            Self::TiltShift(_) => Aux::Depth,
543            Self::Tape(_) => Aux::LinearSampler,
544            Self::OneBit(pass) if pass.kind == NoiseKind::Blue => Aux::Blue,
545            _ => Aux::None,
546        }
547    }
548
549    fn pack(&self, buffers: Buffers) -> [f32; 16] {
550        match self {
551            Self::Exposure(v) => fill(&[(*v, 0)]),
552            Self::Black(v) => fill(&[(*v, 0)]),
553            Self::Bloom(v) => bloom_add_params(v),
554            Self::Saturation(v) => fill(&[
555                (v.amount, 0),
556                (v.weights[0], 1),
557                (v.weights[1], 2),
558                (v.weights[2], 3),
559            ]),
560            Self::Contrast(Contrast::Pivot { amount, pivot }) => {
561                fill(&[(*amount, 0), (*pivot, 1), (0.0, 2)])
562            }
563            Self::Contrast(Contrast::Power { power, lift }) => {
564                fill(&[(*power, 0), (*lift, 1), (1.0, 2)])
565            }
566            Self::Warmth(v) => fill(&[
567                (v.amount, 0),
568                (
569                    if v.kind == WarmthKind::LumaCurve {
570                        1.0
571                    } else {
572                        0.0
573                    },
574                    1,
575                ),
576                (v.shadow, 2),
577                (v.highlight, 3),
578                (v.low[0], 4),
579                (v.low[1], 5),
580                (v.high[0], 6),
581                (v.high[1], 7),
582            ]),
583            Self::Lut(v) => fill(&[(v.size as f32, 0)]),
584            Self::Tone(Tone::Aces { gain }) => fill(&[(0.0, 0), (*gain, 1)]),
585            Self::Tone(Tone::Neutral {
586                start,
587                toe,
588                desaturation,
589                clamp,
590            }) => fill(&[
591                (1.0, 0),
592                (*start, 1),
593                (*toe, 2),
594                (*desaturation, 3),
595                (if *clamp { 1.0 } else { 0.0 }, 4),
596            ]),
597            Self::Tone(Tone::Agx) => fill(&[(2.0, 0)]),
598            Self::Vignette(v) => fill(&[
599                (v.strength, 0),
600                (
601                    if v.kind == grade::VignetteKind::Smoothstep {
602                        1.0
603                    } else {
604                        0.0
605                    },
606                    1,
607                ),
608                (v.power, 2),
609                (v.aspect, 3),
610                (v.scale, 4),
611                (v.inner, 5),
612                (v.outer, 6),
613            ]),
614            Self::Grain(v) => fill(&[
615                (v.strength, 0),
616                (v.response, 1),
617                (if v.clamp { 1.0 } else { 0.0 }, 2),
618            ]),
619            Self::Dither(v) => fill(&[(v.amplitude, 0)]),
620            Self::Encode => [0.0; 16],
621            Self::Cel(v) => fill(&[
622                (v.bands as f32, 0),
623                (v.shadow, 1),
624                (v.threshold, 2),
625                (v.softness, 3),
626                (v.spec, 4),
627                (v.spec_roughness, 5),
628                (v.spec_threshold, 6),
629                (if buffers.normal { 1.0 } else { 0.0 }, 7),
630                (v.light[0], 8),
631                (v.light[1], 9),
632                (v.light[2], 10),
633            ]),
634            Self::Outline(v) => fill(&[
635                (v.thickness, 0),
636                (v.alpha, 1),
637                (v.depth_gap, 2),
638                (v.crease_angle.to_radians().cos(), 3),
639                (v.color[0], 4),
640                (v.color[1], 5),
641                (v.color[2], 6),
642                (if v.enabled { 1.0 } else { 0.0 }, 7),
643                (if buffers.depth { 1.0 } else { 0.0 }, 8),
644                (if buffers.normal { 1.0 } else { 0.0 }, 9),
645                (if v.local_color { 1.0 } else { 0.0 }, 10),
646            ]),
647            Self::Cavity(v) => fill(&[
648                (v.strength, 0),
649                (v.distance, 1),
650                (if buffers.normal { 1.0 } else { 0.0 }, 2),
651            ]),
652            Self::Rim(v) => fill(&[
653                (v.strength, 0),
654                (v.width, 1),
655                (v.color[0], 2),
656                (v.color[1], 3),
657                (v.color[2], 4),
658                (if buffers.normal { 1.0 } else { 0.0 }, 5),
659            ]),
660            Self::Bw(v) => fill(&[(v.weights[0], 0), (v.weights[1], 1), (v.weights[2], 2)]),
661            Self::Noir(v) => {
662                let keep = v.keep.unwrap_or([0.0; 3]);
663                fill(&[
664                    (v.contrast, 0),
665                    (v.crush, 1),
666                    (if v.keep.is_some() { 1.0 } else { 0.0 }, 2),
667                    (v.keep_range, 3),
668                    (keep[0], 4),
669                    (keep[1], 5),
670                    (keep[2], 6),
671                ])
672            }
673            Self::Sepia(v) => fill(&[(*v, 0)]),
674            Self::Posterize(v) => fill(&[(*v, 0)]),
675            Self::Neon(v) => fill(&[(v.saturation, 0), (v.strength, 1), (v.radius as f32, 2)]),
676            Self::Flicker(v)
677            | Self::Weave(v)
678            | Self::Dust(v)
679            | Self::Aberration(v)
680            | Self::Distortion(v)
681            | Self::Curvature(v)
682            | Self::Aperture(v) => fill(&[(*v, 0)]),
683            Self::Scratches(v) => fill(&[(v.count as f32, 0), (v.strength, 1)]),
684            Self::Halation(v) => fill(&[(v.strength, 0)]),
685            Self::Scanlines(v) => fill(&[(v.strength, 0), (v.period as f32, 1)]),
686            Self::OneBit(_) => [0.0; 16],
687            Self::Halftone(v) => fill(&[
688                (v.cell, 0),
689                (v.angle, 1),
690                (v.ink[0], 2),
691                (v.ink[1], 3),
692                (v.ink[2], 4),
693            ]),
694            Self::Comic(v) => fill(&[
695                (v.levels as f32, 0),
696                (v.ink, 1),
697                (if buffers.depth { 1.0 } else { 0.0 }, 2),
698                (if buffers.normal { 1.0 } else { 0.0 }, 3),
699            ]),
700            Self::RubberHose => fill(&[
701                (if buffers.depth { 1.0 } else { 0.0 }, 0),
702                (if buffers.normal { 1.0 } else { 0.0 }, 1),
703            ]),
704            Self::ModernComic(v) => fill(&[
705                (v.tone_steps as f32, 0),
706                (v.black_threshold, 1),
707                (v.line_weight, 2),
708                (v.interior_line_strength, 3),
709                (v.saturation_lift, 4),
710                (v.highlight_threshold, 5),
711                (if buffers.depth { 1.0 } else { 0.0 }, 6),
712                (if buffers.normal { 1.0 } else { 0.0 }, 7),
713            ]),
714            Self::Watercolor(v) => fill(&[(v.darkening, 0), (v.grain, 1), (v.scale, 2)]),
715            Self::PaperGrain(v) => fill(&[(v.strength, 0), (v.scale, 1)]),
716            Self::Pixel(v) => fill(&[
717                (v.size as f32, 0),
718                (v.levels as f32, 1),
719                (if v.palette { 1.0 } else { 0.0 }, 2),
720            ]),
721            Self::Duotone(v) => fill(&[
722                (v.shadow[0], 0),
723                (v.shadow[1], 1),
724                (v.shadow[2], 2),
725                (v.highlight[0], 4),
726                (v.highlight[1], 5),
727                (v.highlight[2], 6),
728            ]),
729            Self::GradientMap(v) => gradient_params(v),
730            Self::Kuwahara(v) => fill(&[(*v as f32, 0)]),
731            Self::TiltShift(v) => fill(&[
732                (v.focus, 0),
733                (v.range, 1),
734                (v.radius, 2),
735                (if buffers.depth { 1.0 } else { 0.0 }, 3),
736            ]),
737            Self::Tape(v) => v.params(),
738            Self::Grade(passes) => grade_params(passes).pop().unwrap_or([0.0; 16]),
739            Self::FusedCavityRim(passes) => {
740                let [Pass::Cavity(cavity), Pass::Rim(rim)] = passes.as_slice() else {
741                    unreachable!()
742                };
743                fill(&[
744                    (cavity.strength, 0),
745                    (cavity.distance, 1),
746                    (rim.strength, 2),
747                    (rim.width, 3),
748                    (rim.color[0], 4),
749                    (rim.color[1], 5),
750                    (rim.color[2], 6),
751                    (if buffers.normal { 1.0 } else { 0.0 }, 7),
752                ])
753            }
754        }
755    }
756}
757
758fn fill(pairs: &[(f32, usize)]) -> [f32; 16] {
759    let mut out = [0.0; 16];
760    for (value, index) in pairs {
761        out[*index] = *value;
762    }
763    out
764}
765
766fn bloom_add_params(bloom: &Bloom) -> [f32; 16] {
767    fill(&[(bloom.strength, 0)])
768}
769
770fn bloom_dispatches(bloom: &Bloom, scale: f32) -> Vec<GpuDispatch> {
771    match bloom.kind {
772        bloom::BloomKind::Rings => vec![
773            GpuDispatch {
774                shader: Cow::Borrowed(wgsl::BLOOM_RING_DOWN),
775                params: fill(&[
776                    (bloom.down.max(1) as f32, 0),
777                    (bloom.threshold, 1),
778                    (bloom.clamp, 2),
779                    (scale, 3),
780                ]),
781                aux: Aux::None,
782            },
783            GpuDispatch {
784                shader: Cow::Borrowed(wgsl::BLOOM_RINGS),
785                params: fill(&[
786                    (bloom.unit / bloom.down.max(1) as f32, 3),
787                    (bloom.radii[0], 4),
788                    (bloom.radii[1], 5),
789                    (bloom.radii[2], 6),
790                    (bloom.radii[3], 7),
791                    (bloom.gains[0], 8),
792                    (bloom.gains[1], 9),
793                    (bloom.gains[2], 10),
794                    (bloom.gains[3], 11),
795                ]),
796                aux: Aux::None,
797            },
798            GpuDispatch {
799                shader: Cow::Borrowed(wgsl::BLOOM_ADD),
800                params: bloom_add_params(bloom),
801                aux: Aux::BloomB,
802            },
803        ],
804        bloom::BloomKind::Box | bloom::BloomKind::Tent => {
805            let tent = if bloom.kind == bloom::BloomKind::Tent {
806                1.0
807            } else {
808                0.0
809            };
810            let box2 = bloom.kind == bloom::BloomKind::Box && bloom.down == 2;
811            let linear = bloom.kind == bloom::BloomKind::Box;
812            let sampled = linear && bloom.linear_taps;
813            let blur = if sampled {
814                Cow::Owned(wgsl::bloom_blur_linear(bloom.radius, bloom.spread))
815            } else {
816                Cow::Borrowed(wgsl::BLOOM_BLUR)
817            };
818            let blur_aux = if sampled {
819                Aux::LinearSampler
820            } else {
821                Aux::None
822            };
823            vec![
824                GpuDispatch {
825                    shader: Cow::Borrowed(if box2 {
826                        wgsl::BLOOM_DOWN_BOX2
827                    } else {
828                        wgsl::BLOOM_DOWN
829                    }),
830                    params: fill(&[
831                        (bloom.down.max(1) as f32, 0),
832                        (bloom.threshold, 1),
833                        (bloom.knee, 2),
834                        (bloom.knee_width, 3),
835                        (bloom.soft, 4),
836                        (tent, 5),
837                        (scale, 6),
838                    ]),
839                    aux: Aux::None,
840                },
841                GpuDispatch {
842                    shader: blur.clone(),
843                    params: fill(&[
844                        (1.0, 0),
845                        (0.0, 1),
846                        (bloom.radius as f32, 2),
847                        (bloom.spread, 3),
848                    ]),
849                    aux: blur_aux,
850                },
851                GpuDispatch {
852                    shader: blur,
853                    params: fill(&[
854                        (0.0, 0),
855                        (1.0, 1),
856                        (bloom.radius as f32, 2),
857                        (bloom.spread, 3),
858                    ]),
859                    aux: blur_aux,
860                },
861                GpuDispatch {
862                    shader: Cow::Borrowed(if linear {
863                        wgsl::BLOOM_ADD_LINEAR
864                    } else {
865                        wgsl::BLOOM_ADD
866                    }),
867                    params: bloom_add_params(bloom),
868                    aux: if linear { Aux::BloomLinear } else { Aux::Bloom },
869                },
870            ]
871        }
872    }
873}
874
875fn grade_parts(passes: &[Pass]) -> (f32, Option<&Bloom>) {
876    let lead = passes
877        .iter()
878        .take_while(|pass| matches!(pass, Pass::Exposure(_)))
879        .count();
880    let scale = passes[..lead]
881        .iter()
882        .map(|pass| match pass {
883            Pass::Exposure(value) => *value,
884            _ => 1.0,
885        })
886        .product();
887    match passes.get(lead) {
888        Some(Pass::Bloom(bloom)) => (scale, Some(bloom)),
889        _ => (scale, None),
890    }
891}
892
893fn grade_aux(bloom: Option<&Bloom>) -> Aux {
894    match bloom.map(|bloom| bloom.kind) {
895        None => Aux::Grade,
896        Some(bloom::BloomKind::Box) => Aux::GradeBloomLinear,
897        Some(bloom::BloomKind::Tent) => Aux::GradeBloom,
898        Some(bloom::BloomKind::Rings) => Aux::GradeBloomB,
899    }
900}
901
902fn grade_params(passes: &[Pass]) -> Vec<[f32; 16]> {
903    let (scale, bloom) = grade_parts(passes);
904    let mut out: Vec<[f32; 16]> = bloom.map_or_else(Vec::new, |bloom| {
905        let mut stages = bloom_dispatches(bloom, scale);
906        stages.pop();
907        stages.into_iter().map(|stage| stage.params).collect()
908    });
909    out.push(fill(&[
910        (1.0, 0),
911        (bloom.map_or(0.0, |bloom| bloom.strength), 1),
912    ]));
913    out
914}
915
916fn grade_dispatches(passes: &[Pass]) -> Vec<GpuDispatch> {
917    let (scale, bloom) = grade_parts(passes);
918    let aux = grade_aux(bloom);
919    let mut out = bloom.map_or_else(Vec::new, |bloom| {
920        let mut stages = bloom_dispatches(bloom, scale);
921        stages.pop();
922        stages
923    });
924    out.push(GpuDispatch {
925        shader: Cow::Owned(grade_shader(passes)),
926        params: grade_params(passes).pop().unwrap_or([0.0; 16]),
927        aux,
928    });
929    out
930}
931
932pub fn grade_shader(passes: &[Pass]) -> String {
933    let aux = grade_aux(grade_parts(passes).1);
934    let mut out = String::from(wgsl::HEAD);
935    match aux {
936        Aux::GradeBloom | Aux::GradeBloomB => out.push_str(wgsl::BLOOM_TEX),
937        Aux::GradeBloomLinear => out.push_str(wgsl::BLOOM_LINEAR),
938        _ => {}
939    }
940    out.push_str("\n@group(0) @binding(5) var<storage, read> grade: array<vec4<f32>>;\n");
941    out.push_str(wgsl::GRADE_OPS);
942    out.push_str(wgsl::COMMON);
943    out.push_str(wgsl::MAIN);
944    out.push_str("    var c = src;\n");
945    let mut member = 0;
946    for pass in passes {
947        if matches!(pass, Pass::Bloom(_)) {
948            out.push_str(match aux {
949                Aux::GradeBloomLinear => {
950                    "    let uv = bloom_uv(p);\n    c = as_half(vec4<f32>(c.rgb + textureSampleLevel(bloom_tex, bloom_sampler, uv, 0.0).rgb * post.p0.y, c.a));\n"
951                }
952                _ => {
953                    "    let full = max(vec2<f32>(src_dim()), vec2<f32>(1.0));\n    let small = vec2<f32>(bloom_dim());\n    let s = sample_bloom((f32(p.x) + 0.5) / full.x * small.x - 0.5, (f32(p.y) + 0.5) / full.y * small.y - 0.5);\n    c = as_half(vec4<f32>(c.rgb + s.rgb * post.p0.y, c.a));\n"
954                }
955            });
956            continue;
957        }
958        let at = member * 4;
959        let first = if member == 0 && matches!(pass, Pass::Exposure(_)) {
960            format!("grade[{at}u] * vec4<f32>(post.p0.x, 1.0, 1.0, 1.0)")
961        } else {
962            format!("grade[{at}u]")
963        };
964        let _ = writeln!(
965            out,
966            "    c = as_half(grade_{}(c, p, {first}, grade[{}u], grade[{}u], grade[{}u]));",
967            pass.name(),
968            at + 1,
969            at + 2,
970            at + 3
971        );
972        member += 1;
973    }
974    out.push_str("    store(p, c);\n}\n");
975    out
976}
977
978pub fn grade_data(passes: &[Pass]) -> Vec<f32> {
979    let mut out: Vec<f32> = passes
980        .iter()
981        .filter(|pass| !matches!(pass, Pass::Bloom(_)))
982        .flat_map(|member| member.pack(Buffers::NONE))
983        .collect();
984    out.resize(out.len().max(16), 0.0);
985    out
986}
987
988fn halation_dispatches(pass: &fx::Halation) -> Vec<GpuDispatch> {
989    let spread = pass.radius.max(1) as f32 * 0.25;
990    vec![
991        GpuDispatch {
992            shader: Cow::Borrowed(wgsl::HALATION_HOT),
993            params: fill(&[(pass.threshold, 0), (4.0, 1)]),
994            aux: Aux::None,
995        },
996        GpuDispatch {
997            shader: Cow::Borrowed(wgsl::BLOOM_BLUR),
998            params: fill(&[
999                (1.0, 0),
1000                (0.0, 1),
1001                (pass.radius.div_ceil(4) as f32, 2),
1002                (spread, 3),
1003            ]),
1004            aux: Aux::None,
1005        },
1006        GpuDispatch {
1007            shader: Cow::Borrowed(wgsl::BLOOM_BLUR),
1008            params: fill(&[
1009                (0.0, 0),
1010                (1.0, 1),
1011                (pass.radius.div_ceil(4) as f32, 2),
1012                (spread, 3),
1013            ]),
1014            aux: Aux::None,
1015        },
1016        GpuDispatch {
1017            shader: Cow::Borrowed(wgsl::HALATION_ADD),
1018            params: fill(&[(pass.strength, 0)]),
1019            aux: Aux::Bloom,
1020        },
1021    ]
1022}
1023
1024fn gradient_params(pass: &fx::GradientMap) -> [f32; 16] {
1025    let count = pass.count.clamp(2, 4) as usize;
1026    let mut stops = [0.0; 16];
1027    for i in 0..4 {
1028        let stop = pass.stops[i.min(count - 1)];
1029        stops[i * 4] = stop[0];
1030        stops[i * 4 + 1] = stop[1];
1031        stops[i * 4 + 2] = stop[2];
1032        stops[i * 4 + 3] = stop[3];
1033    }
1034    stops
1035}