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::overscroll::{self, Overscroll};
use crate::engine::pose_graph::{PoseGraph, PoseGraphError};
use crate::engine::tiles::{TileCache, TileKey};
use crate::kernels::consistency::{
consistency_mask, fixed_mask, sum_limit, FixedMaskInput, MaskInput, Neighbour, Vote,
};
use crate::kernels::fixed_history::FixedHistory;
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;
const DYNAMIC_SHARE: f64 = 0.2;
const DYNAMIC_FRAMES: u32 = 10;
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,
overscroll: Option<[u16; 4]>,
begun: bool,
}
struct FrameJob {
input: FrameInput,
votes: [Option<Arc<ConsistencyRecord>>; 3],
placement: usize,
stage: Stage,
decisions: Vec<RenderDecision>,
latest: Option<String>,
}
struct FixedState {
history: FixedHistory,
frames: u32,
settled: u32,
first: Option<Vec<u32>>,
}
struct FinishJob {
regions: Vec<usize>,
at: usize,
begun: bool,
frame: u32,
time: f64,
}
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>>,
states: HashMap<String, FixedState>,
finish: Option<FinishJob>,
last: Option<(u32, f64)>,
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>,
overscroll: Overscroll,
}
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(),
states: HashMap::new(),
finish: None,
last: None,
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,
overscroll: Overscroll::default(),
}
}
pub fn add_plan(&mut self, index: u32, plan: &FramePlan) {
let (regions, index_of) = (&self.regions, &self.region_index);
self.overscroll.add_plan(index, plan, |layer| {
index_of
.get(layer)
.is_some_and(|&i| regions[i].kind == RegionKind::Moving)
});
}
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 + FixedHistory::bytes(r.rect)
});
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() || self.finish.is_some() {
return Err(RenderError::Busy);
}
self.last = Some((input.index, input.time));
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> {
if self.finish.is_some() {
let result = self.run_finish(io);
if result.is_err() {
self.finish = None;
io.compositor.cancel();
}
return result;
}
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
}
pub fn begin_finish(&mut self) -> Result<bool, RenderError> {
if self.job.is_some() || self.finish.is_some() {
return Err(RenderError::Busy);
}
let Some((last, time)) = self.last else {
return Ok(false);
};
let limit = sum_limit(self.noise);
let mut regions = Vec::new();
for (at, region) in self.regions.iter().enumerate() {
let Some(state) = self.states.get_mut(®ion.id) else {
continue;
};
if state.first.is_some() {
continue;
}
state.history.close(last + 1);
let dynamic = state.settled > DYNAMIC_FRAMES
&& state.settled as f64 > DYNAMIC_SHARE * state.frames as f64;
state.first = Some(if dynamic {
state.history.frame0().to_vec()
} else {
state.history.first(limit)
});
if dynamic && self.previous.contains_key(®ion.id) {
regions.push(at);
}
}
if regions.is_empty() {
return Ok(false);
}
let frame = self.states[&self.regions[regions[0]].id]
.history
.first_frame()
.unwrap_or(0);
self.finish = Some(FinishJob {
regions,
at: 0,
begun: false,
frame,
time,
});
Ok(true)
}
pub fn patch_reference(
&self,
rgba: &mut [u8],
width: usize,
height: usize,
frame: u32,
) -> bool {
if width != self.width || height != self.height || rgba.len() != width * height * 4 {
return false;
}
let mut changed = false;
for region in &self.regions {
let Some(state) = self.states.get(®ion.id) else {
continue;
};
if let (Some(first), Some(from)) = (&state.first, state.history.first_frame()) {
if from == frame {
changed |= state.history.paint(first, rgba, width, height);
}
}
}
changed
}
fn run_finish(&mut self, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
loop {
let job = self.finish.as_mut().unwrap();
let Some(&at) = job.regions.get(job.at) else {
let (frame, time) = (job.frame, job.time);
self.finish = None;
return Ok(RenderStep::Done(FrameOutcome {
observation: Observation {
frame,
time,
decisions: Vec::new(),
},
latest: None,
}));
};
let (begun, frame_index, time) = (job.begun, job.frame, job.time);
let region = &self.regions[at];
let p = self.previous[®ion.id].clone();
let slot = self.ring.fill(-2, None);
if !begun {
let state = &self.states[®ion.id];
state.history.paint(
state.first.as_ref().unwrap(),
&mut self.ring.slots[slot].pixels,
self.width,
self.height,
);
self.mask.fill(1);
io.compositor
.begin(
io.cache,
Placement {
canvas: p.canvas.clone(),
region: region.rect,
code: region.code,
owned_pixels: region.owned_pixels,
x: p.x,
y: p.y,
frame: frame_index,
time,
confidence: 1.0,
uncertain: false,
occlusions: Vec::new(),
fixed: true,
},
self.width,
self.height,
)
.map_err(RenderError::Compositor)?;
self.finish.as_mut().unwrap().begun = true;
}
let frame = Frame {
rgba: &self.ring.slots[slot].pixels,
width: self.width,
height: self.height,
labels: io.labels,
consistent: Some(&self.mask[..]),
};
let outcome = match io.compositor.step(io.cache, &frame, io.now) {
Ok(CompositorStep::NeedTile(key)) => return Ok(RenderStep::NeedTile(key)),
Ok(CompositorStep::NeedTemporal(canvas)) => {
return Ok(RenderStep::NeedTemporal(canvas))
}
Ok(CompositorStep::Done(outcome)) => outcome,
Err(e) => return Err(RenderError::Compositor(e)),
};
if let Some(meta) = self.metas.get_mut(&p.canvas) {
meta.observed_pixels += outcome.stats.added as f64;
meta.provisional_pixels += outcome.stats.provisional_pixels as f64;
}
self.mark_dirty(&p.canvas);
let job = self.finish.as_mut().unwrap();
job.begun = false;
job.at += 1;
}
}
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,
);
if !old {
let region = &self.regions[self.region(&raw.layer)?];
self.states.insert(
region.id.clone(),
FixedState {
history: FixedHistory::new(region.rect),
frames: 0,
settled: 0,
first: None,
},
);
}
job.stage = Stage::Decide(p, old && !changed);
}
Stage::Decide(p, unchanged) => {
self.previous.insert(raw.layer.clone(), p.clone());
if let Some(state) = self.states.get_mut(&raw.layer) {
state.frames += 1;
}
if !unchanged {
job.stage = Stage::Paint(Paint {
p,
current: None,
neighbours: None,
masked: false,
overscroll: None,
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 at = overscroll::Resolved {
canvas: &p.canvas,
x: p.x,
y: p.y,
};
let left_out = self.overscroll.reuse(&raw.layer, at);
let region = self.regions[self.region(&raw.layer)?].rect;
let placement = placed(&raw, &p, overscroll::strips(region, left_out));
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 kind = self.regions[region_at].kind;
let moving = kind == RegionKind::Moving;
let fixed = kind == RegionKind::Fixed;
if (moving || fixed) && !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];
if fixed {
let [prev, next] = [0, 1].map(|side| {
neighbours[side]
.as_ref()
.filter(|n| n.resolved.canvas == paint.p.canvas)
.map(|n| &self.ring.slots[n.slot.unwrap()].pixels[..])
});
fixed_mask(
&FixedMaskInput {
rgba: &self.ring.slots[current].pixels,
labels: io.labels,
width: self.width,
height: self.height,
region: region.rect,
code: region.code,
prev,
next,
noise: self.noise,
},
&mut self.mask,
);
if let Some(state) = self.states.get_mut(®ion.id) {
state.history.update(
&self.ring.slots[current].pixels,
io.labels,
&self.mask,
self.width,
self.height,
region.code,
index,
sum_limit(self.noise),
);
}
paint.masked = true;
}
}
if moving && !paint.masked {
let neighbours = paint.neighbours.as_ref().unwrap();
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;
}
if paint.overscroll.is_none() {
let lines = self.left_out(raw, paint, region_at, current, io)?;
paint.overscroll = Some(lines.map(|n| n.clamp(0, u16::MAX as i64) as u16));
}
let left_out = paint.overscroll.unwrap_or_default().map(i64::from);
let strips = overscroll::strips(self.regions[region_at].rect, left_out);
let region = &self.regions[region_at];
if !paint.begun {
let mut occlusions = raw.occlusions.clone().unwrap_or_default();
occlusions.extend(strips.iter().copied());
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,
fixed,
},
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 || fixed).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;
if fixed && s.settled_blocks > 0 {
if let Some(state) = self.states.get_mut(&raw.layer) {
state.settled += 1;
}
}
let canvas = paint.p.canvas.clone();
if let Some(meta) = self.metas.get_mut(&canvas) {
let placed_rect = overscroll::trim(s.bounds, left_out);
if placed_rect.width > 0.0 {
meta.bounds = union(meta.bounds, placed_rect);
}
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, strips),
Some((s.added as f64, s.conflicts as f64, s.uncertain as f64)),
));
Ok(None)
}
fn left_out(
&mut self,
raw: &PlanPlacement,
paint: &Paint,
region_at: usize,
current: usize,
io: &RenderIo,
) -> Result<[i64; 4], RenderError> {
let graph = io.graph;
let region = &self.regions[region_at];
if region.kind != RegionKind::Moving {
return Ok([0; 4]);
}
let (regions, index_of) = (&self.regions, &self.region_index);
let (attachments, overscroll) = (&self.attachments, &mut self.overscroll);
overscroll.prepare(
|p, frame| {
let resolved = graph
.correction(&p.node, frame as f64)
.and_then(|(cx, cy)| {
let (sx, sy) = render_shift(attachments, &p.canvas_id, graph)?;
Ok((p.x + cx + sx, p.y + cy + sy))
});
let canvas = resolve_target(attachments, &p.canvas_id).to_string();
Ok::<_, RenderError>(
resolved
.ok()
.filter(|&(x, y)| is_valid_pose(x, y))
.map(|(x, y)| (canvas, x, y)),
)
},
|layer| {
let r = regions[*index_of.get(layer)?].rect;
let (_, _, w, h) = overscroll::pixel_rect(r);
Some((w, h))
},
)?;
let moved = |n: &Option<NeighbourPlacement>| {
n.as_ref().and_then(|n| {
let same = n.resolved.canvas == paint.p.canvas
&& js_round(n.resolved.x) == js_round(paint.p.x)
&& js_round(n.resolved.y) == js_round(paint.p.y);
(!same).then_some(n.slot).flatten()
})
};
let slots = paint
.neighbours
.as_ref()
.map_or([None, None], |[a, b]| [moved(a), moved(b)]);
let ring = &self.ring;
let lines = crate::kernels::overscroll::Lines {
rgba: &ring.slots[current].pixels,
width: self.width,
height: self.height,
labels: Some((io.labels, region.code)),
region: overscroll::pixel_rect(region.rect),
neighbours: slots.map(|s| s.map(|s| &ring.slots[s].pixels[..])),
noise: self.noise,
};
let at = overscroll::Resolved {
canvas: &paint.p.canvas,
x: paint.p.x,
y: paint.p.y,
};
Ok(self.overscroll.measure(&raw.layer, at, &lines))
}
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, left_out: Vec<Rect>) -> PlanPlacement {
let mut occlusions = raw.occlusions.clone();
if !left_out.is_empty() {
occlusions.get_or_insert_with(Vec::new).extend(left_out);
}
PlanPlacement {
canvas_id: p.canvas.clone(),
x: p.x,
y: p.y,
occlusions,
..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 fixed_tests;
#[cfg(test)]
mod tests;