Skip to main content

long_screen/kernels/reveal/
run.rs

1//! One reveal run: the four frame-major passes over every moving layer and canvas, driven one decoded frame at
2//! a time by the adapter.
3//!
4//! Statistics passes (1 and 3) compare every distinct pose against a reference: the compositor's canvas as cell
5//! means (pass 1), then the consensus (pass 3). Selection passes (2 and 4) track overlays on every frame but
6//! vote once per run of identical poses, with the run's last frame: a page at rest shows its settled state
7//! (a scrollbar has faded, a hover has passed). A pixel inside an overlay's zone that was still changing between
8//! the run's last two frames votes below clean.
9
10use super::blocks::{
11    self, BlockWrite, Consensus, Deferred, Grid, Output, CONSENSUS_BLOCK_BYTES, FALLBACK_BYTES,
12    OUTPUT_BLOCK_BYTES,
13};
14use super::controls::{self, ControlTracks};
15use super::locate::{self, Cells, TierMap};
16use super::model::{self, LayerModel};
17use super::stats::Acc;
18use super::stats::{CellMap, LayerStats};
19use super::{tolerance, CanvasView, Footprint, Frame, BLOCK, CELL, TIER_FRINGE};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum Pass {
23    Learn,
24    Agree,
25    Relearn,
26    Write,
27}
28
29/// One moving-layer placement of a frame.
30#[derive(Clone, Debug, PartialEq)]
31pub struct Decision {
32    pub layer: usize,
33    pub canvas: usize,
34    pub x: i64,
35    pub y: i64,
36    pub occlusions: Vec<(i64, i64, i64, i64)>,
37}
38
39/// Which decisions the passes use, given every decoded frame's decisions in order, each with whether it is blind
40/// (too little texture to track: the solve held the layer's pose and the compositor painted nothing from it). A
41/// tracked decision always counts. A blind one counts only for a layer whose tracked decisions all sit at one pose,
42/// and only at that pose, where the held pose is certainly right. In b, the floating-button corner is tracked only
43/// while the cursor crosses it; without its blind frames, its one pose run ended with the last frame the cursor was
44/// in, and that frame's vote burned the cursor into the print.
45pub fn admitted(plans: &[Vec<(Decision, bool)>]) -> Vec<Vec<bool>> {
46    #[derive(Clone, Copy, PartialEq)]
47    enum Still {
48        Unseen,
49        At(usize, i64, i64),
50        Moved,
51    }
52    let layers = plans
53        .iter()
54        .flatten()
55        .map(|(d, _)| d.layer + 1)
56        .max()
57        .unwrap_or(0);
58    let mut still = vec![Still::Unseen; layers];
59    for (d, _) in plans.iter().flatten().filter(|(_, blind)| !blind) {
60        let at = Still::At(d.canvas, d.x, d.y);
61        let s = &mut still[d.layer];
62        *s = if *s == Still::Unseen || *s == at {
63            at
64        } else {
65            Still::Moved
66        };
67    }
68    plans
69        .iter()
70        .map(|ds| {
71            ds.iter()
72                .map(|(d, blind)| !blind || still[d.layer] == Still::At(d.canvas, d.x, d.y))
73                .collect()
74        })
75        .collect()
76}
77
78pub struct Layer {
79    controls: Option<ControlTracks>,
80    pub x: i64,
81    pub y: i64,
82    pub width: usize,
83    pub height: usize,
84    /// Atlas label code of the layer (0 = the whole rectangle is owned).
85    pub code: u8,
86    /// The layer's label mask over its rectangle, built from the first frame's label plane.
87    labels: Option<Vec<u8>>,
88    stats: Option<LayerStats>,
89    pub model: Option<LayerModel>,
90    zone: Vec<bool>,
91    /// Pose of the last distinct observation (statistics passes).
92    last: Option<(usize, i64, i64)>,
93    /// The run the selection passes have not voted yet: its last frame, decision and length.
94    pending: Option<(u32, Decision, u32)>,
95}
96
97impl Layer {
98    /// The layer's label mask over its rectangle: pixels whose atlas label is `code` (all of them when `labels` is
99    /// absent or `code` is 0). `labels` is the frame-sized label plane.
100    fn label_mask(
101        &self,
102        labels: Option<&[u8]>,
103        frame_width: usize,
104        frame_height: usize,
105    ) -> Vec<u8> {
106        let mut m = vec![0u8; self.width * self.height];
107        for ly in 0..self.height {
108            let sy = self.y + ly as i64;
109            if sy < 0 || sy >= frame_height as i64 {
110                continue;
111            }
112            for lx in 0..self.width {
113                let sx = self.x + lx as i64;
114                if sx < 0 || sx >= frame_width as i64 {
115                    continue;
116                }
117                m[ly * self.width + lx] = match labels {
118                    Some(l) if self.code != 0 => {
119                        (l[sy as usize * frame_width + sx as usize] == self.code) as u8
120                    }
121                    _ => 1,
122                };
123            }
124        }
125        m
126    }
127}
128
129pub struct Canvas {
130    pub x: i64,
131    pub y: i64,
132    pub width: usize,
133    pub height: usize,
134    pub main: CellMap,
135    pub consensus: Option<Consensus>,
136    pub output: Option<Output>,
137}
138
139#[derive(Default, Clone, Copy, Debug)]
140pub struct Summary {
141    pub objects: usize,
142    pub components: usize,
143    pub coherent: usize,
144    pub component_blocks: usize,
145    pub provisional: usize,
146    pub fallback: usize,
147}
148
149pub struct Reveal {
150    pub width: usize,
151    pub height: usize,
152    pub tau: i32,
153    pub pass: Pass,
154    pub layers: Vec<Layer>,
155    pub canvases: Vec<Canvas>,
156    /// Block copies the adapter owes the output since it last drained them.
157    pub writes: Vec<BlockWrite>,
158}
159
160/// This frame's footprint mask of a decision's layer: the layer's label mask, copied only when the frame occludes
161/// part of it.
162fn layer_mask<'a>(l: &'a Layer, d: &Decision) -> std::borrow::Cow<'a, [u8]> {
163    let base = l.labels.as_deref().unwrap_or(&[]);
164    if d.occlusions.is_empty() {
165        std::borrow::Cow::Borrowed(base)
166    } else {
167        std::borrow::Cow::Owned(Footprint::occlude(
168            base,
169            l.x,
170            l.y,
171            l.width,
172            l.height,
173            &d.occlusions,
174        ))
175    }
176}
177
178fn footprint<'a>(l: &Layer, mask: &'a [u8]) -> Footprint<'a> {
179    Footprint {
180        x: l.x,
181        y: l.y,
182        width: l.width,
183        height: l.height,
184        mask,
185    }
186}
187
188/// Core memory the reveal holds for a canvas of `width × height`: its reference cells, the agree pass's consensus, and
189/// the write pass's per-block output with a held view for every block. The write pass's per-pixel fallback state comes
190/// on top ([`Reveal::fallback_bytes`], known once the agree pass has run). A world-aligned grid can reach one more cell
191/// and block past each edge than the size alone gives.
192pub fn canvas_bytes(width: usize, height: usize) -> usize {
193    let cells = (width / CELL + 2) * (height / CELL + 2);
194    let blocks = (width / BLOCK + 2) * (height / BLOCK + 2);
195    cells * std::mem::size_of::<[u8; 4]>() + blocks * (CONSENSUS_BLOCK_BYTES + OUTPUT_BLOCK_BYTES)
196}
197
198/// Core memory the reveal holds for a layer of `width × height`: its label mask and cell statistics, with room for the
199/// per-cell sums its model is built from.
200pub fn layer_bytes(width: usize, height: usize) -> usize {
201    let cells = width.div_ceil(CELL) * height.div_ceil(CELL);
202    let blocks = (width / BLOCK + 2) * (height / BLOCK + 2);
203    // A complete component's temporary signature fits one observed viewport, even on a large canvas.
204    width * height
205        + cells * (std::mem::size_of::<Acc>() + 8 * std::mem::size_of::<u64>())
206        + blocks * blocks::CELLS * std::mem::size_of::<[u8; 3]>()
207        + controls::memory_bound(cells)
208}
209
210impl Reveal {
211    pub fn new(width: usize, height: usize, noise: f64) -> Self {
212        Self {
213            width,
214            height,
215            tau: tolerance(noise),
216            pass: Pass::Learn,
217            layers: Vec::new(),
218            canvases: Vec::new(),
219            writes: Vec::new(),
220        }
221    }
222
223    pub fn add_layer(&mut self, x: i64, y: i64, width: usize, height: usize, code: u8) -> usize {
224        self.layers.push(Layer {
225            controls: Some(ControlTracks::new(
226                width.div_ceil(CELL) * height.div_ceil(CELL),
227            )),
228            x,
229            y,
230            width,
231            height,
232            code,
233            labels: None,
234            stats: None,
235            model: None,
236            zone: Vec::new(),
237            last: None,
238            pending: None,
239        });
240        self.layers.len() - 1
241    }
242
243    pub fn add_canvas(&mut self, x: i64, y: i64, width: usize, height: usize) -> usize {
244        self.canvases.push(Canvas {
245            x,
246            y,
247            width,
248            height,
249            main: CellMap::covering(x, y, width, height),
250            consensus: None,
251            output: None,
252        });
253        self.canvases.len() - 1
254    }
255
256    /// What the write pass's per-pixel fallback state will take at most, once the agree pass has built every canvas's
257    /// consensus.
258    pub fn fallback_bytes(&self) -> usize {
259        self.canvases
260            .iter()
261            .filter_map(|c| c.consensus.as_ref())
262            .map(|c| c.unseen_blocks() * FALLBACK_BYTES)
263            .sum()
264    }
265
266    /// Adds part of the compositor's canvas (a tile) to its cell-mean reference.
267    pub fn ingest(&mut self, canvas: usize, view: &CanvasView<'_>) {
268        self.canvases[canvas].main.ingest(view);
269    }
270
271    /// Starts a pass, building what it needs from the previous one.
272    pub fn begin(&mut self, pass: Pass) {
273        match pass {
274            Pass::Learn | Pass::Relearn => {
275                for l in &mut self.layers {
276                    l.stats = Some(LayerStats::new(l.x, l.y, l.width, l.height));
277                    l.last = None;
278                }
279            }
280            Pass::Agree | Pass::Write => {
281                for l in &mut self.layers {
282                    if let Some(s) = l.stats.take() {
283                        let m = model::build(&s, self.tau);
284                        l.zone = m.overlay_zone();
285                        l.model = Some(m);
286                    }
287                    l.pending = None;
288                }
289                for c in &mut self.canvases {
290                    if pass == Pass::Agree {
291                        c.consensus =
292                            Some(Consensus::new(Grid::covering(c.x, c.y, c.width, c.height)));
293                    } else if let Some(cons) = &c.consensus {
294                        c.output = Some(Output::new(cons, blocks::components(cons)));
295                    }
296                }
297            }
298        }
299        self.pass = pass;
300        self.writes.clear();
301    }
302
303    /// Fallback blocks of `canvas` (world block coordinates) whose compositor pixels the adapter must supply with
304    /// [`Reveal::set_main`] before the write pass's first frame.
305    pub fn fallback_blocks(&self, canvas: usize) -> Vec<(i64, i64)> {
306        let c = &self.canvases[canvas];
307        let (Some(cons), Some(out)) = (&c.consensus, &c.output) else {
308            return Vec::new();
309        };
310        out.fallback_blocks()
311            .into_iter()
312            .map(|b| {
313                (
314                    cons.grid.gx + (b % cons.grid.bw) as i64,
315                    cons.grid.gy + (b / cons.grid.bw) as i64,
316                )
317            })
318            .collect()
319    }
320
321    pub fn set_main(&mut self, canvas: usize, gx: i64, gy: i64, rgba: &[u8]) {
322        let c = &mut self.canvases[canvas];
323        let (Some(cons), Some(out)) = (&c.consensus, &mut c.output) else {
324            return;
325        };
326        let (bx, by) = (gx - cons.grid.gx, gy - cons.grid.gy);
327        if bx >= 0 && by >= 0 && (bx as usize) < cons.grid.bw && (by as usize) < cons.grid.bh {
328            out.set_main(by as usize * cons.grid.bw + bx as usize, rgba);
329        }
330    }
331
332    /// Which frames this pass reads pixels of, given every decoded frame's decisions in order (`plans[i]` belongs
333    /// to frame `i`): in the statistics passes a frame that shows some layer at a pose it was not just shown at;
334    /// in the selection passes the last frame of every run of identical poses, and the frame before it when the
335    /// run is longer (the settle check compares the two). Mirrors [`Reveal::frame`], so a frame left out here is
336    /// one it never reads.
337    pub fn wanted(&self, plans: &[Vec<Decision>]) -> Vec<bool> {
338        let n = plans.len();
339        let mut want = vec![false; n];
340        let key = |d: &Decision| (d.canvas, d.x, d.y);
341        match self.pass {
342            Pass::Learn | Pass::Relearn => {
343                let mut last = vec![None; self.layers.len()];
344                for (i, ds) in plans.iter().enumerate() {
345                    for d in ds {
346                        if last[d.layer] != Some(key(d)) {
347                            last[d.layer] = Some(key(d));
348                            want[i] = true;
349                        }
350                    }
351                }
352            }
353            Pass::Agree | Pass::Write => {
354                let mut run = vec![0u32; self.layers.len()];
355                let mut prev: Vec<Option<(usize, i64, i64)>> = vec![None; self.layers.len()];
356                for (i, ds) in plans.iter().enumerate() {
357                    let mut seen = vec![None; self.layers.len()];
358                    for d in ds {
359                        seen[d.layer] = Some(key(d));
360                    }
361                    for (l, k) in seen.iter().enumerate() {
362                        run[l] = match k {
363                            Some(k) if prev[l] == Some(*k) => run[l] + 1,
364                            Some(_) => 1,
365                            None => 0,
366                        };
367                        let Some(k) = k else { continue };
368                        let continues = plans
369                            .get(i + 1)
370                            .is_some_and(|next| next.iter().any(|d| d.layer == l && key(d) == *k));
371                        if !continues {
372                            want[i] = true;
373                            if run[l] >= 2 {
374                                want[i - 1] = true;
375                            }
376                        }
377                    }
378                    prev = seen;
379                }
380            }
381        }
382        want
383    }
384
385    /// Builds every layer's label mask from the first frame's label plane.
386    fn ensure_labels(&mut self, labels: Option<&[u8]>) {
387        let (w, h) = (self.width, self.height);
388        for l in &mut self.layers {
389            if l.labels.is_none() {
390                l.labels = Some(l.label_mask(labels, w, h));
391            }
392        }
393    }
394
395    /// One decoded frame. `prev`/`prev2` are the two frames before it (absent at the start); `labels` is the
396    /// atlas label plane.
397    pub fn frame(
398        &mut self,
399        index: u32,
400        current: Option<&[u8]>,
401        prev: Option<&[u8]>,
402        prev2: Option<&[u8]>,
403        labels: Option<&[u8]>,
404        decisions: &[Decision],
405    ) {
406        self.ensure_labels(labels);
407        match self.pass {
408            Pass::Learn | Pass::Relearn => {
409                // A frame [`Reveal::wanted`] leaves out shows every layer where it was just shown.
410                let Some(current) = current else { return };
411                let cur = Frame {
412                    rgba: current,
413                    width: self.width,
414                    height: self.height,
415                };
416                for d in decisions {
417                    let key = (d.canvas, d.x, d.y);
418                    if self.layers[d.layer].last == Some(key) {
419                        continue;
420                    }
421                    self.layers[d.layer].last = Some(key);
422                    // Split borrow: stats live on the layer, the reference on the canvas.
423                    let mut stats = self.layers[d.layer].stats.take().unwrap();
424                    let layer = &self.layers[d.layer];
425                    let mask = layer_mask(layer, d);
426                    let fp = footprint(layer, &mask);
427                    let c = &self.canvases[d.canvas];
428                    match (self.pass, &c.consensus) {
429                        (Pass::Learn, _) => {
430                            stats.accumulate_cells(&cur, &fp, &c.main, (d.x, d.y), self.tau);
431                        }
432                        (_, Some(cons)) => {
433                            stats.accumulate_cells(&cur, &fp, cons, (d.x, d.y), self.tau);
434                        }
435                        _ => {}
436                    }
437                    drop(mask);
438                    self.layers[d.layer].stats = Some(stats);
439                }
440            }
441            Pass::Agree | Pass::Write => {
442                // Overlays are located only in the frame a run votes with, when it ends.
443                let mut fresh = Vec::with_capacity(decisions.len());
444                for d in decisions {
445                    let run = match &self.layers[d.layer].pending {
446                        Some((pt, pd, len))
447                            if *pt + 1 == index
448                                && (pd.canvas, pd.x, pd.y) == (d.canvas, d.x, d.y) =>
449                        {
450                            len + 1
451                        }
452                        _ => 1,
453                    };
454                    fresh.push((d.clone(), run));
455                }
456                // Runs that ended with the previous frame vote with it.
457                for l in 0..self.layers.len() {
458                    let Some((pt, pd, _)) = &self.layers[l].pending else {
459                        continue;
460                    };
461                    let continues = decisions.iter().any(|d| {
462                        d.layer == l
463                            && *pt + 1 == index
464                            && (d.canvas, d.x, d.y) == (pd.canvas, pd.x, pd.y)
465                    });
466                    if continues {
467                        continue;
468                    }
469                    let (pt, pd, len) = self.layers[l].pending.take().unwrap();
470                    if let Some(p) = prev {
471                        self.vote(pt, &pd, p, if len >= 2 { prev2 } else { None }, labels);
472                    }
473                }
474                for (d, run) in fresh {
475                    let l = d.layer;
476                    self.layers[l].pending = Some((index, d, run));
477                }
478            }
479        }
480    }
481
482    /// Ends a selection pass: the runs still open vote with the last frame (`last`, and `before` the frame ahead
483    /// of it).
484    pub fn end(&mut self, last: Option<&[u8]>, before: Option<&[u8]>, labels: Option<&[u8]>) {
485        let (true, Some(last)) = (matches!(self.pass, Pass::Agree | Pass::Write), last) else {
486            return;
487        };
488        for l in 0..self.layers.len() {
489            if let Some((pt, pd, len)) = self.layers[l].pending.take() {
490                self.vote(pt, &pd, last, if len >= 2 { before } else { None }, labels);
491            }
492        }
493    }
494
495    fn vote(
496        &mut self,
497        index: u32,
498        d: &Decision,
499        rgba: &[u8],
500        before: Option<&[u8]>,
501        labels: Option<&[u8]>,
502    ) {
503        let (w, h, tau) = (self.width, self.height, self.tau);
504        let frame = Frame {
505            rgba,
506            width: w,
507            height: h,
508        };
509        let Some(m) = self.layers[d.layer].model.as_mut() else {
510            return;
511        };
512        let cells =
513            (self.pass == Pass::Agree && controls::needs_cells(m)).then(|| Cells::bin(&frame, m));
514        let mut tiers = match &cells {
515            Some(cells) => locate::classify(cells, m, tau),
516            None => locate::tiers(&frame, m, tau),
517        };
518        let mut tracks = self.layers[d.layer].controls.take().unwrap();
519        let layer = &self.layers[d.layer];
520        let m = layer.model.as_ref().unwrap();
521        let mask = layer_mask(layer, d);
522        let fp = footprint(layer, &mask);
523        if let Some(cells) = &cells {
524            tracks.observe(
525                cells,
526                m,
527                &fp,
528                tau,
529                controls::View {
530                    index,
531                    canvas: d.canvas,
532                    pose: (d.x, d.y),
533                },
534            );
535        }
536        if self.pass == Pass::Write {
537            tracks.apply(index, m, &layer.zone, &mut tiers);
538        }
539        if let Some(b) = before {
540            let noise = (self.tau / 4).max(2);
541            for cy in 0..m.ch {
542                for cx in 0..m.cw {
543                    let i = cy * m.cw + cx;
544                    if !layer.zone[i] || tiers[i] <= TIER_FRINGE {
545                        continue;
546                    }
547                    let (sx, sy) = (m.x + (cx * CELL) as i64, m.y + (cy * CELL) as i64);
548                    'cell: for dy in 0..CELL as i64 {
549                        for dx in 0..CELL as i64 {
550                            let (x, y) = (sx + dx, sy + dy);
551                            if x < 0 || y < 0 || x >= w as i64 || y >= h as i64 {
552                                continue;
553                            }
554                            let j = (y as usize * w + x as usize) * 4;
555                            if (0..3).any(|c| (rgba[j + c] as i32 - b[j + c] as i32).abs() > noise)
556                            {
557                                tiers[i] = TIER_FRINGE;
558                                break 'cell;
559                            }
560                        }
561                    }
562                }
563            }
564        }
565        let tm = TierMap {
566            x: m.x,
567            y: m.y,
568            cw: m.cw,
569            ch: m.ch,
570            tier: &tiers,
571        };
572        let _ = labels;
573        let c = &mut self.canvases[d.canvas];
574        match self.pass {
575            Pass::Agree => {
576                if let Some(cons) = c.consensus.as_mut() {
577                    cons.observe(&frame, &fp, &tm, (d.x, d.y));
578                }
579            }
580            Pass::Write => {
581                if let (Some(cons), Some(out)) = (c.consensus.as_ref(), c.output.as_mut()) {
582                    let start = self.writes.len();
583                    let still_previous = before.map(|rgba| Frame {
584                        rgba,
585                        width: w,
586                        height: h,
587                    });
588                    out.emit(
589                        cons,
590                        &frame,
591                        still_previous.as_ref(),
592                        &fp,
593                        &tm,
594                        (d.x, d.y),
595                        tau,
596                        index,
597                        &c.main,
598                        &mut self.writes,
599                    );
600                    for w in &mut self.writes[start..] {
601                        w.canvas = d.canvas as u32;
602                    }
603                }
604            }
605            _ => {}
606        }
607        drop(mask);
608        self.layers[d.layer].controls = Some(tracks);
609    }
610
611    /// Drains the block copies owed since the last call. Each copies from the frame the vote used: the previous
612    /// frame during [`Reveal::frame`], the last frame during [`Reveal::end`].
613    pub fn take_writes(&mut self) -> Vec<BlockWrite> {
614        std::mem::take(&mut self.writes)
615    }
616
617    /// The deferred blocks of a canvas, after the write pass.
618    pub fn finish(&self, canvas: usize) -> Vec<Deferred> {
619        let c = &self.canvases[canvas];
620        match (&c.consensus, &c.output) {
621            (Some(cons), Some(out)) => out.finish(cons, self.tau),
622            _ => Vec::new(),
623        }
624    }
625
626    /// The block grid of a canvas (origin and size in 16-pixel blocks) and where each block's pixels now come from
627    /// ([`blocks::Output::sources`]), given what [`Reveal::finish`] returned for it.
628    pub fn provenance(
629        &self,
630        canvas: usize,
631        deferred: &[blocks::Deferred],
632    ) -> Option<(i64, i64, usize, usize, Vec<i32>)> {
633        let c = &self.canvases[canvas];
634        let (cons, out) = (c.consensus.as_ref()?, c.output.as_ref()?);
635        let g = cons.grid;
636        Some((g.gx, g.gy, g.bw, g.bh, out.sources(cons, deferred)))
637    }
638
639    pub fn summary(&self) -> Summary {
640        let mut s = Summary {
641            objects: self
642                .layers
643                .iter()
644                .filter_map(|l| l.model.as_ref())
645                .map(|m| m.objects.len())
646                .sum(),
647            ..Default::default()
648        };
649        for c in &self.canvases {
650            if let Some(out) = &c.output {
651                s.components += out.components.len();
652                for k in &out.kind {
653                    match k {
654                        blocks::Kind::Coherent | blocks::Kind::Edge => s.coherent += 1,
655                        blocks::Kind::Component => s.component_blocks += 1,
656                        blocks::Kind::Provisional => s.provisional += 1,
657                        blocks::Kind::Fallback => s.fallback += 1,
658                        blocks::Kind::None => {}
659                    }
660                }
661            }
662        }
663        s
664    }
665}
666
667#[cfg(test)]
668mod tests {
669    use super::{admitted, canvas_bytes, layer_bytes, Decision, Pass, Reveal};
670    use std::alloc::{GlobalAlloc, Layout, System};
671    use std::cell::Cell;
672
673    // Bytes this thread holds, for checking memory estimates against what the code really allocates. Per thread, so
674    // tests running alongside do not count; every allocation in this test binary goes through it.
675    thread_local! {
676        static LIVE: Cell<isize> = const { Cell::new(0) };
677    }
678    struct Counting;
679    unsafe impl GlobalAlloc for Counting {
680        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
681            let _ = LIVE.try_with(|l| l.set(l.get() + layout.size() as isize));
682            System.alloc(layout)
683        }
684        unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
685            let _ = LIVE.try_with(|l| l.set(l.get() + layout.size() as isize));
686            System.alloc_zeroed(layout)
687        }
688        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
689            let _ = LIVE.try_with(|l| l.set(l.get() - layout.size() as isize));
690            System.dealloc(ptr, layout)
691        }
692        unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new: usize) -> *mut u8 {
693            let _ = LIVE.try_with(|l| l.set(l.get() + new as isize - layout.size() as isize));
694            System.realloc(ptr, layout, new)
695        }
696    }
697    #[global_allocator]
698    static ALLOCATOR: Counting = Counting;
699    fn live() -> isize {
700        LIVE.with(|l| l.get())
701    }
702
703    // Failure cases for the memory estimates the adapter checks before a reveal (a run with dozens of fragment
704    // canvases needed 3 GB here and aborted the threaded core at its 2 GB ceiling): an estimate below what a
705    // canvas or layer really holds through the passes, at any size or grid phase; a fallback estimate below the
706    // write pass's fallback state when no block was ever seen clean (the most there can be).
707    #[test]
708    fn the_estimates_cover_what_the_reveal_holds() {
709        for (x, y, w, h) in [(0, 0, 320, 240), (-7, 13, 3001, 2002), (5, -3, 17, 4099)] {
710            let before = live();
711            let mut r = Reveal::new(w, h, 10.0);
712            r.add_layer(0, 0, w, h, 0);
713            r.add_canvas(x, y, w, h);
714            r.begin(Pass::Learn);
715            r.begin(Pass::Agree);
716            r.begin(Pass::Relearn);
717            let fallback = r.fallback_bytes();
718            r.begin(Pass::Write);
719            let held = (live() - before) as usize;
720            let estimate = canvas_bytes(w, h) + layer_bytes(w, h) + fallback;
721            assert!(
722                held <= estimate,
723                "{w}x{h}: holds {held} bytes, estimated {estimate}"
724            );
725            // Nothing was observed, so every block falls back.
726            assert!(fallback > 0, "{w}x{h}");
727            drop(r);
728        }
729        // Not so far above real use that an ordinary recording would be refused: the reveal measured 5.7 bytes per
730        // canvas pixel over the 87 fragment canvases of a 3K recording, fallback state included.
731        let per_pixel = canvas_bytes(3001, 2002) as f64 / (3001.0 * 2002.0);
732        assert!(
733            (4.0..7.0).contains(&per_pixel),
734            "{per_pixel} bytes per canvas pixel"
735        );
736    }
737
738    fn d(layer: usize, canvas: usize, x: i64, y: i64) -> Decision {
739        Decision {
740            layer,
741            canvas,
742            x,
743            y,
744            occlusions: Vec::new(),
745        }
746    }
747
748    /// Frames of `(decision, blind)`; returns which decisions count, frame by frame.
749    fn admit(plans: &[&[(Decision, bool)]]) -> Vec<Vec<bool>> {
750        let plans: Vec<Vec<(Decision, bool)>> = plans.iter().map(|p| p.to_vec()).collect();
751        admitted(&plans)
752    }
753
754    #[test]
755    fn tracked_decisions_always_count() {
756        let got = admit(&[
757            &[(d(0, 0, 0, 0), false)],
758            &[(d(0, 0, 0, 40), false)],
759            &[(d(0, 0, 0, 80), false)],
760        ]);
761        assert_eq!(got, vec![vec![true], vec![true], vec![true]]);
762    }
763
764    #[test]
765    fn blind_frames_of_a_still_layer_count_at_its_pose() {
766        // b's floating-button corner: blind before and after the frames it is tracked in, all at one pose.
767        let got = admit(&[
768            &[(d(1, 0, 0, 0), true)],
769            &[(d(1, 0, 0, 0), false)],
770            &[(d(1, 0, 0, 0), false)],
771            &[(d(1, 0, 0, 0), true)],
772            &[(d(1, 0, 0, 0), true)],
773        ]);
774        assert_eq!(got, vec![vec![true]; 5]);
775    }
776
777    #[test]
778    fn a_blind_frame_at_another_pose_does_not_count() {
779        let got = admit(&[
780            &[(d(0, 0, 5, 5), false)],
781            &[(d(0, 0, 5, 6), true)],
782            &[(d(0, 1, 5, 5), true)],
783        ]);
784        assert_eq!(got, vec![vec![true], vec![false], vec![false]]);
785    }
786
787    #[test]
788    fn blind_frames_of_a_layer_that_moved_do_not_count() {
789        // A textureless stretch of a scrolling layer: the held pose may be wrong.
790        let got = admit(&[
791            &[(d(0, 0, 0, 0), false)],
792            &[(d(0, 0, 0, 30), false)],
793            &[(d(0, 0, 0, 30), true)],
794            &[(d(0, 0, 0, 30), true)],
795        ]);
796        assert_eq!(got, vec![vec![true], vec![true], vec![false], vec![false]]);
797    }
798
799    #[test]
800    fn blind_frames_of_a_layer_never_tracked_do_not_count() {
801        let got = admit(&[&[(d(0, 0, 0, 0), true)], &[(d(0, 0, 0, 0), true)]]);
802        assert_eq!(got, vec![vec![false], vec![false]]);
803    }
804
805    #[test]
806    fn each_layer_is_judged_on_its_own() {
807        let got = admit(&[
808            &[(d(0, 0, 0, 0), false), (d(1, 1, 0, 0), false)],
809            &[(d(0, 0, 0, 50), false), (d(1, 1, 0, 0), true)],
810            &[(d(0, 0, 0, 50), true), (d(1, 1, 0, 0), true)],
811        ]);
812        assert_eq!(
813            got,
814            vec![vec![true, true], vec![true, true], vec![false, true]]
815        );
816    }
817}