1use crate::engine::attachments::{render_shift, resolve_target, Attachment, Attachments};
32use crate::engine::compositor::{
33 CompositeError, Compositor, CompositorDiagnostic, Frame, Placement, Step as CompositorStep,
34};
35use crate::engine::diagnostic::{Diagnostic, Severity, Value};
36use crate::engine::pose_graph::{PoseGraph, PoseGraphError};
37use crate::engine::tiles::{TileCache, TileKey};
38use crate::kernels::consistency::{consistency_mask, MaskInput, Neighbour, Vote};
39use crate::kernels::geometry::{js_round, Rect};
40use crate::kernels::raster::fixed_update;
41use std::collections::{HashMap, HashSet, VecDeque};
42use std::fmt;
43use std::sync::Arc;
44
45pub use crate::engine::solve::{is_valid_pose, POSE_BOUND};
46
47pub const RING_FRAMES: usize = 3;
49pub const VOTING_WINDOW: usize = 4;
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum RegionKind {
55 Moving,
56 Fixed,
57 Ignore,
58}
59
60#[derive(Clone, Debug, PartialEq)]
62pub struct RenderRegion {
63 pub id: String,
64 pub kind: RegionKind,
65 pub rect: Rect,
67 pub code: u8,
69 pub owned_pixels: f64,
70}
71
72#[derive(Clone, Debug, PartialEq)]
75pub struct PlanPlacement {
76 pub layer: String,
77 pub canvas_id: String,
78 pub node: String,
79 pub x: f64,
80 pub y: f64,
81 pub confidence: f64,
82 pub uncertain: bool,
83 pub time: f64,
84 pub skip: Option<bool>,
85 pub occlusions: Option<Vec<Rect>>,
86}
87
88impl PlanPlacement {
89 fn skipped(&self) -> bool {
90 self.skip == Some(true)
91 }
92}
93
94#[derive(Clone, Debug, Default, PartialEq)]
96pub struct FramePlan {
97 pub placements: Vec<PlanPlacement>,
98 pub duplicate: bool,
99}
100
101#[derive(Clone, Debug, PartialEq)]
105pub struct ConsistencyVote {
106 pub x0: i32,
107 pub y0: i32,
108 pub w: i32,
109 pub h: i32,
110 pub bits: Vec<u8>,
111 pub clean: Vec<u8>,
112}
113
114pub type ConsistencyRecord = HashMap<String, ConsistencyVote>;
116
117#[derive(Clone, Copy, Debug, PartialEq)]
120pub struct CanvasMeta {
121 pub bounds: Rect,
122 pub tile_count: f64,
123 pub observed_pixels: f64,
124 pub uncertain_pixels: f64,
125 pub conflict_pixels: f64,
126 pub provisional_pixels: f64,
127 pub last_time: f64,
128}
129
130#[derive(Clone, Debug, PartialEq)]
133pub struct RenderDecision {
134 pub canvas_id: String,
135 pub placement: PlanPlacement,
136 pub added_pixels: f64,
137 pub conflict_pixels: f64,
138 pub uncertain_pixels: f64,
139 pub skipped: bool,
141 pub reused_exact_observation: bool,
143}
144
145#[derive(Clone, Debug, PartialEq)]
147pub struct Observation {
148 pub frame: u32,
149 pub time: f64,
150 pub decisions: Vec<RenderDecision>,
151}
152
153#[derive(Clone, Debug, PartialEq)]
156pub struct FrameOutcome {
157 pub observation: Observation,
158 pub latest: Option<String>,
159}
160
161#[derive(Clone, Copy, Debug, Default, PartialEq)]
163pub struct RenderCounters {
164 pub skipped_paints: u64,
166 pub tiles: f64,
168 pub observed_pixels: f64,
170}
171
172#[derive(Clone, Debug, PartialEq)]
173pub enum RenderStep {
174 NeedMeta(String),
176 NeedFrame(u32),
178 NeedTile(TileKey),
180 NeedTemporal(String),
182 Done(FrameOutcome),
183}
184
185#[derive(Clone, Debug, PartialEq)]
186pub enum RenderError {
187 Busy,
189 NoFrame,
190 FrameSize,
192 MissingVote(i64),
194 BadVote,
196 UnknownRegion(String),
198 MissingMeta(String),
200 Graph(PoseGraphError),
202 OutsideCanvas {
204 x: f64,
205 y: f64,
206 region: String,
207 },
208 Disposed(String),
210 Compositor(CompositeError),
212}
213
214impl fmt::Display for RenderError {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 RenderError::Busy => f.write_str("A frame is already being rendered."),
218 RenderError::NoFrame => f.write_str("No frame is being rendered."),
219 RenderError::FrameSize => f.write_str("Frame does not match the run's size."),
220 RenderError::MissingVote(i) => write!(f, "Voting record {i} was not provided."),
221 RenderError::BadVote => f.write_str("CORE_BAD_ARGUMENT: truncated consistency vote bitset."),
222 RenderError::UnknownRegion(id) => write!(f, "Region {id} is not part of this atlas."),
223 RenderError::MissingMeta(_) => f.write_str("Canvas metadata is missing."),
224 RenderError::Graph(e) => e.fmt(f),
225 RenderError::OutsideCanvas { x, y, region } => write!(
226 f,
227 "Placement ({x}, {y}) puts region {region} outside the addressable canvas."
228 ),
229 RenderError::Disposed(canvas) => write!(
230 f,
231 "CORE_BAD_ARGUMENT: temporalIndex({canvas}) called after dispose() already drained and froze its state."
232 ),
233 RenderError::Compositor(e) => write!(f, "Compositor failed: {e:?}."),
234 }
235 }
236}
237
238impl std::error::Error for RenderError {}
239
240impl From<PoseGraphError> for RenderError {
241 fn from(e: PoseGraphError) -> Self {
242 RenderError::Graph(e)
243 }
244}
245
246pub struct RenderIo<'a> {
248 pub cache: &'a mut TileCache,
249 pub compositor: &'a mut Compositor,
250 pub graph: &'a PoseGraph,
251 pub labels: &'a [u8],
253 pub now: f64,
255}
256
257#[derive(Clone, Debug, PartialEq)]
259pub struct FrameInput {
260 pub index: u32,
261 pub time: f64,
263 pub plan: FramePlan,
264 pub has_prev: bool,
266 pub prev_plan: Option<FramePlan>,
267 pub has_next: bool,
268 pub next_plan: Option<FramePlan>,
269}
270
271#[derive(Clone, Debug, PartialEq)]
273pub struct RenderConfig {
274 pub width: usize,
275 pub height: usize,
276 pub factor: i32,
278 pub noise: f64,
280 pub regions: Vec<RenderRegion>,
282 pub attachments: Vec<Attachment>,
283}
284
285#[derive(Clone, Debug, PartialEq)]
287struct Resolved {
288 canvas: String,
289 x: f64,
290 y: f64,
291}
292
293struct Slot {
295 pixels: Vec<u8>,
296 index: i64,
297 used: u64,
298}
299
300struct FrameRing {
303 slots: Vec<Slot>,
304 tick: u64,
305 bytes: usize,
306}
307
308impl FrameRing {
309 fn new(bytes: usize) -> Self {
310 Self {
311 slots: Vec::with_capacity(RING_FRAMES),
312 tick: 0,
313 bytes,
314 }
315 }
316
317 fn get(&self, index: i64) -> Option<usize> {
318 self.slots.iter().position(|s| s.index == index)
319 }
320
321 fn fill(&mut self, index: i64, keep: Option<(i64, i64)>) -> usize {
326 if let Some(slot) = self.get(index) {
327 return slot;
328 }
329 let slot = if self.slots.len() < RING_FRAMES {
330 self.slots.push(Slot {
331 pixels: vec![0; self.bytes],
332 index,
333 used: 0,
334 });
335 self.slots.len() - 1
336 } else {
337 let kept = |s: &Slot| keep.is_some_and(|(a, b)| (a..=b).contains(&s.index));
338 let oldest = |free: bool| {
339 (0..self.slots.len())
340 .filter(|&i| !free || !kept(&self.slots[i]))
341 .min_by_key(|&i| self.slots[i].used)
342 };
343 oldest(true).or_else(|| oldest(false)).unwrap()
344 };
345 self.tick += 1;
346 let s = &mut self.slots[slot];
347 s.index = index;
348 s.used = self.tick;
349 slot
350 }
351}
352
353#[derive(Default)]
355struct VotingWindow {
356 entries: VecDeque<(i64, Option<Arc<ConsistencyRecord>>)>,
357}
358
359impl VotingWindow {
360 fn wanted(&self, gets: &[i64]) -> Vec<i64> {
363 let mut held: VecDeque<i64> = self.entries.iter().map(|(i, _)| *i).collect();
364 let mut wanted = Vec::new();
365 for &index in gets {
366 if held.contains(&index) {
367 continue;
368 }
369 if index >= 0 {
370 wanted.push(index);
371 }
372 held.push_back(index);
373 if held.len() > VOTING_WINDOW {
374 held.pop_front();
375 }
376 }
377 wanted
378 }
379
380 fn get(
381 &mut self,
382 index: i64,
383 provided: &mut HashMap<i64, Option<ConsistencyRecord>>,
384 ) -> Result<Option<Arc<ConsistencyRecord>>, RenderError> {
385 if let Some((_, record)) = self.entries.iter().find(|(i, _)| *i == index) {
386 return Ok(record.clone());
387 }
388 let record = if index < 0 {
389 None
390 } else {
391 provided
392 .remove(&index)
393 .ok_or(RenderError::MissingVote(index))?
394 .map(Arc::new)
395 };
396 self.entries.push_back((index, record.clone()));
397 if self.entries.len() > VOTING_WINDOW {
398 self.entries.pop_front();
399 }
400 Ok(record)
401 }
402}
403
404struct NeighbourPlacement {
406 resolved: Resolved,
407 occlusions: Vec<Rect>,
408 slot: Option<usize>,
409}
410
411enum Stage {
413 Start,
415 Meta(Resolved),
417 Fixed(Resolved),
419 Decide(Resolved, bool),
421 Paint(Paint),
423}
424
425struct Paint {
426 p: Resolved,
427 current: Option<usize>,
428 neighbours: Option<[Option<NeighbourPlacement>; 2]>,
430 masked: bool,
431 begun: bool,
432}
433
434struct FrameJob {
435 input: FrameInput,
436 votes: [Option<Arc<ConsistencyRecord>>; 3],
437 placement: usize,
438 stage: Stage,
439 decisions: Vec<RenderDecision>,
440 latest: Option<String>,
441}
442
443pub struct RenderSession {
444 width: usize,
445 height: usize,
446 factor: i32,
447 noise: f64,
448 regions: Vec<RenderRegion>,
449 region_index: HashMap<String, usize>,
450 attachments: Attachments,
451 ring: FrameRing,
452 mask: Vec<u8>,
453 fixed: HashMap<String, Vec<u8>>,
454 previous: HashMap<String, Resolved>,
455 votes: VotingWindow,
456 metas: HashMap<String, CanvasMeta>,
457 missing_meta: Option<String>,
458 dirty: Vec<String>,
459 dirty_set: HashSet<String>,
460 counters: RenderCounters,
461 events: Vec<Diagnostic>,
462 job: Option<FrameJob>,
463}
464
465impl RenderSession {
466 pub fn new(config: RenderConfig) -> Self {
467 let mut region_index = HashMap::new();
468 for (i, r) in config.regions.iter().enumerate() {
469 region_index.insert(r.id.clone(), i);
470 }
471 let attachments: Attachments = config
472 .attachments
473 .into_iter()
474 .map(|a| (a.id.clone(), a))
475 .collect();
476 let pixels = config.width * config.height;
477 Self {
478 width: config.width,
479 height: config.height,
480 factor: config.factor,
481 noise: config.noise,
482 regions: config.regions,
483 region_index,
484 attachments,
485 ring: FrameRing::new(pixels * 4),
486 mask: vec![1; pixels],
487 fixed: HashMap::new(),
488 previous: HashMap::new(),
489 votes: VotingWindow::default(),
490 metas: HashMap::new(),
491 missing_meta: None,
492 dirty: Vec::new(),
493 dirty_set: HashSet::new(),
494 counters: RenderCounters::default(),
495 events: Vec::new(),
496 job: None,
497 }
498 }
499
500 pub fn configure_tiles(&self, cache: &mut TileCache, memory_mb: f64) -> Option<Diagnostic> {
505 let fixed_bytes = self
506 .regions
507 .iter()
508 .filter(|r| r.kind == RegionKind::Fixed)
509 .fold(0.0, |n, r| {
510 n + r.rect.width.ceil() * r.rect.height.ceil() * 4.0
511 });
512 let (w, h) = (self.width as f64, self.height as f64);
513 cache.configure_budget(
514 memory_mb,
515 w * h * 5.0 + fixed_bytes + 8.0 * 1024.0 * 1024.0 + w * h * 4.0,
516 );
517 let areas: Vec<(f64, f64)> = self
518 .regions
519 .iter()
520 .filter(|r| r.kind != RegionKind::Ignore)
521 .map(|r| (r.rect.width, r.rect.height))
522 .collect();
523 let raised = cache.ensure_footprint(&areas);
524 if raised == 0 {
525 return None;
526 }
527 let to = cache.max_tiles();
528 let size = cache.size() as f64;
529 Some(
530 Diagnostic::new("MEMORY_BUDGET_RAISED", Severity::Info)
531 .param("from", (to - raised) as f64)
532 .param("to", to as f64)
533 .param(
534 "mb",
535 js_round(to as f64 * size * size * 4.3 / 1024.0 / 1024.0) as f64,
536 )
537 .detail(Value::Object(vec![
538 ("budgetMB", Value::Number(memory_mb)),
539 ("tiles", Value::Number(to as f64)),
540 ("raisedBy", Value::Number(raised as f64)),
541 ])),
542 )
543 }
544
545 pub fn counters(&self) -> RenderCounters {
546 self.counters
547 }
548
549 pub fn meta(&self, canvas: &str) -> Option<CanvasMeta> {
551 self.metas.get(canvas).copied()
552 }
553
554 pub fn take_events(&mut self) -> Vec<Diagnostic> {
559 std::mem::take(&mut self.events)
560 }
561
562 pub fn take_dirty_metas(&mut self) -> Vec<(String, CanvasMeta)> {
565 self.dirty_set.clear();
566 std::mem::take(&mut self.dirty)
567 .into_iter()
568 .map(|id| {
569 let meta = self.metas[&id];
570 (id, meta)
571 })
572 .collect()
573 }
574
575 pub fn wanted_votes(&self, index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
579 self.votes.wanted(&vote_gets(index, has_prev, has_next))
580 }
581
582 pub fn begin(
585 &mut self,
586 input: FrameInput,
587 votes: Vec<(i64, Option<ConsistencyRecord>)>,
588 ) -> Result<(), RenderError> {
589 if self.job.is_some() {
590 return Err(RenderError::Busy);
591 }
592 let mut provided: HashMap<i64, Option<ConsistencyRecord>> = votes.into_iter().collect();
593 let index = input.index as i64;
594 let own = self.votes.get(index, &mut provided)?;
595 let prev = if input.has_prev {
596 self.votes.get(index - 1, &mut provided)?
597 } else {
598 None
599 };
600 let next = if input.has_next {
601 self.votes.get(index + 1, &mut provided)?
602 } else {
603 None
604 };
605 self.job = Some(FrameJob {
606 input,
607 votes: [own, prev, next],
608 placement: 0,
609 stage: Stage::Start,
610 decisions: Vec::new(),
611 latest: None,
612 });
613 Ok(())
614 }
615
616 pub fn cancel(&mut self, compositor: &mut Compositor) {
620 if self.job.take().is_some() {
621 compositor.cancel();
622 }
623 self.missing_meta = None;
624 }
625
626 pub fn provide_meta(&mut self, canvas: &str, meta: Option<CanvasMeta>) {
629 match meta {
630 Some(meta) => {
631 self.metas.insert(canvas.to_string(), meta);
632 }
633 None => self.missing_meta = Some(canvas.to_string()),
634 }
635 }
636
637 pub fn frame_slot(&mut self, index: u32) -> &mut [u8] {
640 let keep = self.job.as_ref().map(|j| {
641 let at = j.input.index as i64;
642 (at - 1, at + 1)
643 });
644 let slot = self.ring.fill(index as i64, keep);
645 &mut self.ring.slots[slot].pixels
646 }
647
648 pub fn step(&mut self, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
651 let mut job = self.job.take().ok_or(RenderError::NoFrame)?;
652 let result = if io.labels.len() != self.width * self.height {
653 Err(RenderError::FrameSize)
654 } else {
655 self.run(&mut job, io)
656 };
657 match result {
658 Ok(RenderStep::Done(_)) => {}
659 Ok(_) => self.job = Some(job),
660 Err(_) => {
661 if matches!(job.stage, Stage::Paint(Paint { begun: true, .. })) {
662 io.compositor.cancel();
663 }
664 self.missing_meta = None;
665 }
666 }
667 result
668 }
669
670 fn resolve(
671 &self,
672 raw: &PlanPlacement,
673 frame: f64,
674 graph: &PoseGraph,
675 ) -> Result<Resolved, RenderError> {
676 let (cx, cy) = graph.correction(&raw.node, frame)?;
677 let (sx, sy) = render_shift(&self.attachments, &raw.canvas_id, graph)?;
678 let canvas = resolve_target(&self.attachments, &raw.canvas_id).to_string();
679 Ok(Resolved {
680 canvas,
681 x: raw.x + cx + sx,
682 y: raw.y + cy + sy,
683 })
684 }
685
686 fn region(&self, layer: &str) -> Result<usize, RenderError> {
687 self.region_index
688 .get(layer)
689 .copied()
690 .ok_or_else(|| RenderError::UnknownRegion(layer.to_string()))
691 }
692
693 fn mark_dirty(&mut self, canvas: &str) {
694 if self.dirty_set.insert(canvas.to_string()) {
695 self.dirty.push(canvas.to_string());
696 }
697 }
698
699 fn run(&mut self, job: &mut FrameJob, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
700 loop {
701 let Some(raw) = job.input.plan.placements.get(job.placement) else {
702 return Ok(RenderStep::Done(FrameOutcome {
703 observation: Observation {
704 frame: job.input.index,
705 time: job.input.time,
706 decisions: std::mem::take(&mut job.decisions),
707 },
708 latest: job.latest.take(),
709 }));
710 };
711 let raw = raw.clone();
712 let index = job.input.index;
713 match std::mem::replace(&mut job.stage, Stage::Start) {
714 Stage::Start => {
715 if raw.skipped() {
716 job.decisions
717 .push(decision(raw.canvas_id.clone(), raw, None));
718 job.placement += 1;
719 continue;
720 }
721 let p = self.resolve(&raw, index as f64, io.graph)?;
722 if !is_valid_pose(p.x, p.y) {
723 self.events.push(
724 Diagnostic::new("NONFINITE_POSE", Severity::Error)
725 .at(index, raw.time)
726 .canvas(&p.canvas),
727 );
728 let placement = PlanPlacement {
729 canvas_id: p.canvas.clone(),
730 x: 0.0,
731 y: 0.0,
732 ..raw
733 };
734 job.decisions.push(decision(p.canvas, placement, None));
735 job.placement += 1;
736 continue;
737 }
738 job.stage = Stage::Meta(p);
739 }
740 Stage::Meta(p) => {
741 if !self.metas.contains_key(&p.canvas) {
742 if self.missing_meta.take().as_deref() == Some(p.canvas.as_str()) {
743 return Err(RenderError::MissingMeta(p.canvas));
744 }
745 let canvas = p.canvas.clone();
746 job.stage = Stage::Meta(p);
747 return Ok(RenderStep::NeedMeta(canvas));
748 }
749 let region = &self.regions[self.region(&raw.layer)?];
750 let unchanged = job.input.plan.duplicate
751 && self.previous.get(&raw.layer).is_some_and(|last| *last == p);
752 job.stage = if region.kind == RegionKind::Fixed && !unchanged {
753 Stage::Fixed(p)
754 } else {
755 Stage::Decide(p, unchanged)
756 };
757 }
758 Stage::Fixed(p) => {
759 let Some(slot) = self.ring.get(index as i64) else {
760 job.stage = Stage::Fixed(p);
761 return Ok(RenderStep::NeedFrame(index));
762 };
763 let region = &self.regions[self.region(&raw.layer)?];
764 let (rw, rh) = (
765 region.rect.width.ceil() as usize,
766 region.rect.height.ceil() as usize,
767 );
768 let old = self.fixed.contains_key(®ion.id);
769 let saved = self
770 .fixed
771 .entry(region.id.clone())
772 .or_insert_with(|| vec![0; rw * rh * 4]);
773 let changed = fixed_update(
774 saved,
775 &self.ring.slots[slot].pixels,
776 io.labels,
777 self.width,
778 self.height,
779 region.rect.x.floor() as i64,
780 region.rect.y.floor() as i64,
781 rw,
782 rh,
783 region.code,
784 );
785 job.stage = Stage::Decide(p, old && !changed);
786 }
787 Stage::Decide(p, unchanged) => {
788 self.previous.insert(raw.layer.clone(), p.clone());
789 if !unchanged {
790 job.stage = Stage::Paint(Paint {
791 p,
792 current: None,
793 neighbours: None,
794 masked: false,
795 begun: false,
796 });
797 continue;
798 }
799 let moving = self.regions[self.region(&raw.layer)?].kind == RegionKind::Moving;
800 self.counters.skipped_paints += 1;
801 if let Some(meta) = self.metas.get_mut(&p.canvas) {
802 meta.last_time = raw.time;
803 }
804 self.mark_dirty(&p.canvas);
805 if moving {
806 job.latest = Some(p.canvas.clone());
807 }
808 let placement = placed(&raw, &p);
809 let mut d = decision(p.canvas, placement, Some((0.0, 0.0, 0.0)));
810 d.reused_exact_observation = true;
811 job.decisions.push(d);
812 job.placement += 1;
813 }
814 Stage::Paint(mut paint) => {
815 let step = self.paint(job, &raw, &mut paint, io);
816 match step {
817 Ok(None) => {
818 job.placement += 1;
819 }
820 Ok(Some(need)) => {
821 job.stage = Stage::Paint(paint);
822 return Ok(need);
823 }
824 Err(e) => {
825 job.stage = Stage::Paint(paint);
826 return Err(e);
827 }
828 }
829 }
830 }
831 }
832 }
833
834 fn paint(
836 &mut self,
837 job: &mut FrameJob,
838 raw: &PlanPlacement,
839 paint: &mut Paint,
840 io: &mut RenderIo,
841 ) -> Result<Option<RenderStep>, RenderError> {
842 let index = job.input.index;
843 let region_at = self.region(&raw.layer)?;
844 let current = match paint.current {
845 Some(slot) => slot,
846 None => {
847 let Some(slot) = self.ring.get(index as i64) else {
848 return Ok(Some(RenderStep::NeedFrame(index)));
849 };
850 paint.current = Some(slot);
851 slot
852 }
853 };
854 let moving = self.regions[region_at].kind == RegionKind::Moving;
855 if moving && !paint.masked {
856 if paint.neighbours.is_none() {
857 paint.neighbours = Some(self.neighbours(job, raw, io.graph)?);
858 }
859 let neighbours = paint.neighbours.as_mut().unwrap();
860 for (side, n) in neighbours.iter_mut().enumerate() {
861 let Some(n) = n else { continue };
862 if n.slot.is_none() {
863 let at = if side == 0 { index - 1 } else { index + 1 };
864 match self.ring.get(at as i64) {
865 Some(slot) => n.slot = Some(slot),
866 None => return Ok(Some(RenderStep::NeedFrame(at))),
867 }
868 }
869 }
870 debug_assert!(neighbours.iter().enumerate().all(|(side, n)| {
872 n.as_ref().is_none_or(|n| {
873 self.ring.slots[n.slot.unwrap()].index == index as i64 + 2 * side as i64 - 1
874 })
875 }));
876 let region = &self.regions[region_at];
877 let own = vote(job.votes[0].as_deref(), ®ion.id)?;
878 let mut sides: [Option<Neighbour>; 2] = [None, None];
879 for (side, n) in neighbours.iter().enumerate() {
880 let Some(n) = n else { continue };
881 if n.resolved.canvas != paint.p.canvas {
882 continue;
883 }
884 sides[side] = Some(Neighbour {
885 rgba: &self.ring.slots[n.slot.unwrap()].pixels,
886 pose_x: n.resolved.x,
887 pose_y: n.resolved.y,
888 occlusions: &n.occlusions,
889 vote: vote(job.votes[side + 1].as_deref(), ®ion.id)?,
890 });
891 }
892 let [prev, next] = sides;
893 consistency_mask(
894 &MaskInput {
895 rgba: &self.ring.slots[current].pixels,
896 labels: io.labels,
897 width: self.width,
898 height: self.height,
899 region: region.rect,
900 code: region.code,
901 pose_x: paint.p.x,
902 pose_y: paint.p.y,
903 prev,
904 next,
905 vote: own,
906 factor: self.factor,
907 noise: self.noise,
908 },
909 &mut self.mask,
910 );
911 paint.masked = true;
912 }
913 let region = &self.regions[region_at];
914 if !paint.begun {
915 io.compositor
916 .begin(
917 io.cache,
918 Placement {
919 canvas: paint.p.canvas.clone(),
920 region: region.rect,
921 code: region.code,
922 owned_pixels: region.owned_pixels,
923 x: paint.p.x,
924 y: paint.p.y,
925 frame: index,
926 time: raw.time,
927 confidence: raw.confidence,
928 uncertain: raw.uncertain,
929 occlusions: raw.occlusions.clone().unwrap_or_default(),
930 },
931 self.width,
932 self.height,
933 )
934 .map_err(|e| match e {
935 CompositeError::OutsideCanvas => RenderError::OutsideCanvas {
936 x: paint.p.x,
937 y: paint.p.y,
938 region: region.id.clone(),
939 },
940 e => RenderError::Compositor(e),
941 })?;
942 paint.begun = true;
943 }
944 debug_assert_eq!(self.ring.slots[current].index, index as i64);
945 let frame = Frame {
946 rgba: &self.ring.slots[current].pixels,
947 width: self.width,
948 height: self.height,
949 labels: io.labels,
950 consistent: moving.then_some(&self.mask[..]),
951 };
952 let outcome = match io.compositor.step(io.cache, &frame, io.now) {
953 Ok(CompositorStep::NeedTile(key)) => return Ok(Some(RenderStep::NeedTile(key))),
954 Ok(CompositorStep::NeedTemporal(canvas)) => {
955 return Ok(Some(RenderStep::NeedTemporal(canvas)))
956 }
957 Ok(CompositorStep::Done(outcome)) => outcome,
958 Err(CompositeError::Disposed) => {
959 return Err(RenderError::Disposed(paint.p.canvas.clone()))
960 }
961 Err(e) => return Err(RenderError::Compositor(e)),
962 };
963 paint.begun = false;
964 self.events
965 .extend(outcome.diagnostics.into_iter().map(compositor_diagnostic));
966 let s = outcome.stats;
967 let canvas = paint.p.canvas.clone();
968 if let Some(meta) = self.metas.get_mut(&canvas) {
969 meta.bounds = union(meta.bounds, s.bounds);
970 meta.observed_pixels += s.added as f64;
971 meta.uncertain_pixels += s.uncertain as f64;
972 meta.conflict_pixels += s.conflicts as f64;
973 meta.tile_count += s.tiles as f64;
974 meta.provisional_pixels += s.provisional_pixels as f64;
975 meta.last_time = raw.time;
976 }
977 self.counters.tiles += s.tiles as f64;
978 self.counters.observed_pixels += s.added as f64;
979 self.mark_dirty(&canvas);
980 if moving {
981 job.latest = Some(canvas.clone());
982 }
983 job.decisions.push(decision(
984 canvas,
985 placed(raw, &paint.p),
986 Some((s.added as f64, s.conflicts as f64, s.uncertain as f64)),
987 ));
988 Ok(None)
989 }
990
991 fn neighbours(
994 &self,
995 job: &FrameJob,
996 raw: &PlanPlacement,
997 graph: &PoseGraph,
998 ) -> Result<[Option<NeighbourPlacement>; 2], RenderError> {
999 let input = &job.input;
1000 fn find<'a>(
1001 has: bool,
1002 plan: &'a Option<FramePlan>,
1003 layer: &str,
1004 ) -> Option<&'a PlanPlacement> {
1005 if !has {
1006 return None;
1007 }
1008 plan.as_ref()?
1009 .placements
1010 .iter()
1011 .find(|pl| pl.layer == layer && !pl.skipped())
1012 }
1013 let prev = find(
1014 input.has_prev && input.index > 0,
1015 &input.prev_plan,
1016 &raw.layer,
1017 );
1018 let next = find(input.has_next, &input.next_plan, &raw.layer);
1019 let prev_resolved = match prev {
1020 Some(pl) => Some((self.resolve(pl, input.index as f64 - 1.0, graph)?, pl)),
1021 None => None,
1022 };
1023 let next_resolved = match next {
1024 Some(pl) => Some((self.resolve(pl, input.index as f64 + 1.0, graph)?, pl)),
1025 None => None,
1026 };
1027 let keep = |r: Option<(Resolved, &PlanPlacement)>| {
1028 r.filter(|(p, _)| is_valid_pose(p.x, p.y))
1029 .map(|(resolved, pl)| NeighbourPlacement {
1030 resolved,
1031 occlusions: pl.occlusions.clone().unwrap_or_default(),
1032 slot: None,
1033 })
1034 };
1035 Ok([keep(prev_resolved), keep(next_resolved)])
1036 }
1037}
1038
1039fn compositor_diagnostic(d: CompositorDiagnostic) -> Diagnostic {
1041 match d {
1042 CompositorDiagnostic::IncompletePatch {
1043 canvas,
1044 frame,
1045 time,
1046 region,
1047 } => Diagnostic::new("INCOMPLETE_TEMPORAL_PATCH", Severity::Warning)
1048 .at(frame, time)
1049 .canvas(&canvas)
1050 .region(region),
1051 CompositorDiagnostic::Conflict {
1052 canvas,
1053 frame,
1054 time,
1055 region,
1056 confidence,
1057 regions,
1058 conflicts,
1059 } => Diagnostic::new("TEMPORAL_OR_ALIGNMENT_CONFLICT", Severity::Warning)
1060 .at(frame, time)
1061 .param("conflicts", conflicts as f64)
1062 .canvas(&canvas)
1063 .region(region)
1064 .confidence(confidence)
1065 .detail(Value::Object(vec![(
1066 "regions",
1067 Value::List(regions.into_iter().map(Value::rect).collect()),
1068 )])),
1069 }
1070}
1071
1072fn vote_gets(index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
1074 let index = index as i64;
1075 let mut gets = vec![index];
1076 if has_prev {
1077 gets.push(index - 1);
1078 }
1079 if has_next {
1080 gets.push(index + 1);
1081 }
1082 gets
1083}
1084
1085fn vote<'a>(
1087 record: Option<&'a ConsistencyRecord>,
1088 region: &str,
1089) -> Result<Option<Vote<'a>>, RenderError> {
1090 let Some(v) = record.and_then(|r| r.get(region)) else {
1091 return Ok(None);
1092 };
1093 if v.w <= 0 || v.h <= 0 {
1094 return Err(RenderError::BadVote);
1095 }
1096 let bytes = (v.w as usize * v.h as usize).div_ceil(8);
1097 if v.bits.len() < bytes || v.clean.len() < bytes {
1098 return Err(RenderError::BadVote);
1099 }
1100 Ok(Some(Vote {
1101 x0: v.x0,
1102 y0: v.y0,
1103 w: v.w,
1104 h: v.h,
1105 bits: &v.bits[..bytes],
1106 clean: &v.clean[..bytes],
1107 }))
1108}
1109
1110fn placed(raw: &PlanPlacement, p: &Resolved) -> PlanPlacement {
1112 PlanPlacement {
1113 canvas_id: p.canvas.clone(),
1114 x: p.x,
1115 y: p.y,
1116 ..raw.clone()
1117 }
1118}
1119
1120fn decision(
1122 canvas_id: String,
1123 placement: PlanPlacement,
1124 pixels: Option<(f64, f64, f64)>,
1125) -> RenderDecision {
1126 let (added, conflicts, uncertain) = pixels.unwrap_or_default();
1127 RenderDecision {
1128 canvas_id,
1129 placement,
1130 added_pixels: added,
1131 conflict_pixels: conflicts,
1132 uncertain_pixels: uncertain,
1133 skipped: pixels.is_none(),
1134 reused_exact_observation: false,
1135 }
1136}
1137
1138fn union(a: Rect, b: Rect) -> Rect {
1140 if a.width <= 0.0 || a.height <= 0.0 {
1141 return b;
1142 }
1143 let (x, y) = (a.x.min(b.x), a.y.min(b.y));
1144 Rect {
1145 x,
1146 y,
1147 width: (a.x + a.width).max(b.x + b.width) - x,
1148 height: (a.y + a.height).max(b.y + b.height) - y,
1149 }
1150}
1151
1152#[cfg(test)]
1153mod tests;