Skip to main content

long_screen/kernels/reveal/
blocks.rs

1//! Block-level selection on the world-aligned 16×16 grid (the compositor's quality blocks). What stays resident
2//! per canvas is a few hundred bytes per block, so a canvas of any size fits without paging; pixels are only
3//! ever copied from one decoded frame into the output.
4//!
5//! [`Consensus`] (pass 2): every whole-block view votes with its signature (the block's 8×8 grid of 2×2 cell
6//! means) in a tolerant Boyer–Moore majority within the best tier seen. One clean sighting beats any number of
7//! covered ones, a passing cursor is outvoted, and a tie goes to the later view (content that loads or fades
8//! settles in its final state). The signatures are also the reference of the second statistics pass.
9//!
10//! [`Output`] (pass 4): a block takes the first frame that saw it wholly clean, away from the layer's edge, and
11//! agreeing with the consensus (the most central such view when every one was near the edge);
12//! a dynamic component (blocks whose clean views structurally disagree) takes, once some frame covers nearly all
13//! of it, the latest such frame; a block with no clean agreeing view keeps the compositor's pixels unless a view
14//! of a better tier contradicts them.
15
16use super::locate::TierMap;
17use super::stats::CellRef;
18use super::{Footprint, Frame, BLOCK, CELL, TIER_CLEAN, TIER_VEIL};
19
20/// Cells per block side and per block.
21pub const SIDE: usize = BLOCK / CELL;
22pub const CELLS: usize = SIDE * SIDE;
23// `view` reads each cell as two pixels from each of two rows.
24const _: () = assert!(CELL == 2);
25const PIXELS: usize = BLOCK * BLOCK;
26
27#[derive(Clone, Copy)]
28pub struct BlockVote {
29    /// Best tier seen whole (-1 = never).
30    pub tier: i8,
31    pub clean_views: u16,
32    pub changed_views: u16,
33}
34
35/// The world-aligned block grid covering one canvas.
36#[derive(Clone, Copy, Debug)]
37pub struct Grid {
38    /// World block index of the grid's first block.
39    pub gx: i64,
40    pub gy: i64,
41    pub bw: usize,
42    pub bh: usize,
43}
44
45impl Grid {
46    /// Blocks covering the world rectangle (x, y, width, height).
47    pub fn covering(x: i64, y: i64, width: usize, height: usize) -> Self {
48        let b = BLOCK as i64;
49        let (gx, gy) = (x.div_euclid(b), y.div_euclid(b));
50        let (gx1, gy1) = (
51            (x + width as i64 + b - 1).div_euclid(b),
52            (y + height as i64 + b - 1).div_euclid(b),
53        );
54        Self {
55            gx,
56            gy,
57            bw: (gx1 - gx).max(0) as usize,
58            bh: (gy1 - gy).max(0) as usize,
59        }
60    }
61
62    /// World origin of block `b`.
63    #[inline]
64    pub fn origin(&self, b: usize) -> (i64, i64) {
65        (
66            (self.gx + (b % self.bw) as i64) * BLOCK as i64,
67            (self.gy + (b / self.bw) as i64) * BLOCK as i64,
68        )
69    }
70
71    /// Blocks whose whole area may lie in a footprint at `pose`: (bx0, by0, bx1, by1), half-open.
72    fn span(&self, footprint: &Footprint<'_>, pose: (i64, i64)) -> (usize, usize, usize, usize) {
73        let b = BLOCK as i64;
74        let (x0, y0) = (footprint.x + pose.0, footprint.y + pose.1);
75        let (x1, y1) = (x0 + footprint.width as i64, y0 + footprint.height as i64);
76        let clamp = |v: i64, n: usize| v.clamp(0, n as i64) as usize;
77        (
78            clamp((x0 + b - 1).div_euclid(b) - self.gx, self.bw),
79            clamp((y0 + b - 1).div_euclid(b) - self.gy, self.bh),
80            clamp(x1.div_euclid(b) - self.gx, self.bw),
81            clamp(y1.div_euclid(b) - self.gy, self.bh),
82        )
83    }
84}
85
86/// Signature of the block at world (wx0, wy0) as seen in `frame` at `pose`, and its tier (worst cell). `None` when
87/// any pixel of the block is outside the footprint. The footprint mask and the tier map share the layer rectangle's
88/// origin, so both are read by row with one index each.
89fn view(
90    frame: &Frame<'_>,
91    footprint: &Footprint<'_>,
92    tiers: &TierMap<'_>,
93    pose: (i64, i64),
94    wx0: i64,
95    wy0: i64,
96    out: &mut [[u8; 3]; CELLS],
97) -> Option<u8> {
98    let (sx0, sy0) = (wx0 - pose.0, wy0 - pose.1);
99    let (lx0, ly0) = (sx0 - footprint.x, sy0 - footprint.y);
100    let b = BLOCK as i64;
101    if sx0 < 0 || sy0 < 0 || sx0 + b > frame.width as i64 || sy0 + b > frame.height as i64 {
102        return None;
103    }
104    if lx0 < 0 || ly0 < 0 || lx0 + b > footprint.width as i64 || ly0 + b > footprint.height as i64 {
105        return None;
106    }
107    debug_assert_eq!((tiers.x, tiers.y), (footprint.x, footprint.y));
108    let (w, fw) = (frame.width, footprint.width);
109    let (lx0, ly0, sx0, sy0) = (lx0 as usize, ly0 as usize, sx0 as usize, sy0 as usize);
110    if (0..BLOCK).any(|yy| footprint.mask[(ly0 + yy) * fw + lx0..][..BLOCK].contains(&0)) {
111        return None;
112    }
113    // Each 2×2 cell straight from its two rows: the same sums and rounding as accumulating pixel by pixel.
114    let q = (CELL * CELL) as u32;
115    for (cy, cells) in out.chunks_exact_mut(SIDE).enumerate() {
116        let top = &frame.rgba[((sy0 + cy * CELL) * w + sx0) * 4..][..BLOCK * 4];
117        let bottom = &frame.rgba[((sy0 + cy * CELL + 1) * w + sx0) * 4..][..BLOCK * 4];
118        for ((cell, t), b) in cells
119            .iter_mut()
120            .zip(top.chunks_exact(8))
121            .zip(bottom.chunks_exact(8))
122        {
123            let mean = |c: usize| {
124                ((t[c] as u32 + t[c + 4] as u32 + b[c] as u32 + b[c + 4] as u32 + q / 2) / q) as u8
125            };
126            *cell = [mean(0), mean(1), mean(2)];
127        }
128    }
129    // The block's pixels fall in these tier cells (one more per axis when the block starts mid-cell).
130    let mut worst = TIER_CLEAN;
131    for ty in ly0 / CELL..=(ly0 + BLOCK - 1) / CELL {
132        let row = &tiers.tier[ty * tiers.cw..(ty + 1) * tiers.cw];
133        for &t in &row[lx0 / CELL..=(lx0 + BLOCK - 1) / CELL] {
134            worst = worst.min(t);
135        }
136    }
137    Some(worst)
138}
139
140/// A cell whose luma is this far off is an outlier: a transient (a cursor tip), not codec noise. Luma is decoded at
141/// full resolution, so chroma subsampling, which shifts the colour of a sharp edge with the pose's parity, never
142/// makes an outlier.
143const OUTLIER: i32 = 48;
144
145#[inline]
146fn cell_luma(c: [u8; 3]) -> i32 {
147    super::luma(c[0], c[1], c[2]) as i32
148}
149
150/// (mean |Δ| per channel, |mean signed Δ| per channel, cells whose luma is off by more than [`OUTLIER`]).
151fn compare(a: &[[u8; 3]], b: &[[u8; 3]]) -> (u32, u32, u32) {
152    let (mut sum, mut bias, mut off) = (0u32, 0i32, 0u32);
153    for (p, q) in a.iter().zip(b) {
154        for c in 0..3 {
155            let d = p[c] as i32 - q[c] as i32;
156            sum += d.unsigned_abs();
157            bias += d;
158        }
159        off += ((cell_luma(*p) - cell_luma(*q)).abs() > OUTLIER) as u32;
160    }
161    let n = (a.len() * 3) as u32;
162    (sum / n, bias.unsigned_abs() / n, off)
163}
164
165/// Two views agree when they differ by no more than codec noise on average, without a systematic shift (a
166/// dimming, a highlight), and in at most a couple of cells (codec ringing at a sharp edge).
167#[inline]
168fn agrees(d: (u32, u32, u32), tau: i32) -> bool {
169    d.0 <= (tau as u32 / 4).max(2) && d.1 <= 2 && d.2 <= 2
170}
171
172/// Agreement with no outlying cell at all: a view that may still carry a cursor tip in a couple of cells only
173/// ever agrees loosely.
174#[inline]
175fn agrees_strictly(d: (u32, u32, u32), tau: i32) -> bool {
176    agrees(d, tau) && d.2 == 0
177}
178
179/// A whole clean view counts as a structural change (dynamic content) beyond this mean or shift.
180const CHANGED: u32 = 8;
181const SHIFTED: u32 = 3;
182
183/// Per canvas: consensus signatures and vote state. Each cell keeps its own majority, so a transient that touches
184/// only a few cells of an otherwise agreeing view (a cursor tip) is outvoted there.
185pub struct Consensus {
186    pub grid: Grid,
187    pub sig: Vec<[u8; 3]>,
188    count: Vec<i16>,
189    pub vote: Vec<BlockVote>,
190}
191
192impl Consensus {
193    pub fn new(grid: Grid) -> Self {
194        let n = grid.bw * grid.bh;
195        Self {
196            grid,
197            sig: vec![[0; 3]; n * CELLS],
198            count: vec![0; n * CELLS],
199            vote: vec![
200                BlockVote {
201                    tier: -1,
202                    clean_views: 0,
203                    changed_views: 0
204                };
205                n
206            ],
207        }
208    }
209
210    /// One vote (a distinct pose, its last frame).
211    pub fn observe(
212        &mut self,
213        frame: &Frame<'_>,
214        footprint: &Footprint<'_>,
215        tiers: &TierMap<'_>,
216        pose: (i64, i64),
217    ) {
218        let (bx0, by0, bx1, by1) = self.grid.span(footprint, pose);
219        if bx1 <= bx0 || by1 <= by0 {
220            return;
221        }
222        let rows = by1 - by0;
223        let chunks = crate::kernels::pool::chunks_for(rows * (bx1 - bx0), 64);
224        let grid = self.grid;
225        let (sig_p, count_p, vote_p) = (
226            crate::kernels::pool::SyncPtr(self.sig.as_mut_ptr()),
227            crate::kernels::pool::SyncPtr(self.count.as_mut_ptr()),
228            crate::kernels::pool::SyncPtr(self.vote.as_mut_ptr()),
229        );
230        crate::kernels::pool::par_for(chunks, |k| {
231            let mut sig = [[0u8; 3]; CELLS];
232            for by in by0 + crate::kernels::pool::split(rows, chunks, k)
233                ..by0 + crate::kernels::pool::split(rows, chunks, k + 1)
234            {
235                for bx in bx0..bx1 {
236                    let b = by * grid.bw + bx;
237                    let (wx0, wy0) = grid.origin(b);
238                    let Some(tier) = view(frame, footprint, tiers, pose, wx0, wy0, &mut sig) else {
239                        continue;
240                    };
241                    // SAFETY: chunk `k` owns block rows by0+split(k)..by0+split(k+1); each block's state is its own.
242                    let (cand, count, v) = unsafe {
243                        (
244                            std::slice::from_raw_parts_mut(sig_p.get().add(b * CELLS), CELLS),
245                            std::slice::from_raw_parts_mut(count_p.get().add(b * CELLS), CELLS),
246                            &mut *vote_p.get().add(b),
247                        )
248                    };
249                    vote(cand, count, v, &sig, tier);
250                }
251            }
252        });
253    }
254}
255
256/// One whole-block view's vote.
257fn vote(
258    cand: &mut [[u8; 3]],
259    count: &mut [i16],
260    v: &mut BlockVote,
261    sig: &[[u8; 3]; CELLS],
262    tier: u8,
263) {
264    if tier == TIER_CLEAN {
265        if v.tier == TIER_CLEAN as i8 {
266            let d = compare(sig, cand);
267            if d.0 > CHANGED || d.1 > SHIFTED {
268                v.changed_views = v.changed_views.saturating_add(1);
269            }
270        }
271        v.clean_views = v.clean_views.saturating_add(1);
272    }
273    let t = tier as i8;
274    if t > v.tier {
275        cand.copy_from_slice(sig);
276        count.fill(1);
277        v.tier = t;
278    } else if t == v.tier {
279        // Majority per cell at the outlier threshold: codec-phase variants never flip a cell, so the signature stays
280        // one coherent view except where a transient was outvoted. A tie goes to the later view.
281        for k in 0..CELLS {
282            if (cell_luma(sig[k]) - cell_luma(cand[k])).abs() <= OUTLIER {
283                count[k] = count[k].saturating_add(1);
284            } else if count[k] <= 1 {
285                cand[k] = sig[k];
286                count[k] = 1;
287            } else {
288                count[k] -= 1;
289            }
290        }
291    }
292}
293
294impl CellRef for Consensus {
295    #[inline]
296    fn cell(&self, cx: i64, cy: i64) -> Option<[u8; 3]> {
297        let (wx, wy) = (cx * CELL as i64, cy * CELL as i64);
298        let (bx, by) = (
299            wx.div_euclid(BLOCK as i64) - self.grid.gx,
300            wy.div_euclid(BLOCK as i64) - self.grid.gy,
301        );
302        if bx < 0 || by < 0 || bx >= self.grid.bw as i64 || by >= self.grid.bh as i64 {
303            return None;
304        }
305        let b = by as usize * self.grid.bw + bx as usize;
306        if self.vote[b].tier < 0 {
307            return None;
308        }
309        let (ix, iy) = (
310            (wx.rem_euclid(BLOCK as i64) as usize) / CELL,
311            (wy.rem_euclid(BLOCK as i64) as usize) / CELL,
312        );
313        Some(self.sig[b * CELLS + iy * SIDE + ix])
314    }
315    fn cells(&self) -> (i64, i64, i64, i64) {
316        let s = SIDE as i64;
317        (
318            self.grid.gx * s,
319            self.grid.gy * s,
320            (self.grid.gx + self.grid.bw as i64) * s,
321            (self.grid.gy + self.grid.bh as i64) * s,
322        )
323    }
324}
325
326/// A connected set of dynamic blocks (plus one block of margin) that must come from one frame.
327pub struct Component {
328    pub bx: usize,
329    pub by: usize,
330    pub bw: usize,
331    pub bh: usize,
332    pub mask: Vec<bool>,
333    /// Blocks that really change (the rest is the static margin, which a frame must show as the consensus does).
334    pub dynamic: Vec<bool>,
335    /// Best cover so far: (covers nearly all, untainted, latest frame), or (0, 0, whole blocks covered).
336    pub best: Option<(usize, usize, usize)>,
337}
338
339/// Blocks whose clean views structurally disagree often enough are dynamic.
340pub fn components(c: &Consensus) -> Vec<Component> {
341    let (bw, bh) = (c.grid.bw, c.grid.bh);
342    let dynamic: Vec<bool> = c
343        .vote
344        .iter()
345        .map(|v| v.changed_views >= 2 && v.changed_views as u32 * 4 >= v.clean_views as u32)
346        .collect();
347    let mut grown = vec![false; bw * bh];
348    for y in 0..bh {
349        for x in 0..bw {
350            if dynamic[y * bw + x] {
351                for yy in y.saturating_sub(1)..=(y + 1).min(bh - 1) {
352                    for xx in x.saturating_sub(1)..=(x + 1).min(bw - 1) {
353                        grown[yy * bw + xx] = true;
354                    }
355                }
356            }
357        }
358    }
359    let mut label = vec![0usize; bw * bh];
360    let mut out = Vec::new();
361    for s in 0..bw * bh {
362        if !grown[s] || label[s] != 0 {
363            continue;
364        }
365        let id = out.len() + 1;
366        let mut stack = vec![s];
367        label[s] = id;
368        let mut members = Vec::new();
369        while let Some(i) = stack.pop() {
370            members.push(i);
371            let (x, y) = (i % bw, i / bw);
372            for yy in y.saturating_sub(1)..=(y + 1).min(bh - 1) {
373                for xx in x.saturating_sub(1)..=(x + 1).min(bw - 1) {
374                    let j = yy * bw + xx;
375                    if grown[j] && label[j] == 0 {
376                        label[j] = id;
377                        stack.push(j);
378                    }
379                }
380            }
381        }
382        let x0 = members.iter().map(|&i| i % bw).min().unwrap();
383        let x1 = members.iter().map(|&i| i % bw).max().unwrap();
384        let y0 = members.iter().map(|&i| i / bw).min().unwrap();
385        let y1 = members.iter().map(|&i| i / bw).max().unwrap();
386        let (cw, ch) = (x1 - x0 + 1, y1 - y0 + 1);
387        let mut mask = vec![false; cw * ch];
388        let mut changing = vec![false; cw * ch];
389        for &i in &members {
390            let k = (i / bw - y0) * cw + (i % bw - x0);
391            mask[k] = true;
392            changing[k] = dynamic[i];
393        }
394        out.push(Component {
395            bx: x0,
396            by: y0,
397            bw: cw,
398            bh: ch,
399            mask,
400            dynamic: changing,
401            best: None,
402        });
403    }
404    out
405}
406
407/// How a block's output was decided.
408#[derive(Clone, Copy, Debug, PartialEq, Eq)]
409#[repr(u8)]
410pub enum Kind {
411    /// Not decided: the compositor's pixels stand.
412    None = 0,
413    /// A whole clean view agreeing strictly with the consensus, at least [`CENTRAL`] pixels inside the layer.
414    Coherent = 1,
415    /// One frame for a dynamic component.
416    Component = 2,
417    /// Best-tier whole view held aside; written by [`Output::finish`] unless the compositor's block is as good.
418    Provisional = 3,
419    /// Never seen whole: per-pixel best-tier majority, written by [`Output::finish`] where a better tier
420    /// contradicts the compositor's pixel.
421    Fallback = 4,
422    /// The best clean, agreeing view short of [`Kind::Coherent`] (near the layer's edge, or agreeing only
423    /// loosely), held aside until a better one arrives or the pass ends.
424    Edge = 5,
425}
426
427/// Distance from the layer's edge (pixels) from which a clean agreeing view is final. Views nearer the edge are
428/// slightly degraded (rounded corners, container shadows, the codec at a fast-moving edge).
429pub const CENTRAL: i64 = 48;
430
431/// Copy of one block from a vote frame into the output: world block (gx, gy) from screen (sx, sy).
432#[derive(Clone, Copy, Debug, PartialEq, Eq)]
433pub struct BlockWrite {
434    /// Canvas slot (set by the run driver).
435    pub canvas: u32,
436    pub gx: i64,
437    pub gy: i64,
438    pub sx: i64,
439    pub sy: i64,
440    pub frame: u32,
441    pub kind: Kind,
442}
443
444/// A block written at the end: RGBA with alpha 0 on pixels that keep the compositor's value.
445/// [`Output::sources`]: the compositor's pixels stand.
446pub const UNTOUCHED: i32 = -1;
447/// [`Output::sources`]: per-pixel values that may come from several frames.
448pub const MIXED: i32 = -2;
449
450pub struct Deferred {
451    pub gx: i64,
452    pub gy: i64,
453    pub frame: u32,
454    pub kind: Kind,
455    pub rgba: Box<[u8; PIXELS * 4]>,
456}
457
458struct Fallback {
459    cand: [[u8; 3]; PIXELS],
460    count: [i16; PIXELS],
461    tier: [i8; PIXELS],
462    /// The compositor's pixels (alpha 0 = uncovered) and the best tier of an observation showing them.
463    main: [[u8; 4]; PIXELS],
464    main_tier: [i8; PIXELS],
465}
466
467/// A whole-block view's verdict, decided in parallel and applied in block order.
468#[derive(Clone, Copy, Default)]
469struct Act {
470    seen: bool,
471    tier: u8,
472    strict: bool,
473    loose: bool,
474    inset: i16,
475    /// The view shows what the compositor already holds.
476    main: bool,
477}
478
479/// One partly visible view of a fallback block, per pixel: best tier, tolerant majority within it (a tie to the
480/// later view, whose value is written: the most settled one), and the best tier of any observation showing the
481/// compositor's own pixel.
482fn fallback_view(
483    fb: &mut Fallback,
484    frame: &Frame<'_>,
485    footprint: &Footprint<'_>,
486    tiers: &TierMap<'_>,
487    pose: (i64, i64),
488    origin: (i64, i64),
489    tau: i32,
490) {
491    let (w, near) = (frame.width, (tau / 2).max(2));
492    for j in 0..PIXELS {
493        let (sx, sy) = (
494            origin.0 + (j % BLOCK) as i64 - pose.0,
495            origin.1 + (j / BLOCK) as i64 - pose.1,
496        );
497        if sx < 0
498            || sy < 0
499            || sx >= w as i64
500            || sy >= frame.height as i64
501            || !footprint.contains(sx, sy)
502        {
503            continue;
504        }
505        let fi = (sy as usize * w + sx as usize) * 4;
506        let obs = [frame.rgba[fi], frame.rgba[fi + 1], frame.rgba[fi + 2]];
507        let t = tiers.at(sx, sy) as i8;
508        let m = fb.main[j];
509        if m[3] == 255 && (0..3).all(|k| (obs[k] as i32 - m[k] as i32).abs() <= near) {
510            fb.main_tier[j] = fb.main_tier[j].max(t);
511        }
512        if t > fb.tier[j] {
513            fb.cand[j] = obs;
514            fb.count[j] = 1;
515            fb.tier[j] = t;
516        } else if t == fb.tier[j] {
517            if (0..3).all(|k| (obs[k] as i32 - fb.cand[j][k] as i32).abs() <= tau) {
518                fb.cand[j] = obs;
519                fb.count[j] = fb.count[j].saturating_add(1);
520            } else if fb.count[j] <= 1 {
521                fb.cand[j] = obs;
522                fb.count[j] = 1;
523            } else {
524                fb.count[j] -= 1;
525            }
526        }
527    }
528}
529
530/// Pass-4 state per canvas.
531pub struct Output {
532    acts: Vec<Act>,
533    pub kind: Vec<Kind>,
534    pub frame: Vec<i32>,
535    component: Vec<i32>,
536    provisional: Vec<i8>,
537    /// Rank of the held view: (tier, strictly agreeing, loosely agreeing, inset up to [`CENTRAL`]).
538    rank: Vec<(i8, bool, bool, i16)>,
539    held: Vec<Option<Box<[u8; PIXELS * 3]>>>,
540    main_tier: Vec<i8>,
541    fallback: Vec<Option<Box<Fallback>>>,
542    pub components: Vec<Component>,
543}
544
545/// Bytes a consensus holds per block (signatures and counts of its cells, and its vote).
546pub const CONSENSUS_BLOCK_BYTES: usize = CELLS
547    * (std::mem::size_of::<[u8; 3]>() + std::mem::size_of::<i16>())
548    + std::mem::size_of::<BlockVote>();
549/// Bytes an output holds per block, counting a held view for every block (the most the write pass can hold); its
550/// per-pixel fallback state is [`FALLBACK_BYTES`] per block that needs it.
551pub const OUTPUT_BLOCK_BYTES: usize = std::mem::size_of::<Act>()
552    + std::mem::size_of::<Kind>()
553    + 2 * std::mem::size_of::<i32>()
554    + std::mem::size_of::<i8>()
555    + std::mem::size_of::<(i8, bool, bool, i16)>()
556    + std::mem::size_of::<Option<Box<[u8; PIXELS * 3]>>>()
557    + PIXELS * 3
558    + std::mem::size_of::<i8>()
559    + std::mem::size_of::<Option<Box<Fallback>>>();
560/// Bytes of one block's per-pixel fallback state.
561pub const FALLBACK_BYTES: usize = std::mem::size_of::<Fallback>();
562
563impl Consensus {
564    /// Blocks no clean view covered whole: an upper bound on the blocks [`Output::new`] gives fallback state (it also
565    /// spares those inside a component).
566    pub fn unseen_blocks(&self) -> usize {
567        self.vote.iter().filter(|v| v.clean_views == 0).count()
568    }
569}
570
571impl Output {
572    /// Blocks never seen whole and clean in pass 2 get per-pixel fallback state; their compositor pixels must be
573    /// supplied with [`Output::set_main`] before the first [`Output::emit`].
574    pub fn new(c: &Consensus, components: Vec<Component>) -> Self {
575        let n = c.grid.bw * c.grid.bh;
576        let mut component = vec![-1; n];
577        for (k, comp) in components.iter().enumerate() {
578            for yy in 0..comp.bh {
579                for xx in 0..comp.bw {
580                    if comp.mask[yy * comp.bw + xx] {
581                        component[(comp.by + yy) * c.grid.bw + comp.bx + xx] = k as i32;
582                    }
583                }
584            }
585        }
586        let fallback = c
587            .vote
588            .iter()
589            .zip(&component)
590            .map(|(v, &k)| {
591                (v.clean_views == 0 && k < 0).then(|| {
592                    Box::new(Fallback {
593                        cand: [[0; 3]; PIXELS],
594                        count: [0; PIXELS],
595                        tier: [-1; PIXELS],
596                        main: [[0; 4]; PIXELS],
597                        main_tier: [-1; PIXELS],
598                    })
599                })
600            })
601            .collect();
602        Self {
603            acts: vec![Act::default(); n],
604            kind: vec![Kind::None; n],
605            frame: vec![-1; n],
606            component,
607            provisional: vec![-1; n],
608            rank: vec![(-1, false, false, -1); n],
609            held: (0..n).map(|_| None).collect(),
610            main_tier: vec![-1; n],
611            fallback,
612            components,
613        }
614    }
615
616    /// Blocks that need the compositor's pixels (fallback blocks), as indices into the grid.
617    pub fn fallback_blocks(&self) -> Vec<usize> {
618        self.fallback
619            .iter()
620            .enumerate()
621            .filter(|(_, f)| f.is_some())
622            .map(|(b, _)| b)
623            .collect()
624    }
625
626    /// The compositor's RGBA for fallback block `b` (alpha 255 = covered).
627    pub fn set_main(&mut self, b: usize, rgba: &[u8]) {
628        if let Some(fb) = self.fallback.get_mut(b).and_then(|f| f.as_mut()) {
629            for (j, px) in rgba.chunks_exact(4).take(PIXELS).enumerate() {
630                fb.main[j] = [px[0], px[1], px[2], px[3]];
631            }
632        }
633    }
634
635    /// One vote of pass 4. Blocks to copy from this frame are appended to `writes`; `main` is the compositor's
636    /// output as cell means.
637    #[allow(clippy::too_many_arguments)]
638    pub fn emit(
639        &mut self,
640        c: &Consensus,
641        frame: &Frame<'_>,
642        footprint: &Footprint<'_>,
643        tiers: &TierMap<'_>,
644        pose: (i64, i64),
645        tau: i32,
646        frame_index: u32,
647        main: &(dyn CellRef + Sync),
648        writes: &mut Vec<BlockWrite>,
649    ) {
650        let grid = c.grid;
651        let mut sig = [[0u8; 3]; CELLS];
652        let block_write = |b: usize, kind: Kind| {
653            let (wx0, wy0) = grid.origin(b);
654            BlockWrite {
655                canvas: 0,
656                gx: grid.gx + (b % grid.bw) as i64,
657                gy: grid.gy + (b / grid.bw) as i64,
658                sx: wx0 - pose.0,
659                sy: wy0 - pose.1,
660                frame: frame_index,
661                kind,
662            }
663        };
664        // Dynamic components: once a frame covers nearly all of one, the latest such frame wins; before that the
665        // widest partial cover. Blocks a cover misses keep the best earlier cover.
666        for comp in self.components.iter_mut() {
667            let mut covered = Vec::new();
668            // A frame showing a small blob that is not there in the consensus in the component's static margin
669            // carries a transient (a cursor passing over the widget's edge): it is the component's moment only
670            // when no other frame covers the component.
671            let mut tainted = false;
672            for yy in 0..comp.bh {
673                for xx in 0..comp.bw {
674                    let k = yy * comp.bw + xx;
675                    if !comp.mask[k] {
676                        continue;
677                    }
678                    let b = (comp.by + yy) * grid.bw + comp.bx + xx;
679                    let (wx0, wy0) = grid.origin(b);
680                    if let Some(t) = view(frame, footprint, tiers, pose, wx0, wy0, &mut sig) {
681                        if t >= TIER_VEIL {
682                            if !comp.dynamic[k] {
683                                let off = compare(&sig, &c.sig[b * CELLS..(b + 1) * CELLS]).2;
684                                tainted |= (1..=CELLS as u32 * 3 / 8).contains(&off);
685                            }
686                            covered.push(b);
687                        }
688                    }
689                }
690            }
691            if covered.is_empty() {
692                continue;
693            }
694            let blocks = comp.mask.iter().filter(|&&m| m).count();
695            let eligible = covered.len() * 10 >= blocks * 9;
696            // Among frames that cover nearly all of it, an untainted one beats a tainted one, then the latest.
697            let score = if eligible {
698                (1, !tainted as usize, frame_index as usize)
699            } else {
700                (0, 0, covered.len())
701            };
702            if comp.best.is_some_and(|best| best > score) {
703                continue;
704            }
705            comp.best = Some(score);
706            for b in covered {
707                self.kind[b] = Kind::Component;
708                self.frame[b] = frame_index as i32;
709                writes.push(block_write(b, Kind::Component));
710            }
711        }
712        let (bx0, by0, bx1, by1) = grid.span(footprint, pose);
713        let w = frame.width;
714        // Fallback blocks can be partly in view, so they are visited over the footprint's whole block range.
715        let (fx0, fy0, fx1, fy1) = {
716            let b = BLOCK as i64;
717            let (x0, y0) = (footprint.x + pose.0, footprint.y + pose.1);
718            let (x1, y1) = (x0 + footprint.width as i64, y0 + footprint.height as i64);
719            let clamp = |v: i64, n: usize| v.clamp(0, n as i64) as usize;
720            (
721                clamp(x0.div_euclid(b) - grid.gx, grid.bw),
722                clamp(y0.div_euclid(b) - grid.gy, grid.bh),
723                clamp((x1 + b - 1).div_euclid(b) - grid.gx, grid.bw),
724                clamp((y1 + b - 1).div_euclid(b) - grid.gy, grid.bh),
725            )
726        };
727        // The fallback slots hold boxes (not `Copy`), so their base travels as an address.
728        let (kind_p, fallback_base) = (
729            crate::kernels::pool::SyncPtr(self.kind.as_mut_ptr()),
730            self.fallback.as_mut_ptr() as usize,
731        );
732        if fy1 > fy0 && fx1 > fx0 {
733            let rows = fy1 - fy0;
734            let chunks = crate::kernels::pool::chunks_for(rows * (fx1 - fx0), 64);
735            crate::kernels::pool::par_for(chunks, |k| {
736                for by in fy0 + crate::kernels::pool::split(rows, chunks, k)
737                    ..fy0 + crate::kernels::pool::split(rows, chunks, k + 1)
738                {
739                    for bx in fx0..fx1 {
740                        let b = by * grid.bw + bx;
741                        // SAFETY: chunk `k` owns these block rows; each block's fallback state is its own.
742                        let (slot, kind) = unsafe {
743                            (
744                                &mut *(fallback_base as *mut Option<Box<Fallback>>).add(b),
745                                &mut *kind_p.get().add(b),
746                            )
747                        };
748                        let Some(fb) = slot.as_mut() else { continue };
749                        *kind = Kind::Fallback;
750                        fallback_view(fb, frame, footprint, tiers, pose, grid.origin(b), tau);
751                    }
752                }
753            });
754        }
755        if by1 <= by0 || bx1 <= bx0 {
756            return;
757        }
758        // Decide every whole block in parallel (no allocation in the pool), then apply the decisions in block order.
759        let rows = by1 - by0;
760        let chunks = crate::kernels::pool::chunks_for(rows * (bx1 - bx0), 64);
761        let acts = crate::kernels::pool::SyncPtr(self.acts.as_mut_ptr());
762        let (component, fallback, kind) = (&self.component, &self.fallback, &self.kind);
763        crate::kernels::pool::par_for(chunks, |k| {
764            let mut sig = [[0u8; 3]; CELLS];
765            for by in by0 + crate::kernels::pool::split(rows, chunks, k)
766                ..by0 + crate::kernels::pool::split(rows, chunks, k + 1)
767            {
768                for bx in bx0..bx1 {
769                    let b = by * grid.bw + bx;
770                    // SAFETY: chunk `k` owns these block rows' decision slots.
771                    let act = unsafe { &mut *acts.get().add(b) };
772                    *act = Act::default();
773                    if component[b] >= 0 || fallback[b].is_some() || kind[b] == Kind::Coherent {
774                        continue;
775                    }
776                    let (wx0, wy0) = grid.origin(b);
777                    let Some(tier) = view(frame, footprint, tiers, pose, wx0, wy0, &mut sig) else {
778                        continue;
779                    };
780                    let d = compare(&sig, &c.sig[b * CELLS..(b + 1) * CELLS]);
781                    let (sx0, sy0) = (wx0 - pose.0, wy0 - pose.1);
782                    let inset = (sx0 - footprint.x)
783                        .min(sy0 - footprint.y)
784                        .min(footprint.x + footprint.width as i64 - sx0 - BLOCK as i64)
785                        .min(footprint.y + footprint.height as i64 - sy0 - BLOCK as i64);
786                    // Does this view show what the compositor already holds?
787                    let (cx0, cy0) = (wx0.div_euclid(CELL as i64), wy0.div_euclid(CELL as i64));
788                    let mut msig = [[0u8; 3]; CELLS];
789                    let known = (0..CELLS).all(|k| {
790                        match main.cell(cx0 + (k % SIDE) as i64, cy0 + (k / SIDE) as i64) {
791                            Some(v) => {
792                                msig[k] = v;
793                                true
794                            }
795                            None => false,
796                        }
797                    });
798                    *act = Act {
799                        seen: true,
800                        tier,
801                        strict: agrees_strictly(d, tau),
802                        loose: agrees(d, tau),
803                        inset: inset.min(CENTRAL) as i16,
804                        main: known && agrees(compare(&sig, &msig), tau),
805                    };
806                }
807            }
808        });
809        for by in by0..by1 {
810            for bx in bx0..bx1 {
811                let b = by * grid.bw + bx;
812                let a = self.acts[b];
813                if !a.seen {
814                    continue;
815                }
816                if a.tier == TIER_CLEAN && a.strict && a.inset >= CENTRAL as i16 {
817                    self.kind[b] = Kind::Coherent;
818                    self.frame[b] = frame_index as i32;
819                    self.held[b] = None;
820                    writes.push(block_write(b, Kind::Coherent));
821                    continue;
822                }
823                if a.main {
824                    self.main_tier[b] = self.main_tier[b].max(a.tier as i8);
825                }
826                // Hold the best view so far; a later view wins a tie (the more settled state).
827                let rank = (
828                    a.tier as i8,
829                    a.tier == TIER_CLEAN && a.strict,
830                    a.tier == TIER_CLEAN && a.loose,
831                    a.inset,
832                );
833                if rank >= self.rank[b] {
834                    let held = self.held[b].get_or_insert_with(|| Box::new([0u8; PIXELS * 3]));
835                    let (wx0, wy0) = grid.origin(b);
836                    for j in 0..PIXELS {
837                        let (sx, sy) = (
838                            wx0 + (j % BLOCK) as i64 - pose.0,
839                            wy0 + (j / BLOCK) as i64 - pose.1,
840                        );
841                        let fi = (sy as usize * w + sx as usize) * 4;
842                        held[j * 3..j * 3 + 3].copy_from_slice(&frame.rgba[fi..fi + 3]);
843                    }
844                    self.rank[b] = rank;
845                    self.kind[b] = if rank.2 {
846                        Kind::Edge
847                    } else {
848                        Kind::Provisional
849                    };
850                    self.provisional[b] = a.tier as i8;
851                    self.frame[b] = frame_index as i32;
852                }
853            }
854        }
855    }
856
857    /// Where each block's pixels now come from, given the blocks [`Output::finish`] returned: the frame a whole
858    /// block was copied from, [`MIXED`] for per-pixel fallback values, [`UNTOUCHED`] where the compositor's pixels
859    /// stand.
860    pub fn sources(&self, c: &Consensus, deferred: &[Deferred]) -> Vec<i32> {
861        let grid = c.grid;
862        let mut out: Vec<i32> = self
863            .kind
864            .iter()
865            .zip(&self.frame)
866            .map(|(k, &f)| match k {
867                Kind::Coherent | Kind::Component => f,
868                _ => UNTOUCHED,
869            })
870            .collect();
871        for d in deferred {
872            let b = (d.gy - grid.gy) as usize * grid.bw + (d.gx - grid.gx) as usize;
873            out[b] = if d.kind == Kind::Fallback {
874                MIXED
875            } else {
876                d.frame as i32
877            };
878        }
879        out
880    }
881
882    /// The deferred blocks: a provisional view unless a view agreeing with the compositor's block was at least as
883    /// clean, and fallback pixels where a better-tier observation contradicts the compositor's pixel.
884    pub fn finish(&self, c: &Consensus, tau: i32) -> Vec<Deferred> {
885        let grid = c.grid;
886        let near = (tau / 2).max(2);
887        let mut out = Vec::new();
888        for b in 0..grid.bw * grid.bh {
889            let (gx, gy) = (
890                grid.gx + (b % grid.bw) as i64,
891                grid.gy + (b / grid.bw) as i64,
892            );
893            match self.kind[b] {
894                Kind::Edge => {
895                    let Some(h) = &self.held[b] else { continue };
896                    // Agreeing with a consensus only one clean view built is no evidence: near a layer edge in a
897                    // fast scroll a block is seen whole once, and that view may carry an unmodelled overlay (a
898                    // scrollbar thumb). It then replaces the compositor's block only when it is cleaner.
899                    if c.vote[b].clean_views < 2 && self.main_tier[b] >= self.provisional[b] {
900                        continue;
901                    }
902                    let mut rgba = Box::new([0u8; PIXELS * 4]);
903                    for j in 0..PIXELS {
904                        rgba[j * 4..j * 4 + 3].copy_from_slice(&h[j * 3..j * 3 + 3]);
905                        rgba[j * 4 + 3] = 255;
906                    }
907                    out.push(Deferred {
908                        gx,
909                        gy,
910                        frame: self.frame[b] as u32,
911                        kind: Kind::Edge,
912                        rgba,
913                    });
914                }
915                Kind::Provisional => {
916                    let Some(h) = &self.held[b] else { continue };
917                    if self.main_tier[b] >= self.provisional[b] {
918                        continue;
919                    }
920                    let mut rgba = Box::new([0u8; PIXELS * 4]);
921                    for j in 0..PIXELS {
922                        rgba[j * 4..j * 4 + 3].copy_from_slice(&h[j * 3..j * 3 + 3]);
923                        rgba[j * 4 + 3] = 255;
924                    }
925                    out.push(Deferred {
926                        gx,
927                        gy,
928                        frame: self.frame[b] as u32,
929                        kind: Kind::Provisional,
930                        rgba,
931                    });
932                }
933                Kind::Fallback => {
934                    let Some(fb) = &self.fallback[b] else {
935                        continue;
936                    };
937                    let mut rgba = Box::new([0u8; PIXELS * 4]);
938                    let mut any = false;
939                    // The block is only touched when a better-tier observation contradicts the compositor somewhere
940                    // in it; then pixels that say the same take the majority's settled value too, so the rewrite
941                    // stays consistent, while the compositor's pixel stands wherever an observation at least as
942                    // clean shows it and the majority says otherwise.
943                    let same = |j: usize| {
944                        let m = fb.main[j];
945                        m[3] == 255
946                            && (0..3).all(|k| (fb.cand[j][k] as i32 - m[k] as i32).abs() <= near)
947                    };
948                    if !(0..PIXELS)
949                        .any(|j| fb.tier[j] >= 0 && fb.tier[j] > fb.main_tier[j] && !same(j))
950                    {
951                        continue;
952                    }
953                    for j in 0..PIXELS {
954                        if fb.tier[j] < 0 || (fb.main_tier[j] >= fb.tier[j] && !same(j)) {
955                            continue;
956                        }
957                        rgba[j * 4..j * 4 + 3].copy_from_slice(&fb.cand[j]);
958                        rgba[j * 4 + 3] = 255;
959                        any = true;
960                    }
961                    if any {
962                        out.push(Deferred {
963                            gx,
964                            gy,
965                            frame: 0,
966                            kind: Kind::Fallback,
967                            rgba,
968                        });
969                    }
970                }
971                _ => {}
972            }
973        }
974        out
975    }
976}