use crate::engine::attachments::{render_shift, resolve_target, Attachment, Attachments};
use crate::engine::compositor::{
CompositeError, Compositor, CompositorDiagnostic, Frame, Placement, Step as CompositorStep,
};
use crate::engine::diagnostic::{Diagnostic, Severity, Value};
use crate::engine::pose_graph::{PoseGraph, PoseGraphError};
use crate::engine::tiles::{TileCache, TileKey};
use crate::kernels::consistency::{consistency_mask, MaskInput, Neighbour, Vote};
use crate::kernels::geometry::{js_round, Rect};
use crate::kernels::raster::fixed_update;
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::Arc;
pub use crate::engine::solve::{is_valid_pose, POSE_BOUND};
pub const RING_FRAMES: usize = 3;
pub const VOTING_WINDOW: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RegionKind {
Moving,
Fixed,
Ignore,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderRegion {
pub id: String,
pub kind: RegionKind,
pub rect: Rect,
pub code: u8,
pub owned_pixels: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlanPlacement {
pub layer: String,
pub canvas_id: String,
pub node: String,
pub x: f64,
pub y: f64,
pub confidence: f64,
pub uncertain: bool,
pub time: f64,
pub skip: Option<bool>,
pub occlusions: Option<Vec<Rect>>,
}
impl PlanPlacement {
fn skipped(&self) -> bool {
self.skip == Some(true)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FramePlan {
pub placements: Vec<PlanPlacement>,
pub duplicate: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ConsistencyVote {
pub x0: i32,
pub y0: i32,
pub w: i32,
pub h: i32,
pub bits: Vec<u8>,
pub clean: Vec<u8>,
}
pub type ConsistencyRecord = HashMap<String, ConsistencyVote>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CanvasMeta {
pub bounds: Rect,
pub tile_count: f64,
pub observed_pixels: f64,
pub uncertain_pixels: f64,
pub conflict_pixels: f64,
pub provisional_pixels: f64,
pub last_time: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderDecision {
pub canvas_id: String,
pub placement: PlanPlacement,
pub added_pixels: f64,
pub conflict_pixels: f64,
pub uncertain_pixels: f64,
pub skipped: bool,
pub reused_exact_observation: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Observation {
pub frame: u32,
pub time: f64,
pub decisions: Vec<RenderDecision>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FrameOutcome {
pub observation: Observation,
pub latest: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct RenderCounters {
pub skipped_paints: u64,
pub tiles: f64,
pub observed_pixels: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum RenderStep {
NeedMeta(String),
NeedFrame(u32),
NeedTile(TileKey),
NeedTemporal(String),
Done(FrameOutcome),
}
#[derive(Clone, Debug, PartialEq)]
pub enum RenderError {
Busy,
NoFrame,
FrameSize,
MissingVote(i64),
BadVote,
UnknownRegion(String),
MissingMeta(String),
Graph(PoseGraphError),
OutsideCanvas {
x: f64,
y: f64,
region: String,
},
Disposed(String),
Compositor(CompositeError),
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderError::Busy => f.write_str("A frame is already being rendered."),
RenderError::NoFrame => f.write_str("No frame is being rendered."),
RenderError::FrameSize => f.write_str("Frame does not match the run's size."),
RenderError::MissingVote(i) => write!(f, "Voting record {i} was not provided."),
RenderError::BadVote => f.write_str("CORE_BAD_ARGUMENT: truncated consistency vote bitset."),
RenderError::UnknownRegion(id) => write!(f, "Region {id} is not part of this atlas."),
RenderError::MissingMeta(_) => f.write_str("Canvas metadata is missing."),
RenderError::Graph(e) => e.fmt(f),
RenderError::OutsideCanvas { x, y, region } => write!(
f,
"Placement ({x}, {y}) puts region {region} outside the addressable canvas."
),
RenderError::Disposed(canvas) => write!(
f,
"CORE_BAD_ARGUMENT: temporalIndex({canvas}) called after dispose() already drained and froze its state."
),
RenderError::Compositor(e) => write!(f, "Compositor failed: {e:?}."),
}
}
}
impl std::error::Error for RenderError {}
impl From<PoseGraphError> for RenderError {
fn from(e: PoseGraphError) -> Self {
RenderError::Graph(e)
}
}
pub struct RenderIo<'a> {
pub cache: &'a mut TileCache,
pub compositor: &'a mut Compositor,
pub graph: &'a PoseGraph,
pub labels: &'a [u8],
pub now: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FrameInput {
pub index: u32,
pub time: f64,
pub plan: FramePlan,
pub has_prev: bool,
pub prev_plan: Option<FramePlan>,
pub has_next: bool,
pub next_plan: Option<FramePlan>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderConfig {
pub width: usize,
pub height: usize,
pub factor: i32,
pub noise: f64,
pub regions: Vec<RenderRegion>,
pub attachments: Vec<Attachment>,
}
#[derive(Clone, Debug, PartialEq)]
struct Resolved {
canvas: String,
x: f64,
y: f64,
}
struct Slot {
pixels: Vec<u8>,
index: i64,
used: u64,
}
struct FrameRing {
slots: Vec<Slot>,
tick: u64,
bytes: usize,
}
impl FrameRing {
fn new(bytes: usize) -> Self {
Self {
slots: Vec::with_capacity(RING_FRAMES),
tick: 0,
bytes,
}
}
fn get(&self, index: i64) -> Option<usize> {
self.slots.iter().position(|s| s.index == index)
}
fn fill(&mut self, index: i64, keep: Option<(i64, i64)>) -> usize {
if let Some(slot) = self.get(index) {
return slot;
}
let slot = if self.slots.len() < RING_FRAMES {
self.slots.push(Slot {
pixels: vec![0; self.bytes],
index,
used: 0,
});
self.slots.len() - 1
} else {
let kept = |s: &Slot| keep.is_some_and(|(a, b)| (a..=b).contains(&s.index));
let oldest = |free: bool| {
(0..self.slots.len())
.filter(|&i| !free || !kept(&self.slots[i]))
.min_by_key(|&i| self.slots[i].used)
};
oldest(true).or_else(|| oldest(false)).unwrap()
};
self.tick += 1;
let s = &mut self.slots[slot];
s.index = index;
s.used = self.tick;
slot
}
}
#[derive(Default)]
struct VotingWindow {
entries: VecDeque<(i64, Option<Arc<ConsistencyRecord>>)>,
}
impl VotingWindow {
fn wanted(&self, gets: &[i64]) -> Vec<i64> {
let mut held: VecDeque<i64> = self.entries.iter().map(|(i, _)| *i).collect();
let mut wanted = Vec::new();
for &index in gets {
if held.contains(&index) {
continue;
}
if index >= 0 {
wanted.push(index);
}
held.push_back(index);
if held.len() > VOTING_WINDOW {
held.pop_front();
}
}
wanted
}
fn get(
&mut self,
index: i64,
provided: &mut HashMap<i64, Option<ConsistencyRecord>>,
) -> Result<Option<Arc<ConsistencyRecord>>, RenderError> {
if let Some((_, record)) = self.entries.iter().find(|(i, _)| *i == index) {
return Ok(record.clone());
}
let record = if index < 0 {
None
} else {
provided
.remove(&index)
.ok_or(RenderError::MissingVote(index))?
.map(Arc::new)
};
self.entries.push_back((index, record.clone()));
if self.entries.len() > VOTING_WINDOW {
self.entries.pop_front();
}
Ok(record)
}
}
struct NeighbourPlacement {
resolved: Resolved,
occlusions: Vec<Rect>,
slot: Option<usize>,
}
enum Stage {
Start,
Meta(Resolved),
Fixed(Resolved),
Decide(Resolved, bool),
Paint(Paint),
}
struct Paint {
p: Resolved,
current: Option<usize>,
neighbours: Option<[Option<NeighbourPlacement>; 2]>,
masked: bool,
begun: bool,
}
struct FrameJob {
input: FrameInput,
votes: [Option<Arc<ConsistencyRecord>>; 3],
placement: usize,
stage: Stage,
decisions: Vec<RenderDecision>,
latest: Option<String>,
}
pub struct RenderSession {
width: usize,
height: usize,
factor: i32,
noise: f64,
regions: Vec<RenderRegion>,
region_index: HashMap<String, usize>,
attachments: Attachments,
ring: FrameRing,
mask: Vec<u8>,
fixed: HashMap<String, Vec<u8>>,
previous: HashMap<String, Resolved>,
votes: VotingWindow,
metas: HashMap<String, CanvasMeta>,
missing_meta: Option<String>,
dirty: Vec<String>,
dirty_set: HashSet<String>,
counters: RenderCounters,
events: Vec<Diagnostic>,
job: Option<FrameJob>,
}
impl RenderSession {
pub fn new(config: RenderConfig) -> Self {
let mut region_index = HashMap::new();
for (i, r) in config.regions.iter().enumerate() {
region_index.insert(r.id.clone(), i);
}
let attachments: Attachments = config
.attachments
.into_iter()
.map(|a| (a.id.clone(), a))
.collect();
let pixels = config.width * config.height;
Self {
width: config.width,
height: config.height,
factor: config.factor,
noise: config.noise,
regions: config.regions,
region_index,
attachments,
ring: FrameRing::new(pixels * 4),
mask: vec![1; pixels],
fixed: HashMap::new(),
previous: HashMap::new(),
votes: VotingWindow::default(),
metas: HashMap::new(),
missing_meta: None,
dirty: Vec::new(),
dirty_set: HashSet::new(),
counters: RenderCounters::default(),
events: Vec::new(),
job: None,
}
}
pub fn configure_tiles(&self, cache: &mut TileCache, memory_mb: f64) -> Option<Diagnostic> {
let fixed_bytes = self
.regions
.iter()
.filter(|r| r.kind == RegionKind::Fixed)
.fold(0.0, |n, r| {
n + r.rect.width.ceil() * r.rect.height.ceil() * 4.0
});
let (w, h) = (self.width as f64, self.height as f64);
cache.configure_budget(
memory_mb,
w * h * 5.0 + fixed_bytes + 8.0 * 1024.0 * 1024.0 + w * h * 4.0,
);
let areas: Vec<(f64, f64)> = self
.regions
.iter()
.filter(|r| r.kind != RegionKind::Ignore)
.map(|r| (r.rect.width, r.rect.height))
.collect();
let raised = cache.ensure_footprint(&areas);
if raised == 0 {
return None;
}
let to = cache.max_tiles();
let size = cache.size() as f64;
Some(
Diagnostic::new("MEMORY_BUDGET_RAISED", Severity::Info)
.param("from", (to - raised) as f64)
.param("to", to as f64)
.param(
"mb",
js_round(to as f64 * size * size * 4.3 / 1024.0 / 1024.0) as f64,
)
.detail(Value::Object(vec![
("budgetMB", Value::Number(memory_mb)),
("tiles", Value::Number(to as f64)),
("raisedBy", Value::Number(raised as f64)),
])),
)
}
pub fn counters(&self) -> RenderCounters {
self.counters
}
pub fn meta(&self, canvas: &str) -> Option<CanvasMeta> {
self.metas.get(canvas).copied()
}
pub fn take_events(&mut self) -> Vec<Diagnostic> {
std::mem::take(&mut self.events)
}
pub fn take_dirty_metas(&mut self) -> Vec<(String, CanvasMeta)> {
self.dirty_set.clear();
std::mem::take(&mut self.dirty)
.into_iter()
.map(|id| {
let meta = self.metas[&id];
(id, meta)
})
.collect()
}
pub fn wanted_votes(&self, index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
self.votes.wanted(&vote_gets(index, has_prev, has_next))
}
pub fn begin(
&mut self,
input: FrameInput,
votes: Vec<(i64, Option<ConsistencyRecord>)>,
) -> Result<(), RenderError> {
if self.job.is_some() {
return Err(RenderError::Busy);
}
let mut provided: HashMap<i64, Option<ConsistencyRecord>> = votes.into_iter().collect();
let index = input.index as i64;
let own = self.votes.get(index, &mut provided)?;
let prev = if input.has_prev {
self.votes.get(index - 1, &mut provided)?
} else {
None
};
let next = if input.has_next {
self.votes.get(index + 1, &mut provided)?
} else {
None
};
self.job = Some(FrameJob {
input,
votes: [own, prev, next],
placement: 0,
stage: Stage::Start,
decisions: Vec::new(),
latest: None,
});
Ok(())
}
pub fn cancel(&mut self, compositor: &mut Compositor) {
if self.job.take().is_some() {
compositor.cancel();
}
self.missing_meta = None;
}
pub fn provide_meta(&mut self, canvas: &str, meta: Option<CanvasMeta>) {
match meta {
Some(meta) => {
self.metas.insert(canvas.to_string(), meta);
}
None => self.missing_meta = Some(canvas.to_string()),
}
}
pub fn frame_slot(&mut self, index: u32) -> &mut [u8] {
let keep = self.job.as_ref().map(|j| {
let at = j.input.index as i64;
(at - 1, at + 1)
});
let slot = self.ring.fill(index as i64, keep);
&mut self.ring.slots[slot].pixels
}
pub fn step(&mut self, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
let mut job = self.job.take().ok_or(RenderError::NoFrame)?;
let result = if io.labels.len() != self.width * self.height {
Err(RenderError::FrameSize)
} else {
self.run(&mut job, io)
};
match result {
Ok(RenderStep::Done(_)) => {}
Ok(_) => self.job = Some(job),
Err(_) => {
if matches!(job.stage, Stage::Paint(Paint { begun: true, .. })) {
io.compositor.cancel();
}
self.missing_meta = None;
}
}
result
}
fn resolve(
&self,
raw: &PlanPlacement,
frame: f64,
graph: &PoseGraph,
) -> Result<Resolved, RenderError> {
let (cx, cy) = graph.correction(&raw.node, frame)?;
let (sx, sy) = render_shift(&self.attachments, &raw.canvas_id, graph)?;
let canvas = resolve_target(&self.attachments, &raw.canvas_id).to_string();
Ok(Resolved {
canvas,
x: raw.x + cx + sx,
y: raw.y + cy + sy,
})
}
fn region(&self, layer: &str) -> Result<usize, RenderError> {
self.region_index
.get(layer)
.copied()
.ok_or_else(|| RenderError::UnknownRegion(layer.to_string()))
}
fn mark_dirty(&mut self, canvas: &str) {
if self.dirty_set.insert(canvas.to_string()) {
self.dirty.push(canvas.to_string());
}
}
fn run(&mut self, job: &mut FrameJob, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
loop {
let Some(raw) = job.input.plan.placements.get(job.placement) else {
return Ok(RenderStep::Done(FrameOutcome {
observation: Observation {
frame: job.input.index,
time: job.input.time,
decisions: std::mem::take(&mut job.decisions),
},
latest: job.latest.take(),
}));
};
let raw = raw.clone();
let index = job.input.index;
match std::mem::replace(&mut job.stage, Stage::Start) {
Stage::Start => {
if raw.skipped() {
job.decisions
.push(decision(raw.canvas_id.clone(), raw, None));
job.placement += 1;
continue;
}
let p = self.resolve(&raw, index as f64, io.graph)?;
if !is_valid_pose(p.x, p.y) {
self.events.push(
Diagnostic::new("NONFINITE_POSE", Severity::Error)
.at(index, raw.time)
.canvas(&p.canvas),
);
let placement = PlanPlacement {
canvas_id: p.canvas.clone(),
x: 0.0,
y: 0.0,
..raw
};
job.decisions.push(decision(p.canvas, placement, None));
job.placement += 1;
continue;
}
job.stage = Stage::Meta(p);
}
Stage::Meta(p) => {
if !self.metas.contains_key(&p.canvas) {
if self.missing_meta.take().as_deref() == Some(p.canvas.as_str()) {
return Err(RenderError::MissingMeta(p.canvas));
}
let canvas = p.canvas.clone();
job.stage = Stage::Meta(p);
return Ok(RenderStep::NeedMeta(canvas));
}
let region = &self.regions[self.region(&raw.layer)?];
let unchanged = job.input.plan.duplicate
&& self.previous.get(&raw.layer).is_some_and(|last| *last == p);
job.stage = if region.kind == RegionKind::Fixed && !unchanged {
Stage::Fixed(p)
} else {
Stage::Decide(p, unchanged)
};
}
Stage::Fixed(p) => {
let Some(slot) = self.ring.get(index as i64) else {
job.stage = Stage::Fixed(p);
return Ok(RenderStep::NeedFrame(index));
};
let region = &self.regions[self.region(&raw.layer)?];
let (rw, rh) = (
region.rect.width.ceil() as usize,
region.rect.height.ceil() as usize,
);
let old = self.fixed.contains_key(®ion.id);
let saved = self
.fixed
.entry(region.id.clone())
.or_insert_with(|| vec![0; rw * rh * 4]);
let changed = fixed_update(
saved,
&self.ring.slots[slot].pixels,
io.labels,
self.width,
self.height,
region.rect.x.floor() as i64,
region.rect.y.floor() as i64,
rw,
rh,
region.code,
);
job.stage = Stage::Decide(p, old && !changed);
}
Stage::Decide(p, unchanged) => {
self.previous.insert(raw.layer.clone(), p.clone());
if !unchanged {
job.stage = Stage::Paint(Paint {
p,
current: None,
neighbours: None,
masked: false,
begun: false,
});
continue;
}
let moving = self.regions[self.region(&raw.layer)?].kind == RegionKind::Moving;
self.counters.skipped_paints += 1;
if let Some(meta) = self.metas.get_mut(&p.canvas) {
meta.last_time = raw.time;
}
self.mark_dirty(&p.canvas);
if moving {
job.latest = Some(p.canvas.clone());
}
let placement = placed(&raw, &p);
let mut d = decision(p.canvas, placement, Some((0.0, 0.0, 0.0)));
d.reused_exact_observation = true;
job.decisions.push(d);
job.placement += 1;
}
Stage::Paint(mut paint) => {
let step = self.paint(job, &raw, &mut paint, io);
match step {
Ok(None) => {
job.placement += 1;
}
Ok(Some(need)) => {
job.stage = Stage::Paint(paint);
return Ok(need);
}
Err(e) => {
job.stage = Stage::Paint(paint);
return Err(e);
}
}
}
}
}
}
fn paint(
&mut self,
job: &mut FrameJob,
raw: &PlanPlacement,
paint: &mut Paint,
io: &mut RenderIo,
) -> Result<Option<RenderStep>, RenderError> {
let index = job.input.index;
let region_at = self.region(&raw.layer)?;
let current = match paint.current {
Some(slot) => slot,
None => {
let Some(slot) = self.ring.get(index as i64) else {
return Ok(Some(RenderStep::NeedFrame(index)));
};
paint.current = Some(slot);
slot
}
};
let moving = self.regions[region_at].kind == RegionKind::Moving;
if moving && !paint.masked {
if paint.neighbours.is_none() {
paint.neighbours = Some(self.neighbours(job, raw, io.graph)?);
}
let neighbours = paint.neighbours.as_mut().unwrap();
for (side, n) in neighbours.iter_mut().enumerate() {
let Some(n) = n else { continue };
if n.slot.is_none() {
let at = if side == 0 { index - 1 } else { index + 1 };
match self.ring.get(at as i64) {
Some(slot) => n.slot = Some(slot),
None => return Ok(Some(RenderStep::NeedFrame(at))),
}
}
}
debug_assert!(neighbours.iter().enumerate().all(|(side, n)| {
n.as_ref().is_none_or(|n| {
self.ring.slots[n.slot.unwrap()].index == index as i64 + 2 * side as i64 - 1
})
}));
let region = &self.regions[region_at];
let own = vote(job.votes[0].as_deref(), ®ion.id)?;
let mut sides: [Option<Neighbour>; 2] = [None, None];
for (side, n) in neighbours.iter().enumerate() {
let Some(n) = n else { continue };
if n.resolved.canvas != paint.p.canvas {
continue;
}
sides[side] = Some(Neighbour {
rgba: &self.ring.slots[n.slot.unwrap()].pixels,
pose_x: n.resolved.x,
pose_y: n.resolved.y,
occlusions: &n.occlusions,
vote: vote(job.votes[side + 1].as_deref(), ®ion.id)?,
});
}
let [prev, next] = sides;
consistency_mask(
&MaskInput {
rgba: &self.ring.slots[current].pixels,
labels: io.labels,
width: self.width,
height: self.height,
region: region.rect,
code: region.code,
pose_x: paint.p.x,
pose_y: paint.p.y,
prev,
next,
vote: own,
factor: self.factor,
noise: self.noise,
},
&mut self.mask,
);
paint.masked = true;
}
let region = &self.regions[region_at];
if !paint.begun {
io.compositor
.begin(
io.cache,
Placement {
canvas: paint.p.canvas.clone(),
region: region.rect,
code: region.code,
owned_pixels: region.owned_pixels,
x: paint.p.x,
y: paint.p.y,
frame: index,
time: raw.time,
confidence: raw.confidence,
uncertain: raw.uncertain,
occlusions: raw.occlusions.clone().unwrap_or_default(),
},
self.width,
self.height,
)
.map_err(|e| match e {
CompositeError::OutsideCanvas => RenderError::OutsideCanvas {
x: paint.p.x,
y: paint.p.y,
region: region.id.clone(),
},
e => RenderError::Compositor(e),
})?;
paint.begun = true;
}
debug_assert_eq!(self.ring.slots[current].index, index as i64);
let frame = Frame {
rgba: &self.ring.slots[current].pixels,
width: self.width,
height: self.height,
labels: io.labels,
consistent: moving.then_some(&self.mask[..]),
};
let outcome = match io.compositor.step(io.cache, &frame, io.now) {
Ok(CompositorStep::NeedTile(key)) => return Ok(Some(RenderStep::NeedTile(key))),
Ok(CompositorStep::NeedTemporal(canvas)) => {
return Ok(Some(RenderStep::NeedTemporal(canvas)))
}
Ok(CompositorStep::Done(outcome)) => outcome,
Err(CompositeError::Disposed) => {
return Err(RenderError::Disposed(paint.p.canvas.clone()))
}
Err(e) => return Err(RenderError::Compositor(e)),
};
paint.begun = false;
self.events
.extend(outcome.diagnostics.into_iter().map(compositor_diagnostic));
let s = outcome.stats;
let canvas = paint.p.canvas.clone();
if let Some(meta) = self.metas.get_mut(&canvas) {
meta.bounds = union(meta.bounds, s.bounds);
meta.observed_pixels += s.added as f64;
meta.uncertain_pixels += s.uncertain as f64;
meta.conflict_pixels += s.conflicts as f64;
meta.tile_count += s.tiles as f64;
meta.provisional_pixels += s.provisional_pixels as f64;
meta.last_time = raw.time;
}
self.counters.tiles += s.tiles as f64;
self.counters.observed_pixels += s.added as f64;
self.mark_dirty(&canvas);
if moving {
job.latest = Some(canvas.clone());
}
job.decisions.push(decision(
canvas,
placed(raw, &paint.p),
Some((s.added as f64, s.conflicts as f64, s.uncertain as f64)),
));
Ok(None)
}
fn neighbours(
&self,
job: &FrameJob,
raw: &PlanPlacement,
graph: &PoseGraph,
) -> Result<[Option<NeighbourPlacement>; 2], RenderError> {
let input = &job.input;
fn find<'a>(
has: bool,
plan: &'a Option<FramePlan>,
layer: &str,
) -> Option<&'a PlanPlacement> {
if !has {
return None;
}
plan.as_ref()?
.placements
.iter()
.find(|pl| pl.layer == layer && !pl.skipped())
}
let prev = find(
input.has_prev && input.index > 0,
&input.prev_plan,
&raw.layer,
);
let next = find(input.has_next, &input.next_plan, &raw.layer);
let prev_resolved = match prev {
Some(pl) => Some((self.resolve(pl, input.index as f64 - 1.0, graph)?, pl)),
None => None,
};
let next_resolved = match next {
Some(pl) => Some((self.resolve(pl, input.index as f64 + 1.0, graph)?, pl)),
None => None,
};
let keep = |r: Option<(Resolved, &PlanPlacement)>| {
r.filter(|(p, _)| is_valid_pose(p.x, p.y))
.map(|(resolved, pl)| NeighbourPlacement {
resolved,
occlusions: pl.occlusions.clone().unwrap_or_default(),
slot: None,
})
};
Ok([keep(prev_resolved), keep(next_resolved)])
}
}
fn compositor_diagnostic(d: CompositorDiagnostic) -> Diagnostic {
match d {
CompositorDiagnostic::IncompletePatch {
canvas,
frame,
time,
region,
} => Diagnostic::new("INCOMPLETE_TEMPORAL_PATCH", Severity::Warning)
.at(frame, time)
.canvas(&canvas)
.region(region),
CompositorDiagnostic::Conflict {
canvas,
frame,
time,
region,
confidence,
regions,
conflicts,
} => Diagnostic::new("TEMPORAL_OR_ALIGNMENT_CONFLICT", Severity::Warning)
.at(frame, time)
.param("conflicts", conflicts as f64)
.canvas(&canvas)
.region(region)
.confidence(confidence)
.detail(Value::Object(vec![(
"regions",
Value::List(regions.into_iter().map(Value::rect).collect()),
)])),
}
}
fn vote_gets(index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
let index = index as i64;
let mut gets = vec![index];
if has_prev {
gets.push(index - 1);
}
if has_next {
gets.push(index + 1);
}
gets
}
fn vote<'a>(
record: Option<&'a ConsistencyRecord>,
region: &str,
) -> Result<Option<Vote<'a>>, RenderError> {
let Some(v) = record.and_then(|r| r.get(region)) else {
return Ok(None);
};
if v.w <= 0 || v.h <= 0 {
return Err(RenderError::BadVote);
}
let bytes = (v.w as usize * v.h as usize).div_ceil(8);
if v.bits.len() < bytes || v.clean.len() < bytes {
return Err(RenderError::BadVote);
}
Ok(Some(Vote {
x0: v.x0,
y0: v.y0,
w: v.w,
h: v.h,
bits: &v.bits[..bytes],
clean: &v.clean[..bytes],
}))
}
fn placed(raw: &PlanPlacement, p: &Resolved) -> PlanPlacement {
PlanPlacement {
canvas_id: p.canvas.clone(),
x: p.x,
y: p.y,
..raw.clone()
}
}
fn decision(
canvas_id: String,
placement: PlanPlacement,
pixels: Option<(f64, f64, f64)>,
) -> RenderDecision {
let (added, conflicts, uncertain) = pixels.unwrap_or_default();
RenderDecision {
canvas_id,
placement,
added_pixels: added,
conflict_pixels: conflicts,
uncertain_pixels: uncertain,
skipped: pixels.is_none(),
reused_exact_observation: false,
}
}
fn union(a: Rect, b: Rect) -> Rect {
if a.width <= 0.0 || a.height <= 0.0 {
return b;
}
let (x, y) = (a.x.min(b.x), a.y.min(b.y));
Rect {
x,
y,
width: (a.x + a.width).max(b.x + b.width) - x,
height: (a.y + a.height).max(b.y + b.height) - y,
}
}
#[cfg(test)]
mod tests;