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::overscroll::{self, Overscroll};
37use crate::engine::pose_graph::{PoseGraph, PoseGraphError};
38use crate::engine::tiles::{TileCache, TileKey};
39use crate::kernels::consistency::{
40 consistency_mask, fixed_mask, sum_limit, FixedMaskInput, MaskInput, Neighbour, Vote,
41};
42use crate::kernels::fixed_history::FixedHistory;
43use crate::kernels::geometry::{js_round, Rect};
44use crate::kernels::raster::fixed_update;
45use std::collections::{HashMap, HashSet, VecDeque};
46use std::fmt;
47use std::sync::Arc;
48
49const DYNAMIC_SHARE: f64 = 0.2;
54const DYNAMIC_FRAMES: u32 = 10;
55
56pub use crate::engine::solve::{is_valid_pose, POSE_BOUND};
57
58pub const RING_FRAMES: usize = 3;
60pub const VOTING_WINDOW: usize = 4;
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum RegionKind {
66 Moving,
67 Fixed,
68 Ignore,
69}
70
71#[derive(Clone, Debug, PartialEq)]
73pub struct RenderRegion {
74 pub id: String,
75 pub kind: RegionKind,
76 pub rect: Rect,
78 pub code: u8,
80 pub owned_pixels: f64,
81}
82
83#[derive(Clone, Debug, PartialEq)]
86pub struct PlanPlacement {
87 pub layer: String,
88 pub canvas_id: String,
89 pub node: String,
90 pub x: f64,
91 pub y: f64,
92 pub confidence: f64,
93 pub uncertain: bool,
94 pub time: f64,
95 pub skip: Option<bool>,
96 pub occlusions: Option<Vec<Rect>>,
97}
98
99impl PlanPlacement {
100 fn skipped(&self) -> bool {
101 self.skip == Some(true)
102 }
103}
104
105#[derive(Clone, Debug, Default, PartialEq)]
107pub struct FramePlan {
108 pub placements: Vec<PlanPlacement>,
109 pub duplicate: bool,
110}
111
112#[derive(Clone, Debug, PartialEq)]
116pub struct ConsistencyVote {
117 pub x0: i32,
118 pub y0: i32,
119 pub w: i32,
120 pub h: i32,
121 pub bits: Vec<u8>,
122 pub clean: Vec<u8>,
123}
124
125pub type ConsistencyRecord = HashMap<String, ConsistencyVote>;
127
128#[derive(Clone, Copy, Debug, PartialEq)]
131pub struct CanvasMeta {
132 pub bounds: Rect,
133 pub tile_count: f64,
134 pub observed_pixels: f64,
135 pub uncertain_pixels: f64,
136 pub conflict_pixels: f64,
137 pub provisional_pixels: f64,
138 pub last_time: f64,
139}
140
141#[derive(Clone, Debug, PartialEq)]
144pub struct RenderDecision {
145 pub canvas_id: String,
146 pub placement: PlanPlacement,
147 pub added_pixels: f64,
148 pub conflict_pixels: f64,
149 pub uncertain_pixels: f64,
150 pub skipped: bool,
152 pub reused_exact_observation: bool,
154}
155
156#[derive(Clone, Debug, PartialEq)]
158pub struct Observation {
159 pub frame: u32,
160 pub time: f64,
161 pub decisions: Vec<RenderDecision>,
162}
163
164#[derive(Clone, Debug, PartialEq)]
167pub struct FrameOutcome {
168 pub observation: Observation,
169 pub latest: Option<String>,
170}
171
172#[derive(Clone, Copy, Debug, Default, PartialEq)]
174pub struct RenderCounters {
175 pub skipped_paints: u64,
177 pub tiles: f64,
179 pub observed_pixels: f64,
181}
182
183#[derive(Clone, Debug, PartialEq)]
184pub enum RenderStep {
185 NeedMeta(String),
187 NeedFrame(u32),
189 NeedTile(TileKey),
191 NeedTemporal(String),
193 Done(FrameOutcome),
194}
195
196#[derive(Clone, Debug, PartialEq)]
197pub enum RenderError {
198 Busy,
200 NoFrame,
201 FrameSize,
203 MissingVote(i64),
205 BadVote,
207 UnknownRegion(String),
209 MissingMeta(String),
211 Graph(PoseGraphError),
213 OutsideCanvas {
215 x: f64,
216 y: f64,
217 region: String,
218 },
219 Disposed(String),
221 Compositor(CompositeError),
223}
224
225impl fmt::Display for RenderError {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 RenderError::Busy => f.write_str("A frame is already being rendered."),
229 RenderError::NoFrame => f.write_str("No frame is being rendered."),
230 RenderError::FrameSize => f.write_str("Frame does not match the run's size."),
231 RenderError::MissingVote(i) => write!(f, "Voting record {i} was not provided."),
232 RenderError::BadVote => f.write_str("CORE_BAD_ARGUMENT: truncated consistency vote bitset."),
233 RenderError::UnknownRegion(id) => write!(f, "Region {id} is not part of this atlas."),
234 RenderError::MissingMeta(_) => f.write_str("Canvas metadata is missing."),
235 RenderError::Graph(e) => e.fmt(f),
236 RenderError::OutsideCanvas { x, y, region } => write!(
237 f,
238 "Placement ({x}, {y}) puts region {region} outside the addressable canvas."
239 ),
240 RenderError::Disposed(canvas) => write!(
241 f,
242 "CORE_BAD_ARGUMENT: temporalIndex({canvas}) called after dispose() already drained and froze its state."
243 ),
244 RenderError::Compositor(e) => write!(f, "Compositor failed: {e:?}."),
245 }
246 }
247}
248
249impl std::error::Error for RenderError {}
250
251impl From<PoseGraphError> for RenderError {
252 fn from(e: PoseGraphError) -> Self {
253 RenderError::Graph(e)
254 }
255}
256
257pub struct RenderIo<'a> {
259 pub cache: &'a mut TileCache,
260 pub compositor: &'a mut Compositor,
261 pub graph: &'a PoseGraph,
262 pub labels: &'a [u8],
264 pub now: f64,
266}
267
268#[derive(Clone, Debug, PartialEq)]
270pub struct FrameInput {
271 pub index: u32,
272 pub time: f64,
274 pub plan: FramePlan,
275 pub has_prev: bool,
277 pub prev_plan: Option<FramePlan>,
278 pub has_next: bool,
279 pub next_plan: Option<FramePlan>,
280}
281
282#[derive(Clone, Debug, PartialEq)]
284pub struct RenderConfig {
285 pub width: usize,
286 pub height: usize,
287 pub factor: i32,
289 pub noise: f64,
291 pub regions: Vec<RenderRegion>,
293 pub attachments: Vec<Attachment>,
294}
295
296#[derive(Clone, Debug, PartialEq)]
298struct Resolved {
299 canvas: String,
300 x: f64,
301 y: f64,
302}
303
304struct Slot {
306 pixels: Vec<u8>,
307 index: i64,
308 used: u64,
309}
310
311struct FrameRing {
314 slots: Vec<Slot>,
315 tick: u64,
316 bytes: usize,
317}
318
319impl FrameRing {
320 fn new(bytes: usize) -> Self {
321 Self {
322 slots: Vec::with_capacity(RING_FRAMES),
323 tick: 0,
324 bytes,
325 }
326 }
327
328 fn get(&self, index: i64) -> Option<usize> {
329 self.slots.iter().position(|s| s.index == index)
330 }
331
332 fn fill(&mut self, index: i64, keep: Option<(i64, i64)>) -> usize {
337 if let Some(slot) = self.get(index) {
338 return slot;
339 }
340 let slot = if self.slots.len() < RING_FRAMES {
341 self.slots.push(Slot {
342 pixels: vec![0; self.bytes],
343 index,
344 used: 0,
345 });
346 self.slots.len() - 1
347 } else {
348 let kept = |s: &Slot| keep.is_some_and(|(a, b)| (a..=b).contains(&s.index));
349 let oldest = |free: bool| {
350 (0..self.slots.len())
351 .filter(|&i| !free || !kept(&self.slots[i]))
352 .min_by_key(|&i| self.slots[i].used)
353 };
354 oldest(true).or_else(|| oldest(false)).unwrap()
355 };
356 self.tick += 1;
357 let s = &mut self.slots[slot];
358 s.index = index;
359 s.used = self.tick;
360 slot
361 }
362}
363
364#[derive(Default)]
366struct VotingWindow {
367 entries: VecDeque<(i64, Option<Arc<ConsistencyRecord>>)>,
368}
369
370impl VotingWindow {
371 fn wanted(&self, gets: &[i64]) -> Vec<i64> {
374 let mut held: VecDeque<i64> = self.entries.iter().map(|(i, _)| *i).collect();
375 let mut wanted = Vec::new();
376 for &index in gets {
377 if held.contains(&index) {
378 continue;
379 }
380 if index >= 0 {
381 wanted.push(index);
382 }
383 held.push_back(index);
384 if held.len() > VOTING_WINDOW {
385 held.pop_front();
386 }
387 }
388 wanted
389 }
390
391 fn get(
392 &mut self,
393 index: i64,
394 provided: &mut HashMap<i64, Option<ConsistencyRecord>>,
395 ) -> Result<Option<Arc<ConsistencyRecord>>, RenderError> {
396 if let Some((_, record)) = self.entries.iter().find(|(i, _)| *i == index) {
397 return Ok(record.clone());
398 }
399 let record = if index < 0 {
400 None
401 } else {
402 provided
403 .remove(&index)
404 .ok_or(RenderError::MissingVote(index))?
405 .map(Arc::new)
406 };
407 self.entries.push_back((index, record.clone()));
408 if self.entries.len() > VOTING_WINDOW {
409 self.entries.pop_front();
410 }
411 Ok(record)
412 }
413}
414
415struct NeighbourPlacement {
417 resolved: Resolved,
418 occlusions: Vec<Rect>,
419 slot: Option<usize>,
420}
421
422enum Stage {
424 Start,
426 Meta(Resolved),
428 Fixed(Resolved),
430 Decide(Resolved, bool),
432 Paint(Paint),
434}
435
436struct Paint {
437 p: Resolved,
438 current: Option<usize>,
439 neighbours: Option<[Option<NeighbourPlacement>; 2]>,
441 masked: bool,
442 overscroll: Option<[u16; 4]>,
444 begun: bool,
445}
446
447struct FrameJob {
448 input: FrameInput,
449 votes: [Option<Arc<ConsistencyRecord>>; 3],
450 placement: usize,
451 stage: Stage,
452 decisions: Vec<RenderDecision>,
453 latest: Option<String>,
454}
455
456struct FixedState {
458 history: FixedHistory,
459 frames: u32,
461 settled: u32,
462 first: Option<Vec<u32>>,
464}
465
466struct FinishJob {
468 regions: Vec<usize>,
469 at: usize,
470 begun: bool,
471 frame: u32,
472 time: f64,
473}
474
475pub struct RenderSession {
476 width: usize,
477 height: usize,
478 factor: i32,
479 noise: f64,
480 regions: Vec<RenderRegion>,
481 region_index: HashMap<String, usize>,
482 attachments: Attachments,
483 ring: FrameRing,
484 mask: Vec<u8>,
485 fixed: HashMap<String, Vec<u8>>,
486 states: HashMap<String, FixedState>,
487 finish: Option<FinishJob>,
488 last: Option<(u32, f64)>,
489 previous: HashMap<String, Resolved>,
490 votes: VotingWindow,
491 metas: HashMap<String, CanvasMeta>,
492 missing_meta: Option<String>,
493 dirty: Vec<String>,
494 dirty_set: HashSet<String>,
495 counters: RenderCounters,
496 events: Vec<Diagnostic>,
497 job: Option<FrameJob>,
498 overscroll: Overscroll,
499}
500
501impl RenderSession {
502 pub fn new(config: RenderConfig) -> Self {
503 let mut region_index = HashMap::new();
504 for (i, r) in config.regions.iter().enumerate() {
505 region_index.insert(r.id.clone(), i);
506 }
507 let attachments: Attachments = config
508 .attachments
509 .into_iter()
510 .map(|a| (a.id.clone(), a))
511 .collect();
512 let pixels = config.width * config.height;
513 Self {
514 width: config.width,
515 height: config.height,
516 factor: config.factor,
517 noise: config.noise,
518 regions: config.regions,
519 region_index,
520 attachments,
521 ring: FrameRing::new(pixels * 4),
522 mask: vec![1; pixels],
523 fixed: HashMap::new(),
524 states: HashMap::new(),
525 finish: None,
526 last: None,
527 previous: HashMap::new(),
528 votes: VotingWindow::default(),
529 metas: HashMap::new(),
530 missing_meta: None,
531 dirty: Vec::new(),
532 dirty_set: HashSet::new(),
533 counters: RenderCounters::default(),
534 events: Vec::new(),
535 job: None,
536 overscroll: Overscroll::default(),
537 }
538 }
539
540 pub fn add_plan(&mut self, index: u32, plan: &FramePlan) {
544 let (regions, index_of) = (&self.regions, &self.region_index);
545 self.overscroll.add_plan(index, plan, |layer| {
546 index_of
547 .get(layer)
548 .is_some_and(|&i| regions[i].kind == RegionKind::Moving)
549 });
550 }
551
552 pub fn configure_tiles(&self, cache: &mut TileCache, memory_mb: f64) -> Option<Diagnostic> {
557 let fixed_bytes = self
558 .regions
559 .iter()
560 .filter(|r| r.kind == RegionKind::Fixed)
561 .fold(0.0, |n, r| {
562 n + r.rect.width.ceil() * r.rect.height.ceil() * 4.0 + FixedHistory::bytes(r.rect)
563 });
564 let (w, h) = (self.width as f64, self.height as f64);
565 cache.configure_budget(
566 memory_mb,
567 w * h * 5.0 + fixed_bytes + 8.0 * 1024.0 * 1024.0 + w * h * 4.0,
568 );
569 let areas: Vec<(f64, f64)> = self
570 .regions
571 .iter()
572 .filter(|r| r.kind != RegionKind::Ignore)
573 .map(|r| (r.rect.width, r.rect.height))
574 .collect();
575 let raised = cache.ensure_footprint(&areas);
576 if raised == 0 {
577 return None;
578 }
579 let to = cache.max_tiles();
580 let size = cache.size() as f64;
581 Some(
582 Diagnostic::new("MEMORY_BUDGET_RAISED", Severity::Info)
583 .param("from", (to - raised) as f64)
584 .param("to", to as f64)
585 .param(
586 "mb",
587 js_round(to as f64 * size * size * 4.3 / 1024.0 / 1024.0) as f64,
588 )
589 .detail(Value::Object(vec![
590 ("budgetMB", Value::Number(memory_mb)),
591 ("tiles", Value::Number(to as f64)),
592 ("raisedBy", Value::Number(raised as f64)),
593 ])),
594 )
595 }
596
597 pub fn counters(&self) -> RenderCounters {
598 self.counters
599 }
600
601 pub fn meta(&self, canvas: &str) -> Option<CanvasMeta> {
603 self.metas.get(canvas).copied()
604 }
605
606 pub fn take_events(&mut self) -> Vec<Diagnostic> {
611 std::mem::take(&mut self.events)
612 }
613
614 pub fn take_dirty_metas(&mut self) -> Vec<(String, CanvasMeta)> {
617 self.dirty_set.clear();
618 std::mem::take(&mut self.dirty)
619 .into_iter()
620 .map(|id| {
621 let meta = self.metas[&id];
622 (id, meta)
623 })
624 .collect()
625 }
626
627 pub fn wanted_votes(&self, index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
631 self.votes.wanted(&vote_gets(index, has_prev, has_next))
632 }
633
634 pub fn begin(
637 &mut self,
638 input: FrameInput,
639 votes: Vec<(i64, Option<ConsistencyRecord>)>,
640 ) -> Result<(), RenderError> {
641 if self.job.is_some() || self.finish.is_some() {
642 return Err(RenderError::Busy);
643 }
644 self.last = Some((input.index, input.time));
645 let mut provided: HashMap<i64, Option<ConsistencyRecord>> = votes.into_iter().collect();
646 let index = input.index as i64;
647 let own = self.votes.get(index, &mut provided)?;
648 let prev = if input.has_prev {
649 self.votes.get(index - 1, &mut provided)?
650 } else {
651 None
652 };
653 let next = if input.has_next {
654 self.votes.get(index + 1, &mut provided)?
655 } else {
656 None
657 };
658 self.job = Some(FrameJob {
659 input,
660 votes: [own, prev, next],
661 placement: 0,
662 stage: Stage::Start,
663 decisions: Vec::new(),
664 latest: None,
665 });
666 Ok(())
667 }
668
669 pub fn cancel(&mut self, compositor: &mut Compositor) {
673 if self.job.take().is_some() {
674 compositor.cancel();
675 }
676 self.missing_meta = None;
677 }
678
679 pub fn provide_meta(&mut self, canvas: &str, meta: Option<CanvasMeta>) {
682 match meta {
683 Some(meta) => {
684 self.metas.insert(canvas.to_string(), meta);
685 }
686 None => self.missing_meta = Some(canvas.to_string()),
687 }
688 }
689
690 pub fn frame_slot(&mut self, index: u32) -> &mut [u8] {
693 let keep = self.job.as_ref().map(|j| {
694 let at = j.input.index as i64;
695 (at - 1, at + 1)
696 });
697 let slot = self.ring.fill(index as i64, keep);
698 &mut self.ring.slots[slot].pixels
699 }
700
701 pub fn step(&mut self, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
704 if self.finish.is_some() {
705 let result = self.run_finish(io);
706 if result.is_err() {
707 self.finish = None;
708 io.compositor.cancel();
709 }
710 return result;
711 }
712 let mut job = self.job.take().ok_or(RenderError::NoFrame)?;
713 let result = if io.labels.len() != self.width * self.height {
714 Err(RenderError::FrameSize)
715 } else {
716 self.run(&mut job, io)
717 };
718 match result {
719 Ok(RenderStep::Done(_)) => {}
720 Ok(_) => self.job = Some(job),
721 Err(_) => {
722 if matches!(job.stage, Stage::Paint(Paint { begun: true, .. })) {
723 io.compositor.cancel();
724 }
725 self.missing_meta = None;
726 }
727 }
728 result
729 }
730
731 pub fn begin_finish(&mut self) -> Result<bool, RenderError> {
738 if self.job.is_some() || self.finish.is_some() {
739 return Err(RenderError::Busy);
740 }
741 let Some((last, time)) = self.last else {
742 return Ok(false);
743 };
744 let limit = sum_limit(self.noise);
745 let mut regions = Vec::new();
746 for (at, region) in self.regions.iter().enumerate() {
747 let Some(state) = self.states.get_mut(®ion.id) else {
748 continue;
749 };
750 if state.first.is_some() {
751 continue;
752 }
753 state.history.close(last + 1);
754 let dynamic = state.settled > DYNAMIC_FRAMES
755 && state.settled as f64 > DYNAMIC_SHARE * state.frames as f64;
756 state.first = Some(if dynamic {
759 state.history.frame0().to_vec()
760 } else {
761 state.history.first(limit)
762 });
763 if dynamic && self.previous.contains_key(®ion.id) {
764 regions.push(at);
765 }
766 }
767 if regions.is_empty() {
768 return Ok(false);
769 }
770 let frame = self.states[&self.regions[regions[0]].id]
771 .history
772 .first_frame()
773 .unwrap_or(0);
774 self.finish = Some(FinishJob {
775 regions,
776 at: 0,
777 begun: false,
778 frame,
779 time,
780 });
781 Ok(true)
782 }
783
784 pub fn patch_reference(
788 &self,
789 rgba: &mut [u8],
790 width: usize,
791 height: usize,
792 frame: u32,
793 ) -> bool {
794 if width != self.width || height != self.height || rgba.len() != width * height * 4 {
795 return false;
796 }
797 let mut changed = false;
798 for region in &self.regions {
799 let Some(state) = self.states.get(®ion.id) else {
800 continue;
801 };
802 if let (Some(first), Some(from)) = (&state.first, state.history.first_frame()) {
803 if from == frame {
804 changed |= state.history.paint(first, rgba, width, height);
805 }
806 }
807 }
808 changed
809 }
810
811 fn run_finish(&mut self, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
812 loop {
813 let job = self.finish.as_mut().unwrap();
814 let Some(&at) = job.regions.get(job.at) else {
815 let (frame, time) = (job.frame, job.time);
816 self.finish = None;
817 return Ok(RenderStep::Done(FrameOutcome {
818 observation: Observation {
819 frame,
820 time,
821 decisions: Vec::new(),
822 },
823 latest: None,
824 }));
825 };
826 let (begun, frame_index, time) = (job.begun, job.frame, job.time);
827 let region = &self.regions[at];
828 let p = self.previous[®ion.id].clone();
829 let slot = self.ring.fill(-2, None);
830 if !begun {
831 let state = &self.states[®ion.id];
832 state.history.paint(
833 state.first.as_ref().unwrap(),
834 &mut self.ring.slots[slot].pixels,
835 self.width,
836 self.height,
837 );
838 self.mask.fill(1);
839 io.compositor
840 .begin(
841 io.cache,
842 Placement {
843 canvas: p.canvas.clone(),
844 region: region.rect,
845 code: region.code,
846 owned_pixels: region.owned_pixels,
847 x: p.x,
848 y: p.y,
849 frame: frame_index,
850 time,
851 confidence: 1.0,
852 uncertain: false,
853 occlusions: Vec::new(),
854 fixed: true,
855 },
856 self.width,
857 self.height,
858 )
859 .map_err(RenderError::Compositor)?;
860 self.finish.as_mut().unwrap().begun = true;
861 }
862 let frame = Frame {
863 rgba: &self.ring.slots[slot].pixels,
864 width: self.width,
865 height: self.height,
866 labels: io.labels,
867 consistent: Some(&self.mask[..]),
868 };
869 let outcome = match io.compositor.step(io.cache, &frame, io.now) {
870 Ok(CompositorStep::NeedTile(key)) => return Ok(RenderStep::NeedTile(key)),
871 Ok(CompositorStep::NeedTemporal(canvas)) => {
872 return Ok(RenderStep::NeedTemporal(canvas))
873 }
874 Ok(CompositorStep::Done(outcome)) => outcome,
875 Err(e) => return Err(RenderError::Compositor(e)),
876 };
877 if let Some(meta) = self.metas.get_mut(&p.canvas) {
879 meta.observed_pixels += outcome.stats.added as f64;
880 meta.provisional_pixels += outcome.stats.provisional_pixels as f64;
881 }
882 self.mark_dirty(&p.canvas);
883 let job = self.finish.as_mut().unwrap();
884 job.begun = false;
885 job.at += 1;
886 }
887 }
888
889 fn resolve(
890 &self,
891 raw: &PlanPlacement,
892 frame: f64,
893 graph: &PoseGraph,
894 ) -> Result<Resolved, RenderError> {
895 let (cx, cy) = graph.correction(&raw.node, frame)?;
896 let (sx, sy) = render_shift(&self.attachments, &raw.canvas_id, graph)?;
897 let canvas = resolve_target(&self.attachments, &raw.canvas_id).to_string();
898 Ok(Resolved {
899 canvas,
900 x: raw.x + cx + sx,
901 y: raw.y + cy + sy,
902 })
903 }
904
905 fn region(&self, layer: &str) -> Result<usize, RenderError> {
906 self.region_index
907 .get(layer)
908 .copied()
909 .ok_or_else(|| RenderError::UnknownRegion(layer.to_string()))
910 }
911
912 fn mark_dirty(&mut self, canvas: &str) {
913 if self.dirty_set.insert(canvas.to_string()) {
914 self.dirty.push(canvas.to_string());
915 }
916 }
917
918 fn run(&mut self, job: &mut FrameJob, io: &mut RenderIo) -> Result<RenderStep, RenderError> {
919 loop {
920 let Some(raw) = job.input.plan.placements.get(job.placement) else {
921 return Ok(RenderStep::Done(FrameOutcome {
922 observation: Observation {
923 frame: job.input.index,
924 time: job.input.time,
925 decisions: std::mem::take(&mut job.decisions),
926 },
927 latest: job.latest.take(),
928 }));
929 };
930 let raw = raw.clone();
931 let index = job.input.index;
932 match std::mem::replace(&mut job.stage, Stage::Start) {
933 Stage::Start => {
934 if raw.skipped() {
935 job.decisions
936 .push(decision(raw.canvas_id.clone(), raw, None));
937 job.placement += 1;
938 continue;
939 }
940 let p = self.resolve(&raw, index as f64, io.graph)?;
941 if !is_valid_pose(p.x, p.y) {
942 self.events.push(
943 Diagnostic::new("NONFINITE_POSE", Severity::Error)
944 .at(index, raw.time)
945 .canvas(&p.canvas),
946 );
947 let placement = PlanPlacement {
948 canvas_id: p.canvas.clone(),
949 x: 0.0,
950 y: 0.0,
951 ..raw
952 };
953 job.decisions.push(decision(p.canvas, placement, None));
954 job.placement += 1;
955 continue;
956 }
957 job.stage = Stage::Meta(p);
958 }
959 Stage::Meta(p) => {
960 if !self.metas.contains_key(&p.canvas) {
961 if self.missing_meta.take().as_deref() == Some(p.canvas.as_str()) {
962 return Err(RenderError::MissingMeta(p.canvas));
963 }
964 let canvas = p.canvas.clone();
965 job.stage = Stage::Meta(p);
966 return Ok(RenderStep::NeedMeta(canvas));
967 }
968 let region = &self.regions[self.region(&raw.layer)?];
969 let unchanged = job.input.plan.duplicate
970 && self.previous.get(&raw.layer).is_some_and(|last| *last == p);
971 job.stage = if region.kind == RegionKind::Fixed && !unchanged {
972 Stage::Fixed(p)
973 } else {
974 Stage::Decide(p, unchanged)
975 };
976 }
977 Stage::Fixed(p) => {
978 let Some(slot) = self.ring.get(index as i64) else {
979 job.stage = Stage::Fixed(p);
980 return Ok(RenderStep::NeedFrame(index));
981 };
982 let region = &self.regions[self.region(&raw.layer)?];
983 let (rw, rh) = (
984 region.rect.width.ceil() as usize,
985 region.rect.height.ceil() as usize,
986 );
987 let old = self.fixed.contains_key(®ion.id);
988 let saved = self
989 .fixed
990 .entry(region.id.clone())
991 .or_insert_with(|| vec![0; rw * rh * 4]);
992 let changed = fixed_update(
993 saved,
994 &self.ring.slots[slot].pixels,
995 io.labels,
996 self.width,
997 self.height,
998 region.rect.x.floor() as i64,
999 region.rect.y.floor() as i64,
1000 rw,
1001 rh,
1002 region.code,
1003 );
1004 if !old {
1005 let region = &self.regions[self.region(&raw.layer)?];
1006 self.states.insert(
1007 region.id.clone(),
1008 FixedState {
1009 history: FixedHistory::new(region.rect),
1010 frames: 0,
1011 settled: 0,
1012 first: None,
1013 },
1014 );
1015 }
1016 job.stage = Stage::Decide(p, old && !changed);
1017 }
1018 Stage::Decide(p, unchanged) => {
1019 self.previous.insert(raw.layer.clone(), p.clone());
1020 if let Some(state) = self.states.get_mut(&raw.layer) {
1021 state.frames += 1;
1022 }
1023 if !unchanged {
1024 job.stage = Stage::Paint(Paint {
1025 p,
1026 current: None,
1027 neighbours: None,
1028 masked: false,
1029 overscroll: None,
1030 begun: false,
1031 });
1032 continue;
1033 }
1034 let moving = self.regions[self.region(&raw.layer)?].kind == RegionKind::Moving;
1035 self.counters.skipped_paints += 1;
1036 if let Some(meta) = self.metas.get_mut(&p.canvas) {
1037 meta.last_time = raw.time;
1038 }
1039 self.mark_dirty(&p.canvas);
1040 if moving {
1041 job.latest = Some(p.canvas.clone());
1042 }
1043 let at = overscroll::Resolved {
1044 canvas: &p.canvas,
1045 x: p.x,
1046 y: p.y,
1047 };
1048 let left_out = self.overscroll.reuse(&raw.layer, at);
1049 let region = self.regions[self.region(&raw.layer)?].rect;
1050 let placement = placed(&raw, &p, overscroll::strips(region, left_out));
1051 let mut d = decision(p.canvas, placement, Some((0.0, 0.0, 0.0)));
1052 d.reused_exact_observation = true;
1053 job.decisions.push(d);
1054 job.placement += 1;
1055 }
1056 Stage::Paint(mut paint) => {
1057 let step = self.paint(job, &raw, &mut paint, io);
1058 match step {
1059 Ok(None) => {
1060 job.placement += 1;
1061 }
1062 Ok(Some(need)) => {
1063 job.stage = Stage::Paint(paint);
1064 return Ok(need);
1065 }
1066 Err(e) => {
1067 job.stage = Stage::Paint(paint);
1068 return Err(e);
1069 }
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1076 fn paint(
1078 &mut self,
1079 job: &mut FrameJob,
1080 raw: &PlanPlacement,
1081 paint: &mut Paint,
1082 io: &mut RenderIo,
1083 ) -> Result<Option<RenderStep>, RenderError> {
1084 let index = job.input.index;
1085 let region_at = self.region(&raw.layer)?;
1086 let current = match paint.current {
1087 Some(slot) => slot,
1088 None => {
1089 let Some(slot) = self.ring.get(index as i64) else {
1090 return Ok(Some(RenderStep::NeedFrame(index)));
1091 };
1092 paint.current = Some(slot);
1093 slot
1094 }
1095 };
1096 let kind = self.regions[region_at].kind;
1097 let moving = kind == RegionKind::Moving;
1098 let fixed = kind == RegionKind::Fixed;
1099 if (moving || fixed) && !paint.masked {
1100 if paint.neighbours.is_none() {
1101 paint.neighbours = Some(self.neighbours(job, raw, io.graph)?);
1102 }
1103 let neighbours = paint.neighbours.as_mut().unwrap();
1104 for (side, n) in neighbours.iter_mut().enumerate() {
1105 let Some(n) = n else { continue };
1106 if n.slot.is_none() {
1107 let at = if side == 0 { index - 1 } else { index + 1 };
1108 match self.ring.get(at as i64) {
1109 Some(slot) => n.slot = Some(slot),
1110 None => return Ok(Some(RenderStep::NeedFrame(at))),
1111 }
1112 }
1113 }
1114 debug_assert!(neighbours.iter().enumerate().all(|(side, n)| {
1116 n.as_ref().is_none_or(|n| {
1117 self.ring.slots[n.slot.unwrap()].index == index as i64 + 2 * side as i64 - 1
1118 })
1119 }));
1120 let region = &self.regions[region_at];
1121 if fixed {
1122 let [prev, next] = [0, 1].map(|side| {
1124 neighbours[side]
1125 .as_ref()
1126 .filter(|n| n.resolved.canvas == paint.p.canvas)
1127 .map(|n| &self.ring.slots[n.slot.unwrap()].pixels[..])
1128 });
1129 fixed_mask(
1130 &FixedMaskInput {
1131 rgba: &self.ring.slots[current].pixels,
1132 labels: io.labels,
1133 width: self.width,
1134 height: self.height,
1135 region: region.rect,
1136 code: region.code,
1137 prev,
1138 next,
1139 noise: self.noise,
1140 },
1141 &mut self.mask,
1142 );
1143 if let Some(state) = self.states.get_mut(®ion.id) {
1144 state.history.update(
1145 &self.ring.slots[current].pixels,
1146 io.labels,
1147 &self.mask,
1148 self.width,
1149 self.height,
1150 region.code,
1151 index,
1152 sum_limit(self.noise),
1153 );
1154 }
1155 paint.masked = true;
1156 }
1157 }
1158 if moving && !paint.masked {
1159 let neighbours = paint.neighbours.as_ref().unwrap();
1160 let region = &self.regions[region_at];
1161 let own = vote(job.votes[0].as_deref(), ®ion.id)?;
1162 let mut sides: [Option<Neighbour>; 2] = [None, None];
1163 for (side, n) in neighbours.iter().enumerate() {
1164 let Some(n) = n else { continue };
1165 if n.resolved.canvas != paint.p.canvas {
1166 continue;
1167 }
1168 sides[side] = Some(Neighbour {
1169 rgba: &self.ring.slots[n.slot.unwrap()].pixels,
1170 pose_x: n.resolved.x,
1171 pose_y: n.resolved.y,
1172 occlusions: &n.occlusions,
1173 vote: vote(job.votes[side + 1].as_deref(), ®ion.id)?,
1174 });
1175 }
1176 let [prev, next] = sides;
1177 consistency_mask(
1178 &MaskInput {
1179 rgba: &self.ring.slots[current].pixels,
1180 labels: io.labels,
1181 width: self.width,
1182 height: self.height,
1183 region: region.rect,
1184 code: region.code,
1185 pose_x: paint.p.x,
1186 pose_y: paint.p.y,
1187 prev,
1188 next,
1189 vote: own,
1190 factor: self.factor,
1191 noise: self.noise,
1192 },
1193 &mut self.mask,
1194 );
1195 paint.masked = true;
1196 }
1197 if paint.overscroll.is_none() {
1198 let lines = self.left_out(raw, paint, region_at, current, io)?;
1199 paint.overscroll = Some(lines.map(|n| n.clamp(0, u16::MAX as i64) as u16));
1200 }
1201 let left_out = paint.overscroll.unwrap_or_default().map(i64::from);
1202 let strips = overscroll::strips(self.regions[region_at].rect, left_out);
1203 let region = &self.regions[region_at];
1204 if !paint.begun {
1205 let mut occlusions = raw.occlusions.clone().unwrap_or_default();
1206 occlusions.extend(strips.iter().copied());
1207 io.compositor
1208 .begin(
1209 io.cache,
1210 Placement {
1211 canvas: paint.p.canvas.clone(),
1212 region: region.rect,
1213 code: region.code,
1214 owned_pixels: region.owned_pixels,
1215 x: paint.p.x,
1216 y: paint.p.y,
1217 frame: index,
1218 time: raw.time,
1219 confidence: raw.confidence,
1220 uncertain: raw.uncertain,
1221 occlusions,
1222 fixed,
1223 },
1224 self.width,
1225 self.height,
1226 )
1227 .map_err(|e| match e {
1228 CompositeError::OutsideCanvas => RenderError::OutsideCanvas {
1229 x: paint.p.x,
1230 y: paint.p.y,
1231 region: region.id.clone(),
1232 },
1233 e => RenderError::Compositor(e),
1234 })?;
1235 paint.begun = true;
1236 }
1237 debug_assert_eq!(self.ring.slots[current].index, index as i64);
1238 let frame = Frame {
1239 rgba: &self.ring.slots[current].pixels,
1240 width: self.width,
1241 height: self.height,
1242 labels: io.labels,
1243 consistent: (moving || fixed).then_some(&self.mask[..]),
1244 };
1245 let outcome = match io.compositor.step(io.cache, &frame, io.now) {
1246 Ok(CompositorStep::NeedTile(key)) => return Ok(Some(RenderStep::NeedTile(key))),
1247 Ok(CompositorStep::NeedTemporal(canvas)) => {
1248 return Ok(Some(RenderStep::NeedTemporal(canvas)))
1249 }
1250 Ok(CompositorStep::Done(outcome)) => outcome,
1251 Err(CompositeError::Disposed) => {
1252 return Err(RenderError::Disposed(paint.p.canvas.clone()))
1253 }
1254 Err(e) => return Err(RenderError::Compositor(e)),
1255 };
1256 paint.begun = false;
1257 self.events
1258 .extend(outcome.diagnostics.into_iter().map(compositor_diagnostic));
1259 let s = outcome.stats;
1260 if fixed && s.settled_blocks > 0 {
1261 if let Some(state) = self.states.get_mut(&raw.layer) {
1262 state.settled += 1;
1263 }
1264 }
1265 let canvas = paint.p.canvas.clone();
1266 if let Some(meta) = self.metas.get_mut(&canvas) {
1267 let placed_rect = overscroll::trim(s.bounds, left_out);
1268 if placed_rect.width > 0.0 {
1269 meta.bounds = union(meta.bounds, placed_rect);
1270 }
1271 meta.observed_pixels += s.added as f64;
1272 meta.uncertain_pixels += s.uncertain as f64;
1273 meta.conflict_pixels += s.conflicts as f64;
1274 meta.tile_count += s.tiles as f64;
1275 meta.provisional_pixels += s.provisional_pixels as f64;
1276 meta.last_time = raw.time;
1277 }
1278 self.counters.tiles += s.tiles as f64;
1279 self.counters.observed_pixels += s.added as f64;
1280 self.mark_dirty(&canvas);
1281 if moving {
1282 job.latest = Some(canvas.clone());
1283 }
1284 job.decisions.push(decision(
1285 canvas,
1286 placed(raw, &paint.p, strips),
1287 Some((s.added as f64, s.conflicts as f64, s.uncertain as f64)),
1288 ));
1289 Ok(None)
1290 }
1291
1292 fn left_out(
1295 &mut self,
1296 raw: &PlanPlacement,
1297 paint: &Paint,
1298 region_at: usize,
1299 current: usize,
1300 io: &RenderIo,
1301 ) -> Result<[i64; 4], RenderError> {
1302 let graph = io.graph;
1303 let region = &self.regions[region_at];
1304 if region.kind != RegionKind::Moving {
1305 return Ok([0; 4]);
1306 }
1307 let (regions, index_of) = (&self.regions, &self.region_index);
1308 let (attachments, overscroll) = (&self.attachments, &mut self.overscroll);
1309 overscroll.prepare(
1310 |p, frame| {
1312 let resolved = graph
1313 .correction(&p.node, frame as f64)
1314 .and_then(|(cx, cy)| {
1315 let (sx, sy) = render_shift(attachments, &p.canvas_id, graph)?;
1316 Ok((p.x + cx + sx, p.y + cy + sy))
1317 });
1318 let canvas = resolve_target(attachments, &p.canvas_id).to_string();
1319 Ok::<_, RenderError>(
1320 resolved
1321 .ok()
1322 .filter(|&(x, y)| is_valid_pose(x, y))
1323 .map(|(x, y)| (canvas, x, y)),
1324 )
1325 },
1326 |layer| {
1327 let r = regions[*index_of.get(layer)?].rect;
1328 let (_, _, w, h) = overscroll::pixel_rect(r);
1329 Some((w, h))
1330 },
1331 )?;
1332 let moved = |n: &Option<NeighbourPlacement>| {
1334 n.as_ref().and_then(|n| {
1335 let same = n.resolved.canvas == paint.p.canvas
1336 && js_round(n.resolved.x) == js_round(paint.p.x)
1337 && js_round(n.resolved.y) == js_round(paint.p.y);
1338 (!same).then_some(n.slot).flatten()
1339 })
1340 };
1341 let slots = paint
1342 .neighbours
1343 .as_ref()
1344 .map_or([None, None], |[a, b]| [moved(a), moved(b)]);
1345 let ring = &self.ring;
1346 let lines = crate::kernels::overscroll::Lines {
1347 rgba: &ring.slots[current].pixels,
1348 width: self.width,
1349 height: self.height,
1350 labels: Some((io.labels, region.code)),
1351 region: overscroll::pixel_rect(region.rect),
1352 neighbours: slots.map(|s| s.map(|s| &ring.slots[s].pixels[..])),
1353 noise: self.noise,
1354 };
1355 let at = overscroll::Resolved {
1356 canvas: &paint.p.canvas,
1357 x: paint.p.x,
1358 y: paint.p.y,
1359 };
1360 Ok(self.overscroll.measure(&raw.layer, at, &lines))
1361 }
1362
1363 fn neighbours(
1366 &self,
1367 job: &FrameJob,
1368 raw: &PlanPlacement,
1369 graph: &PoseGraph,
1370 ) -> Result<[Option<NeighbourPlacement>; 2], RenderError> {
1371 let input = &job.input;
1372 fn find<'a>(
1373 has: bool,
1374 plan: &'a Option<FramePlan>,
1375 layer: &str,
1376 ) -> Option<&'a PlanPlacement> {
1377 if !has {
1378 return None;
1379 }
1380 plan.as_ref()?
1381 .placements
1382 .iter()
1383 .find(|pl| pl.layer == layer && !pl.skipped())
1384 }
1385 let prev = find(
1386 input.has_prev && input.index > 0,
1387 &input.prev_plan,
1388 &raw.layer,
1389 );
1390 let next = find(input.has_next, &input.next_plan, &raw.layer);
1391 let prev_resolved = match prev {
1392 Some(pl) => Some((self.resolve(pl, input.index as f64 - 1.0, graph)?, pl)),
1393 None => None,
1394 };
1395 let next_resolved = match next {
1396 Some(pl) => Some((self.resolve(pl, input.index as f64 + 1.0, graph)?, pl)),
1397 None => None,
1398 };
1399 let keep = |r: Option<(Resolved, &PlanPlacement)>| {
1400 r.filter(|(p, _)| is_valid_pose(p.x, p.y))
1401 .map(|(resolved, pl)| NeighbourPlacement {
1402 resolved,
1403 occlusions: pl.occlusions.clone().unwrap_or_default(),
1404 slot: None,
1405 })
1406 };
1407 Ok([keep(prev_resolved), keep(next_resolved)])
1408 }
1409}
1410
1411fn compositor_diagnostic(d: CompositorDiagnostic) -> Diagnostic {
1413 match d {
1414 CompositorDiagnostic::IncompletePatch {
1415 canvas,
1416 frame,
1417 time,
1418 region,
1419 } => Diagnostic::new("INCOMPLETE_TEMPORAL_PATCH", Severity::Warning)
1420 .at(frame, time)
1421 .canvas(&canvas)
1422 .region(region),
1423 CompositorDiagnostic::Conflict {
1424 canvas,
1425 frame,
1426 time,
1427 region,
1428 confidence,
1429 regions,
1430 conflicts,
1431 } => Diagnostic::new("TEMPORAL_OR_ALIGNMENT_CONFLICT", Severity::Warning)
1432 .at(frame, time)
1433 .param("conflicts", conflicts as f64)
1434 .canvas(&canvas)
1435 .region(region)
1436 .confidence(confidence)
1437 .detail(Value::Object(vec![(
1438 "regions",
1439 Value::List(regions.into_iter().map(Value::rect).collect()),
1440 )])),
1441 }
1442}
1443
1444fn vote_gets(index: u32, has_prev: bool, has_next: bool) -> Vec<i64> {
1446 let index = index as i64;
1447 let mut gets = vec![index];
1448 if has_prev {
1449 gets.push(index - 1);
1450 }
1451 if has_next {
1452 gets.push(index + 1);
1453 }
1454 gets
1455}
1456
1457fn vote<'a>(
1459 record: Option<&'a ConsistencyRecord>,
1460 region: &str,
1461) -> Result<Option<Vote<'a>>, RenderError> {
1462 let Some(v) = record.and_then(|r| r.get(region)) else {
1463 return Ok(None);
1464 };
1465 if v.w <= 0 || v.h <= 0 {
1466 return Err(RenderError::BadVote);
1467 }
1468 let bytes = (v.w as usize * v.h as usize).div_ceil(8);
1469 if v.bits.len() < bytes || v.clean.len() < bytes {
1470 return Err(RenderError::BadVote);
1471 }
1472 Ok(Some(Vote {
1473 x0: v.x0,
1474 y0: v.y0,
1475 w: v.w,
1476 h: v.h,
1477 bits: &v.bits[..bytes],
1478 clean: &v.clean[..bytes],
1479 }))
1480}
1481
1482fn placed(raw: &PlanPlacement, p: &Resolved, left_out: Vec<Rect>) -> PlanPlacement {
1485 let mut occlusions = raw.occlusions.clone();
1486 if !left_out.is_empty() {
1487 occlusions.get_or_insert_with(Vec::new).extend(left_out);
1488 }
1489 PlanPlacement {
1490 canvas_id: p.canvas.clone(),
1491 x: p.x,
1492 y: p.y,
1493 occlusions,
1494 ..raw.clone()
1495 }
1496}
1497
1498fn decision(
1500 canvas_id: String,
1501 placement: PlanPlacement,
1502 pixels: Option<(f64, f64, f64)>,
1503) -> RenderDecision {
1504 let (added, conflicts, uncertain) = pixels.unwrap_or_default();
1505 RenderDecision {
1506 canvas_id,
1507 placement,
1508 added_pixels: added,
1509 conflict_pixels: conflicts,
1510 uncertain_pixels: uncertain,
1511 skipped: pixels.is_none(),
1512 reused_exact_observation: false,
1513 }
1514}
1515
1516fn union(a: Rect, b: Rect) -> Rect {
1518 if a.width <= 0.0 || a.height <= 0.0 {
1519 return b;
1520 }
1521 let (x, y) = (a.x.min(b.x), a.y.min(b.y));
1522 Rect {
1523 x,
1524 y,
1525 width: (a.x + a.width).max(b.x + b.width) - x,
1526 height: (a.y + a.height).max(b.y + b.height) - y,
1527 }
1528}
1529
1530#[cfg(test)]
1531mod fixed_tests;
1532#[cfg(test)]
1533mod tests;