use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::{Error, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::num::NonZeroU32;
pub struct Compositor<W: HasDisplayHandle + HasWindowHandle, Theme> {
context: Option<softbuffer::Context<W>>,
settings: Settings,
_theme: PhantomData<Theme>,
}
pub struct Surface<W: HasDisplayHandle + HasWindowHandle> {
window: softbuffer::Surface<W, W>,
clip_mask: tiny_skia::Mask,
// Primitives of existing buffers, by decreasing age
primitives: VecDeque<Vec<Primitive>>,
background_color: Color,
}
// XXX avoid clone bound?
impl<W: HasDisplayHandle + HasWindowHandle + Clone, Theme>
crate::graphics::Compositor<W> for Compositor<W, Theme>
{
type Settings = Settings;
type Renderer = Renderer<Theme>;
type Surface = Surface<W>;
fn new(
settings: Self::Settings,
compatible_window: Option<W>,
) -> Result<Self, Error> {
Ok(new(settings, compatible_window))
}
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
Backend::new(),
self.settings.default_font,
self.settings.default_text_size,
)
}
fn create_surface(
&mut self,
window: W,
width: u32,
height: u32,
) -> Surface<W> {
let window = if let Some(context) = self.context.as_ref() {
softbuffer::Surface::new(context, window)
.expect("Create softbuffer surface for window")
} else {
let context = softbuffer::Context::new(window.clone())
.expect("Create softbuffer context for window");
softbuffer::Surface::new(&context, window)
.expect("Create softbuffer surface for window")
};
Surface {
window,
clip_mask: tiny_skia::Mask::new(width, height)
.expect("Create clip mask"),
primitives: VecDeque::new(),
background_color: Color::BLACK,
}
}
fn configure_surface(
&mut self,
surface: &mut Surface<W>,
width: u32,
height: u32,
) {
surface.clip_mask =
tiny_skia::Mask::new(width, height).expect("Create clip mask");
surface.primitives.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 Surface<W>,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
})
}
fn screenshot<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| {
screenshot(
surface,
backend,
primitives,
viewport,
background_color,
overlay,
)
})
}
}
pub fn new<W: HasWindowHandle + HasDisplayHandle, Theme>(
settings: Settings,
compatible_window: Option<W>,
) -> Compositor<W, Theme> {
#[allow(unsafe_code)]
let context =
compatible_window.and_then(|w| softbuffer::Context::new(w).ok());
Compositor {
context,
settings,
_theme: PhantomData,
}
}
pub fn present<W: HasDisplayHandle + HasWindowHandle, T: AsRef<str>>(
backend: &mut Backend,
surface: &mut Surface<W>,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
surface
.window
.resize(
NonZeroU32::new(physical_size.width).unwrap(),
NonZeroU32::new(physical_size.height).unwrap(),
)
.unwrap();
// TODO Add variants to `SurfaceError`?
let mut buffer = surface
.window
.buffer_mut()
.map_err(|_| compositor::SurfaceError::Lost)?;
let age = buffer.age();
// Forget primatives for back buffers older than `age`
// Or if this is a new buffer, keep at most two.
let max = if age == 0 { 2 } else { age };
while surface.primitives.len() as u8 > max {
let _ = surface.primitives.pop_front();
}
let last_primitives = if surface.primitives.len() as u8 == age {
surface.primitives.pop_front()
} else {
None
};
let damage = last_primitives
.and_then(|last_primitives| {
(surface.background_color == background_color)
.then(|| damage::list(&last_primitives, primitives))
})
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
surface.primitives.push_back(primitives.to_vec());
surface.background_color = background_color;
if !damage.is_empty() {
let damage = damage::group(damage, scale_factor, physical_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");
backend.draw(
&mut pixels,
&mut surface.clip_mask,
primitives,
viewport,
&damage,
background_color,
overlay,
);
}
buffer.present().map_err(|_| compositor::SurfaceError::Lost)
}
pub fn screenshot<W: HasDisplayHandle + HasWindowHandle, T: AsRef<str>>(
surface: &mut Surface<W>,
backend: &mut Backend,
primitives: &[Primitive],
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];
backend.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
size.width,
size.height,
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
primitives,
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
},
)
}