use crate::backends::RenderBackend;
use crate::pipeline::Pipeline;
use crate::utils::RenderError;
use ass_core::parser::Script;
#[cfg(feature = "nostd")]
use alloc::{boxed::Box, vec::Vec};
#[cfg(not(feature = "nostd"))]
use std::boxed::Box;
mod context;
mod event_selector;
mod frame;
mod metrics;
mod probing;
mod time_index;
pub use context::RenderContext;
pub use event_selector::{ActiveEvents, DirtyRegion, EventSelector};
pub use frame::Frame;
pub use metrics::{CacheStatistics, PerformanceMetrics};
pub use probing::BackendProber;
pub struct Renderer {
context: RenderContext,
backend: Box<dyn RenderBackend>,
pipeline: Box<dyn Pipeline>,
event_selector: event_selector::EventSelector,
frame_cache: Option<(Vec<(usize, usize)>, Frame)>,
}
impl Renderer {
pub fn new(
backend_type: crate::backends::BackendType,
context: RenderContext,
) -> Result<Self, RenderError> {
let backend =
crate::backends::create_backend(backend_type, context.width(), context.height())?;
let pipeline = backend.create_pipeline()?;
Ok(Self {
context,
backend,
pipeline,
event_selector: event_selector::EventSelector::new(),
frame_cache: None,
})
}
pub fn with_backend(
context: RenderContext,
backend: Box<dyn RenderBackend>,
) -> Result<Self, RenderError> {
let pipeline = backend.create_pipeline()?;
Ok(Self {
context,
backend,
pipeline,
event_selector: event_selector::EventSelector::new(),
frame_cache: None,
})
}
#[cfg(feature = "backend-probing")]
pub fn with_auto_backend(context: RenderContext) -> Result<Self, RenderError> {
let prober = BackendProber::new();
let backend = prober.probe_best_backend(&context)?;
Self::with_backend(context, backend)
}
pub fn render_frame(&mut self, script: &Script, time_cs: u32) -> Result<Frame, RenderError> {
for section in script.sections() {
if let ass_core::parser::Section::ScriptInfo(info) = section {
if let Some((play_x, play_y)) = info.play_resolution() {
self.context.set_playback_resolution(play_x, play_y);
}
if let Some((layout_x, layout_y)) = info.layout_resolution() {
self.context.set_storage_resolution(layout_x, layout_y);
}
break; }
}
let active = self.event_selector.select_active(script, time_cs)?;
let events = active.events;
if events.is_empty() {
return Ok(Frame::empty(
self.context.width(),
self.context.height(),
time_cs,
));
}
let animated = events.iter().any(|e| Self::event_is_animated(e.text));
let cache_key: Option<Vec<(usize, usize)>> = (!animated).then(|| {
events
.iter()
.map(|e| (e.text.as_ptr() as usize, e.text.len()))
.collect()
});
if let (Some(key), Some((cached_key, cached))) =
(cache_key.as_ref(), self.frame_cache.as_ref())
{
if cached_key == key {
return Ok(cached.with_timestamp(time_cs));
}
}
self.pipeline.prepare_script(script, None)?;
let layers = self
.pipeline
.process_events(&events, time_cs, &self.context)?;
let frame_data = self.backend.composite_layers(&layers, &self.context)?;
let frame = Frame::new(
frame_data,
self.context.width(),
self.context.height(),
time_cs,
);
self.frame_cache = cache_key.map(|key| (key, frame.clone()));
Ok(frame)
}
#[cfg(feature = "software-backend")]
pub fn render_frame_bitmaps(
&mut self,
script: &Script,
time_cs: u32,
) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
for section in script.sections() {
if let ass_core::parser::Section::ScriptInfo(info) = section {
if let Some((play_x, play_y)) = info.play_resolution() {
self.context.set_playback_resolution(play_x, play_y);
}
if let Some((layout_x, layout_y)) = info.layout_resolution() {
self.context.set_storage_resolution(layout_x, layout_y);
}
break;
}
}
let active = self.event_selector.select_active(script, time_cs)?;
let events = active.events;
if events.is_empty() {
return Ok(Vec::new());
}
self.pipeline.prepare_script(script, None)?;
let layers = self
.pipeline
.process_events(&events, time_cs, &self.context)?;
self.backend
.render_layers_to_bitmaps(&layers, &self.context)
}
fn event_is_animated(text: &str) -> bool {
text.contains("\\t")
|| text.contains("\\move")
|| text.contains("\\fad")
|| text.contains("\\k")
|| text.contains("\\K")
}
pub fn render_frame_incremental(
&mut self,
script: &Script,
time_cs: u32,
previous_frame: &Frame,
) -> Result<Frame, RenderError> {
let active = self.event_selector.select_active(script, time_cs)?;
let events = active.events;
let dirty_regions =
self.pipeline
.compute_dirty_regions(&events, time_cs, previous_frame.timestamp())?;
if dirty_regions.is_empty() {
return Ok(previous_frame.clone());
}
self.pipeline.prepare_script(script, None)?;
let layers = self
.pipeline
.process_events(&events, time_cs, &self.context)?;
let frame_data = self.backend.composite_layers_incremental(
&layers,
&dirty_regions,
previous_frame.data(),
&self.context,
)?;
Ok(Frame::new(
frame_data,
self.context.width(),
self.context.height(),
time_cs,
))
}
pub fn backend_type(&self) -> crate::backends::BackendType {
self.backend.backend_type()
}
#[cfg(feature = "backend-metrics")]
pub fn backend_metrics(&self) -> Option<crate::backends::BackendMetrics> {
self.backend.metrics()
}
pub fn set_context(&mut self, context: RenderContext) {
self.context = context;
}
pub fn context(&self) -> &RenderContext {
&self.context
}
pub fn context_mut(&mut self) -> &mut RenderContext {
&mut self.context
}
pub fn set_collision_resolver(
&mut self,
_resolver: Box<dyn crate::collision::CollisionDetector>,
) {
}
pub fn metrics(&self) -> Option<PerformanceMetrics> {
None
}
pub fn cache_stats(&self) -> CacheStatistics {
CacheStatistics {
glyph_hits: 0,
font_entries: 0,
}
}
}