Skip to main content

pfx_live/
demand.rs

1use crate::effects::ParticleInstance;
2use crate::flat::FlatScene;
3use crate::frame::{Matrix, Scene, multiply};
4use crate::renderer::{Effects, Finish, Text, invert};
5use crate::text::{PackedGlyph, TextSpace};
6use pfx_gpu::{GpuProfiler, wgpu};
7use std::sync::mpsc::{Receiver, TryRecvError};
8
9pub const SETTLE: u32 = crate::taa::CYCLE;
10pub const MARGIN: u32 = 4;
11pub const DEPTH_PROBES: usize = 16;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct Region {
15    pub x: u32,
16    pub y: u32,
17    pub width: u32,
18    pub height: u32,
19}
20
21impl Region {
22    pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
23        Self {
24            x,
25            y,
26            width,
27            height,
28        }
29    }
30
31    pub fn whole(width: u32, height: u32) -> Self {
32        Self::new(0, 0, width, height)
33    }
34
35    pub fn from_bounds(bounds: [f32; 4], width: u32, height: u32) -> Option<Self> {
36        if !bounds.iter().all(|value| value.is_finite()) {
37            return Some(Self::whole(width, height));
38        }
39        let left = bounds[0].floor().clamp(0.0, width as f32) as u32;
40        let top = bounds[1].floor().clamp(0.0, height as f32) as u32;
41        let right = bounds[2].ceil().clamp(0.0, width as f32) as u32;
42        let bottom = bounds[3].ceil().clamp(0.0, height as f32) as u32;
43        (right > left && bottom > top).then(|| Self::new(left, top, right - left, bottom - top))
44    }
45
46    pub fn right(&self) -> u32 {
47        self.x + self.width
48    }
49
50    pub fn bottom(&self) -> u32 {
51        self.y + self.height
52    }
53
54    pub fn area(&self) -> u64 {
55        u64::from(self.width) * u64::from(self.height)
56    }
57
58    pub fn is_empty(&self) -> bool {
59        self.width == 0 || self.height == 0
60    }
61
62    pub fn contains(&self, x: u32, y: u32) -> bool {
63        x >= self.x && y >= self.y && x < self.right() && y < self.bottom()
64    }
65
66    pub fn clip(self, width: u32, height: u32) -> Self {
67        let x = self.x.min(width);
68        let y = self.y.min(height);
69        Self::new(
70            x,
71            y,
72            self.right().min(width) - x,
73            self.bottom().min(height) - y,
74        )
75    }
76
77    pub fn grow(self, margin: u32, width: u32, height: u32) -> Self {
78        let x = self.x.saturating_sub(margin);
79        let y = self.y.saturating_sub(margin);
80        Self::new(
81            x,
82            y,
83            (self.right() + margin).min(width) - x,
84            (self.bottom() + margin).min(height) - y,
85        )
86    }
87
88    pub fn union(self, other: Self) -> Self {
89        let x = self.x.min(other.x);
90        let y = self.y.min(other.y);
91        Self::new(
92            x,
93            y,
94            self.right().max(other.right()) - x,
95            self.bottom().max(other.bottom()) - y,
96        )
97    }
98
99    pub fn touches(&self, other: &Self, gap: u32) -> bool {
100        self.x <= other.right() + gap
101            && other.x <= self.right() + gap
102            && self.y <= other.bottom() + gap
103            && other.y <= self.bottom() + gap
104    }
105}
106
107pub fn merge(regions: &[Region], width: u32, height: u32) -> Vec<Region> {
108    let mut merged: Vec<Region> = regions
109        .iter()
110        .map(|region| region.clip(width, height))
111        .filter(|region| !region.is_empty())
112        .collect();
113    loop {
114        let mut joined = false;
115        let mut index = 0;
116        while index < merged.len() {
117            let mut other = index + 1;
118            while other < merged.len() {
119                if merged[index].touches(&merged[other], 16) {
120                    let region = merged.swap_remove(other);
121                    merged[index] = merged[index].union(region);
122                    joined = true;
123                } else {
124                    other += 1;
125                }
126            }
127            index += 1;
128        }
129        if !joined {
130            break;
131        }
132    }
133    let covered: u64 = merged.iter().map(Region::area).sum();
134    if covered * 5 > Region::whole(width, height).area() * 3 {
135        return vec![Region::whole(width, height)];
136    }
137    merged.sort_by_key(|region| (region.y, region.x));
138    merged
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub enum Need {
143    None,
144    Reproject,
145    Partial(Vec<Region>),
146    Full,
147}
148
149impl Need {
150    pub fn draws(&self) -> bool {
151        !matches!(self, Self::None)
152    }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum Shown {
157    Full,
158    Reproject,
159    Partial,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq)]
163pub struct Limits {
164    pub angle: f32,
165    pub zoom: f32,
166    pub holes: f32,
167    pub frames: u32,
168    pub settle: u32,
169}
170
171impl Default for Limits {
172    fn default() -> Self {
173        Self {
174            angle: 0.6_f32.to_radians(),
175            zoom: 0.03,
176            holes: 0.005,
177            frames: 90,
178            settle: SETTLE,
179        }
180    }
181}
182
183pub struct Next<'a> {
184    pub scene: &'a Scene<'a>,
185    pub text: &'a Text<'a>,
186    pub effects: &'a Effects<'a>,
187    pub style: Finish,
188    pub hud: Option<&'a FlatScene<'a>>,
189    pub content: u64,
190    pub overlay: u64,
191    pub moving: &'a [Region],
192}
193
194impl<'a> Next<'a> {
195    pub fn new(
196        scene: &'a Scene<'a>,
197        text: &'a Text<'a>,
198        effects: &'a Effects<'a>,
199        style: Finish,
200    ) -> Self {
201        Self {
202            scene,
203            text,
204            effects,
205            style,
206            hud: None,
207            content: 0,
208            overlay: 0,
209            moving: &[],
210        }
211    }
212}
213
214#[derive(Clone, Copy, Debug, Default, PartialEq)]
215pub struct DemandStats {
216    pub shown: Option<Shown>,
217    pub since_full: u32,
218    pub settle: u32,
219    pub holes: f32,
220    pub depth: Option<f32>,
221}
222
223#[derive(Clone, Debug, PartialEq)]
224pub(crate) struct Summary {
225    pub view: Matrix,
226    pub projection: Matrix,
227    pub position: [f32; 3],
228    pub size: [u32; 2],
229    pub world: u64,
230    pub anchors: Vec<Matrix>,
231    pub overlay: u64,
232    pub ticking: bool,
233    pub layered: Vec<Region>,
234    pub moving: Vec<Region>,
235}
236
237impl Summary {
238    fn same_camera(&self, other: &Self) -> bool {
239        self.view == other.view && self.projection == other.projection
240    }
241
242    fn same_world(&self, other: &Self) -> bool {
243        self.world == other.world
244            && self.anchors.len() == other.anchors.len()
245            && self
246                .anchors
247                .iter()
248                .zip(&other.anchors)
249                .all(|(a, b)| close(a, b))
250    }
251}
252
253fn close(a: &Matrix, b: &Matrix) -> bool {
254    a.iter()
255        .flatten()
256        .zip(b.iter().flatten())
257        .all(|(x, y)| (x - y).abs() <= 1e-4 * x.abs().max(y.abs()).max(1.0))
258}
259
260#[derive(Clone, Debug, Default)]
261pub(crate) struct Book {
262    pub cached: Option<Summary>,
263    pub shown: Option<Summary>,
264    pub kind: Option<Shown>,
265    pub since_full: u32,
266    pub settle: u32,
267    pub holes: f32,
268    pub depth: Option<f32>,
269    pub still: bool,
270}
271
272#[derive(Clone, Copy, Debug, PartialEq)]
273pub struct Motion {
274    pub angle: f32,
275    pub zoom: f32,
276}
277
278fn rotation(view: &Matrix) -> [[f32; 3]; 3] {
279    std::array::from_fn(|column| std::array::from_fn(|row| view[column][row]))
280}
281
282pub fn motion(
283    from: (&Matrix, &Matrix, [f32; 3]),
284    to: (&Matrix, &Matrix, [f32; 3]),
285    depth: Option<f32>,
286) -> Option<Motion> {
287    let (view0, projection0, position0) = from;
288    let (view1, projection1, position1) = to;
289    if projection0[2][3] != -1.0 || projection0[3][3] != 0.0 {
290        return None;
291    }
292    let scale0 = projection0[1][1];
293    let scale1 = projection1[1][1];
294    if !(scale0 > 0.0 && scale1 > 0.0) {
295        return None;
296    }
297    let aspect0 = projection0[0][0] / scale0;
298    let aspect1 = projection1[0][0] / scale1;
299    if (aspect0 - aspect1).abs() > 1e-5 * aspect0.abs().max(1.0) {
300        return None;
301    }
302    for (column, row) in [
303        (2, 0),
304        (2, 1),
305        (2, 2),
306        (2, 3),
307        (3, 2),
308        (3, 3),
309        (3, 0),
310        (3, 1),
311    ] {
312        let a = projection0[column][row];
313        let b = projection1[column][row];
314        if (a - b).abs() > 1e-5 * a.abs().max(b.abs()).max(1.0) {
315            return None;
316        }
317    }
318    let r0 = rotation(view0);
319    let r1 = rotation(view1);
320    let relative: [[f64; 3]; 3] = std::array::from_fn(|i| {
321        std::array::from_fn(|j| {
322            (0..3)
323                .map(|k| f64::from(r1[k][i]) * f64::from(r0[k][j]))
324                .sum()
325        })
326    });
327    let trace = relative[0][0] + relative[1][1] + relative[2][2];
328    let skew = [
329        relative[2][1] - relative[1][2],
330        relative[0][2] - relative[2][0],
331        relative[1][0] - relative[0][1],
332    ];
333    let sine = (skew[0] * skew[0] + skew[1] * skew[1] + skew[2] * skew[2]).sqrt() * 0.5;
334    let turned = sine.atan2((trace - 1.0) * 0.5) as f32;
335    let shift = {
336        let d = [
337            position1[0] - position0[0],
338            position1[1] - position0[1],
339            position1[2] - position0[2],
340        ];
341        (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
342    };
343    let shifted = if shift <= 1e-7 {
344        0.0
345    } else {
346        match depth {
347            Some(depth) if depth.is_finite() && depth > 0.0 => shift / depth,
348            Some(_) => 0.0,
349            None => return None,
350        }
351    };
352    Some(Motion {
353        angle: turned + shifted,
354        zoom: (scale1 / scale0).ln().abs(),
355    })
356}
357
358pub(crate) fn decide(book: &Book, next: &Summary, limits: &Limits) -> Need {
359    let Some(cached) = &book.cached else {
360        return Need::Full;
361    };
362    if next.size != cached.size
363        || !next.same_world(cached)
364        || next.ticking
365        || book.settle > 0
366        || (!next.layered.is_empty() && !book.still)
367    {
368        return Need::Full;
369    }
370    let shown = book.shown.as_ref().unwrap_or(cached);
371    let kind = book.kind.unwrap_or(Shown::Full);
372    let [width, height] = next.size;
373    let mut layered: Vec<Region> = next.layered.clone();
374    layered.extend(shown.layered.iter().copied());
375    let overlay_changed = next.overlay != shown.overlay;
376    if !next.same_camera(cached) {
377        let Some(moved) = motion(
378            (&cached.view, &cached.projection, cached.position),
379            (&next.view, &next.projection, next.position),
380            book.depth,
381        ) else {
382            return Need::Full;
383        };
384        if moved.angle > limits.angle
385            || moved.zoom > limits.zoom
386            || book.since_full >= limits.frames
387            || book.holes > limits.holes
388        {
389            return Need::Full;
390        }
391        if kind == Shown::Reproject
392            && next.same_camera(shown)
393            && layered.is_empty()
394            && !overlay_changed
395            && next.moving.is_empty()
396        {
397            return Need::Full;
398        }
399        return Need::Reproject;
400    }
401    if kind == Shown::Reproject {
402        return Need::Reproject;
403    }
404    let mut regions = layered;
405    regions.extend(next.moving.iter().copied());
406    regions.extend(shown.moving.iter().copied());
407    if overlay_changed {
408        regions.push(Region::whole(width, height));
409    }
410    if regions.is_empty() {
411        Need::None
412    } else {
413        Need::Partial(merge(&regions, width, height))
414    }
415}
416
417pub(crate) fn record(book: &mut Book, summary: Summary, kind: Shown, limits: &Limits) {
418    match kind {
419        Shown::Full => {
420            let changed = book
421                .cached
422                .as_ref()
423                .is_none_or(|cached| !cached.same_world(&summary) || cached.size != summary.size);
424            book.settle = if changed {
425                limits.settle
426            } else {
427                book.settle.saturating_sub(1)
428            };
429            book.cached = Some(summary.clone());
430            book.since_full = 0;
431            book.holes = 0.0;
432        }
433        Shown::Reproject => book.since_full = book.since_full.saturating_add(1),
434        Shown::Partial => {}
435    }
436    book.shown = Some(summary);
437    book.kind = Some(kind);
438}
439
440#[derive(Clone, Copy)]
441pub(crate) struct Print(u64);
442
443impl Print {
444    pub fn new() -> Self {
445        Self(0xcbf2_9ce4_8422_2325)
446    }
447
448    pub fn word(&mut self, value: u32) {
449        self.0 = (self.0 ^ u64::from(value)).wrapping_mul(0x0000_0100_0000_01b3);
450    }
451
452    pub fn wide(&mut self, value: u64) {
453        self.word(value as u32);
454        self.word((value >> 32) as u32);
455    }
456
457    pub fn float(&mut self, value: f32) {
458        self.word(value.to_bits());
459    }
460
461    pub fn floats(&mut self, values: &[f32]) {
462        for value in values {
463            self.float(*value);
464        }
465    }
466
467    pub fn matrix(&mut self, matrix: &Matrix) {
468        for column in matrix {
469            self.floats(column);
470        }
471    }
472
473    pub fn bytes(&mut self, bytes: &[u8]) {
474        let mut chunks = bytes.chunks_exact(4);
475        for chunk in &mut chunks {
476            self.word(u32::from_le_bytes(chunk.try_into().unwrap()));
477        }
478        for byte in chunks.remainder() {
479            self.word(u32::from(*byte) | 0x100);
480        }
481        self.word(bytes.len() as u32);
482    }
483
484    pub fn debug(&mut self, value: &impl std::fmt::Debug) {
485        use std::fmt::Write;
486        let _ = write!(self, "{value:?}");
487        self.word(0xffff_ffff);
488    }
489
490    pub fn finish(self) -> u64 {
491        self.0
492    }
493}
494
495impl std::fmt::Write for Print {
496    fn write_str(&mut self, text: &str) -> std::fmt::Result {
497        self.bytes(text.as_bytes());
498        Ok(())
499    }
500}
501
502pub(crate) fn print_scene(print: &mut Print, scene: &Scene<'_>) {
503    print.word(scene.seed);
504    print.floats(&scene.sun.direction);
505    print.floats(&scene.sun.colour);
506    print.float(scene.sun.intensity);
507    print.float(scene.wind.current.strength);
508    print.floats(&scene.wind.current.direction);
509    print.word(scene.instances.len() as u32);
510    for instance in scene.instances {
511        print.word(instance.mesh.0);
512        print.matrix(&instance.model);
513        print.word(instance.material);
514        print.word(instance.id);
515        print.float(instance.opacity);
516        print.float(instance.alpha_cutoff);
517        print.word(instance.deformer.0);
518        print.float(instance.age);
519        print.float(instance.coverage);
520        print.word(
521            u32::from(instance.shadow_only)
522                | u32::from(instance.casts_shadow) << 1
523                | u32::from(instance.two_sided) << 2,
524        );
525    }
526    print.word(scene.materials.len() as u32);
527    for material in scene.materials {
528        print.debug(material);
529    }
530    print.word(scene.deformers.len() as u32);
531    for deformer in scene.deformers {
532        print.debug(deformer);
533    }
534}
535
536fn print_space(
537    print: &mut Print,
538    space: &TextSpace,
539    anchors: &mut Vec<Matrix>,
540    view_projection: &Matrix,
541) {
542    match space {
543        TextSpace::Surface {
544            model_view_projection,
545        } => {
546            print.word(1);
547            anchors.push(anchor(view_projection, model_view_projection));
548        }
549        TextSpace::Deformed {
550            model,
551            layout,
552            normal,
553            lift,
554            deformer,
555            ..
556        } => {
557            print.word(2);
558            print.matrix(model);
559            print.matrix(layout);
560            print.floats(normal);
561            print.float(*lift);
562            print.word(deformer.0);
563        }
564        TextSpace::Overlay { width, height } => {
565            print.word(3);
566            print.word(*width);
567            print.word(*height);
568        }
569        TextSpace::LitSurface {
570            model_view_projection,
571            model,
572        } => {
573            print.word(4);
574            print.matrix(model);
575            anchors.push(anchor(view_projection, model_view_projection));
576        }
577        TextSpace::LitDeformed {
578            model,
579            layout,
580            normal,
581            lift,
582            deformer,
583            ..
584        } => {
585            print.word(5);
586            print.matrix(model);
587            print.matrix(layout);
588            print.floats(normal);
589            print.float(*lift);
590            print.word(deformer.0);
591        }
592    }
593}
594
595fn anchor(view_projection: &Matrix, model_view_projection: &Matrix) -> Matrix {
596    invert(*view_projection)
597        .map(|inverse| multiply(inverse, *model_view_projection))
598        .unwrap_or(*model_view_projection)
599}
600
601pub(crate) fn print_text(
602    world: &mut Print,
603    overlay: &mut Print,
604    anchors: &mut Vec<Matrix>,
605    text: &Text<'_>,
606    view_projection: &Matrix,
607) {
608    for (print, items) in [(&mut *world, &text.surface), (&mut *overlay, &text.overlay)] {
609        print.word(items.len() as u32);
610        for item in items {
611            print.wide(std::ptr::from_ref(item.atlas) as usize as u64);
612            print.floats(&item.color);
613            print.word(item.id);
614            print_space(print, &item.space, anchors, view_projection);
615            print.word(item.paragraph.glyphs.len() as u32);
616            for glyph in &item.paragraph.glyphs {
617                print.bytes(bytemuck::bytes_of(&PackedGlyph::from(glyph)));
618            }
619        }
620    }
621    for (print, items) in [
622        (&mut *world, &text.surface_quads),
623        (&mut *overlay, &text.overlay_quads),
624    ] {
625        print.word(items.len() as u32);
626        for item in items {
627            print.wide(std::ptr::from_ref(item.atlas) as usize as u64);
628            print.word(u32::from(item.icons));
629            print.word(item.id);
630            print_space(print, &item.space, anchors, view_projection);
631            print.word(item.quads.len() as u32);
632            for quad in item.quads {
633                print.bytes(bytemuck::bytes_of(&PackedGlyph::from(quad)));
634            }
635        }
636    }
637}
638
639pub(crate) fn print_effects(print: &mut Print, effects: &Effects<'_>) {
640    print.word(effects.glass.len() as u32);
641    for surface in effects.glass {
642        print.word(surface.mesh.0);
643        print.matrix(&surface.model);
644        print.debug(&surface.material);
645        print.word(u32::from(surface.liquid));
646        print.float(surface.fluid_height);
647        print.float(surface.ripple_height);
648        print.float(surface.caustic_strength);
649        print.debug(&surface.tinted);
650        print.word(surface.id);
651        print.word(u32::from(surface.casts_shadow) | u32::from(surface.shadow_only) << 1);
652        print.floats(surface.clip.as_flattened());
653    }
654    print.word(effects.props.len() as u32);
655    for prop in effects.props {
656        print.bytes(bytemuck::bytes_of(prop));
657    }
658}
659
660fn transform(matrix: &Matrix, point: [f32; 4]) -> [f32; 4] {
661    std::array::from_fn(|row| {
662        (0..4)
663            .map(|column| matrix[column][row] * point[column])
664            .sum()
665    })
666}
667
668pub fn box_region(
669    view_projection: &Matrix,
670    lo: [f32; 3],
671    hi: [f32; 3],
672    margin: u32,
673    width: u32,
674    height: u32,
675) -> Option<Region> {
676    let mut bounds = [
677        f32::INFINITY,
678        f32::INFINITY,
679        f32::NEG_INFINITY,
680        f32::NEG_INFINITY,
681    ];
682    for x in [lo[0], hi[0]] {
683        for y in [lo[1], hi[1]] {
684            for z in [lo[2], hi[2]] {
685                let clip = transform(view_projection, [x, y, z, 1.0]);
686                if clip[3] <= 0.0 || !clip.iter().all(|value| value.is_finite()) {
687                    return Some(Region::whole(width, height));
688                }
689                let screen = [
690                    (clip[0] / clip[3] * 0.5 + 0.5) * width as f32,
691                    (0.5 - clip[1] / clip[3] * 0.5) * height as f32,
692                ];
693                bounds[0] = bounds[0].min(screen[0]);
694                bounds[1] = bounds[1].min(screen[1]);
695                bounds[2] = bounds[2].max(screen[0]);
696                bounds[3] = bounds[3].max(screen[1]);
697            }
698        }
699    }
700    let margin = margin as f32;
701    Region::from_bounds(
702        [
703            bounds[0] - margin,
704            bounds[1] - margin,
705            bounds[2] + margin,
706            bounds[3] + margin,
707        ],
708        width,
709        height,
710    )
711}
712
713pub fn particle_region(
714    view_projection: &Matrix,
715    particles: &[ParticleInstance],
716    margin: u32,
717    width: u32,
718    height: u32,
719) -> Option<Region> {
720    let mut lo = [f32::INFINITY; 3];
721    let mut hi = [f32::NEG_INFINITY; 3];
722    for particle in particles {
723        let reach = particle.position_size[3].abs() * 1.5;
724        for axis in 0..3 {
725            lo[axis] = lo[axis].min(particle.position_size[axis] - reach);
726            hi[axis] = hi[axis].max(particle.position_size[axis] + reach);
727        }
728    }
729    if particles.is_empty() {
730        return None;
731    }
732    box_region(view_projection, lo, hi, margin, width, height)
733}
734
735pub fn local_post(chain: &pfx_post::Chain) -> bool {
736    chain.is_local()
737}
738
739pub(crate) const WARP_WGSL: &str = r#"
740struct Warp {
741    across: vec4f,
742    down: vec4f,
743    corner: vec4f,
744    origin: vec4f,
745    size: vec4f,
746    limits: vec4f,
747}
748@group(0) @binding(0) var<uniform> warp: Warp;
749@group(0) @binding(1) var colour: texture_2d<f32>;
750@group(0) @binding(2) var depth: texture_2d<f32>;
751@group(0) @binding(3) var smooth_sampler: sampler;
752@group(0) @binding(4) var<storage, read_write> holes: array<atomic<u32>, 4>;
753@group(0) @binding(5) var tiles: texture_2d<f32>;
754@group(0) @binding(6) var overlay: texture_2d<f32>;
755
756@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
757    let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
758    return vec4f(xy[index], 0.0, 1.0);
759}
760
761fn depth_at(uv: vec2f) -> f32 {
762    let size = vec2i(warp.size.xy);
763    let p = clamp(vec2i(floor(uv * warp.size.xy)), vec2i(0), size - vec2i(1));
764    return textureLoad(depth, p, 0).r;
765}
766
767fn moved(uv: vec2f, d: f32) -> vec2f {
768    let toward = warp.across * uv.x + warp.down * uv.y + warp.corner;
769    let clip = select(toward * d + warp.origin, toward, d >= warp.limits.x);
770    let ndc = clip.xy / clip.w;
771    return vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
772}
773
774struct Found {
775    uv: vec2f,
776    miss: f32,
777    depth: f32,
778}
779
780fn solve(goal: vec2f, start: vec2f) -> Found {
781    var s = start;
782    var found: Found;
783    for (var step = 0; step < 4; step = step + 1) {
784        found.depth = depth_at(s);
785        let error = goal - moved(s, found.depth);
786        found.miss = length(error * warp.size.xy);
787        found.uv = s;
788        if (found.miss < 0.05) {
789            break;
790        }
791        s = s + error;
792    }
793    return found;
794}
795
796fn seed(goal: vec2f, d: f32) -> vec2f {
797    return goal - (moved(goal, d) - goal);
798}
799
800fn pick(goal: vec2f) -> Found {
801    let first = solve(goal, seed(goal, depth_at(goal)));
802    let last = vec2i(textureDimensions(tiles)) - vec2i(1);
803    let tile = textureLoad(tiles, clamp(vec2i(first.uv * warp.size.xy / warp.limits.z), vec2i(0), last), 0).rg;
804    let near = min(tile.x, first.depth);
805    let far = max(tile.y, first.depth);
806    let tolerance = warp.limits.y;
807    if (far <= near * 1.02 && first.miss < tolerance) {
808        return first;
809    }
810    let front = solve(goal, seed(goal, near));
811    let back = solve(goal, seed(goal, far));
812    var best = back;
813    var hit = back.miss < tolerance;
814    if (first.miss < tolerance && (!hit || first.depth < best.depth)) {
815        best = first;
816        hit = true;
817    }
818    if (front.miss < tolerance && (!hit || front.depth < best.depth)) {
819        best = front;
820        hit = true;
821    }
822    if (!hit) {
823        best.miss = 1.0e9;
824    }
825    return best;
826}
827
828fn fetch(uv: vec2f) -> vec4f {
829    return textureSampleLevel(colour, smooth_sampler, uv, 0.0);
830}
831
832fn catmull_rom(uv: vec2f) -> vec4f {
833    let size = warp.size.xy;
834    let inv = warp.size.zw;
835    let at = uv * size;
836    let centre = floor(at - 0.5) + 0.5;
837    let f = at - centre;
838    let w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
839    let w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
840    let w2 = f * (0.5 + f * (2.0 - 1.5 * f));
841    let w3 = f * f * (-0.5 + 0.5 * f);
842    let w12 = w1 + w2;
843    let offset = w2 / w12;
844    let t0 = (centre - 1.0) * inv;
845    let t3 = (centre + 2.0) * inv;
846    let t12 = (centre + offset) * inv;
847    var sum = vec4f(0.0);
848    var weight = 0.0;
849    let a = w12.x * w0.y;
850    let ta = fetch(vec2f(t12.x, t0.y));
851    sum += ta * a;
852    let b = w0.x * w12.y;
853    let tb = fetch(vec2f(t0.x, t12.y));
854    sum += tb * b;
855    let c = w12.x * w12.y;
856    let tc = fetch(vec2f(t12.x, t12.y));
857    sum += tc * c;
858    let d = w3.x * w12.y;
859    let td = fetch(vec2f(t3.x, t12.y));
860    sum += td * d;
861    let e = w12.x * w3.y;
862    let te = fetch(vec2f(t12.x, t3.y));
863    sum += te * e;
864    weight = a + b + c + d + e;
865    let lo = min(min(min(ta, tb), min(tc, td)), te);
866    let hi = max(max(max(ta, tb), max(tc, td)), te);
867    return clamp(sum / weight, lo, hi);
868}
869
870fn warped(position: vec4f) -> Found {
871    let goal = position.xy * warp.size.zw;
872    var found = pick(goal);
873    let outside = any(found.uv < vec2f(0.0)) || any(found.uv > vec2f(1.0));
874    if (found.miss >= warp.limits.y || outside) {
875        atomicAdd(&holes[0], 1u);
876    }
877    found.uv = clamp(found.uv, vec2f(0.0), vec2f(1.0));
878    return found;
879}
880
881fn resample(uv: vec2f) -> vec4f {
882    let at = uv * warp.size.xy;
883    let centre = floor(at) + 0.5;
884    if (all(abs(at - centre) < vec2f(0.002))) {
885        return textureLoad(colour, vec2i(floor(at)), 0);
886    }
887    return catmull_rom(uv);
888}
889
890@fragment fn fragment(@builtin(position) position: vec4f) -> @location(0) vec4f {
891    let found = warped(position);
892    let base = resample(found.uv);
893    if (warp.limits.w > 0.5) {
894        let over = textureLoad(overlay, vec2i(position.xy), 0);
895        return over + base * (1.0 - over.a);
896    }
897    return base;
898}
899
900struct Layered {
901    @location(0) colour: vec4f,
902    @location(1) depth: f32,
903}
904
905@fragment fn layered(@builtin(position) position: vec4f) -> Layered {
906    let found = warped(position);
907    var out: Layered;
908    out.colour = resample(found.uv);
909    out.depth = found.depth;
910    return out;
911}
912"#;
913
914pub const WAKE_GROUPS: u32 = 1024;
915pub const TILE: u32 = 4;
916
917pub(crate) const TILES_WGSL: &str = r#"
918@group(0) @binding(0) var depth: texture_2d<f32>;
919@group(0) @binding(1) var raw: texture_storage_2d<rg32float, write>;
920@compute @workgroup_size(8, 8)
921fn reduce(@builtin(global_invocation_id) id: vec3u) {
922    let tiles = textureDimensions(raw);
923    if (id.x >= tiles.x || id.y >= tiles.y) {
924        return;
925    }
926    let size = vec2i(textureDimensions(depth));
927    let origin = vec2i(id.xy) * 4;
928    var lo = 3.0e38;
929    var hi = 0.0;
930    for (var y = 0; y < 4; y = y + 1) {
931        for (var x = 0; x < 4; x = x + 1) {
932            let d = textureLoad(depth, min(origin + vec2i(x, y), size - vec2i(1)), 0).r;
933            lo = min(lo, d);
934            hi = max(hi, d);
935        }
936    }
937    textureStore(raw, vec2i(id.xy), vec4f(lo, hi, 0.0, 0.0));
938}
939"#;
940
941pub(crate) const SPREAD_WGSL: &str = r#"
942@group(0) @binding(0) var raw: texture_2d<f32>;
943@group(0) @binding(1) var tiles: texture_storage_2d<rg32float, write>;
944@compute @workgroup_size(8, 8)
945fn spread(@builtin(global_invocation_id) id: vec3u) {
946    let size = vec2i(textureDimensions(raw));
947    if (i32(id.x) >= size.x || i32(id.y) >= size.y) {
948        return;
949    }
950    var lo = 3.0e38;
951    var hi = 0.0;
952    for (var y = -1; y <= 1; y = y + 1) {
953        for (var x = -1; x <= 1; x = x + 1) {
954            let p = clamp(vec2i(id.xy) + vec2i(x, y), vec2i(0), size - vec2i(1));
955            let range = textureLoad(raw, p, 0).rg;
956            lo = min(lo, range.x);
957            hi = max(hi, range.y);
958        }
959    }
960    textureStore(tiles, vec2i(id.xy), vec4f(lo, hi, 0.0, 0.0));
961}
962"#;
963
964pub(crate) const WAKE_WGSL: &str = r#"
965@group(0) @binding(0) var<storage, read_write> sink: array<f32, 4>;
966@compute @workgroup_size(64)
967fn main(@builtin(global_invocation_id) id: vec3u) {
968    var x = f32(id.x) * 1.0e-3;
969    for (var step = 0u; step < 2048u; step = step + 1u) {
970        x = fma(x, 1.0000001, 1.0e-7);
971    }
972    if (x == 12345.0) {
973        sink[id.x & 3u] = x;
974    }
975}
976"#;
977
978pub(crate) const COMPOSITE_WGSL: &str = r#"
979@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
980    let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
981    return vec4f(xy[index], 0.0, 1.0);
982}
983@group(0) @binding(0) var base: texture_2d<f32>;
984@group(0) @binding(1) var overlay: texture_2d<f32>;
985@fragment fn fragment(@builtin(position) position: vec4f) -> @location(0) vec4f {
986    let under = textureLoad(base, vec2i(position.xy), 0);
987    let over = textureLoad(overlay, vec2i(position.xy), 0);
988    return over + under * (1.0 - over.a);
989}
990"#;
991
992pub(crate) const COPY_WGSL: &str = r#"
993@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
994    let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
995    return vec4f(xy[index], 0.0, 1.0);
996}
997@group(0) @binding(0) var source: texture_2d<f32>;
998@fragment fn fragment(@builtin(position) position: vec4f) -> @location(0) vec4f {
999    return textureLoad(source, vec2i(position.xy), 0);
1000}
1001"#;
1002
1003#[derive(Clone, Copy)]
1004pub(crate) struct WarpUniform {
1005    pub across: [f32; 4],
1006    pub down: [f32; 4],
1007    pub corner: [f32; 4],
1008    pub origin: [f32; 4],
1009    pub size: [f32; 4],
1010    pub limits: [f32; 4],
1011}
1012
1013impl WarpUniform {
1014    pub fn new(
1015        from: (&Matrix, &Matrix),
1016        to: (&Matrix, &Matrix),
1017        size: [u32; 2],
1018        far: f32,
1019    ) -> Option<Self> {
1020        let (view0, projection0) = from;
1021        let (view1, projection1) = to;
1022        let inverse_projection = invert(*projection0)?;
1023        let mapped = multiply(*projection1, multiply(*view1, invert(*view0)?));
1024        let ray = |u: f32, v: f32| {
1025            let h = transform(
1026                &inverse_projection,
1027                [u * 2.0 - 1.0, 1.0 - v * 2.0, 0.5, 1.0],
1028            );
1029            [h[0] / -h[2], h[1] / -h[2]]
1030        };
1031        let start = ray(0.0, 0.0);
1032        let right = ray(1.0, 0.0)[0] - start[0];
1033        let below = ray(0.0, 1.0)[1] - start[1];
1034        let column = |index: usize| mapped[index];
1035        let scaled = |vector: [f32; 4], by: f32| vector.map(|value| value * by);
1036        let corner: [f32; 4] = std::array::from_fn(|row| {
1037            column(0)[row] * start[0] + column(1)[row] * start[1] - column(2)[row]
1038        });
1039        Some(Self {
1040            across: scaled(column(0), right),
1041            down: scaled(column(1), below),
1042            corner,
1043            origin: column(3),
1044            size: [
1045                size[0] as f32,
1046                size[1] as f32,
1047                1.0 / size[0] as f32,
1048                1.0 / size[1] as f32,
1049            ],
1050            limits: [far * 0.999, 0.75, TILE as f32, 0.0],
1051        })
1052    }
1053
1054    pub fn bytes(&self) -> Vec<u8> {
1055        let mut values: Vec<f32> = Vec::with_capacity(24);
1056        for vector in [
1057            self.across,
1058            self.down,
1059            self.corner,
1060            self.origin,
1061            self.size,
1062            self.limits,
1063        ] {
1064            values.extend(vector);
1065        }
1066        bytemuck::cast_slice(&values).to_vec()
1067    }
1068}
1069
1070pub(crate) struct Readback {
1071    buffer: wgpu::Buffer,
1072    pending: Option<Receiver<Result<(), wgpu::BufferAsyncError>>>,
1073    encoded: bool,
1074}
1075
1076impl Readback {
1077    fn new(device: &wgpu::Device, size: u64, label: &str) -> Self {
1078        Self {
1079            buffer: device.create_buffer(&wgpu::BufferDescriptor {
1080                label: Some(label),
1081                size,
1082                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1083                mapped_at_creation: false,
1084            }),
1085            pending: None,
1086            encoded: false,
1087        }
1088    }
1089
1090    pub fn free(&self) -> bool {
1091        self.pending.is_none() && !self.encoded
1092    }
1093
1094    pub fn submitted(&mut self) {
1095        if !self.encoded {
1096            return;
1097        }
1098        self.encoded = false;
1099        let (sender, receiver) = std::sync::mpsc::channel();
1100        self.buffer
1101            .slice(..)
1102            .map_async(wgpu::MapMode::Read, move |result| {
1103                let _ = sender.send(result);
1104            });
1105        self.pending = Some(receiver);
1106    }
1107
1108    pub fn take(&mut self) -> Option<Vec<u8>> {
1109        let receiver = self.pending.as_ref()?;
1110        match receiver.try_recv() {
1111            Ok(Ok(())) => {
1112                let bytes = self
1113                    .buffer
1114                    .slice(..)
1115                    .get_mapped_range()
1116                    .expect("a mapped range")
1117                    .to_vec();
1118                self.buffer.unmap();
1119                self.pending = None;
1120                Some(bytes)
1121            }
1122            Ok(Err(_)) | Err(TryRecvError::Disconnected) => {
1123                self.pending = None;
1124                None
1125            }
1126            Err(TryRecvError::Empty) => None,
1127        }
1128    }
1129}
1130
1131pub(crate) struct Image {
1132    pub view: wgpu::TextureView,
1133}
1134
1135fn image(
1136    device: &wgpu::Device,
1137    size: [u32; 2],
1138    format: wgpu::TextureFormat,
1139    usage: wgpu::TextureUsages,
1140    label: &str,
1141) -> Image {
1142    let texture = device.create_texture(&wgpu::TextureDescriptor {
1143        label: Some(label),
1144        size: wgpu::Extent3d {
1145            width: size[0],
1146            height: size[1],
1147            depth_or_array_layers: 1,
1148        },
1149        mip_level_count: 1,
1150        sample_count: 1,
1151        dimension: wgpu::TextureDimension::D2,
1152        format,
1153        usage,
1154        view_formats: &[],
1155    });
1156    Image {
1157        view: texture.create_view(&Default::default()),
1158    }
1159}
1160
1161#[derive(Clone, Copy)]
1162pub(crate) enum Last {
1163    Colour,
1164    Scratch,
1165    Warp(WarpUniform),
1166}
1167
1168pub(crate) struct Cached {
1169    pub view: Matrix,
1170    pub projection: Matrix,
1171    pub far: f32,
1172    pub depth_index: usize,
1173    pub shadow: Vec<u8>,
1174    pub frame_number: u32,
1175    pub exposure: f32,
1176}
1177
1178pub(crate) struct Cache {
1179    pub size: [u32; 2],
1180    pub colour: Image,
1181    pub still: Image,
1182    pub layer: Image,
1183    pub depth: Image,
1184    pub scratch: Image,
1185    pub tiles: Image,
1186    raw_tiles: Image,
1187    pub limits: Limits,
1188    pub book: Book,
1189    pub cached: Option<Cached>,
1190    pub exposure_moving: bool,
1191    pub colour_layers: bool,
1192    pub last: Last,
1193    pub overlaid: bool,
1194    pub overlay_print: Option<u64>,
1195    pub overlay: Image,
1196    layout: wgpu::BindGroupLayout,
1197    module: wgpu::ShaderModule,
1198    pipeline_layout: wgpu::PipelineLayout,
1199    pipelines: Vec<(wgpu::TextureFormat, bool, wgpu::RenderPipeline)>,
1200    copy_layout: wgpu::BindGroupLayout,
1201    copy_pipeline: wgpu::RenderPipeline,
1202    sampler: wgpu::Sampler,
1203    holes: wgpu::Buffer,
1204    wake: Option<(wgpu::ComputePipeline, wgpu::BindGroup)>,
1205    tiler: wgpu::ComputePipeline,
1206    spreader: wgpu::ComputePipeline,
1207    composite_layout: wgpu::BindGroupLayout,
1208    composite_pipeline_layout: wgpu::PipelineLayout,
1209    composite_module: wgpu::ShaderModule,
1210    composites: Vec<(wgpu::TextureFormat, wgpu::RenderPipeline)>,
1211    holes_back: Readback,
1212    depth_back: Readback,
1213}
1214
1215const COLOUR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
1216
1217impl Cache {
1218    pub fn new(device: &wgpu::Device, size: [u32; 2], limits: Limits) -> Self {
1219        let colour = image(
1220            device,
1221            size,
1222            COLOUR,
1223            wgpu::TextureUsages::RENDER_ATTACHMENT
1224                | wgpu::TextureUsages::TEXTURE_BINDING
1225                | wgpu::TextureUsages::STORAGE_BINDING,
1226            "demand colour",
1227        );
1228        let still = image(
1229            device,
1230            size,
1231            COLOUR,
1232            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1233            "demand still",
1234        );
1235        let layer = image(
1236            device,
1237            size,
1238            COLOUR,
1239            wgpu::TextureUsages::RENDER_ATTACHMENT
1240                | wgpu::TextureUsages::TEXTURE_BINDING
1241                | wgpu::TextureUsages::STORAGE_BINDING,
1242            "demand layer",
1243        );
1244        let depth = image(
1245            device,
1246            size,
1247            wgpu::TextureFormat::R32Float,
1248            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1249            "demand warped depth",
1250        );
1251        let scratch = image(
1252            device,
1253            size,
1254            COLOUR,
1255            wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1256            "demand post",
1257        );
1258        let tiles = image(
1259            device,
1260            [size[0].div_ceil(TILE), size[1].div_ceil(TILE)],
1261            wgpu::TextureFormat::Rg32Float,
1262            wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1263            "demand depth tiles",
1264        );
1265        let raw_tiles = image(
1266            device,
1267            [size[0].div_ceil(TILE), size[1].div_ceil(TILE)],
1268            wgpu::TextureFormat::Rg32Float,
1269            wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1270            "demand raw depth tiles",
1271        );
1272        let compute = |source: &str, entry: &str, label: &str| {
1273            let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1274                label: Some(label),
1275                source: wgpu::ShaderSource::Wgsl(source.into()),
1276            });
1277            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1278                label: Some(label),
1279                layout: None,
1280                module: &module,
1281                entry_point: Some(entry),
1282                compilation_options: Default::default(),
1283                cache: None,
1284            })
1285        };
1286        let tiler = compute(TILES_WGSL, "reduce", "demand depth tiles");
1287        let spreader = compute(SPREAD_WGSL, "spread", "demand depth spread");
1288        let float = |binding: u32, filterable: bool| wgpu::BindGroupLayoutEntry {
1289            binding,
1290            visibility: wgpu::ShaderStages::FRAGMENT,
1291            ty: wgpu::BindingType::Texture {
1292                sample_type: wgpu::TextureSampleType::Float { filterable },
1293                view_dimension: wgpu::TextureViewDimension::D2,
1294                multisampled: false,
1295            },
1296            count: None,
1297        };
1298        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1299            label: Some("demand warp"),
1300            entries: &[
1301                wgpu::BindGroupLayoutEntry {
1302                    binding: 0,
1303                    visibility: wgpu::ShaderStages::FRAGMENT,
1304                    ty: wgpu::BindingType::Buffer {
1305                        ty: wgpu::BufferBindingType::Uniform,
1306                        has_dynamic_offset: false,
1307                        min_binding_size: None,
1308                    },
1309                    count: None,
1310                },
1311                float(1, true),
1312                float(2, false),
1313                wgpu::BindGroupLayoutEntry {
1314                    binding: 3,
1315                    visibility: wgpu::ShaderStages::FRAGMENT,
1316                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1317                    count: None,
1318                },
1319                wgpu::BindGroupLayoutEntry {
1320                    binding: 4,
1321                    visibility: wgpu::ShaderStages::FRAGMENT,
1322                    ty: wgpu::BindingType::Buffer {
1323                        ty: wgpu::BufferBindingType::Storage { read_only: false },
1324                        has_dynamic_offset: false,
1325                        min_binding_size: None,
1326                    },
1327                    count: None,
1328                },
1329                float(5, false),
1330                float(6, false),
1331            ],
1332        });
1333        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1334            label: Some("demand warp"),
1335            bind_group_layouts: &[Some(&layout)],
1336            immediate_size: 0,
1337        });
1338        let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1339            label: Some("demand warp"),
1340            source: wgpu::ShaderSource::Wgsl(WARP_WGSL.into()),
1341        });
1342        let overlay = image(
1343            device,
1344            size,
1345            COLOUR,
1346            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1347            "demand overlay",
1348        );
1349        let composite_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1350            label: Some("demand composite"),
1351            entries: &[float(0, false), float(1, false)],
1352        });
1353        let composite_pipeline_layout =
1354            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1355                label: Some("demand composite"),
1356                bind_group_layouts: &[Some(&composite_layout)],
1357                immediate_size: 0,
1358            });
1359        let composite_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1360            label: Some("demand composite"),
1361            source: wgpu::ShaderSource::Wgsl(COMPOSITE_WGSL.into()),
1362        });
1363        let copy_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1364            label: Some("demand copy"),
1365            entries: &[float(0, false)],
1366        });
1367        let copy_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1368            label: Some("demand copy"),
1369            bind_group_layouts: &[Some(&copy_layout)],
1370            immediate_size: 0,
1371        });
1372        let copy_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1373            label: Some("demand copy"),
1374            source: wgpu::ShaderSource::Wgsl(COPY_WGSL.into()),
1375        });
1376        let copy_pipeline = pipeline(
1377            device,
1378            &copy_pipeline_layout,
1379            &copy_module,
1380            "fragment",
1381            &[COLOUR],
1382            "demand copy",
1383        );
1384        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1385            label: Some("demand warp"),
1386            mag_filter: wgpu::FilterMode::Linear,
1387            min_filter: wgpu::FilterMode::Linear,
1388            address_mode_u: wgpu::AddressMode::ClampToEdge,
1389            address_mode_v: wgpu::AddressMode::ClampToEdge,
1390            ..Default::default()
1391        });
1392        let holes = device.create_buffer(&wgpu::BufferDescriptor {
1393            label: Some("demand holes"),
1394            size: 16,
1395            usage: wgpu::BufferUsages::STORAGE
1396                | wgpu::BufferUsages::COPY_SRC
1397                | wgpu::BufferUsages::COPY_DST,
1398            mapped_at_creation: false,
1399        });
1400        Self {
1401            size,
1402            colour,
1403            still,
1404            layer,
1405            depth,
1406            scratch,
1407            tiles,
1408            raw_tiles,
1409            limits,
1410            book: Book::default(),
1411            cached: None,
1412            exposure_moving: false,
1413            colour_layers: false,
1414            last: Last::Colour,
1415            overlaid: false,
1416            overlay_print: None,
1417            overlay,
1418            layout,
1419            module,
1420            pipeline_layout,
1421            pipelines: Vec::new(),
1422            copy_layout,
1423            copy_pipeline,
1424            sampler,
1425            holes,
1426            wake: None,
1427            tiler,
1428            spreader,
1429            composite_layout,
1430            composite_pipeline_layout,
1431            composite_module,
1432            composites: Vec::new(),
1433            holes_back: Readback::new(device, 16, "demand holes readback"),
1434            depth_back: Readback::new(device, DEPTH_PROBES as u64 * 256, "demand depth readback"),
1435        }
1436    }
1437
1438    pub fn stats(&self) -> DemandStats {
1439        DemandStats {
1440            shown: self.book.kind,
1441            since_full: self.book.since_full,
1442            settle: self.book.settle,
1443            holes: self.book.holes,
1444            depth: self.book.depth,
1445        }
1446    }
1447
1448    pub fn gather(&mut self) {
1449        if let Some(bytes) = self.holes_back.take() {
1450            let count = u32::from_le_bytes(bytes[..4].try_into().unwrap());
1451            let pixels = (u64::from(self.size[0]) * u64::from(self.size[1])).max(1);
1452            self.book.holes = count as f32 / pixels as f32;
1453        }
1454        if let Some(bytes) = self.depth_back.take() {
1455            let far = self
1456                .cached
1457                .as_ref()
1458                .map_or(f32::INFINITY, |cached| cached.far);
1459            let mut depths: Vec<f32> = (0..DEPTH_PROBES)
1460                .map(|index| {
1461                    f32::from_le_bytes(bytes[index * 256..index * 256 + 4].try_into().unwrap())
1462                })
1463                .filter(|depth| depth.is_finite() && *depth > 0.0 && *depth < far * 0.999)
1464                .collect();
1465            depths.sort_by(f32::total_cmp);
1466            self.book.depth = Some(
1467                depths
1468                    .get(depths.len() / 2)
1469                    .copied()
1470                    .unwrap_or(f32::INFINITY),
1471            );
1472        }
1473    }
1474
1475    pub fn submitted(&mut self) {
1476        self.holes_back.submitted();
1477        self.depth_back.submitted();
1478    }
1479
1480    pub fn probe_depth(&mut self, encoder: &mut wgpu::CommandEncoder, depth: &wgpu::Texture) {
1481        if !self.depth_back.free() {
1482            return;
1483        }
1484        let [width, height] = self.size;
1485        for index in 0..DEPTH_PROBES {
1486            let column = (index % 4) as u32;
1487            let row = (index / 4) as u32;
1488            let x = ((2 * column + 1) * width / 8).min(width - 1);
1489            let y = ((2 * row + 1) * height / 8).min(height - 1);
1490            encoder.copy_texture_to_buffer(
1491                wgpu::TexelCopyTextureInfo {
1492                    texture: depth,
1493                    mip_level: 0,
1494                    origin: wgpu::Origin3d { x, y, z: 0 },
1495                    aspect: wgpu::TextureAspect::All,
1496                },
1497                wgpu::TexelCopyBufferInfo {
1498                    buffer: &self.depth_back.buffer,
1499                    layout: wgpu::TexelCopyBufferLayout {
1500                        offset: index as u64 * 256,
1501                        bytes_per_row: Some(256),
1502                        rows_per_image: Some(1),
1503                    },
1504                },
1505                wgpu::Extent3d {
1506                    width: 1,
1507                    height: 1,
1508                    depth_or_array_layers: 1,
1509                },
1510            );
1511        }
1512        self.depth_back.encoded = true;
1513    }
1514
1515    pub fn encode_clear(&self, encoder: &mut wgpu::CommandEncoder) {
1516        encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1517            multiview_mask: None,
1518            label: Some("demand overlay clear"),
1519            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1520                view: &self.overlay.view,
1521                resolve_target: None,
1522                depth_slice: None,
1523                ops: wgpu::Operations {
1524                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1525                    store: wgpu::StoreOp::Store,
1526                },
1527            })],
1528            depth_stencil_attachment: None,
1529            timestamp_writes: None,
1530            occlusion_query_set: None,
1531        });
1532    }
1533
1534    pub fn encode_composite(
1535        &mut self,
1536        device: &wgpu::Device,
1537        encoder: &mut wgpu::CommandEncoder,
1538        base: &wgpu::TextureView,
1539        target: (&wgpu::TextureView, wgpu::TextureFormat),
1540        profiler: &mut GpuProfiler,
1541    ) {
1542        let index = match self
1543            .composites
1544            .iter()
1545            .position(|(format, _)| *format == target.1)
1546        {
1547            Some(index) => index,
1548            None => {
1549                let built = pipeline(
1550                    device,
1551                    &self.composite_pipeline_layout,
1552                    &self.composite_module,
1553                    "fragment",
1554                    &[target.1],
1555                    "demand composite",
1556                );
1557                self.composites.push((target.1, built));
1558                self.composites.len() - 1
1559            }
1560        };
1561        let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1562            label: Some("demand composite"),
1563            layout: &self.composite_layout,
1564            entries: &[
1565                wgpu::BindGroupEntry {
1566                    binding: 0,
1567                    resource: wgpu::BindingResource::TextureView(base),
1568                },
1569                wgpu::BindGroupEntry {
1570                    binding: 1,
1571                    resource: wgpu::BindingResource::TextureView(&self.overlay.view),
1572                },
1573            ],
1574        });
1575        let timing = profiler.pass("composite");
1576        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1577            multiview_mask: None,
1578            label: Some("demand composite"),
1579            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1580                view: target.0,
1581                resolve_target: None,
1582                depth_slice: None,
1583                ops: wgpu::Operations {
1584                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1585                    store: wgpu::StoreOp::Store,
1586                },
1587            })],
1588            depth_stencil_attachment: None,
1589            timestamp_writes: profiler.render_writes(timing),
1590            occlusion_query_set: None,
1591        });
1592        pass.set_pipeline(&self.composites[index].1);
1593        pass.set_bind_group(0, &bind, &[]);
1594        pass.draw(0..3, 0..1);
1595    }
1596
1597    pub fn encode_tiles(
1598        &self,
1599        device: &wgpu::Device,
1600        encoder: &mut wgpu::CommandEncoder,
1601        depth: &wgpu::TextureView,
1602        profiler: &mut GpuProfiler,
1603    ) {
1604        let [width, height] = [self.size[0].div_ceil(TILE), self.size[1].div_ceil(TILE)];
1605        let timing = profiler.pass("demand tiles");
1606        let writes = profiler.compute_writes(timing);
1607        let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1608            label: Some("demand depth tiles"),
1609            timestamp_writes: writes,
1610        });
1611        for (pipeline, source, target) in [
1612            (&self.tiler, depth, &self.raw_tiles.view),
1613            (&self.spreader, &self.raw_tiles.view, &self.tiles.view),
1614        ] {
1615            let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1616                label: Some("demand depth tiles"),
1617                layout: &pipeline.get_bind_group_layout(0),
1618                entries: &[
1619                    wgpu::BindGroupEntry {
1620                        binding: 0,
1621                        resource: wgpu::BindingResource::TextureView(source),
1622                    },
1623                    wgpu::BindGroupEntry {
1624                        binding: 1,
1625                        resource: wgpu::BindingResource::TextureView(target),
1626                    },
1627                ],
1628            });
1629            pass.set_pipeline(pipeline);
1630            pass.set_bind_group(0, &bind, &[]);
1631            pass.dispatch_workgroups(width.div_ceil(8), height.div_ceil(8), 1);
1632        }
1633    }
1634
1635    pub fn encode_wake(
1636        &mut self,
1637        device: &wgpu::Device,
1638        encoder: &mut wgpu::CommandEncoder,
1639        groups: u32,
1640        profiler: &mut GpuProfiler,
1641    ) {
1642        let (pipeline, bind) = self.wake.get_or_insert_with(|| {
1643            let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1644                label: Some("demand wake"),
1645                source: wgpu::ShaderSource::Wgsl(WAKE_WGSL.into()),
1646            });
1647            let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1648                label: Some("demand wake"),
1649                layout: None,
1650                module: &module,
1651                entry_point: Some("main"),
1652                compilation_options: Default::default(),
1653                cache: None,
1654            });
1655            let sink = device.create_buffer(&wgpu::BufferDescriptor {
1656                label: Some("demand wake"),
1657                size: 16,
1658                usage: wgpu::BufferUsages::STORAGE,
1659                mapped_at_creation: false,
1660            });
1661            let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1662                label: Some("demand wake"),
1663                layout: &pipeline.get_bind_group_layout(0),
1664                entries: &[wgpu::BindGroupEntry {
1665                    binding: 0,
1666                    resource: sink.as_entire_binding(),
1667                }],
1668            });
1669            (pipeline, bind)
1670        });
1671        let timing = profiler.pass("wake");
1672        let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1673            label: Some("demand wake"),
1674            timestamp_writes: profiler.compute_writes(timing),
1675        });
1676        pass.set_pipeline(pipeline);
1677        pass.set_bind_group(0, &*bind, &[]);
1678        pass.dispatch_workgroups(groups.max(1), 1, 1);
1679    }
1680
1681    fn warp_pipeline(
1682        &mut self,
1683        device: &wgpu::Device,
1684        format: wgpu::TextureFormat,
1685        layered: bool,
1686    ) -> usize {
1687        if let Some(index) = self
1688            .pipelines
1689            .iter()
1690            .position(|(known, kind, _)| *known == format && *kind == layered)
1691        {
1692            return index;
1693        }
1694        let built = if layered {
1695            pipeline(
1696                device,
1697                &self.pipeline_layout,
1698                &self.module,
1699                "layered",
1700                &[format, wgpu::TextureFormat::R32Float],
1701                "demand warp layered",
1702            )
1703        } else {
1704            pipeline(
1705                device,
1706                &self.pipeline_layout,
1707                &self.module,
1708                "fragment",
1709                &[format],
1710                "demand warp",
1711            )
1712        };
1713        self.pipelines.push((format, layered, built));
1714        self.pipelines.len() - 1
1715    }
1716
1717    #[allow(clippy::too_many_arguments)]
1718    pub fn encode_warp(
1719        &mut self,
1720        device: &wgpu::Device,
1721        encoder: &mut wgpu::CommandEncoder,
1722        uniform: &WarpUniform,
1723        source: &wgpu::TextureView,
1724        depth: &wgpu::TextureView,
1725        target: (&wgpu::TextureView, wgpu::TextureFormat),
1726        layered: bool,
1727        profiler: &mut GpuProfiler,
1728    ) {
1729        let index = self.warp_pipeline(device, target.1, layered);
1730        let buffer = wgpu::util::DeviceExt::create_buffer_init(
1731            device,
1732            &wgpu::util::BufferInitDescriptor {
1733                label: Some("demand warp"),
1734                contents: &uniform.bytes(),
1735                usage: wgpu::BufferUsages::UNIFORM,
1736            },
1737        );
1738        let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1739            label: Some("demand warp"),
1740            layout: &self.layout,
1741            entries: &[
1742                wgpu::BindGroupEntry {
1743                    binding: 0,
1744                    resource: buffer.as_entire_binding(),
1745                },
1746                wgpu::BindGroupEntry {
1747                    binding: 1,
1748                    resource: wgpu::BindingResource::TextureView(source),
1749                },
1750                wgpu::BindGroupEntry {
1751                    binding: 2,
1752                    resource: wgpu::BindingResource::TextureView(depth),
1753                },
1754                wgpu::BindGroupEntry {
1755                    binding: 3,
1756                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1757                },
1758                wgpu::BindGroupEntry {
1759                    binding: 4,
1760                    resource: self.holes.as_entire_binding(),
1761                },
1762                wgpu::BindGroupEntry {
1763                    binding: 5,
1764                    resource: wgpu::BindingResource::TextureView(&self.tiles.view),
1765                },
1766                wgpu::BindGroupEntry {
1767                    binding: 6,
1768                    resource: wgpu::BindingResource::TextureView(&self.overlay.view),
1769                },
1770            ],
1771        });
1772        let measure = self.holes_back.free();
1773        if measure {
1774            encoder.clear_buffer(&self.holes, 0, None);
1775        }
1776        let timing = profiler.pass("reproject");
1777        {
1778            let mut attachments = vec![Some(wgpu::RenderPassColorAttachment {
1779                view: target.0,
1780                resolve_target: None,
1781                depth_slice: None,
1782                ops: wgpu::Operations {
1783                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1784                    store: wgpu::StoreOp::Store,
1785                },
1786            })];
1787            if layered {
1788                attachments.push(Some(wgpu::RenderPassColorAttachment {
1789                    view: &self.depth.view,
1790                    resolve_target: None,
1791                    depth_slice: None,
1792                    ops: wgpu::Operations {
1793                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1794                        store: wgpu::StoreOp::Store,
1795                    },
1796                }));
1797            }
1798            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1799                multiview_mask: None,
1800                label: Some("demand reproject"),
1801                color_attachments: &attachments,
1802                depth_stencil_attachment: None,
1803                timestamp_writes: profiler.render_writes(timing),
1804                occlusion_query_set: None,
1805            });
1806            pass.set_pipeline(&self.pipelines[index].2);
1807            pass.set_bind_group(0, &bind, &[]);
1808            pass.draw(0..3, 0..1);
1809        }
1810        if measure {
1811            encoder.copy_buffer_to_buffer(&self.holes, 0, &self.holes_back.buffer, 0, 16);
1812            self.holes_back.encoded = true;
1813        }
1814    }
1815
1816    #[allow(clippy::too_many_arguments)]
1817    pub fn encode_copy(
1818        &self,
1819        device: &wgpu::Device,
1820        encoder: &mut wgpu::CommandEncoder,
1821        source: &wgpu::TextureView,
1822        target: &wgpu::TextureView,
1823        regions: &[Region],
1824        label: &'static str,
1825        profiler: &mut GpuProfiler,
1826    ) {
1827        if regions.is_empty() {
1828            return;
1829        }
1830        let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1831            label: Some("demand copy"),
1832            layout: &self.copy_layout,
1833            entries: &[wgpu::BindGroupEntry {
1834                binding: 0,
1835                resource: wgpu::BindingResource::TextureView(source),
1836            }],
1837        });
1838        let timing = profiler.pass(label);
1839        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1840            multiview_mask: None,
1841            label: Some(label),
1842            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1843                view: target,
1844                resolve_target: None,
1845                depth_slice: None,
1846                ops: wgpu::Operations {
1847                    load: wgpu::LoadOp::Load,
1848                    store: wgpu::StoreOp::Store,
1849                },
1850            })],
1851            depth_stencil_attachment: None,
1852            timestamp_writes: profiler.render_writes(timing),
1853            occlusion_query_set: None,
1854        });
1855        pass.set_pipeline(&self.copy_pipeline);
1856        pass.set_bind_group(0, &bind, &[]);
1857        for region in regions {
1858            let region = region.clip(self.size[0], self.size[1]);
1859            if region.is_empty() {
1860                continue;
1861            }
1862            pass.set_scissor_rect(region.x, region.y, region.width, region.height);
1863            pass.draw(0..3, 0..1);
1864        }
1865    }
1866}
1867
1868fn pipeline(
1869    device: &wgpu::Device,
1870    layout: &wgpu::PipelineLayout,
1871    module: &wgpu::ShaderModule,
1872    entry: &str,
1873    formats: &[wgpu::TextureFormat],
1874    label: &str,
1875) -> wgpu::RenderPipeline {
1876    let targets: Vec<Option<wgpu::ColorTargetState>> = formats
1877        .iter()
1878        .map(|format| {
1879            Some(wgpu::ColorTargetState {
1880                format: *format,
1881                blend: None,
1882                write_mask: wgpu::ColorWrites::ALL,
1883            })
1884        })
1885        .collect();
1886    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1887        label: Some(label),
1888        layout: Some(layout),
1889        vertex: wgpu::VertexState {
1890            module,
1891            entry_point: Some("vertex"),
1892            buffers: &[],
1893            compilation_options: Default::default(),
1894        },
1895        primitive: Default::default(),
1896        depth_stencil: None,
1897        multisample: Default::default(),
1898        fragment: Some(wgpu::FragmentState {
1899            module,
1900            entry_point: Some(entry),
1901            targets: &targets,
1902            compilation_options: Default::default(),
1903        }),
1904        multiview_mask: None,
1905        cache: None,
1906    })
1907}
1908
1909#[cfg(test)]
1910mod tests {
1911    use super::*;
1912
1913    const W: u32 = 1280;
1914    const H: u32 = 800;
1915
1916    fn projection(scale: f32) -> Matrix {
1917        let near = 0.05;
1918        let far = 40.0;
1919        [
1920            [scale / 1.6, 0.0, 0.0, 0.0],
1921            [0.0, scale, 0.0, 0.0],
1922            [0.0, 0.0, far / (near - far), -1.0],
1923            [0.0, 0.0, far * near / (near - far), 0.0],
1924        ]
1925    }
1926
1927    fn view(yaw: f32, position: [f32; 3]) -> Matrix {
1928        let (s, c) = yaw.sin_cos();
1929        let right = [c, 0.0, -s];
1930        let up = [0.0, 1.0, 0.0];
1931        let back = [s, 0.0, c];
1932        let dot = |a: [f32; 3]| a[0] * position[0] + a[1] * position[1] + a[2] * position[2];
1933        [
1934            [right[0], up[0], back[0], 0.0],
1935            [right[1], up[1], back[1], 0.0],
1936            [right[2], up[2], back[2], 0.0],
1937            [-dot(right), -dot(up), -dot(back), 1.0],
1938        ]
1939    }
1940
1941    fn summary() -> Summary {
1942        Summary {
1943            view: view(0.0, [0.0, 1.0, 2.0]),
1944            projection: projection(1.5),
1945            position: [0.0, 1.0, 2.0],
1946            size: [W, H],
1947            world: 7,
1948            anchors: Vec::new(),
1949            overlay: 3,
1950            ticking: false,
1951            layered: Vec::new(),
1952            moving: Vec::new(),
1953        }
1954    }
1955
1956    fn turned(degrees: f32) -> Summary {
1957        Summary {
1958            view: view(degrees.to_radians(), [0.0, 1.0, 2.0]),
1959            ..summary()
1960        }
1961    }
1962
1963    fn settled(shown: Summary) -> Book {
1964        let limits = Limits {
1965            settle: 0,
1966            ..Limits::default()
1967        };
1968        let mut book = Book::default();
1969        record(&mut book, shown, Shown::Full, &limits);
1970        book.depth = Some(2.0);
1971        book.still = true;
1972        book
1973    }
1974
1975    #[test]
1976    fn regions_clip_grow_and_merge() {
1977        let region = Region::new(10, 20, 30, 40);
1978        assert_eq!(region.grow(15, W, H), Region::new(0, 5, 55, 70));
1979        assert_eq!(
1980            Region::new(1270, 790, 50, 50).clip(W, H),
1981            Region::new(1270, 790, 10, 10)
1982        );
1983        assert!(Region::new(1300, 10, 5, 5).clip(W, H).is_empty());
1984        assert_eq!(
1985            Region::from_bounds([10.2, 20.9, 40.1, 60.0], W, H),
1986            Some(Region::new(10, 20, 31, 40))
1987        );
1988        assert_eq!(Region::from_bounds([50.0, 50.0, 40.0, 60.0], W, H), None);
1989        assert_eq!(
1990            Region::from_bounds([f32::NAN, 0.0, 1.0, 1.0], W, H),
1991            Some(Region::whole(W, H))
1992        );
1993        let merged = merge(
1994            &[
1995                Region::new(0, 0, 10, 10),
1996                Region::new(20, 0, 10, 10),
1997                Region::new(600, 400, 10, 10),
1998            ],
1999            W,
2000            H,
2001        );
2002        assert_eq!(
2003            merged,
2004            vec![Region::new(0, 0, 30, 10), Region::new(600, 400, 10, 10)]
2005        );
2006        assert_eq!(
2007            merge(&[Region::new(0, 0, 1000, 700)], W, H),
2008            vec![Region::whole(W, H)]
2009        );
2010        assert!(merge(&[Region::new(0, 0, 0, 10)], W, H).is_empty());
2011    }
2012
2013    #[test]
2014    fn motion_measures_turns_shifts_and_zoom() {
2015        let base = summary();
2016        fn at(s: &Summary) -> (&Matrix, &Matrix, [f32; 3]) {
2017            (&s.view, &s.projection, s.position)
2018        }
2019        let still = motion(at(&base), at(&base), None).unwrap();
2020        assert!(still.angle.abs() < 1e-6 && still.zoom.abs() < 1e-6);
2021        let yaw = turned(0.25);
2022        let moved = motion(at(&base), at(&yaw), None).unwrap();
2023        assert!((moved.angle.to_degrees() - 0.25).abs() < 2e-3, "{moved:?}");
2024        let tiny = turned(0.002);
2025        let small = motion(at(&base), at(&tiny), None).unwrap();
2026        assert!((small.angle.to_degrees() - 0.002).abs() < 5e-4, "{small:?}");
2027        let pushed = Summary {
2028            view: view(0.0, [0.0, 1.0, 1.98]),
2029            position: [0.0, 1.0, 1.98],
2030            ..summary()
2031        };
2032        assert_eq!(motion(at(&base), at(&pushed), None), None);
2033        let shift = motion(at(&base), at(&pushed), Some(2.0)).unwrap();
2034        assert!((shift.angle - 0.01).abs() < 1e-4, "{shift:?}");
2035        let sky = motion(at(&base), at(&pushed), Some(f32::INFINITY)).unwrap();
2036        assert!(sky.angle < 1e-4);
2037        let zoomed = Summary {
2038            projection: projection(1.53),
2039            ..summary()
2040        };
2041        let zoom = motion(at(&base), at(&zoomed), None).unwrap();
2042        assert!((zoom.zoom - (1.53_f32 / 1.5).ln()).abs() < 1e-5);
2043        let mut squeezed = summary();
2044        squeezed.projection[0][0] *= 1.1;
2045        assert_eq!(motion(at(&base), at(&squeezed), None), None);
2046        let mut nearer = summary();
2047        nearer.projection[3][2] *= 0.5;
2048        assert_eq!(motion(at(&base), at(&nearer), None), None);
2049    }
2050
2051    #[test]
2052    fn nothing_cached_needs_a_full_frame() {
2053        assert_eq!(
2054            decide(&Book::default(), &summary(), &Limits::default()),
2055            Need::Full
2056        );
2057    }
2058
2059    #[test]
2060    fn an_unchanged_frame_needs_nothing() {
2061        let book = settled(summary());
2062        assert_eq!(decide(&book, &summary(), &Limits::default()), Need::None);
2063    }
2064
2065    #[test]
2066    fn each_kind_of_change_is_seen() {
2067        let limits = Limits::default();
2068        let book = settled(summary());
2069        let world = Summary {
2070            world: 8,
2071            ..summary()
2072        };
2073        assert_eq!(decide(&book, &world, &limits), Need::Full);
2074        let ticking = Summary {
2075            ticking: true,
2076            ..summary()
2077        };
2078        assert_eq!(decide(&book, &ticking, &limits), Need::Full);
2079        let resized = Summary {
2080            size: [W, H + 2],
2081            ..summary()
2082        };
2083        assert_eq!(decide(&book, &resized, &limits), Need::Full);
2084        let mut anchored = settled(Summary {
2085            anchors: vec![view(0.0, [0.0, 0.0, 0.0])],
2086            ..summary()
2087        });
2088        anchored.depth = Some(2.0);
2089        let nudged = Summary {
2090            anchors: vec![view(0.0, [0.0, 0.0, 1e-6])],
2091            ..summary()
2092        };
2093        assert_eq!(decide(&anchored, &nudged, &limits), Need::None);
2094        let slid = Summary {
2095            anchors: vec![view(0.0, [0.0, 0.01, 0.0])],
2096            ..summary()
2097        };
2098        assert_eq!(decide(&anchored, &slid, &limits), Need::Full);
2099        let overlay = Summary {
2100            overlay: 4,
2101            ..summary()
2102        };
2103        assert_eq!(
2104            decide(&book, &overlay, &limits),
2105            Need::Partial(vec![Region::whole(W, H)])
2106        );
2107        let moving = Summary {
2108            moving: vec![Region::new(10, 10, 40, 20)],
2109            ..summary()
2110        };
2111        assert_eq!(
2112            decide(&book, &moving, &limits),
2113            Need::Partial(vec![Region::new(10, 10, 40, 20)])
2114        );
2115    }
2116
2117    #[test]
2118    fn small_moves_reproject_and_large_ones_redraw() {
2119        let limits = Limits::default();
2120        let book = settled(summary());
2121        assert_eq!(decide(&book, &turned(0.3), &limits), Need::Reproject);
2122        assert_eq!(decide(&book, &turned(0.7), &limits), Need::Full);
2123        let zoomed = Summary {
2124            projection: projection(1.5 * 1.01),
2125            ..summary()
2126        };
2127        assert_eq!(decide(&book, &zoomed, &limits), Need::Reproject);
2128        let far_zoom = Summary {
2129            projection: projection(1.5 * 1.05),
2130            ..summary()
2131        };
2132        assert_eq!(decide(&book, &far_zoom, &limits), Need::Full);
2133        let mut unknown = book.clone();
2134        unknown.depth = None;
2135        let pushed = Summary {
2136            view: view(0.0, [0.0, 1.0, 1.995]),
2137            position: [0.0, 1.0, 1.995],
2138            ..summary()
2139        };
2140        assert_eq!(decide(&unknown, &pushed, &limits), Need::Full);
2141        assert_eq!(decide(&book, &pushed, &limits), Need::Reproject);
2142        let mut worn = book.clone();
2143        worn.since_full = limits.frames;
2144        assert_eq!(decide(&worn, &turned(0.1), &limits), Need::Full);
2145        let mut holed = book.clone();
2146        holed.holes = limits.holes * 2.0;
2147        assert_eq!(decide(&holed, &turned(0.1), &limits), Need::Full);
2148    }
2149
2150    #[test]
2151    fn a_camera_at_rest_after_reprojecting_gets_a_fresh_frame() {
2152        let limits = Limits::default();
2153        let mut book = settled(summary());
2154        record(&mut book, turned(0.2), Shown::Reproject, &limits);
2155        assert_eq!(book.since_full, 1);
2156        assert_eq!(decide(&book, &turned(0.25), &limits), Need::Reproject);
2157        assert_eq!(decide(&book, &turned(0.2), &limits), Need::Full);
2158        assert_eq!(decide(&book, &summary(), &limits), Need::Reproject);
2159        let steaming = Summary {
2160            layered: vec![Region::new(100, 100, 50, 80)],
2161            ..turned(0.2)
2162        };
2163        assert_eq!(decide(&book, &steaming, &limits), Need::Reproject);
2164    }
2165
2166    #[test]
2167    fn time_driven_layers_redraw_their_rects_and_where_they_were() {
2168        let limits = Limits::default();
2169        let steam = Region::new(100, 100, 50, 80);
2170        let mut book = settled(Summary {
2171            layered: vec![steam],
2172            ..summary()
2173        });
2174        let still = Summary {
2175            layered: vec![steam],
2176            ..summary()
2177        };
2178        assert_eq!(decide(&book, &still, &limits), Need::Partial(vec![steam]));
2179        let mut bare = book.clone();
2180        bare.still = false;
2181        assert_eq!(
2182            decide(&bare, &still, &limits),
2183            Need::Full,
2184            "no still layer was kept"
2185        );
2186        let gone = summary();
2187        assert_eq!(decide(&book, &gone, &limits), Need::Partial(vec![steam]));
2188        record(&mut book, gone.clone(), Shown::Partial, &limits);
2189        assert_eq!(decide(&book, &gone, &limits), Need::None);
2190        let elsewhere = Region::new(900, 500, 40, 40);
2191        record(
2192            &mut book,
2193            Summary {
2194                layered: vec![steam],
2195                ..summary()
2196            },
2197            Shown::Partial,
2198            &limits,
2199        );
2200        let moved = Summary {
2201            layered: vec![elsewhere],
2202            ..summary()
2203        };
2204        assert_eq!(
2205            decide(&book, &moved, &limits),
2206            Need::Partial(vec![steam, elsewhere])
2207        );
2208    }
2209
2210    #[test]
2211    fn a_change_settles_the_history_before_going_idle() {
2212        let limits = Limits::default();
2213        let mut book = Book::default();
2214        record(&mut book, summary(), Shown::Full, &limits);
2215        book.depth = Some(2.0);
2216        assert_eq!(book.settle, limits.settle);
2217        let mut fulls = 0;
2218        while decide(&book, &summary(), &limits) == Need::Full {
2219            record(&mut book, summary(), Shown::Full, &limits);
2220            fulls += 1;
2221            assert!(fulls <= limits.settle);
2222        }
2223        assert_eq!(fulls, limits.settle);
2224        assert_eq!(decide(&book, &summary(), &limits), Need::None);
2225        record(&mut book, turned(0.2), Shown::Reproject, &limits);
2226        record(&mut book, turned(0.7), Shown::Full, &limits);
2227        assert_eq!(book.settle, 0);
2228        assert_eq!(book.since_full, 0);
2229        let changed = Summary {
2230            world: 99,
2231            ..summary()
2232        };
2233        record(&mut book, changed, Shown::Full, &limits);
2234        assert_eq!(book.settle, limits.settle);
2235    }
2236
2237    #[test]
2238    fn prints_see_single_word_changes() {
2239        let print = |values: &[f32]| {
2240            let mut print = Print::new();
2241            print.floats(values);
2242            print.finish()
2243        };
2244        let base = print(&[1.0, 2.0, 3.0]);
2245        assert_eq!(base, print(&[1.0, 2.0, 3.0]));
2246        assert_ne!(base, print(&[1.0, 2.0, 3.0000002]));
2247        assert_ne!(base, print(&[1.0, 3.0, 2.0]));
2248        assert_ne!(base, print(&[1.0, 2.0]));
2249        let mut text = Print::new();
2250        text.debug(&"ab");
2251        let mut other = Print::new();
2252        other.debug(&"ab ");
2253        assert_ne!(text.finish(), other.finish());
2254    }
2255
2256    #[test]
2257    fn layer_rects_cover_what_they_draw() {
2258        let projection = projection(1.5);
2259        let view = view(0.0, [0.0, 1.0, 2.0]);
2260        let view_projection = multiply(projection, view);
2261        let region = box_region(
2262            &view_projection,
2263            [-0.1, 0.9, -0.1],
2264            [0.1, 1.1, 0.1],
2265            8,
2266            W,
2267            H,
2268        )
2269        .unwrap();
2270        assert!(region.contains(W / 2, H / 2));
2271        assert!(region.width < W / 4 && region.height < H / 3, "{region:?}");
2272        let behind = box_region(&view_projection, [-0.1, 0.9, 2.5], [0.1, 1.1, 2.6], 8, W, H);
2273        assert_eq!(behind, Some(Region::whole(W, H)));
2274        let particle = ParticleInstance {
2275            position_size: [0.0, 1.0, 0.0, 0.05],
2276            velocity_alpha: [0.0; 4],
2277            color_rotation: [0.0; 4],
2278            detail: [0.0; 4],
2279        };
2280        let around = particle_region(&view_projection, &[particle], 4, W, H).unwrap();
2281        assert!(around.contains(W / 2, H / 2));
2282        assert_eq!(particle_region(&view_projection, &[], 4, W, H), None);
2283    }
2284
2285    #[test]
2286    fn only_pointwise_finishes_redraw_in_rects() {
2287        use pfx_post::{Bloom, Chain, Pass, Style, Tone};
2288        let plain = Chain {
2289            passes: vec![
2290                Pass::Exposure(1.0),
2291                Pass::Bloom(Bloom::neutral(0.0)),
2292                Pass::Tone(Tone::aces()),
2293            ],
2294            frame: 0,
2295            seed: 0,
2296        };
2297        assert!(local_post(&plain));
2298        let glowing = Chain {
2299            passes: vec![Pass::Bloom(Bloom::neutral(0.4))],
2300            frame: 0,
2301            seed: 0,
2302        };
2303        assert!(!local_post(&glowing));
2304        let styled = Style::ALL
2305            .iter()
2306            .filter(|style| local_post(&style.chain()))
2307            .count();
2308        assert!(styled < Style::ALL.len());
2309    }
2310
2311    #[test]
2312    fn warp_shaders_validate() {
2313        for source in [
2314            WARP_WGSL,
2315            COPY_WGSL,
2316            WAKE_WGSL,
2317            TILES_WGSL,
2318            SPREAD_WGSL,
2319            COMPOSITE_WGSL,
2320        ] {
2321            let module = naga::front::wgsl::parse_str(source).unwrap();
2322            naga::valid::Validator::new(
2323                naga::valid::ValidationFlags::all(),
2324                naga::valid::Capabilities::all(),
2325            )
2326            .validate(&module)
2327            .unwrap();
2328        }
2329    }
2330
2331    #[test]
2332    fn warp_uniform_maps_the_cached_view_onto_itself_and_follows_a_turn() {
2333        let at = |uniform: &WarpUniform, uv: [f32; 2], depth: f32| {
2334            let toward: [f32; 4] = std::array::from_fn(|row| {
2335                uniform.across[row] * uv[0] + uniform.down[row] * uv[1] + uniform.corner[row]
2336            });
2337            let clip: [f32; 4] =
2338                std::array::from_fn(|row| toward[row] * depth + uniform.origin[row]);
2339            [clip[0] / clip[3] * 0.5 + 0.5, 0.5 - clip[1] / clip[3] * 0.5]
2340        };
2341        let still = view(0.1, [0.0, 1.0, 2.0]);
2342        let projection = projection(1.5);
2343        let same =
2344            WarpUniform::new((&still, &projection), (&still, &projection), [W, H], 40.0).unwrap();
2345        for uv in [[0.1, 0.2], [0.5, 0.5], [0.93, 0.71]] {
2346            for depth in [0.3, 2.0, 25.0] {
2347                let back = at(&same, uv, depth);
2348                assert!((back[0] - uv[0]).abs() < 1e-5 && (back[1] - uv[1]).abs() < 1e-5);
2349            }
2350        }
2351        let turned = view(0.1 + 0.5_f32.to_radians(), [0.0, 1.0, 2.0]);
2352        let warp =
2353            WarpUniform::new((&still, &projection), (&turned, &projection), [W, H], 40.0).unwrap();
2354        let near = at(&warp, [0.5, 0.5], 1.0);
2355        let far = at(&warp, [0.5, 0.5], 20.0);
2356        let shift = 0.5_f32.to_radians().tan() * 1.5 / 1.6 * 0.5;
2357        assert!((near[0] - 0.5 - shift).abs() < 1e-3, "{near:?}");
2358        assert!(
2359            (near[0] - far[0]).abs() < 1e-5,
2360            "a pure turn has no parallax"
2361        );
2362        assert_eq!(same.bytes().len(), 24 * 4);
2363    }
2364}