backend.rs raw

//! The trait render backends implement to consume a fragment tree.

use std::error::Error;
use std::fmt;

use crate::display_list::{FragmentId, FragmentStore};

/// A renderer that can rasterize a fragment tree into its current target.
///
/// How a backend acquires its target (a wgpu surface, a pixmap, …) is the
/// backend's own affair, configured outside this trait; "render a fragment
/// tree" is the only portable operation.
pub trait RenderBackend {
    /// Render the tree rooted at `root` into the backend's target.
    ///
    /// The target is `width_px` × `height_px` physical pixels; `scale` is the
    /// logical-to-physical scale factor and is applied as the root transform,
    /// so fragment coordinates are in logical units.
    fn render(
        &mut self,
        root: FragmentId,
        store: &FragmentStore,
        width_px: u32,
        height_px: u32,
        scale: f64,
    ) -> Result<(), RenderError>;
}

/// Failure while rendering a fragment tree.
#[derive(Debug)]
pub enum RenderError {
    /// A fragment id in the tree does not exist in the store.
    MissingFragment(FragmentId),
    /// A fragment's display list has unbalanced push/pop items.
    UnbalancedFragment(FragmentId),
    /// Fragment nesting exceeded the depth limit, which almost always means
    /// the fragment graph contains a cycle.
    ExcessiveDepth(FragmentId),
    /// A backend-specific failure (device lost, surface error, …).
    Backend(Box<dyn Error + Send + Sync>),
}

impl fmt::Display for RenderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RenderError::MissingFragment(id) => {
                write!(f, "fragment {id:?} is not present in the store")
            }
            RenderError::UnbalancedFragment(id) => {
                write!(f, "fragment {id:?} has unbalanced push/pop display items")
            }
            RenderError::ExcessiveDepth(id) => {
                write!(
                    f,
                    "fragment nesting exceeded the depth limit at {id:?}; the fragment graph likely contains a cycle"
                )
            }
            RenderError::Backend(err) => write!(f, "render backend error: {err}"),
        }
    }
}

impl Error for RenderError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            RenderError::Backend(err) => Some(err.as_ref()),
            _ => None,
        }
    }
}