use crate::engine::diagnostic::{Diagnostic, Severity, Value};
use crate::engine::tiles::{Tile, TileCache, TileKey, QUALITY_BLOCK};
use crate::kernels::geometry::{js_round, Rect};
use crate::kernels::reveal::blocks::{BlockWrite, Deferred, Kind};
use crate::kernels::reveal::run::{admitted, canvas_bytes, layer_bytes, Decision, Reveal};
use crate::kernels::reveal::CanvasView;
use std::collections::HashMap;
pub use crate::kernels::reveal::run::Pass;
pub const PASSES: [Pass; 4] = [Pass::Learn, Pass::Agree, Pass::Relearn, Pass::Write];
pub const RING_FRAMES: usize = 3;
pub const REVEALED_QUALITY: u8 = 204;
#[derive(Clone, Debug, PartialEq)]
pub struct RevealLayer {
pub id: String,
pub rect: Rect,
pub code: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RevealCanvas {
pub id: String,
pub bounds: Rect,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ObservedDecision {
pub canvas: String,
pub layer: String,
pub x: f64,
pub y: f64,
pub skip: bool,
pub skipped: bool,
pub occlusions: Vec<Rect>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryStage {
Canvases,
Fallback,
}
impl MemoryStage {
pub fn as_str(self) -> &'static str {
match self {
MemoryStage::Canvases => "canvases",
MemoryStage::Fallback => "fallback",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Provenance {
pub canvas: String,
pub gx: i64,
pub gy: i64,
pub bw: usize,
pub bh: usize,
pub source: Vec<i32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Summary {
pub objects: u32,
pub components: u32,
pub coherent_blocks: u32,
pub component_blocks: u32,
pub provisional_blocks: u32,
pub fallback_blocks: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Outcome {
Nothing,
Skipped(Diagnostic),
Revealed(Summary),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Step {
NeedMemory {
bytes: f64,
stage: MemoryStage,
},
NeedObservations,
NeedTile(TileKey),
Checkpoint,
Decode {
pass: Pass,
wanted: Vec<bool>,
},
NextFrame,
PassDone(Pass),
Provenance(Provenance),
Done(Outcome),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RevealError {
Unexpected,
NotProvided,
BadLayer,
BadLabels,
}
fn pixel_span(start: f64, size: f64) -> (i64, i64) {
let a = start.ceil();
(a as i64, ((start + size).ceil() - a).max(0.0) as i64)
}
fn block_tile(gx: i64, gy: i64, size: usize) -> (i32, i32, usize, usize) {
let (x0, y0, s) = (
gx * QUALITY_BLOCK as i64,
gy * QUALITY_BLOCK as i64,
size as i64,
);
(
x0.div_euclid(s) as i32,
y0.div_euclid(s) as i32,
x0.rem_euclid(s) as usize,
y0.rem_euclid(s) as usize,
)
}
fn write_block<'a>(
tile: &mut Tile,
size: usize,
(lx, ly): (usize, usize),
frame: u32,
kind: Kind,
row: impl Fn(usize) -> &'a [u8],
masked: bool,
) {
let settled = matches!(kind, Kind::Coherent | Kind::Component | Kind::Edge);
for r in 0..QUALITY_BLOCK {
let src = row(r);
let p = (ly + r) * size + lx;
if !masked {
tile.pixels[p * 4..(p + QUALITY_BLOCK) * 4].copy_from_slice(&src[..QUALITY_BLOCK * 4]);
tile.coverage[p >> 3] = 0xff;
tile.coverage[(p >> 3) + 1] = 0xff;
if settled {
tile.provisional[p >> 3] = 0;
tile.provisional[(p >> 3) + 1] = 0;
}
continue;
}
for x in 0..QUALITY_BLOCK {
if src[x * 4 + 3] != 255 {
continue;
}
let q = p + x;
tile.pixels[q * 4..q * 4 + 3].copy_from_slice(&src[x * 4..x * 4 + 3]);
tile.pixels[q * 4 + 3] = 255;
tile.coverage[q >> 3] |= 1 << (q & 7);
if settled {
tile.provisional[q >> 3] &= !(1 << (q & 7));
}
}
}
let b = (ly / QUALITY_BLOCK) * (size / QUALITY_BLOCK) + lx / QUALITY_BLOCK;
if kind != Kind::Fallback {
tile.owner[b] = frame.wrapping_add(1);
}
if settled {
tile.conflicts[b] = 0;
tile.quality[b] = tile.quality[b].max(REVEALED_QUALITY);
}
}
enum Phase {
Start,
AwaitMemory {
stage: MemoryStage,
bytes: f64,
},
Refused(Diagnostic),
Observe,
AwaitObservations,
Ingest {
canvas: usize,
ty: i64,
tx: i64,
},
Begin {
pass: usize,
memory_checked: bool,
},
Fallbacks {
canvas: usize,
blocks: Vec<(i64, i64)>,
at: usize,
},
Decoding {
pass: usize,
},
Ending {
pass: usize,
},
Finish {
canvas: usize,
deferred: Option<Vec<Deferred>>,
at: usize,
},
Done,
}
struct Copies {
source: usize,
groups: Vec<(TileKey, Vec<BlockWrite>)>,
at: usize,
}
pub struct RevealSession {
width: usize,
height: usize,
noise: f64,
size: usize,
frames: u32,
layers: Vec<RevealLayer>,
canvases: Vec<RevealCanvas>,
layer_slot: HashMap<String, usize>,
canvas_slot: HashMap<String, usize>,
run: Option<Reveal>,
observed: Vec<(u32, Vec<(Decision, bool)>)>,
plans: Vec<Vec<Decision>>,
phase: Phase,
asked: Option<TileKey>,
slots: Vec<Vec<u8>>,
staged: Option<usize>,
last: Option<usize>,
before: Option<usize>,
pushed: bool,
copies: Option<Copies>,
}
impl RevealSession {
pub fn new(
width: usize,
height: usize,
noise: f64,
tile_size: usize,
frames: u32,
layers: Vec<RevealLayer>,
canvases: Vec<RevealCanvas>,
) -> Result<RevealSession, RevealError> {
for l in &layers {
let (_, w) = pixel_span(l.rect.x, l.rect.width);
let (_, h) = pixel_span(l.rect.y, l.rect.height);
if w == 0 || h == 0 || l.code > 255 {
return Err(RevealError::BadLayer);
}
}
let layer_slot = layers
.iter()
.enumerate()
.map(|(i, l)| (l.id.clone(), i))
.collect();
let canvas_slot = canvases
.iter()
.enumerate()
.map(|(i, c)| (c.id.clone(), i))
.collect();
Ok(RevealSession {
width,
height,
noise,
size: tile_size,
frames,
layers,
canvases,
layer_slot,
canvas_slot,
run: None,
observed: Vec::new(),
plans: Vec::new(),
phase: Phase::Start,
asked: None,
slots: Vec::new(),
staged: None,
last: None,
before: None,
pushed: false,
copies: None,
})
}
pub fn estimate(&self) -> f64 {
let canvases: f64 = self
.canvases
.iter()
.map(|c| {
let (_, w) = pixel_span(c.bounds.x, c.bounds.width);
let (_, h) = pixel_span(c.bounds.y, c.bounds.height);
canvas_bytes(w as usize, h as usize) as f64
})
.sum();
let layers: f64 = self
.layers
.iter()
.map(|l| {
let (_, w) = pixel_span(l.rect.x, l.rect.width);
let (_, h) = pixel_span(l.rect.y, l.rect.height);
layer_bytes(w as usize, h as usize) as f64
})
.sum();
canvases + layers + (RING_FRAMES * self.width * self.height * 4) as f64
}
pub fn memory(&mut self, fits: bool, core_bytes: f64) -> Result<(), RevealError> {
let Phase::AwaitMemory { stage, bytes } = self.phase else {
return Err(RevealError::Unexpected);
};
if !fits {
let mb = js_round(bytes / 1048576.0) as f64;
let diagnostic = Diagnostic::new("REVEAL_SKIPPED", Severity::Warning)
.param("need", mb)
.detail(Value::Object(vec![
("stage", Value::Text(stage.as_str().to_string())),
("needMB", Value::Number(mb)),
(
"coreMB",
Value::Number(js_round(core_bytes / 1048576.0) as f64),
),
]));
self.phase = Phase::Refused(diagnostic);
return Ok(());
}
match stage {
MemoryStage::Canvases => {
let mut run = Reveal::new(self.width, self.height, self.noise);
for l in &self.layers {
let (x, w) = pixel_span(l.rect.x, l.rect.width);
let (y, h) = pixel_span(l.rect.y, l.rect.height);
run.add_layer(x, y, w as usize, h as usize, l.code as u8);
}
for c in &self.canvases {
let b = c.bounds;
run.add_canvas(
b.x as i32 as i64,
b.y as i32 as i64,
b.width as u32 as usize,
b.height as u32 as usize,
);
}
self.run = Some(run);
self.phase = Phase::Observe;
}
MemoryStage::Fallback => {
self.phase = Phase::Begin {
pass: 3,
memory_checked: true,
};
}
}
Ok(())
}
pub fn observation(
&mut self,
frame: u32,
decisions: &[ObservedDecision],
) -> Result<(), RevealError> {
if !matches!(self.phase, Phase::AwaitObservations) {
return Err(RevealError::Unexpected);
}
let mut out = Vec::new();
for d in decisions {
let (Some(&canvas), Some(&layer)) = (
self.canvas_slot.get(&d.canvas),
self.layer_slot.get(&d.layer),
) else {
continue;
};
if d.skipped && !d.skip {
continue;
}
let occlusions = d
.occlusions
.iter()
.map(|r| {
let (x, w) = pixel_span(r.x, r.width);
let (y, h) = pixel_span(r.y, r.height);
(x, y, w, h)
})
.collect();
out.push((
Decision {
layer,
canvas,
x: js_round(d.x) as i64,
y: js_round(d.y) as i64,
occlusions,
},
d.skipped,
));
}
self.observed.push((frame, out));
Ok(())
}
pub fn frame_buffer(&mut self) -> Result<&mut [u8], RevealError> {
if !matches!(self.phase, Phase::Decoding { .. }) || self.copies.is_some() {
return Err(RevealError::Unexpected);
}
let slot = match self.staged {
Some(slot) => slot,
None => {
let held = |s: usize| self.last == Some(s) || self.before == Some(s);
let slot = match (0..self.slots.len()).find(|&s| !held(s)) {
Some(slot) => slot,
None => {
self.slots.push(vec![0; self.width * self.height * 4]);
self.slots.len() - 1
}
};
self.staged = Some(slot);
slot
}
};
Ok(&mut self.slots[slot])
}
fn check_labels(&self, labels: Option<&[u8]>) -> Result<(), RevealError> {
match labels {
Some(l) if l.len() != self.width * self.height => Err(RevealError::BadLabels),
_ => Ok(()),
}
}
pub fn push_frame(&mut self, index: u32, labels: Option<&[u8]>) -> Result<(), RevealError> {
let Phase::Decoding { pass } = self.phase else {
return Err(RevealError::Unexpected);
};
if self.copies.is_some() {
return Err(RevealError::Unexpected);
}
self.check_labels(labels)?;
let current = self.staged.take();
let run = self.run.as_mut().unwrap();
let slot = |s: Option<usize>| s.map(|s| &self.slots[s][..]);
let decisions = self.plans.get(index as usize).map_or(&[][..], |p| &p[..]);
run.frame(
index,
slot(current),
slot(self.last),
slot(self.before),
labels,
decisions,
);
if PASSES[pass] == Pass::Write {
if let Some(source) = self.last {
self.copies = Some(self.group(source));
}
}
self.before = self.last;
self.last = current;
self.pushed = true;
Ok(())
}
pub fn end_pass(&mut self, labels: Option<&[u8]>) -> Result<(), RevealError> {
let Phase::Decoding { pass } = self.phase else {
return Err(RevealError::Unexpected);
};
if self.copies.is_some() {
return Err(RevealError::Unexpected);
}
self.check_labels(labels)?;
self.staged = None;
if self.pushed {
let run = self.run.as_mut().unwrap();
let slot = |s: Option<usize>| s.map(|s| &self.slots[s][..]);
run.end(slot(self.last), slot(self.before), labels);
if PASSES[pass] == Pass::Write {
if let Some(source) = self.last {
self.copies = Some(self.group(source));
}
}
}
self.phase = Phase::Ending { pass };
Ok(())
}
fn group(&mut self, source: usize) -> Copies {
let writes = self.run.as_mut().unwrap().take_writes();
let mut groups: Vec<(TileKey, Vec<BlockWrite>)> = Vec::new();
let mut index: HashMap<TileKey, usize> = HashMap::new();
for w in writes {
let (tx, ty, _, _) = block_tile(w.gx, w.gy, self.size);
let key = TileKey::new(&self.canvases[w.canvas as usize].id, 0, tx, ty);
match index.get(&key) {
Some(&g) => groups[g].1.push(w),
None => {
index.insert(key.clone(), groups.len());
groups.push((key, vec![w]));
}
}
}
Copies {
source,
groups,
at: 0,
}
}
fn tile(
&mut self,
cache: &mut TileCache,
key: TileKey,
) -> Result<Result<u32, Step>, RevealError> {
if let Some(id) = cache.touch(&key) {
self.asked = None;
return Ok(Ok(id));
}
if self.asked.as_ref() == Some(&key) {
return Err(RevealError::NotProvided);
}
self.asked = Some(key.clone());
Ok(Err(Step::NeedTile(key)))
}
fn apply_copies(
&mut self,
cache: &mut TileCache,
now: f64,
) -> Result<Option<Step>, RevealError> {
while let Some(copies) = &self.copies {
let Some((key, _)) = copies.groups.get(copies.at) else {
self.copies = None;
break;
};
let id = match self.tile(cache, key.clone())? {
Ok(id) => id,
Err(step) => return Ok(Some(step)),
};
let copies = self.copies.as_mut().unwrap();
let (size, w) = (self.size, self.width);
let source = &self.slots[copies.source];
let tile = cache.get_mut(id).unwrap();
for wr in &copies.groups[copies.at].1 {
let (_, _, lx, ly) = block_tile(wr.gx, wr.gy, size);
let (sx, sy) = (wr.sx as usize, wr.sy as usize);
write_block(
tile,
size,
(lx, ly),
wr.frame,
wr.kind,
|row| {
let at = ((sy + row) * w + sx) * 4;
&source[at..at + QUALITY_BLOCK * 4]
},
false,
);
}
cache.mark_dirty(id, now);
copies.at += 1;
}
Ok(None)
}
pub fn step(&mut self, cache: &mut TileCache, now: f64) -> Result<Step, RevealError> {
loop {
match &mut self.phase {
Phase::Start => {
if self.layers.is_empty() || self.canvases.is_empty() {
self.phase = Phase::Done;
return Ok(Step::Done(Outcome::Nothing));
}
let bytes = self.estimate();
self.phase = Phase::AwaitMemory {
stage: MemoryStage::Canvases,
bytes,
};
return Ok(Step::NeedMemory {
bytes,
stage: MemoryStage::Canvases,
});
}
Phase::AwaitMemory { .. } | Phase::Done => return Err(RevealError::Unexpected),
Phase::Refused(_) => {
let Phase::Refused(d) = std::mem::replace(&mut self.phase, Phase::Done) else {
unreachable!()
};
self.run = None;
return Ok(Step::Done(Outcome::Skipped(d)));
}
Phase::Observe => {
self.phase = Phase::AwaitObservations;
return Ok(Step::NeedObservations);
}
Phase::AwaitObservations => {
self.plan();
self.phase = self.ingest_from(0);
}
Phase::Ingest { canvas, ty, tx } => {
let (slot, mut x, mut y) = (*canvas, *tx, *ty);
let Some(c) = self.canvases.get(slot) else {
self.phase = Phase::Begin {
pass: 0,
memory_checked: false,
};
continue;
};
let (b, s) = (c.bounds, self.size as f64);
if x as f64 * s >= b.x + b.width {
y += 1;
x = (b.x / s).floor() as i64;
}
if y as f64 * s >= b.y + b.height || x as f64 * s >= b.x + b.width {
self.phase = self.ingest_from(slot + 1);
continue;
}
self.phase = Phase::Ingest {
canvas: slot,
ty: y,
tx: x,
};
let key = TileKey::new(&self.canvases[slot].id, 0, x as i32, y as i32);
let id = match self.tile(cache, key)? {
Ok(id) => id,
Err(step) => return Ok(step),
};
let tile = cache.get(id).unwrap();
let size = self.size;
self.run.as_mut().unwrap().ingest(
slot,
&CanvasView {
x: x * size as i64,
y: y * size as i64,
width: size,
height: size,
rgba: &tile.pixels,
coverage: &tile.coverage,
},
);
self.phase = Phase::Ingest {
canvas: slot,
ty: y,
tx: x + 1,
};
return Ok(Step::Checkpoint);
}
Phase::Begin {
pass,
memory_checked,
} => {
let (pass, checked) = (*pass, *memory_checked);
let Some(&p) = PASSES.get(pass) else {
self.phase = Phase::Finish {
canvas: 0,
deferred: None,
at: 0,
};
continue;
};
let run = self.run.as_mut().unwrap();
if p == Pass::Write && !checked {
let bytes = run.fallback_bytes() as f64;
self.phase = Phase::AwaitMemory {
stage: MemoryStage::Fallback,
bytes,
};
return Ok(Step::NeedMemory {
bytes,
stage: MemoryStage::Fallback,
});
}
run.begin(p);
if p != Pass::Write {
return Ok(self.decode(pass));
}
self.phase = Phase::Fallbacks {
canvas: 0,
blocks: run.fallback_blocks(0),
at: 0,
};
}
Phase::Fallbacks { canvas, blocks, at } => {
let slot = *canvas;
let Some(&(gx, gy)) = blocks.get(*at) else {
if slot + 1 < self.canvases.len() {
let blocks = self.run.as_ref().unwrap().fallback_blocks(slot + 1);
self.phase = Phase::Fallbacks {
canvas: slot + 1,
blocks,
at: 0,
};
continue;
}
return Ok(self.decode(PASSES.len() - 1));
};
let (tx, ty, lx, ly) = block_tile(gx, gy, self.size);
let key = TileKey::new(&self.canvases[slot].id, 0, tx, ty);
let id = match self.tile(cache, key)? {
Ok(id) => id,
Err(step) => return Ok(step),
};
let tile = cache.get(id).unwrap();
let size = self.size;
let mut rgba = [0u8; QUALITY_BLOCK * QUALITY_BLOCK * 4];
for (row, out) in rgba.chunks_exact_mut(QUALITY_BLOCK * 4).enumerate() {
let p = (ly + row) * size + lx;
out.copy_from_slice(&tile.pixels[p * 4..(p + QUALITY_BLOCK) * 4]);
for x in 0..QUALITY_BLOCK {
let q = p + x;
if tile.coverage[q >> 3] & (1 << (q & 7)) == 0 {
out[x * 4 + 3] = 0;
}
}
}
self.run.as_mut().unwrap().set_main(slot, gx, gy, &rgba);
if let Phase::Fallbacks { at, .. } = &mut self.phase {
*at += 1;
}
}
Phase::Decoding { .. } => {
if let Some(step) = self.apply_copies(cache, now)? {
return Ok(step);
}
return Ok(Step::NextFrame);
}
Phase::Ending { pass } => {
let pass = *pass;
if let Some(step) = self.apply_copies(cache, now)? {
return Ok(step);
}
self.phase = Phase::Begin {
pass: pass + 1,
memory_checked: false,
};
return Ok(Step::PassDone(PASSES[pass]));
}
Phase::Finish {
canvas,
deferred,
at,
} => {
let slot = *canvas;
if slot >= self.canvases.len() {
self.phase = Phase::Done;
return Ok(Step::Done(Outcome::Revealed(self.summary())));
}
let run = self.run.as_ref().unwrap();
let list = deferred.get_or_insert_with(|| run.finish(slot));
let Some(d) = list.get(*at) else {
let provenance = run.provenance(slot, list);
self.phase = Phase::Finish {
canvas: slot + 1,
deferred: None,
at: 0,
};
if let Some((gx, gy, bw, bh, source)) = provenance {
return Ok(Step::Provenance(Provenance {
canvas: self.canvases[slot].id.clone(),
gx,
gy,
bw,
bh,
source,
}));
}
continue;
};
let (tx, ty, lx, ly) = block_tile(d.gx, d.gy, self.size);
let key = TileKey::new(&self.canvases[slot].id, 0, tx, ty);
let id = match self.tile(cache, key)? {
Ok(id) => id,
Err(step) => return Ok(step),
};
let Phase::Finish {
deferred: Some(list),
at,
..
} = &mut self.phase
else {
unreachable!()
};
let d = &list[*at];
write_block(
cache.get_mut(id).unwrap(),
self.size,
(lx, ly),
d.frame,
d.kind,
|row| &d.rgba[row * QUALITY_BLOCK * 4..(row + 1) * QUALITY_BLOCK * 4],
true,
);
cache.mark_dirty(id, now);
*at += 1;
}
}
}
}
fn ingest_from(&self, canvas: usize) -> Phase {
match self.canvases.get(canvas) {
Some(c) => {
let s = self.size as f64;
Phase::Ingest {
canvas,
ty: (c.bounds.y / s).floor() as i64,
tx: (c.bounds.x / s).floor() as i64,
}
}
None => Phase::Begin {
pass: 0,
memory_checked: false,
},
}
}
fn decode(&mut self, pass: usize) -> Step {
self.staged = None;
self.last = None;
self.before = None;
self.pushed = false;
self.phase = Phase::Decoding { pass };
Step::Decode {
pass: PASSES[pass],
wanted: self.run.as_ref().unwrap().wanted(&self.plans),
}
}
fn plan(&mut self) {
let rows: Vec<Vec<(Decision, bool)>> =
self.observed.iter().map(|(_, ds)| ds.clone()).collect();
let keep = admitted(&rows);
let mut plans = vec![Vec::new(); self.frames as usize];
for ((frame, ds), keep) in std::mem::take(&mut self.observed).into_iter().zip(keep) {
if let Some(plan) = plans.get_mut(frame as usize) {
*plan = ds
.into_iter()
.zip(keep)
.filter(|(_, k)| *k)
.map(|((d, _), _)| d)
.collect();
}
}
self.plans = plans;
}
fn summary(&self) -> Summary {
let s = self.run.as_ref().unwrap().summary();
Summary {
objects: s.objects as u32,
components: s.components as u32,
coherent_blocks: s.coherent as u32,
component_blocks: s.component_blocks as u32,
provisional_blocks: s.provisional as u32,
fallback_blocks: s.fallback as u32,
}
}
}
#[cfg(test)]
#[path = "reveal_tests.rs"]
mod tests;