ass-renderer 0.1.2

High-performance ASS subtitle renderer with modular backends
Documentation
//! Rendering backend trait and implementations

use crate::pipeline::{IntermediateLayer, Pipeline};
use crate::renderer::RenderContext;
use crate::utils::{DirtyRegion, RenderError};

#[cfg(feature = "nostd")]
use alloc::{boxed::Box, format, vec::Vec};
#[cfg(not(feature = "nostd"))]
use std::{boxed::Box, vec::Vec};

#[cfg(feature = "software-backend")]
pub mod blur;

#[cfg(feature = "software-backend")]
pub mod coverage;

#[cfg(feature = "software-backend")]
pub mod geometry;

#[cfg(feature = "software-backend")]
pub mod raster;

#[cfg(feature = "software-backend")]
pub mod software;

#[cfg(feature = "gpu")]
pub mod gpu;

/// Backend type enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendType {
    /// Auto-detect best available backend
    Auto,
    /// CPU-based software renderer
    Software,
    /// Native hybrid GPU compositor (wgpu) over software-produced tiles
    Gpu,
}

impl BackendType {
    /// Get backend type as string
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Auto => "Auto",
            Self::Software => "Software",
            Self::Gpu => "Gpu",
        }
    }
}

/// Core rendering backend trait
pub trait RenderBackend: Send + Sync {
    /// Get the backend type
    fn backend_type(&self) -> BackendType;

    /// Create a pipeline for this backend
    fn create_pipeline(&self) -> Result<Box<dyn Pipeline>, RenderError>;

    /// Composite layers into final frame
    fn composite_layers(
        &mut self,
        layers: &[IntermediateLayer],
        context: &RenderContext,
    ) -> Result<Vec<u8>, RenderError>;

    /// Composite layers incrementally (dirty regions only)
    fn composite_layers_incremental(
        &mut self,
        layers: &[IntermediateLayer],
        dirty_regions: &[DirtyRegion],
        previous_frame: &[u8],
        context: &RenderContext,
    ) -> Result<Vec<u8>, RenderError> {
        // Default implementation: full re-render
        let _ = (dirty_regions, previous_frame);
        self.composite_layers(layers, context)
    }

    /// Render layers to a positioned bitmap list (libass `ASS_Image` style)
    /// instead of a composited frame buffer, skipping the full-frame clear/copy.
    /// The default reports the backend does not support it.
    #[cfg(feature = "software-backend")]
    fn render_layers_to_bitmaps(
        &mut self,
        layers: &[IntermediateLayer],
        context: &RenderContext,
    ) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
        let _ = (layers, context);
        Err(RenderError::BackendError(
            "bitmap-list output not supported by this backend".into(),
        ))
    }

    /// Check if backend supports a specific feature
    fn supports_feature(&self, feature: BackendFeature) -> bool {
        match feature {
            BackendFeature::IncrementalRendering => false,
            BackendFeature::HardwareAcceleration => false,
            BackendFeature::ComputeShaders => false,
            BackendFeature::AsyncRendering => false,
        }
    }

    /// Get backend metrics if available
    #[cfg(feature = "backend-metrics")]
    fn metrics(&self) -> Option<BackendMetrics> {
        None
    }
}

/// Backend feature capabilities
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendFeature {
    /// Supports incremental rendering of dirty regions
    IncrementalRendering,
    /// Hardware accelerated rendering
    HardwareAcceleration,
    /// Compute shader support
    ComputeShaders,
    /// Async rendering operations
    AsyncRendering,
}

/// Backend performance metrics
#[cfg(feature = "backend-metrics")]
#[derive(Debug, Clone)]
pub struct BackendMetrics {
    /// VRAM usage in bytes
    pub vram_usage: u64,
    /// Number of draw calls per frame
    pub draw_calls: usize,
    /// Batch threshold for events
    pub batch_threshold: usize,
    /// Average frame time in milliseconds
    pub avg_frame_time_ms: f32,
    /// Peak frame time in milliseconds
    pub peak_frame_time_ms: f32,
}

#[cfg(feature = "backend-metrics")]
impl BackendMetrics {
    /// Create new metrics with defaults
    pub fn new() -> Self {
        Self {
            vram_usage: 0,
            draw_calls: 0,
            batch_threshold: 100,
            avg_frame_time_ms: 0.0,
            peak_frame_time_ms: 0.0,
        }
    }
}

#[cfg(feature = "backend-metrics")]
impl Default for BackendMetrics {
    fn default() -> Self {
        Self::new()
    }
}

/// Create a backend instance for the given type
pub fn create_backend(
    backend_type: BackendType,
    width: u32,
    height: u32,
) -> Result<Box<dyn RenderBackend>, RenderError> {
    match backend_type {
        BackendType::Auto => {
            // The software backend is the fast, always-available, parity-perfect
            // default; the GPU backend is opt-in via `BackendType::Gpu`.
            #[cfg(feature = "software-backend")]
            return create_backend(BackendType::Software, width, height);

            #[allow(unreachable_code)]
            Err(RenderError::BackendError("No backend available".into()))
        }

        #[cfg(feature = "software-backend")]
        BackendType::Software => {
            let context = crate::renderer::RenderContext::new(width, height);
            let backend = software::SoftwareBackend::new(&context)?;
            Ok(Box::new(backend))
        }

        #[cfg(feature = "gpu")]
        BackendType::Gpu => {
            let backend = gpu::GpuBackend::new(width, height)?;
            Ok(Box::new(backend))
        }

        #[allow(unreachable_patterns)]
        _ => {
            let backend_name = backend_type.as_str();
            Err(RenderError::BackendError(format!(
                "{backend_name} backend not available in this build"
            )))
        }
    }
}