#[cfg(feature = "nostd")]
use alloc::{boxed::Box, format, sync::Arc, vec::Vec};
#[cfg(not(feature = "nostd"))]
use std::{boxed::Box, sync::Arc, vec::Vec};
use crate::backends::{BackendFeature, BackendType, RenderBackend};
use crate::pipeline::{IntermediateLayer, Pipeline, SoftwarePipeline};
use crate::renderer::RenderContext;
use crate::utils::{DirtyRegion, RenderError};
use tiny_skia::Pixmap;
mod cache;
mod dirty;
mod text;
mod vector;
#[cfg(not(feature = "nostd"))]
use cache::{DIRTY_BBOX, EMIT_SINK};
#[cfg(not(feature = "nostd"))]
use dirty::{clear_region, crop_pixmap};
pub struct SoftwareBackend {
pixmap: Pixmap,
font_database: Arc<fontdb::Database>,
glyph_renderer: crate::pipeline::shaping::GlyphRenderer,
#[cfg(not(feature = "nostd"))]
scratch: Pixmap,
#[cfg(feature = "backend-metrics")]
metrics: super::BackendMetrics,
}
impl SoftwareBackend {
pub fn new(context: &RenderContext) -> Result<Self, RenderError> {
let pixmap =
Pixmap::new(context.width(), context.height()).ok_or(RenderError::InvalidDimensions)?;
#[cfg(not(feature = "nostd"))]
let font_database = crate::pipeline::font_loader::shared_system_fonts();
#[cfg(feature = "nostd")]
let font_database = Arc::new(fontdb::Database::new());
#[cfg(not(feature = "nostd"))]
let scratch =
Pixmap::new(context.width(), context.height()).ok_or(RenderError::InvalidDimensions)?;
Ok(Self {
pixmap,
font_database,
glyph_renderer: crate::pipeline::shaping::GlyphRenderer::new(),
#[cfg(not(feature = "nostd"))]
scratch,
#[cfg(feature = "backend-metrics")]
metrics: super::BackendMetrics::new(),
})
}
pub fn resize(&mut self, width: u32, height: u32) -> Result<(), RenderError> {
self.pixmap = Pixmap::new(width, height).ok_or(RenderError::InvalidDimensions)?;
#[cfg(not(feature = "nostd"))]
{
self.scratch = Pixmap::new(width, height).ok_or(RenderError::InvalidDimensions)?;
}
Ok(())
}
#[cfg(not(feature = "nostd"))]
fn render_to_bitmaps(
&mut self,
layers: &[IntermediateLayer],
context: &RenderContext,
) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
self.resize(context.width(), context.height())?;
}
self.scratch.fill(tiny_skia::Color::TRANSPARENT);
let mut out = Vec::new();
for layer in layers {
EMIT_SINK.with(|sink| *sink.borrow_mut() = Some(Vec::new()));
DIRTY_BBOX.with(|b| *b.borrow_mut() = None);
std::mem::swap(&mut self.pixmap, &mut self.scratch);
let result = self.composite_layer(layer, context);
std::mem::swap(&mut self.pixmap, &mut self.scratch);
result?;
let coverage = EMIT_SINK.with(|sink| sink.borrow_mut().take().unwrap_or_default());
if coverage.is_empty() {
let hint = DIRTY_BBOX.with(|b| *b.borrow());
if let Some(bitmap) = crop_pixmap(&self.scratch, hint) {
if let crate::backends::coverage::RenderBitmap::Rgba {
x,
y,
width,
height,
..
} = &bitmap
{
clear_region(&mut self.scratch, (*x, *y, *width, *height));
}
out.push(bitmap);
}
} else {
out.extend(coverage);
}
}
EMIT_SINK.with(|sink| *sink.borrow_mut() = None);
Ok(out)
}
fn composite_layer(
&mut self,
layer: &IntermediateLayer,
_context: &RenderContext,
) -> Result<(), RenderError> {
match layer {
IntermediateLayer::Raster(raster_data) => {
self.draw_raster_layer(raster_data)?;
}
IntermediateLayer::Vector(path_data) => {
self.draw_vector_layer(path_data)?;
}
IntermediateLayer::Text(text_data) => {
self.draw_text_layer(text_data)?;
}
}
Ok(())
}
}
#[cfg(not(feature = "nostd"))]
type LayerColors = (Option<[u8; 4]>, Option<([u8; 4], (i32, i32))>, [u8; 4]);
impl RenderBackend for SoftwareBackend {
fn backend_type(&self) -> BackendType {
BackendType::Software
}
fn create_pipeline(&self) -> Result<Box<dyn Pipeline>, RenderError> {
Ok(Box::new(SoftwarePipeline::new()))
}
fn composite_layers(
&mut self,
layers: &[IntermediateLayer],
context: &RenderContext,
) -> Result<Vec<u8>, RenderError> {
if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
self.resize(context.width(), context.height())?;
}
self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
for layer in layers {
self.composite_layer(layer, context)?;
}
Ok(self.pixmap.data().to_vec())
}
fn render_layers_to_bitmaps(
&mut self,
layers: &[IntermediateLayer],
context: &RenderContext,
) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
self.render_to_bitmaps(layers, context)
}
fn composite_layers_incremental(
&mut self,
layers: &[IntermediateLayer],
dirty_regions: &[DirtyRegion],
previous_frame: &[u8],
context: &RenderContext,
) -> Result<Vec<u8>, RenderError> {
if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
self.resize(context.width(), context.height())?;
}
if previous_frame.len() == self.pixmap.data().len() {
self.pixmap.data_mut().copy_from_slice(previous_frame);
} else {
self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
}
for region in dirty_regions {
let _ = region;
for layer in layers {
if layer.intersects_region(region) {
self.composite_layer(layer, context)?;
}
}
}
Ok(self.pixmap.data().to_vec())
}
fn supports_feature(&self, feature: BackendFeature) -> bool {
match feature {
BackendFeature::IncrementalRendering => true,
BackendFeature::HardwareAcceleration => false,
BackendFeature::ComputeShaders => false,
BackendFeature::AsyncRendering => false,
}
}
#[cfg(feature = "backend-metrics")]
fn metrics(&self) -> Option<super::BackendMetrics> {
Some(self.metrics.clone())
}
}