use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
use crate::{Layer, Renderer, Settings};
use std::collections::VecDeque;
use std::num::NonZeroU32;
#[allow(missing_debug_implementations)]
pub struct Compositor {
context: softbuffer::Context<Box<dyn compositor::Window>>,
settings: Settings,
}
#[allow(missing_debug_implementations)]
pub struct Surface {
window: softbuffer::Surface<
Box<dyn compositor::Window>,
Box<dyn compositor::Window>,
>,
clip_mask: tiny_skia::Mask,
layer_stack: VecDeque<Vec<Layer>>,
background_color: Color,
max_age: u8,
}
impl crate::graphics::Compositor for Compositor {
type Renderer = Renderer;
type Surface = Surface;
async fn with_backend<W: compositor::Window>(
settings: graphics::Settings,
compatible_window: W,
backend: Option<&str>,
) -> Result<Self, Error> {
match backend {
None | Some("tiny-skia") | Some("tiny_skia") => {
Ok(new(settings.into(), compatible_window))
}
Some(backend) => Err(Error::GraphicsAdapterNotFound {
backend: "tiny-skia",
reason: error::Reason::DidNotMatch {
preferred_backend: backend.to_owned(),
},
}),
}
}
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
self.settings.default_font,
self.settings.default_text_size,
)
}
fn create_surface<W: compositor::Window + Clone>(
&mut self,
window: W,
width: u32,
height: u32,
) -> Self::Surface {
let window = softbuffer::Surface::new(
&self.context,
Box::new(window.clone()) as _,
)
.expect("Create softbuffer surface for window");
let mut surface = Surface {
window,
clip_mask: tiny_skia::Mask::new(width, height)
.expect("Create clip mask"),
layer_stack: VecDeque::new(),
background_color: Color::BLACK,
max_age: 0,
};
self.configure_surface(&mut surface, width, height);
surface
}
fn configure_surface(
&mut self,
surface: &mut Self::Surface,
width: u32,
height: u32,
) {
surface
.window
.resize(
NonZeroU32::new(width).expect("Non-zero width"),
NonZeroU32::new(height).expect("Non-zero height"),
)
.expect("Resize surface");
surface.clip_mask =
tiny_skia::Mask::new(width, height).expect("Create clip mask");
surface.layer_stack.clear();
}
fn fetch_information(&self) -> Information {
Information {
adapter: String::from("CPU"),
backend: String::from("tiny-skia"),
}
}
fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
present(renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
screenshot(renderer, viewport, background_color, overlay)
}
}
pub fn new<W: compositor::Window>(
settings: Settings,
compatible_window: W,
) -> Compositor {
#[allow(unsafe_code)]
let context = softbuffer::Context::new(Box::new(compatible_window) as _)
.expect("Create softbuffer context");
Compositor { context, settings }
}
pub fn present<T: AsRef<str>>(
renderer: &mut Renderer,
surface: &mut Surface,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
let mut buffer = surface
.window
.buffer_mut()
.map_err(|_| compositor::SurfaceError::Lost)?;
let last_layers = {
let age = buffer.age();
surface.max_age = surface.max_age.max(age);
surface.layer_stack.truncate(surface.max_age as usize);
if age > 0 {
surface.layer_stack.get(age as usize - 1)
} else {
None
}
};
let damage = last_layers
.and_then(|last_layers| {
(surface.background_color == background_color).then(|| {
damage::diff(
last_layers,
renderer.layers(),
|layer| vec![layer.bounds],
Layer::damage,
)
})
})
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
if damage.is_empty() {
return Ok(());
}
surface.layer_stack.push_front(renderer.layers().to_vec());
surface.background_color = background_color;
let damage =
damage::group(damage, Rectangle::with_size(viewport.logical_size()));
let mut pixels = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut buffer),
physical_size.width,
physical_size.height,
)
.expect("Create pixel map");
renderer.draw(
&mut pixels,
&mut surface.clip_mask,
viewport,
&damage,
background_color,
overlay,
);
buffer.present().map_err(|_| compositor::SurfaceError::Lost)
}
pub fn screenshot<T: AsRef<str>>(
renderer: &mut Renderer,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
let size = viewport.physical_size();
let mut offscreen_buffer: Vec<u32> =
vec![0; size.width as usize * size.height as usize];
let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
.expect("Create clip mask");
renderer.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
size.width,
size.height,
)
.expect("Create offscreen pixel map"),
&mut clip_mask,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,
size.height as f32,
))],
background_color,
overlay,
);
offscreen_buffer.iter().fold(
Vec::with_capacity(offscreen_buffer.len() * 4),
|mut acc, pixel| {
const A_MASK: u32 = 0xFF_00_00_00;
const R_MASK: u32 = 0x00_FF_00_00;
const G_MASK: u32 = 0x00_00_FF_00;
const B_MASK: u32 = 0x00_00_00_FF;
let a = ((A_MASK & pixel) >> 24) as u8;
let r = ((R_MASK & pixel) >> 16) as u8;
let g = ((G_MASK & pixel) >> 8) as u8;
let b = (B_MASK & pixel) as u8;
acc.extend([r, g, b, a]);
acc
},
)
}