ass_renderer/backends/software/
mod.rs1#[cfg(feature = "nostd")]
4use alloc::{boxed::Box, format, sync::Arc, vec::Vec};
5#[cfg(not(feature = "nostd"))]
6use std::{boxed::Box, sync::Arc, vec::Vec};
7
8use crate::backends::{BackendFeature, BackendType, RenderBackend};
9use crate::pipeline::{IntermediateLayer, Pipeline, SoftwarePipeline};
10use crate::renderer::RenderContext;
11use crate::utils::{DirtyRegion, RenderError};
12use tiny_skia::Pixmap;
13
14mod cache;
15mod dirty;
16mod text;
17mod vector;
18#[cfg(not(feature = "nostd"))]
19use cache::{DIRTY_BBOX, EMIT_SINK};
20#[cfg(not(feature = "nostd"))]
21use dirty::{clear_region, crop_pixmap};
22
23pub struct SoftwareBackend {
25 pixmap: Pixmap,
26 font_database: Arc<fontdb::Database>,
27 glyph_renderer: crate::pipeline::shaping::GlyphRenderer,
28 #[cfg(not(feature = "nostd"))]
31 scratch: Pixmap,
32 #[cfg(feature = "backend-metrics")]
33 metrics: super::BackendMetrics,
34}
35
36impl SoftwareBackend {
37 pub fn new(context: &RenderContext) -> Result<Self, RenderError> {
39 let pixmap =
40 Pixmap::new(context.width(), context.height()).ok_or(RenderError::InvalidDimensions)?;
41
42 #[cfg(not(feature = "nostd"))]
46 let font_database = crate::pipeline::font_loader::shared_system_fonts();
47 #[cfg(feature = "nostd")]
48 let font_database = Arc::new(fontdb::Database::new());
49
50 #[cfg(not(feature = "nostd"))]
51 let scratch =
52 Pixmap::new(context.width(), context.height()).ok_or(RenderError::InvalidDimensions)?;
53
54 Ok(Self {
55 pixmap,
56 font_database,
57 glyph_renderer: crate::pipeline::shaping::GlyphRenderer::new(),
58 #[cfg(not(feature = "nostd"))]
59 scratch,
60 #[cfg(feature = "backend-metrics")]
61 metrics: super::BackendMetrics::new(),
62 })
63 }
64
65 pub fn resize(&mut self, width: u32, height: u32) -> Result<(), RenderError> {
67 self.pixmap = Pixmap::new(width, height).ok_or(RenderError::InvalidDimensions)?;
68 #[cfg(not(feature = "nostd"))]
69 {
70 self.scratch = Pixmap::new(width, height).ok_or(RenderError::InvalidDimensions)?;
71 }
72 Ok(())
73 }
74
75 #[cfg(not(feature = "nostd"))]
84 fn render_to_bitmaps(
85 &mut self,
86 layers: &[IntermediateLayer],
87 context: &RenderContext,
88 ) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
89 if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
90 self.resize(context.width(), context.height())?;
91 }
92
93 self.scratch.fill(tiny_skia::Color::TRANSPARENT);
98 let mut out = Vec::new();
99 for layer in layers {
100 EMIT_SINK.with(|sink| *sink.borrow_mut() = Some(Vec::new()));
101 DIRTY_BBOX.with(|b| *b.borrow_mut() = None);
102 std::mem::swap(&mut self.pixmap, &mut self.scratch);
103 let result = self.composite_layer(layer, context);
104 std::mem::swap(&mut self.pixmap, &mut self.scratch);
105 result?;
106
107 let coverage = EMIT_SINK.with(|sink| sink.borrow_mut().take().unwrap_or_default());
108 if coverage.is_empty() {
109 let hint = DIRTY_BBOX.with(|b| *b.borrow());
111 if let Some(bitmap) = crop_pixmap(&self.scratch, hint) {
112 if let crate::backends::coverage::RenderBitmap::Rgba {
116 x,
117 y,
118 width,
119 height,
120 ..
121 } = &bitmap
122 {
123 clear_region(&mut self.scratch, (*x, *y, *width, *height));
124 }
125 out.push(bitmap);
126 }
127 } else {
128 out.extend(coverage);
129 }
130 }
131 EMIT_SINK.with(|sink| *sink.borrow_mut() = None);
132 Ok(out)
133 }
134
135 fn composite_layer(
136 &mut self,
137 layer: &IntermediateLayer,
138 _context: &RenderContext,
139 ) -> Result<(), RenderError> {
140 match layer {
141 IntermediateLayer::Raster(raster_data) => {
142 self.draw_raster_layer(raster_data)?;
143 }
144 IntermediateLayer::Vector(path_data) => {
145 self.draw_vector_layer(path_data)?;
146 }
147 IntermediateLayer::Text(text_data) => {
148 self.draw_text_layer(text_data)?;
149 }
150 }
151 Ok(())
152 }
153}
154
155#[cfg(not(feature = "nostd"))]
158type LayerColors = (Option<[u8; 4]>, Option<([u8; 4], (i32, i32))>, [u8; 4]);
159
160impl RenderBackend for SoftwareBackend {
161 fn backend_type(&self) -> BackendType {
162 BackendType::Software
163 }
164
165 fn create_pipeline(&self) -> Result<Box<dyn Pipeline>, RenderError> {
166 Ok(Box::new(SoftwarePipeline::new()))
167 }
168
169 fn composite_layers(
170 &mut self,
171 layers: &[IntermediateLayer],
172 context: &RenderContext,
173 ) -> Result<Vec<u8>, RenderError> {
174 if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
179 self.resize(context.width(), context.height())?;
180 }
181
182 self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
183
184 for layer in layers {
185 self.composite_layer(layer, context)?;
186 }
187
188 Ok(self.pixmap.data().to_vec())
189 }
190
191 fn render_layers_to_bitmaps(
192 &mut self,
193 layers: &[IntermediateLayer],
194 context: &RenderContext,
195 ) -> Result<Vec<crate::backends::coverage::RenderBitmap>, RenderError> {
196 self.render_to_bitmaps(layers, context)
197 }
198
199 fn composite_layers_incremental(
200 &mut self,
201 layers: &[IntermediateLayer],
202 dirty_regions: &[DirtyRegion],
203 previous_frame: &[u8],
204 context: &RenderContext,
205 ) -> Result<Vec<u8>, RenderError> {
206 if self.pixmap.width() != context.width() || self.pixmap.height() != context.height() {
207 self.resize(context.width(), context.height())?;
208 }
209
210 if previous_frame.len() == self.pixmap.data().len() {
212 self.pixmap.data_mut().copy_from_slice(previous_frame);
213 } else {
214 self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
215 }
216
217 for region in dirty_regions {
219 let _ = region; for layer in layers {
225 if layer.intersects_region(region) {
226 self.composite_layer(layer, context)?;
227 }
228 }
229 }
230
231 Ok(self.pixmap.data().to_vec())
232 }
233
234 fn supports_feature(&self, feature: BackendFeature) -> bool {
235 match feature {
236 BackendFeature::IncrementalRendering => true,
237 BackendFeature::HardwareAcceleration => false,
238 BackendFeature::ComputeShaders => false,
239 BackendFeature::AsyncRendering => false,
240 }
241 }
242
243 #[cfg(feature = "backend-metrics")]
244 fn metrics(&self) -> Option<super::BackendMetrics> {
245 Some(self.metrics.clone())
246 }
247}