Skip to main content

ass_renderer/backends/
mod.rs

1//! Rendering backend trait and implementations
2
3use crate::pipeline::{IntermediateLayer, Pipeline};
4use crate::renderer::RenderContext;
5use crate::utils::{DirtyRegion, RenderError};
6
7#[cfg(feature = "nostd")]
8use alloc::{boxed::Box, format, vec::Vec};
9#[cfg(not(feature = "nostd"))]
10use std::{boxed::Box, vec::Vec};
11
12#[cfg(feature = "software-backend")]
13pub mod blur;
14
15#[cfg(feature = "software-backend")]
16pub mod coverage;
17
18#[cfg(feature = "software-backend")]
19pub mod geometry;
20
21#[cfg(feature = "software-backend")]
22pub mod raster;
23
24#[cfg(feature = "software-backend")]
25pub mod software;
26
27#[cfg(feature = "gpu")]
28pub mod gpu;
29
30/// Backend type enumeration
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum BackendType {
33    /// Auto-detect best available backend
34    Auto,
35    /// CPU-based software renderer
36    Software,
37    /// Native hybrid GPU compositor (wgpu) over software-produced tiles
38    Gpu,
39}
40
41impl BackendType {
42    /// Get backend type as string
43    pub fn as_str(&self) -> &'static str {
44        match self {
45            Self::Auto => "Auto",
46            Self::Software => "Software",
47            Self::Gpu => "Gpu",
48        }
49    }
50}
51
52/// Core rendering backend trait
53pub trait RenderBackend: Send + Sync {
54    /// Get the backend type
55    fn backend_type(&self) -> BackendType;
56
57    /// Create a pipeline for this backend
58    fn create_pipeline(&self) -> Result<Box<dyn Pipeline>, RenderError>;
59
60    /// Composite layers into final frame
61    fn composite_layers(
62        &mut self,
63        layers: &[IntermediateLayer],
64        context: &RenderContext,
65    ) -> Result<Vec<u8>, RenderError>;
66
67    /// Composite layers incrementally (dirty regions only)
68    fn composite_layers_incremental(
69        &mut self,
70        layers: &[IntermediateLayer],
71        dirty_regions: &[DirtyRegion],
72        previous_frame: &[u8],
73        context: &RenderContext,
74    ) -> Result<Vec<u8>, RenderError> {
75        // Default implementation: full re-render
76        let _ = (dirty_regions, previous_frame);
77        self.composite_layers(layers, context)
78    }
79
80    /// Render layers to a positioned bitmap list (libass `ASS_Image` style)
81    /// instead of a composited frame buffer, skipping the full-frame clear/copy.
82    /// The default reports the backend does not support it.
83    #[cfg(feature = "software-backend")]
84    fn render_layers_to_bitmaps(
85        &mut self,
86        layers: &[IntermediateLayer],
87        context: &RenderContext,
88    ) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
89        let _ = (layers, context);
90        Err(RenderError::BackendError(
91            "bitmap-list output not supported by this backend".into(),
92        ))
93    }
94
95    /// Check if backend supports a specific feature
96    fn supports_feature(&self, feature: BackendFeature) -> bool {
97        match feature {
98            BackendFeature::IncrementalRendering => false,
99            BackendFeature::HardwareAcceleration => false,
100            BackendFeature::ComputeShaders => false,
101            BackendFeature::AsyncRendering => false,
102        }
103    }
104
105    /// Get backend metrics if available
106    #[cfg(feature = "backend-metrics")]
107    fn metrics(&self) -> Option<BackendMetrics> {
108        None
109    }
110}
111
112/// Backend feature capabilities
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum BackendFeature {
115    /// Supports incremental rendering of dirty regions
116    IncrementalRendering,
117    /// Hardware accelerated rendering
118    HardwareAcceleration,
119    /// Compute shader support
120    ComputeShaders,
121    /// Async rendering operations
122    AsyncRendering,
123}
124
125/// Backend performance metrics
126#[cfg(feature = "backend-metrics")]
127#[derive(Debug, Clone)]
128pub struct BackendMetrics {
129    /// VRAM usage in bytes
130    pub vram_usage: u64,
131    /// Number of draw calls per frame
132    pub draw_calls: usize,
133    /// Batch threshold for events
134    pub batch_threshold: usize,
135    /// Average frame time in milliseconds
136    pub avg_frame_time_ms: f32,
137    /// Peak frame time in milliseconds
138    pub peak_frame_time_ms: f32,
139}
140
141#[cfg(feature = "backend-metrics")]
142impl BackendMetrics {
143    /// Create new metrics with defaults
144    pub fn new() -> Self {
145        Self {
146            vram_usage: 0,
147            draw_calls: 0,
148            batch_threshold: 100,
149            avg_frame_time_ms: 0.0,
150            peak_frame_time_ms: 0.0,
151        }
152    }
153}
154
155#[cfg(feature = "backend-metrics")]
156impl Default for BackendMetrics {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162/// Create a backend instance for the given type
163pub fn create_backend(
164    backend_type: BackendType,
165    width: u32,
166    height: u32,
167) -> Result<Box<dyn RenderBackend>, RenderError> {
168    match backend_type {
169        BackendType::Auto => {
170            // The software backend is the fast, always-available, parity-perfect
171            // default; the GPU backend is opt-in via `BackendType::Gpu`.
172            #[cfg(feature = "software-backend")]
173            return create_backend(BackendType::Software, width, height);
174
175            #[allow(unreachable_code)]
176            Err(RenderError::BackendError("No backend available".into()))
177        }
178
179        #[cfg(feature = "software-backend")]
180        BackendType::Software => {
181            let context = crate::renderer::RenderContext::new(width, height);
182            let backend = software::SoftwareBackend::new(&context)?;
183            Ok(Box::new(backend))
184        }
185
186        #[cfg(feature = "gpu")]
187        BackendType::Gpu => {
188            let backend = gpu::GpuBackend::new(width, height)?;
189            Ok(Box::new(backend))
190        }
191
192        #[allow(unreachable_patterns)]
193        _ => {
194            let backend_name = backend_type.as_str();
195            Err(RenderError::BackendError(format!(
196                "{backend_name} backend not available in this build"
197            )))
198        }
199    }
200}