use smudge_core::app::FrameOutput;
use smudge_core::geometry::{PixelRect, Point};
use smudge_core::paint::DisplayItem;
use smudge_core::renderer::Renderer;
use tiny_skia::Pixmap;
use crate::draw::{Canvas, PlannedOp, RasterPlan, Surface, for_each_op};
use crate::glyph_cache::GlyphCache;
pub struct CpuRenderer {
pixmap: Option<Pixmap>,
glyphs: GlyphCache,
plan: RasterPlan,
}
impl CpuRenderer {
pub fn new(width: u32, height: u32) -> CpuRenderer {
CpuRenderer {
pixmap: Pixmap::new(width, height),
glyphs: GlyphCache::new(),
plan: RasterPlan::new(),
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.pixmap = Pixmap::new(width, height);
}
pub fn render(&mut self, frame: &FrameOutput<'_>) {
let width = self.width();
let height = self.height();
for rect in frame.damage {
assert!(
inside_surface(*rect, width, height),
"damage {rect:?} reaches outside the {width}x{height} surface, \
so the frame was produced for a different surface size"
);
}
let Some(pixmap) = self.pixmap.as_mut() else {
return;
};
if frame.damage.is_empty() {
return;
}
let mut canvas = Canvas {
pixels: pixmap.data_mut(),
width,
scale: frame.scale,
glyphs: &mut self.glyphs,
};
match frame.damage {
[rect] => {
paint_single(
&mut canvas,
&mut self.plan.clips,
&mut self.plan.offsets,
frame,
*rect,
height,
);
}
damage => {
self.plan
.build(frame.list.items(), frame.scale, width, height);
for &rect in damage {
paint_rect(&mut canvas, frame, self.plan.ops(), rect);
}
}
}
}
pub fn width(&self) -> u32 {
self.pixmap.as_ref().map_or(0, Pixmap::width)
}
pub fn height(&self) -> u32 {
self.pixmap.as_ref().map_or(0, Pixmap::height)
}
pub fn pixels(&self) -> &[u8] {
self.pixmap.as_ref().map_or(&[], Pixmap::data)
}
pub fn encode_png(&self) -> Vec<u8> {
let Some(pixmap) = &self.pixmap else {
return Vec::new();
};
pixmap
.encode_png()
.expect("encoding a surface into memory cannot fail")
}
}
fn inside_surface(rect: PixelRect, width: u32, height: u32) -> bool {
let right = u64::from(rect.x) + u64::from(rect.width);
let bottom = u64::from(rect.y) + u64::from(rect.height);
right <= u64::from(width) && bottom <= u64::from(height)
}
fn paint_rect(
canvas: &mut Canvas<'_>,
frame: &FrameOutput<'_>,
plan: &[PlannedOp],
rect: PixelRect,
) {
canvas.clear(rect, frame.clear_color);
let items = frame.list.items();
for planned in plan {
if let Some(part) = planned.area.intersect(rect) {
let DisplayItem::Op(op) = &items[planned.index] else {
unreachable!("a planned index is an operation item");
};
canvas.draw(op, part, planned.offset);
}
}
}
fn paint_single(
canvas: &mut Canvas<'_>,
clips: &mut Vec<Option<PixelRect>>,
offsets: &mut Vec<Point>,
frame: &FrameOutput<'_>,
rect: PixelRect,
height: u32,
) {
canvas.clear(rect, frame.clear_color);
let width = canvas.width;
let scale = frame.scale;
let items = frame.list.items();
for_each_op(
|_, op, area, offset| canvas.draw(op, area, offset),
clips,
offsets,
items,
Surface {
scale,
width,
height,
},
rect,
);
}
impl Renderer for CpuRenderer {
fn render(&mut self, frame: &FrameOutput<'_>) {
CpuRenderer::render(self, frame);
}
}
#[cfg(test)]
#[path = "renderer_tests.rs"]
mod tests;