Skip to main content

long_screen/engine/
reveal.rs

1//! The reveal stage (docs/ARCHITECTURE.md §七 "揭示", §十 "揭示"): after the compositor has written every canvas, four
2//! more frame-major passes over the recording replace what overlays burned in — a composer bar, a floating button, a
3//! scrollbar thumb, a cursor, a fade veil — with the cleanest observation of the same page content, and give content
4//! that really changed over time a single moment. The algorithm is `crate::kernels::reveal`; [`RevealSession`] is the
5//! run around it: which layers and canvases take part, which decisions of the solve count, the memory check that skips
6//! the stage, feeding the compositor's tiles in, the order of the passes, and copying the chosen 16×16 blocks into the
7//! tiles.
8//!
9//! The host answers what each [`RevealSession::step`] asks for:
10//!
11//! - [`Step::NeedMemory`]: whether that many more bytes (plus whatever the host keeps free beside them) can be
12//!   allocated, through [`RevealSession::memory`]. A refusal ends the stage with `REVEAL_SKIPPED`.
13//! - [`Step::NeedObservations`]: every stored `observation/` row, in key order, through
14//!   [`RevealSession::observation`].
15//! - [`Step::NeedTile`]: a tile that is not resident (make room with [`TileCache::victim`], storing what is dirty
16//!   before [`TileCache::evict`], then [`TileCache::insert`]).
17//! - [`Step::Checkpoint`]: one compositor tile went into the reference; check for pause and stop.
18//! - [`Step::Decode`]: decode the recording for one pass. For each decoded frame below the session's frame count, write
19//!   its RGBA into [`RevealSession::frame_buffer`] when `wanted` marks it (the pass reads no other frame's pixels),
20//!   call [`RevealSession::push_frame`], then step until [`Step::NextFrame`]. After the last frame, call
21//!   [`RevealSession::end_pass`] and step until [`Step::PassDone`].
22//! - [`Step::Provenance`]: store the canvas's `reveal/<canvas>` row.
23//! - [`Step::Done`]: the stage is over; on [`Outcome::Revealed`] the host flushes the tiles and stores
24//!   `reveal-summary`.
25//!
26//! Every tile the session reads or writes is taken from the cache in the order the stage has always used — the
27//! compositor's tiles row by row per canvas, one tile per fallback block, one per group of block copies from a vote
28//! frame in the order the copies came, one per deferred block — so the cache sees the same uses, evictions and
29//! reloads. The session keeps the three decoded frames the passes compare (the vote frame, the one before it and
30//! the frame being written) in its own buffers.
31
32use crate::engine::diagnostic::{Diagnostic, Severity, Value};
33use crate::engine::tiles::{Tile, TileCache, TileKey, QUALITY_BLOCK};
34use crate::kernels::geometry::{js_round, Rect};
35use crate::kernels::reveal::blocks::{BlockWrite, Deferred, Kind};
36use crate::kernels::reveal::run::{admitted, canvas_bytes, layer_bytes, Decision, Reveal};
37use crate::kernels::reveal::CanvasView;
38use std::collections::HashMap;
39
40pub use crate::kernels::reveal::run::Pass;
41
42/// The four frame-major passes, in order.
43pub const PASSES: [Pass; 4] = [Pass::Learn, Pass::Agree, Pass::Relearn, Pass::Write];
44/// Decoded frames the session holds: the frame being written and the two before it.
45pub const RING_FRAMES: usize = 3;
46/// Quality a block taken from a clean, agreeing view is shown with (the viewer's low-quality line is 153).
47pub const REVEALED_QUALITY: u8 = 204;
48
49/// A moving layer: its id, its screen rectangle and its atlas code (0 when it owns the whole rectangle).
50#[derive(Clone, Debug, PartialEq)]
51pub struct RevealLayer {
52    pub id: String,
53    pub rect: Rect,
54    pub code: u32,
55}
56
57/// A moving canvas the compositor wrote: its id and world bounds.
58#[derive(Clone, Debug, PartialEq)]
59pub struct RevealCanvas {
60    pub id: String,
61    pub bounds: Rect,
62}
63
64/// One decision of a stored `observation/` row: the canvas and layer it places, the solved pose, whether the
65/// placement itself is a skip (`placement.skip`), whether the solve held the pose because the layer was blind
66/// (`skipped`: too little texture to track, so the compositor painted nothing), and the placement's occlusions.
67#[derive(Clone, Debug, PartialEq)]
68pub struct ObservedDecision {
69    pub canvas: String,
70    pub layer: String,
71    pub x: f64,
72    pub y: f64,
73    pub skip: bool,
74    pub skipped: bool,
75    pub occlusions: Vec<Rect>,
76}
77
78/// What a [`Step::NeedMemory`] is for: every canvas and layer with the decoded frames, or the write pass's per-pixel
79/// fallback state.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum MemoryStage {
82    Canvases,
83    Fallback,
84}
85
86impl MemoryStage {
87    /// The `stage` the `REVEAL_SKIPPED` row's detail names.
88    pub fn as_str(self) -> &'static str {
89        match self {
90            MemoryStage::Canvases => "canvases",
91            MemoryStage::Fallback => "fallback",
92        }
93    }
94}
95
96/// Where each 16-pixel block of a canvas takes its pixels from after the write pass: the frame a whole block was
97/// copied from, `-2` for per-pixel values from several frames, `-1` where the compositor's pixels stand. Block
98/// `(gx + i % bw, gy + i / bw)` in world block coordinates. The host stores it as `reveal/<canvas>`.
99#[derive(Clone, Debug, PartialEq)]
100pub struct Provenance {
101    pub canvas: String,
102    pub gx: i64,
103    pub gy: i64,
104    pub bw: usize,
105    pub bh: usize,
106    pub source: Vec<i32>,
107}
108
109/// The counters of the `reveal-summary` row, in its field order.
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
111pub struct Summary {
112    pub objects: u32,
113    pub components: u32,
114    pub coherent_blocks: u32,
115    pub component_blocks: u32,
116    pub provisional_blocks: u32,
117    pub fallback_blocks: u32,
118}
119
120#[derive(Clone, Debug, PartialEq)]
121pub enum Outcome {
122    /// No moving layer or no moving canvas: the stage does nothing.
123    Nothing,
124    /// `REVEAL_SKIPPED` (warning, parameter `need` in MB; detail `stage`, `needMB`, `coreMB`): the memory the stage
125    /// needs could not be allocated, so the compositor's pixels stay as they are.
126    Skipped(Diagnostic),
127    Revealed(Summary),
128}
129
130#[derive(Clone, Debug, PartialEq)]
131pub enum Step {
132    /// Answer with [`RevealSession::memory`]: can `bytes` more be allocated?
133    NeedMemory {
134        bytes: f64,
135        stage: MemoryStage,
136    },
137    /// Pass every stored `observation/` row to [`RevealSession::observation`], then step again.
138    NeedObservations,
139    /// The tile is not resident: make room, insert it, step again.
140    NeedTile(TileKey),
141    /// A compositor tile went into the reference; check for pause and stop, then step again.
142    Checkpoint,
143    /// Decode the recording for `pass`; `wanted[i]` marks the frames whose pixels it reads.
144    Decode {
145        pass: Pass,
146        wanted: Vec<bool>,
147    },
148    /// The pushed frame is done: push the next one, or end the pass after the last.
149    NextFrame,
150    /// The pass is over (its timings end here); step again for the next one.
151    PassDone(Pass),
152    /// Store this canvas's provenance row, then step again.
153    Provenance(Provenance),
154    Done(Outcome),
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub enum RevealError {
159    /// A call out of turn: `memory` without a pending [`Step::NeedMemory`], `observation` before
160    /// [`Step::NeedObservations`], a frame outside [`Step::Decode`], a step while a frame is owed, or a step after the
161    /// session finished.
162    Unexpected,
163    /// Stepped again after [`Step::NeedTile`] without inserting the tile.
164    NotProvided,
165    /// A layer whose pixel span is empty or whose atlas code is above 255.
166    BadLayer,
167    /// A label plane that is not `width × height` bytes.
168    BadLabels,
169}
170
171/// Integer pixel span of a float extent: pixel p is inside iff `start ≤ p < start + size`.
172fn pixel_span(start: f64, size: f64) -> (i64, i64) {
173    let a = start.ceil();
174    (a as i64, ((start + size).ceil() - a).max(0.0) as i64)
175}
176
177/// Where a block's tile is and where the block sits inside it.
178fn block_tile(gx: i64, gy: i64, size: usize) -> (i32, i32, usize, usize) {
179    let (x0, y0, s) = (
180        gx * QUALITY_BLOCK as i64,
181        gy * QUALITY_BLOCK as i64,
182        size as i64,
183    );
184    (
185        x0.div_euclid(s) as i32,
186        y0.div_euclid(s) as i32,
187        x0.rem_euclid(s) as usize,
188        y0.rem_euclid(s) as usize,
189    )
190}
191
192/// Writes one world block of `tile` (16 rows of RGBA from `row`). With `masked`, pixels whose alpha is not 255 keep
193/// the canvas. Kinds that settle a block clear its provisional bits and conflicts and raise its quality; per-pixel
194/// fallback values keep the compositor's owner frame.
195fn write_block<'a>(
196    tile: &mut Tile,
197    size: usize,
198    (lx, ly): (usize, usize),
199    frame: u32,
200    kind: Kind,
201    row: impl Fn(usize) -> &'a [u8],
202    masked: bool,
203) {
204    let settled = matches!(kind, Kind::Coherent | Kind::Component | Kind::Edge);
205    for r in 0..QUALITY_BLOCK {
206        let src = row(r);
207        let p = (ly + r) * size + lx;
208        if !masked {
209            // A whole row of an opaque frame: the alpha comes with it, and the row's 16 coverage/provisional bits are
210            // two whole bytes (blocks start on a multiple of 16 pixels).
211            tile.pixels[p * 4..(p + QUALITY_BLOCK) * 4].copy_from_slice(&src[..QUALITY_BLOCK * 4]);
212            tile.coverage[p >> 3] = 0xff;
213            tile.coverage[(p >> 3) + 1] = 0xff;
214            if settled {
215                tile.provisional[p >> 3] = 0;
216                tile.provisional[(p >> 3) + 1] = 0;
217            }
218            continue;
219        }
220        for x in 0..QUALITY_BLOCK {
221            if src[x * 4 + 3] != 255 {
222                continue;
223            }
224            let q = p + x;
225            tile.pixels[q * 4..q * 4 + 3].copy_from_slice(&src[x * 4..x * 4 + 3]);
226            tile.pixels[q * 4 + 3] = 255;
227            tile.coverage[q >> 3] |= 1 << (q & 7);
228            if settled {
229                tile.provisional[q >> 3] &= !(1 << (q & 7));
230            }
231        }
232    }
233    let b = (ly / QUALITY_BLOCK) * (size / QUALITY_BLOCK) + lx / QUALITY_BLOCK;
234    // A whole block copied from one frame is owned by it; per-pixel fallback values keep the compositor's owner.
235    if kind != Kind::Fallback {
236        tile.owner[b] = frame.wrapping_add(1);
237    }
238    if settled {
239        tile.conflicts[b] = 0;
240        tile.quality[b] = tile.quality[b].max(REVEALED_QUALITY);
241    }
242}
243
244enum Phase {
245    Start,
246    /// [`Step::NeedMemory`] was returned for `bytes`.
247    AwaitMemory {
248        stage: MemoryStage,
249        bytes: f64,
250    },
251    /// The host refused the memory: the next step reports it.
252    Refused(Diagnostic),
253    /// The memory was granted: ask for the observations.
254    Observe,
255    /// [`Step::NeedObservations`] was returned.
256    AwaitObservations,
257    /// Feeding the compositor's tiles of `canvas` into its reference; the next tile is (`tx`, `ty`).
258    Ingest {
259        canvas: usize,
260        ty: i64,
261        tx: i64,
262    },
263    /// The pass `PASSES[pass]` is next.
264    Begin {
265        pass: usize,
266        memory_checked: bool,
267    },
268    /// Supplying the compositor's pixels of the fallback blocks of `canvas` before the write pass.
269    Fallbacks {
270        canvas: usize,
271        blocks: Vec<(i64, i64)>,
272        at: usize,
273    },
274    /// The host is decoding for `PASSES[pass]`.
275    Decoding {
276        pass: usize,
277    },
278    /// The decode for `PASSES[pass]` ended; its last block copies are being applied.
279    Ending {
280        pass: usize,
281    },
282    /// Writing the deferred blocks of `canvas`, then its provenance.
283    Finish {
284        canvas: usize,
285        deferred: Option<Vec<Deferred>>,
286        at: usize,
287    },
288    Done,
289}
290
291/// Block copies from one vote frame, grouped by tile in the order the copies came.
292struct Copies {
293    /// The frame slot they copy from.
294    source: usize,
295    groups: Vec<(TileKey, Vec<BlockWrite>)>,
296    at: usize,
297}
298
299/// One reveal run, driven by the host step by step (see the module documentation).
300pub struct RevealSession {
301    width: usize,
302    height: usize,
303    noise: f64,
304    size: usize,
305    frames: u32,
306    layers: Vec<RevealLayer>,
307    canvases: Vec<RevealCanvas>,
308    layer_slot: HashMap<String, usize>,
309    canvas_slot: HashMap<String, usize>,
310    /// Created once the memory check passed.
311    run: Option<Reveal>,
312    /// Every stored observation: its frame and its decisions with whether each is blind.
313    observed: Vec<(u32, Vec<(Decision, bool)>)>,
314    /// The decisions the passes use, per frame below `frames`.
315    plans: Vec<Vec<Decision>>,
316    phase: Phase,
317    /// The tile the last [`Step::NeedTile`] asked for.
318    asked: Option<TileKey>,
319    slots: Vec<Vec<u8>>,
320    /// The slot [`RevealSession::frame_buffer`] handed out for the frame not pushed yet.
321    staged: Option<usize>,
322    /// The last pushed frame's slot (none when it was not converted) and the one before.
323    last: Option<usize>,
324    before: Option<usize>,
325    pushed: bool,
326    copies: Option<Copies>,
327}
328
329impl RevealSession {
330    /// A reveal over `frames` decoded frames of `width × height` with the source's decode `noise`, for the moving
331    /// `layers` and `canvases` (in the order the host lists them), on `tile_size`-pixel tiles.
332    pub fn new(
333        width: usize,
334        height: usize,
335        noise: f64,
336        tile_size: usize,
337        frames: u32,
338        layers: Vec<RevealLayer>,
339        canvases: Vec<RevealCanvas>,
340    ) -> Result<RevealSession, RevealError> {
341        for l in &layers {
342            let (_, w) = pixel_span(l.rect.x, l.rect.width);
343            let (_, h) = pixel_span(l.rect.y, l.rect.height);
344            if w == 0 || h == 0 || l.code > 255 {
345                return Err(RevealError::BadLayer);
346            }
347        }
348        // Later ids win, as a map built from the list does.
349        let layer_slot = layers
350            .iter()
351            .enumerate()
352            .map(|(i, l)| (l.id.clone(), i))
353            .collect();
354        let canvas_slot = canvases
355            .iter()
356            .enumerate()
357            .map(|(i, c)| (c.id.clone(), i))
358            .collect();
359        Ok(RevealSession {
360            width,
361            height,
362            noise,
363            size: tile_size,
364            frames,
365            layers,
366            canvases,
367            layer_slot,
368            canvas_slot,
369            run: None,
370            observed: Vec::new(),
371            plans: Vec::new(),
372            phase: Phase::Start,
373            asked: None,
374            slots: Vec::new(),
375            staged: None,
376            last: None,
377            before: None,
378            pushed: false,
379            copies: None,
380        })
381    }
382
383    /// Core memory the stage needs before it starts: every canvas and layer, and the decoded frames it holds.
384    pub fn estimate(&self) -> f64 {
385        let canvases: f64 = self
386            .canvases
387            .iter()
388            .map(|c| {
389                let (_, w) = pixel_span(c.bounds.x, c.bounds.width);
390                let (_, h) = pixel_span(c.bounds.y, c.bounds.height);
391                canvas_bytes(w as usize, h as usize) as f64
392            })
393            .sum();
394        let layers: f64 = self
395            .layers
396            .iter()
397            .map(|l| {
398                let (_, w) = pixel_span(l.rect.x, l.rect.width);
399                let (_, h) = pixel_span(l.rect.y, l.rect.height);
400                layer_bytes(w as usize, h as usize) as f64
401            })
402            .sum();
403        canvases + layers + (RING_FRAMES * self.width * self.height * 4) as f64
404    }
405
406    /// The answer to [`Step::NeedMemory`]: whether the bytes fit, and the core's current size in bytes (reported in
407    /// `REVEAL_SKIPPED` when they do not).
408    pub fn memory(&mut self, fits: bool, core_bytes: f64) -> Result<(), RevealError> {
409        let Phase::AwaitMemory { stage, bytes } = self.phase else {
410            return Err(RevealError::Unexpected);
411        };
412        if !fits {
413            let mb = js_round(bytes / 1048576.0) as f64;
414            let diagnostic = Diagnostic::new("REVEAL_SKIPPED", Severity::Warning)
415                .param("need", mb)
416                .detail(Value::Object(vec![
417                    ("stage", Value::Text(stage.as_str().to_string())),
418                    ("needMB", Value::Number(mb)),
419                    (
420                        "coreMB",
421                        Value::Number(js_round(core_bytes / 1048576.0) as f64),
422                    ),
423                ]));
424            self.phase = Phase::Refused(diagnostic);
425            return Ok(());
426        }
427        match stage {
428            MemoryStage::Canvases => {
429                let mut run = Reveal::new(self.width, self.height, self.noise);
430                for l in &self.layers {
431                    let (x, w) = pixel_span(l.rect.x, l.rect.width);
432                    let (y, h) = pixel_span(l.rect.y, l.rect.height);
433                    run.add_layer(x, y, w as usize, h as usize, l.code as u8);
434                }
435                for c in &self.canvases {
436                    // Truncated, as the integer arguments the stage has always passed them as.
437                    let b = c.bounds;
438                    run.add_canvas(
439                        b.x as i32 as i64,
440                        b.y as i32 as i64,
441                        b.width as u32 as usize,
442                        b.height as u32 as usize,
443                    );
444                }
445                self.run = Some(run);
446                self.phase = Phase::Observe;
447            }
448            MemoryStage::Fallback => {
449                self.phase = Phase::Begin {
450                    pass: 3,
451                    memory_checked: true,
452                };
453            }
454        }
455        Ok(())
456    }
457
458    /// One stored `observation/` row (frame and decisions), after [`Step::NeedObservations`]. Decisions on a canvas
459    /// or layer the stage does not cover are left out, and so is a blind decision whose placement is not a skip.
460    pub fn observation(
461        &mut self,
462        frame: u32,
463        decisions: &[ObservedDecision],
464    ) -> Result<(), RevealError> {
465        if !matches!(self.phase, Phase::AwaitObservations) {
466            return Err(RevealError::Unexpected);
467        }
468        let mut out = Vec::new();
469        for d in decisions {
470            let (Some(&canvas), Some(&layer)) = (
471                self.canvas_slot.get(&d.canvas),
472                self.layer_slot.get(&d.layer),
473            ) else {
474                continue;
475            };
476            if d.skipped && !d.skip {
477                continue;
478            }
479            let occlusions = d
480                .occlusions
481                .iter()
482                .map(|r| {
483                    let (x, w) = pixel_span(r.x, r.width);
484                    let (y, h) = pixel_span(r.y, r.height);
485                    (x, y, w, h)
486                })
487                .collect();
488            out.push((
489                Decision {
490                    layer,
491                    canvas,
492                    x: js_round(d.x) as i64,
493                    y: js_round(d.y) as i64,
494                    occlusions,
495                },
496                d.skipped,
497            ));
498        }
499        self.observed.push((frame, out));
500        Ok(())
501    }
502
503    /// The buffer the next decoded frame's RGBA (`width × height × 4` bytes) goes into, during [`Step::Decode`] and
504    /// only for a frame `wanted` marks. Asking again before [`RevealSession::push_frame`] returns the same buffer.
505    pub fn frame_buffer(&mut self) -> Result<&mut [u8], RevealError> {
506        if !matches!(self.phase, Phase::Decoding { .. }) || self.copies.is_some() {
507            return Err(RevealError::Unexpected);
508        }
509        let slot = match self.staged {
510            Some(slot) => slot,
511            None => {
512                let held = |s: usize| self.last == Some(s) || self.before == Some(s);
513                let slot = match (0..self.slots.len()).find(|&s| !held(s)) {
514                    Some(slot) => slot,
515                    None => {
516                        self.slots.push(vec![0; self.width * self.height * 4]);
517                        self.slots.len() - 1
518                    }
519                };
520                self.staged = Some(slot);
521                slot
522            }
523        };
524        Ok(&mut self.slots[slot])
525    }
526
527    fn check_labels(&self, labels: Option<&[u8]>) -> Result<(), RevealError> {
528        match labels {
529            Some(l) if l.len() != self.width * self.height => Err(RevealError::BadLabels),
530            _ => Ok(()),
531        }
532    }
533
534    /// Frame `index` was decoded: the frame written into [`RevealSession::frame_buffer`] since the last push, or no
535    /// pixels when it was not wanted. `labels` is the atlas label plane. Step until [`Step::NextFrame`] afterwards.
536    pub fn push_frame(&mut self, index: u32, labels: Option<&[u8]>) -> Result<(), RevealError> {
537        let Phase::Decoding { pass } = self.phase else {
538            return Err(RevealError::Unexpected);
539        };
540        if self.copies.is_some() {
541            return Err(RevealError::Unexpected);
542        }
543        self.check_labels(labels)?;
544        let current = self.staged.take();
545        let run = self.run.as_mut().unwrap();
546        let slot = |s: Option<usize>| s.map(|s| &self.slots[s][..]);
547        let decisions = self.plans.get(index as usize).map_or(&[][..], |p| &p[..]);
548        run.frame(
549            index,
550            slot(current),
551            slot(self.last),
552            slot(self.before),
553            labels,
554            decisions,
555        );
556        if PASSES[pass] == Pass::Write {
557            if let Some(source) = self.last {
558                self.copies = Some(self.group(source));
559            }
560        }
561        self.before = self.last;
562        self.last = current;
563        self.pushed = true;
564        Ok(())
565    }
566
567    /// The decode for this pass is over: runs still open vote with the last frame. Step until [`Step::PassDone`].
568    pub fn end_pass(&mut self, labels: Option<&[u8]>) -> Result<(), RevealError> {
569        let Phase::Decoding { pass } = self.phase else {
570            return Err(RevealError::Unexpected);
571        };
572        if self.copies.is_some() {
573            return Err(RevealError::Unexpected);
574        }
575        self.check_labels(labels)?;
576        self.staged = None;
577        if self.pushed {
578            let run = self.run.as_mut().unwrap();
579            let slot = |s: Option<usize>| s.map(|s| &self.slots[s][..]);
580            run.end(slot(self.last), slot(self.before), labels);
581            if PASSES[pass] == Pass::Write {
582                if let Some(source) = self.last {
583                    self.copies = Some(self.group(source));
584                }
585            }
586        }
587        self.phase = Phase::Ending { pass };
588        Ok(())
589    }
590
591    /// Drains the run's block copies, grouped by tile in the order they came.
592    fn group(&mut self, source: usize) -> Copies {
593        let writes = self.run.as_mut().unwrap().take_writes();
594        let mut groups: Vec<(TileKey, Vec<BlockWrite>)> = Vec::new();
595        let mut index: HashMap<TileKey, usize> = HashMap::new();
596        for w in writes {
597            let (tx, ty, _, _) = block_tile(w.gx, w.gy, self.size);
598            let key = TileKey::new(&self.canvases[w.canvas as usize].id, 0, tx, ty);
599            match index.get(&key) {
600                Some(&g) => groups[g].1.push(w),
601                None => {
602                    index.insert(key.clone(), groups.len());
603                    groups.push((key, vec![w]));
604                }
605            }
606        }
607        Copies {
608            source,
609            groups,
610            at: 0,
611        }
612    }
613
614    /// The tile `key`, touched as the most recently used one, or the step that asks the host for it.
615    fn tile(
616        &mut self,
617        cache: &mut TileCache,
618        key: TileKey,
619    ) -> Result<Result<u32, Step>, RevealError> {
620        if let Some(id) = cache.touch(&key) {
621            self.asked = None;
622            return Ok(Ok(id));
623        }
624        if self.asked.as_ref() == Some(&key) {
625            return Err(RevealError::NotProvided);
626        }
627        self.asked = Some(key.clone());
628        Ok(Err(Step::NeedTile(key)))
629    }
630
631    /// Applies the pending block copies; `Some(step)` when a tile must come in first.
632    fn apply_copies(
633        &mut self,
634        cache: &mut TileCache,
635        now: f64,
636    ) -> Result<Option<Step>, RevealError> {
637        while let Some(copies) = &self.copies {
638            let Some((key, _)) = copies.groups.get(copies.at) else {
639                self.copies = None;
640                break;
641            };
642            let id = match self.tile(cache, key.clone())? {
643                Ok(id) => id,
644                Err(step) => return Ok(Some(step)),
645            };
646            let copies = self.copies.as_mut().unwrap();
647            let (size, w) = (self.size, self.width);
648            let source = &self.slots[copies.source];
649            let tile = cache.get_mut(id).unwrap();
650            for wr in &copies.groups[copies.at].1 {
651                let (_, _, lx, ly) = block_tile(wr.gx, wr.gy, size);
652                let (sx, sy) = (wr.sx as usize, wr.sy as usize);
653                write_block(
654                    tile,
655                    size,
656                    (lx, ly),
657                    wr.frame,
658                    wr.kind,
659                    |row| {
660                        let at = ((sy + row) * w + sx) * 4;
661                        &source[at..at + QUALITY_BLOCK * 4]
662                    },
663                    false,
664                );
665            }
666            cache.mark_dirty(id, now);
667            copies.at += 1;
668        }
669        Ok(None)
670    }
671
672    /// Runs the stage until it needs something from the host. `now` is the host's clock, stamped on every tile this
673    /// step writes.
674    pub fn step(&mut self, cache: &mut TileCache, now: f64) -> Result<Step, RevealError> {
675        loop {
676            match &mut self.phase {
677                Phase::Start => {
678                    if self.layers.is_empty() || self.canvases.is_empty() {
679                        self.phase = Phase::Done;
680                        return Ok(Step::Done(Outcome::Nothing));
681                    }
682                    let bytes = self.estimate();
683                    self.phase = Phase::AwaitMemory {
684                        stage: MemoryStage::Canvases,
685                        bytes,
686                    };
687                    return Ok(Step::NeedMemory {
688                        bytes,
689                        stage: MemoryStage::Canvases,
690                    });
691                }
692                Phase::AwaitMemory { .. } | Phase::Done => return Err(RevealError::Unexpected),
693                Phase::Refused(_) => {
694                    let Phase::Refused(d) = std::mem::replace(&mut self.phase, Phase::Done) else {
695                        unreachable!()
696                    };
697                    self.run = None;
698                    return Ok(Step::Done(Outcome::Skipped(d)));
699                }
700                Phase::Observe => {
701                    self.phase = Phase::AwaitObservations;
702                    return Ok(Step::NeedObservations);
703                }
704                Phase::AwaitObservations => {
705                    self.plan();
706                    self.phase = self.ingest_from(0);
707                }
708                Phase::Ingest { canvas, ty, tx } => {
709                    let (slot, mut x, mut y) = (*canvas, *tx, *ty);
710                    let Some(c) = self.canvases.get(slot) else {
711                        self.phase = Phase::Begin {
712                            pass: 0,
713                            memory_checked: false,
714                        };
715                        continue;
716                    };
717                    let (b, s) = (c.bounds, self.size as f64);
718                    if x as f64 * s >= b.x + b.width {
719                        y += 1;
720                        x = (b.x / s).floor() as i64;
721                    }
722                    // Past the last row, or a canvas without width.
723                    if y as f64 * s >= b.y + b.height || x as f64 * s >= b.x + b.width {
724                        self.phase = self.ingest_from(slot + 1);
725                        continue;
726                    }
727                    self.phase = Phase::Ingest {
728                        canvas: slot,
729                        ty: y,
730                        tx: x,
731                    };
732                    let key = TileKey::new(&self.canvases[slot].id, 0, x as i32, y as i32);
733                    let id = match self.tile(cache, key)? {
734                        Ok(id) => id,
735                        Err(step) => return Ok(step),
736                    };
737                    let tile = cache.get(id).unwrap();
738                    let size = self.size;
739                    self.run.as_mut().unwrap().ingest(
740                        slot,
741                        &CanvasView {
742                            x: x * size as i64,
743                            y: y * size as i64,
744                            width: size,
745                            height: size,
746                            rgba: &tile.pixels,
747                            coverage: &tile.coverage,
748                        },
749                    );
750                    self.phase = Phase::Ingest {
751                        canvas: slot,
752                        ty: y,
753                        tx: x + 1,
754                    };
755                    return Ok(Step::Checkpoint);
756                }
757                Phase::Begin {
758                    pass,
759                    memory_checked,
760                } => {
761                    let (pass, checked) = (*pass, *memory_checked);
762                    let Some(&p) = PASSES.get(pass) else {
763                        self.phase = Phase::Finish {
764                            canvas: 0,
765                            deferred: None,
766                            at: 0,
767                        };
768                        continue;
769                    };
770                    let run = self.run.as_mut().unwrap();
771                    if p == Pass::Write && !checked {
772                        let bytes = run.fallback_bytes() as f64;
773                        self.phase = Phase::AwaitMemory {
774                            stage: MemoryStage::Fallback,
775                            bytes,
776                        };
777                        return Ok(Step::NeedMemory {
778                            bytes,
779                            stage: MemoryStage::Fallback,
780                        });
781                    }
782                    run.begin(p);
783                    if p != Pass::Write {
784                        return Ok(self.decode(pass));
785                    }
786                    self.phase = Phase::Fallbacks {
787                        canvas: 0,
788                        blocks: run.fallback_blocks(0),
789                        at: 0,
790                    };
791                }
792                Phase::Fallbacks { canvas, blocks, at } => {
793                    let slot = *canvas;
794                    let Some(&(gx, gy)) = blocks.get(*at) else {
795                        if slot + 1 < self.canvases.len() {
796                            let blocks = self.run.as_ref().unwrap().fallback_blocks(slot + 1);
797                            self.phase = Phase::Fallbacks {
798                                canvas: slot + 1,
799                                blocks,
800                                at: 0,
801                            };
802                            continue;
803                        }
804                        return Ok(self.decode(PASSES.len() - 1));
805                    };
806                    let (tx, ty, lx, ly) = block_tile(gx, gy, self.size);
807                    let key = TileKey::new(&self.canvases[slot].id, 0, tx, ty);
808                    let id = match self.tile(cache, key)? {
809                        Ok(id) => id,
810                        Err(step) => return Ok(step),
811                    };
812                    let tile = cache.get(id).unwrap();
813                    let size = self.size;
814                    let mut rgba = [0u8; QUALITY_BLOCK * QUALITY_BLOCK * 4];
815                    for (row, out) in rgba.chunks_exact_mut(QUALITY_BLOCK * 4).enumerate() {
816                        let p = (ly + row) * size + lx;
817                        out.copy_from_slice(&tile.pixels[p * 4..(p + QUALITY_BLOCK) * 4]);
818                        for x in 0..QUALITY_BLOCK {
819                            let q = p + x;
820                            if tile.coverage[q >> 3] & (1 << (q & 7)) == 0 {
821                                out[x * 4 + 3] = 0;
822                            }
823                        }
824                    }
825                    self.run.as_mut().unwrap().set_main(slot, gx, gy, &rgba);
826                    if let Phase::Fallbacks { at, .. } = &mut self.phase {
827                        *at += 1;
828                    }
829                }
830                Phase::Decoding { .. } => {
831                    if let Some(step) = self.apply_copies(cache, now)? {
832                        return Ok(step);
833                    }
834                    return Ok(Step::NextFrame);
835                }
836                Phase::Ending { pass } => {
837                    let pass = *pass;
838                    if let Some(step) = self.apply_copies(cache, now)? {
839                        return Ok(step);
840                    }
841                    self.phase = Phase::Begin {
842                        pass: pass + 1,
843                        memory_checked: false,
844                    };
845                    return Ok(Step::PassDone(PASSES[pass]));
846                }
847                Phase::Finish {
848                    canvas,
849                    deferred,
850                    at,
851                } => {
852                    let slot = *canvas;
853                    if slot >= self.canvases.len() {
854                        self.phase = Phase::Done;
855                        return Ok(Step::Done(Outcome::Revealed(self.summary())));
856                    }
857                    let run = self.run.as_ref().unwrap();
858                    let list = deferred.get_or_insert_with(|| run.finish(slot));
859                    let Some(d) = list.get(*at) else {
860                        let provenance = run.provenance(slot, list);
861                        self.phase = Phase::Finish {
862                            canvas: slot + 1,
863                            deferred: None,
864                            at: 0,
865                        };
866                        if let Some((gx, gy, bw, bh, source)) = provenance {
867                            return Ok(Step::Provenance(Provenance {
868                                canvas: self.canvases[slot].id.clone(),
869                                gx,
870                                gy,
871                                bw,
872                                bh,
873                                source,
874                            }));
875                        }
876                        continue;
877                    };
878                    let (tx, ty, lx, ly) = block_tile(d.gx, d.gy, self.size);
879                    let key = TileKey::new(&self.canvases[slot].id, 0, tx, ty);
880                    let id = match self.tile(cache, key)? {
881                        Ok(id) => id,
882                        Err(step) => return Ok(step),
883                    };
884                    let Phase::Finish {
885                        deferred: Some(list),
886                        at,
887                        ..
888                    } = &mut self.phase
889                    else {
890                        unreachable!()
891                    };
892                    let d = &list[*at];
893                    write_block(
894                        cache.get_mut(id).unwrap(),
895                        self.size,
896                        (lx, ly),
897                        d.frame,
898                        d.kind,
899                        |row| &d.rgba[row * QUALITY_BLOCK * 4..(row + 1) * QUALITY_BLOCK * 4],
900                        true,
901                    );
902                    cache.mark_dirty(id, now);
903                    *at += 1;
904                }
905            }
906        }
907    }
908
909    /// Ingesting from the first tile of canvas `canvas` on (the passes begin once every canvas is in).
910    fn ingest_from(&self, canvas: usize) -> Phase {
911        match self.canvases.get(canvas) {
912            Some(c) => {
913                let s = self.size as f64;
914                Phase::Ingest {
915                    canvas,
916                    ty: (c.bounds.y / s).floor() as i64,
917                    tx: (c.bounds.x / s).floor() as i64,
918                }
919            }
920            None => Phase::Begin {
921                pass: 0,
922                memory_checked: false,
923            },
924        }
925    }
926
927    /// Starts the decode for `PASSES[pass]`, with no frame held from the pass before.
928    fn decode(&mut self, pass: usize) -> Step {
929        self.staged = None;
930        self.last = None;
931        self.before = None;
932        self.pushed = false;
933        self.phase = Phase::Decoding { pass };
934        Step::Decode {
935            pass: PASSES[pass],
936            wanted: self.run.as_ref().unwrap().wanted(&self.plans),
937        }
938    }
939
940    /// Which decisions count ([`admitted`]) and the plan of every frame below the session's frame count.
941    fn plan(&mut self) {
942        let rows: Vec<Vec<(Decision, bool)>> =
943            self.observed.iter().map(|(_, ds)| ds.clone()).collect();
944        let keep = admitted(&rows);
945        let mut plans = vec![Vec::new(); self.frames as usize];
946        for ((frame, ds), keep) in std::mem::take(&mut self.observed).into_iter().zip(keep) {
947            if let Some(plan) = plans.get_mut(frame as usize) {
948                *plan = ds
949                    .into_iter()
950                    .zip(keep)
951                    .filter(|(_, k)| *k)
952                    .map(|((d, _), _)| d)
953                    .collect();
954            }
955        }
956        self.plans = plans;
957    }
958
959    fn summary(&self) -> Summary {
960        let s = self.run.as_ref().unwrap().summary();
961        Summary {
962            objects: s.objects as u32,
963            components: s.components as u32,
964            coherent_blocks: s.coherent as u32,
965            component_blocks: s.component_blocks as u32,
966            provisional_blocks: s.provisional as u32,
967            fallback_blocks: s.fallback as u32,
968        }
969    }
970}
971
972#[cfg(test)]
973#[path = "reveal_tests.rs"]
974mod tests;