1use crate::backends::RenderBackend;
4use crate::pipeline::Pipeline;
5use crate::utils::RenderError;
6use ass_core::parser::Script;
7
8#[cfg(feature = "nostd")]
9use alloc::{boxed::Box, vec::Vec};
10#[cfg(not(feature = "nostd"))]
11use std::boxed::Box;
12
13mod context;
14mod event_selector;
15mod frame;
16mod metrics;
17mod probing;
18mod time_index;
19
20pub use context::RenderContext;
21pub use event_selector::{ActiveEvents, DirtyRegion, EventSelector};
22pub use frame::Frame;
23pub use metrics::{CacheStatistics, PerformanceMetrics};
24pub use probing::BackendProber;
25
26pub struct Renderer {
28 context: RenderContext,
29 backend: Box<dyn RenderBackend>,
30 pipeline: Box<dyn Pipeline>,
31 event_selector: event_selector::EventSelector,
32 frame_cache: Option<(Vec<(usize, usize)>, Frame)>,
37}
38
39impl Renderer {
40 pub fn new(
42 backend_type: crate::backends::BackendType,
43 context: RenderContext,
44 ) -> Result<Self, RenderError> {
45 let backend =
46 crate::backends::create_backend(backend_type, context.width(), context.height())?;
47 let pipeline = backend.create_pipeline()?;
48
49 Ok(Self {
50 context,
51 backend,
52 pipeline,
53 event_selector: event_selector::EventSelector::new(),
54 frame_cache: None,
55 })
56 }
57
58 pub fn with_backend(
60 context: RenderContext,
61 backend: Box<dyn RenderBackend>,
62 ) -> Result<Self, RenderError> {
63 let pipeline = backend.create_pipeline()?;
64
65 Ok(Self {
66 context,
67 backend,
68 pipeline,
69 event_selector: event_selector::EventSelector::new(),
70 frame_cache: None,
71 })
72 }
73
74 #[cfg(feature = "backend-probing")]
76 pub fn with_auto_backend(context: RenderContext) -> Result<Self, RenderError> {
77 let prober = BackendProber::new();
78 let backend = prober.probe_best_backend(&context)?;
79 Self::with_backend(context, backend)
80 }
81
82 pub fn render_frame(&mut self, script: &Script, time_cs: u32) -> Result<Frame, RenderError> {
84 for section in script.sections() {
86 if let ass_core::parser::Section::ScriptInfo(info) = section {
87 if let Some((play_x, play_y)) = info.play_resolution() {
88 self.context.set_playback_resolution(play_x, play_y);
89 }
90 if let Some((layout_x, layout_y)) = info.layout_resolution() {
91 self.context.set_storage_resolution(layout_x, layout_y);
92 }
93 break; }
95 }
96
97 let active = self.event_selector.select_active(script, time_cs)?;
98 let events = active.events;
99
100 if events.is_empty() {
101 return Ok(Frame::empty(
102 self.context.width(),
103 self.context.height(),
104 time_cs,
105 ));
106 }
107
108 let animated = events.iter().any(|e| Self::event_is_animated(e.text));
113 let cache_key: Option<Vec<(usize, usize)>> = (!animated).then(|| {
114 events
115 .iter()
116 .map(|e| (e.text.as_ptr() as usize, e.text.len()))
117 .collect()
118 });
119 if let (Some(key), Some((cached_key, cached))) =
120 (cache_key.as_ref(), self.frame_cache.as_ref())
121 {
122 if cached_key == key {
123 return Ok(cached.with_timestamp(time_cs));
124 }
125 }
126
127 self.pipeline.prepare_script(script, None)?;
131 let layers = self
132 .pipeline
133 .process_events(&events, time_cs, &self.context)?;
134 let frame_data = self.backend.composite_layers(&layers, &self.context)?;
135
136 let frame = Frame::new(
137 frame_data,
138 self.context.width(),
139 self.context.height(),
140 time_cs,
141 );
142 self.frame_cache = cache_key.map(|key| (key, frame.clone()));
143 Ok(frame)
144 }
145
146 #[cfg(feature = "software-backend")]
154 pub fn render_frame_bitmaps(
155 &mut self,
156 script: &Script,
157 time_cs: u32,
158 ) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
159 for section in script.sections() {
160 if let ass_core::parser::Section::ScriptInfo(info) = section {
161 if let Some((play_x, play_y)) = info.play_resolution() {
162 self.context.set_playback_resolution(play_x, play_y);
163 }
164 if let Some((layout_x, layout_y)) = info.layout_resolution() {
165 self.context.set_storage_resolution(layout_x, layout_y);
166 }
167 break;
168 }
169 }
170
171 let active = self.event_selector.select_active(script, time_cs)?;
172 let events = active.events;
173 if events.is_empty() {
174 return Ok(Vec::new());
175 }
176
177 self.pipeline.prepare_script(script, None)?;
178 let layers = self
179 .pipeline
180 .process_events(&events, time_cs, &self.context)?;
181 self.backend
182 .render_layers_to_bitmaps(&layers, &self.context)
183 }
184
185 fn event_is_animated(text: &str) -> bool {
189 text.contains("\\t")
190 || text.contains("\\move")
191 || text.contains("\\fad")
192 || text.contains("\\k")
193 || text.contains("\\K")
194 }
195
196 pub fn render_frame_incremental(
198 &mut self,
199 script: &Script,
200 time_cs: u32,
201 previous_frame: &Frame,
202 ) -> Result<Frame, RenderError> {
203 let active = self.event_selector.select_active(script, time_cs)?;
204 let events = active.events;
205 let dirty_regions =
206 self.pipeline
207 .compute_dirty_regions(&events, time_cs, previous_frame.timestamp())?;
208
209 if dirty_regions.is_empty() {
210 return Ok(previous_frame.clone());
211 }
212
213 self.pipeline.prepare_script(script, None)?;
214 let layers = self
215 .pipeline
216 .process_events(&events, time_cs, &self.context)?;
217 let frame_data = self.backend.composite_layers_incremental(
218 &layers,
219 &dirty_regions,
220 previous_frame.data(),
221 &self.context,
222 )?;
223
224 Ok(Frame::new(
225 frame_data,
226 self.context.width(),
227 self.context.height(),
228 time_cs,
229 ))
230 }
231
232 pub fn backend_type(&self) -> crate::backends::BackendType {
234 self.backend.backend_type()
235 }
236
237 #[cfg(feature = "backend-metrics")]
239 pub fn backend_metrics(&self) -> Option<crate::backends::BackendMetrics> {
240 self.backend.metrics()
241 }
242
243 pub fn set_context(&mut self, context: RenderContext) {
245 self.context = context;
246 }
247
248 pub fn context(&self) -> &RenderContext {
250 &self.context
251 }
252
253 pub fn context_mut(&mut self) -> &mut RenderContext {
255 &mut self.context
256 }
257
258 pub fn set_collision_resolver(
260 &mut self,
261 _resolver: Box<dyn crate::collision::CollisionDetector>,
262 ) {
263 }
265
266 pub fn metrics(&self) -> Option<PerformanceMetrics> {
268 None
270 }
271
272 pub fn cache_stats(&self) -> CacheStatistics {
274 CacheStatistics {
275 glyph_hits: 0,
276 font_entries: 0,
277 }
278 }
279}