oximedia_edit/
incremental_render.rs1use oximedia_core::Rational;
8
9use crate::error::EditResult;
10use crate::render::RenderConfig;
11use crate::timeline::Timeline;
12use std::sync::Arc;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct DirtyRegion {
21 pub start_frame: u64,
23 pub end_frame: u64,
25}
26
27impl DirtyRegion {
28 #[must_use]
33 pub fn new(start_frame: u64, end_frame: u64) -> Self {
34 let end_frame = end_frame.max(start_frame + 1);
35 Self {
36 start_frame,
37 end_frame,
38 }
39 }
40
41 #[must_use]
43 pub fn overlaps(&self, other: &DirtyRegion) -> bool {
44 self.start_frame <= other.end_frame && other.start_frame <= self.end_frame
46 }
47
48 #[must_use]
50 pub fn merge(&self, other: &DirtyRegion) -> DirtyRegion {
51 DirtyRegion {
52 start_frame: self.start_frame.min(other.start_frame),
53 end_frame: self.end_frame.max(other.end_frame),
54 }
55 }
56
57 #[must_use]
59 pub fn frame_count(&self) -> u64 {
60 self.end_frame.saturating_sub(self.start_frame)
61 }
62
63 #[must_use]
65 pub fn contains(&self, frame: u64) -> bool {
66 frame >= self.start_frame && frame < self.end_frame
67 }
68}
69
70pub struct IncrementalRenderer {
79 dirty_regions: Vec<DirtyRegion>,
81 pub config: RenderConfig,
83 pub frame_rate: Rational,
85}
86
87impl IncrementalRenderer {
88 #[must_use]
90 pub fn new(config: RenderConfig, frame_rate: Rational) -> Self {
91 Self {
92 dirty_regions: Vec::new(),
93 config,
94 frame_rate,
95 }
96 }
97
98 pub fn mark_dirty(&mut self, start_frame: u64, end_frame: u64) {
103 self.dirty_regions
104 .push(DirtyRegion::new(start_frame, end_frame));
105 self.coalesce();
106 }
107
108 pub fn mark_all_dirty(&mut self, total_frames: u64) {
110 if total_frames == 0 {
111 return;
112 }
113 self.dirty_regions = vec![DirtyRegion::new(0, total_frames)];
114 }
115
116 #[must_use]
118 pub fn is_dirty(&self, frame: u64) -> bool {
119 self.dirty_regions.iter().any(|r| r.contains(frame))
120 }
121
122 #[must_use]
124 pub fn get_dirty_regions(&self) -> &[DirtyRegion] {
125 &self.dirty_regions
126 }
127
128 #[must_use]
130 pub fn dirty_frame_count(&self) -> u64 {
131 self.dirty_regions
132 .iter()
133 .map(DirtyRegion::frame_count)
134 .sum()
135 }
136
137 pub fn clear_dirty(&mut self) {
139 self.dirty_regions.clear();
140 }
141
142 pub fn render_incremental(&mut self, _timeline: &Arc<Timeline>) -> EditResult<usize> {
150 let count = self.dirty_frame_count() as usize;
151 self.clear_dirty();
152 Ok(count)
153 }
154
155 fn coalesce(&mut self) {
159 if self.dirty_regions.len() <= 1 {
160 return;
161 }
162
163 self.dirty_regions.sort_by_key(|r| r.start_frame);
165
166 let mut merged: Vec<DirtyRegion> = Vec::with_capacity(self.dirty_regions.len());
167 for region in &self.dirty_regions {
168 if let Some(last) = merged.last_mut() {
169 if last.end_frame >= region.start_frame {
170 last.end_frame = last.end_frame.max(region.end_frame);
172 continue;
173 }
174 }
175 merged.push(*region);
176 }
177
178 self.dirty_regions = merged;
179 }
180}
181
182#[cfg(test)]
187mod tests {
188 use super::*;
189 use oximedia_core::Rational;
190
191 fn renderer() -> IncrementalRenderer {
192 IncrementalRenderer::new(RenderConfig::default(), Rational::new(30, 1))
193 }
194
195 #[test]
196 fn test_mark_dirty_and_is_dirty() {
197 let mut r = renderer();
198 assert!(!r.is_dirty(10));
199 r.mark_dirty(5, 20);
200 assert!(r.is_dirty(5));
201 assert!(r.is_dirty(10));
202 assert!(r.is_dirty(19));
203 assert!(!r.is_dirty(20));
204 assert!(!r.is_dirty(4));
205 }
206
207 #[test]
208 fn test_clear_dirty() {
209 let mut r = renderer();
210 r.mark_dirty(0, 100);
211 assert_eq!(r.dirty_frame_count(), 100);
212 r.clear_dirty();
213 assert_eq!(r.dirty_frame_count(), 0);
214 assert!(r.get_dirty_regions().is_empty());
215 }
216
217 #[test]
218 fn test_merge_overlapping_regions() {
219 let mut r = renderer();
220 r.mark_dirty(0, 50);
221 r.mark_dirty(30, 80);
222 assert_eq!(r.get_dirty_regions().len(), 1);
224 assert_eq!(r.get_dirty_regions()[0].start_frame, 0);
225 assert_eq!(r.get_dirty_regions()[0].end_frame, 80);
226 assert_eq!(r.dirty_frame_count(), 80);
227 }
228
229 #[test]
230 fn test_merge_adjacent_regions() {
231 let mut r = renderer();
232 r.mark_dirty(0, 10);
233 r.mark_dirty(10, 20);
234 assert_eq!(r.get_dirty_regions().len(), 1);
236 assert_eq!(r.get_dirty_regions()[0].end_frame, 20);
237 }
238
239 #[test]
240 fn test_non_overlapping_regions_stay_separate() {
241 let mut r = renderer();
242 r.mark_dirty(0, 10);
243 r.mark_dirty(20, 30);
244 assert_eq!(r.get_dirty_regions().len(), 2);
245 assert_eq!(r.dirty_frame_count(), 20);
246 }
247
248 #[test]
249 fn test_mark_all_dirty() {
250 let mut r = renderer();
251 r.mark_dirty(5, 10);
252 r.mark_all_dirty(1000);
253 assert_eq!(r.get_dirty_regions().len(), 1);
254 assert_eq!(r.dirty_frame_count(), 1000);
255 }
256
257 #[test]
258 fn test_render_incremental_clears_dirty() {
259 let mut r = renderer();
260 r.mark_dirty(0, 60);
261 let timeline = std::sync::Arc::new(crate::timeline::Timeline::default());
262 let count = r
263 .render_incremental(&timeline)
264 .expect("render_incremental ok");
265 assert_eq!(count, 60);
266 assert!(r.get_dirty_regions().is_empty());
267 }
268
269 #[test]
270 fn test_dirty_region_frame_count() {
271 let region = DirtyRegion::new(10, 50);
272 assert_eq!(region.frame_count(), 40);
273 }
274
275 #[test]
276 fn test_dirty_region_merge() {
277 let a = DirtyRegion::new(0, 10);
278 let b = DirtyRegion::new(5, 20);
279 let merged = a.merge(&b);
280 assert_eq!(merged.start_frame, 0);
281 assert_eq!(merged.end_frame, 20);
282 }
283}