#[cfg(feature = "wgpu")]
extern crate wgpu0 as wgpu;
#[cfg(all(feature = "gl", not(target_arch = "wasm32")))]
mod desktop_gl;
mod swrast;
mod text;
#[cfg(all(feature = "gl", target_arch = "wasm32"))]
mod web_gl;
#[cfg(feature = "wgpu")]
#[path = "wgpu.rs"]
mod wgpu_backend;
use piet::kurbo::{Affine, Point, Shape, Size};
use piet::{kurbo::Rect, Error};
use piet::{FixedGradient, ImageFormat, InterpolationMode, IntoBrush, StrokeStyle};
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use std::borrow::Cow;
use std::cell::Cell;
use std::ffi::c_void;
use std::fmt;
use std::marker::PhantomData;
use std::rc::Rc;
pub use text::{Text, TextLayout, TextLayoutBuilder};
std::thread_local! {
static HAS_CONTEXT: Cell<bool> = Cell::new(false);
}
pub type XlibErrorHook = Box<dyn Fn(*mut c_void, *mut c_void) -> bool + Send + Sync>;
type XlibErrorHookRegistrar = Box<dyn Fn(XlibErrorHook)>;
pub struct DisplayBuilder {
window: Option<RawWindowHandle>,
glx_error_hook: Option<XlibErrorHookRegistrar>,
transparent: bool,
force_swrast: bool,
_thread_unsafe: PhantomData<*mut ()>,
}
impl Default for DisplayBuilder {
fn default() -> Self {
Self {
window: None,
glx_error_hook: None,
transparent: true,
force_swrast: false,
_thread_unsafe: PhantomData,
}
}
}
impl DisplayBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn window(mut self, window: impl HasRawWindowHandle) -> Self {
self.window = Some(window.raw_window_handle());
self
}
pub fn glx_error_hook(mut self, hook: impl Fn(XlibErrorHook) + 'static) -> Self {
self.glx_error_hook = Some(Box::new(hook));
self
}
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
pub fn force_swrast(mut self, force_swrast: bool) -> Self {
self.force_swrast = force_swrast;
self
}
pub unsafe fn build(self, display: impl HasRawDisplayHandle) -> Result<Display, Error> {
self.build_from_raw(display.raw_display_handle())
}
}
pub struct Display {
dispatch: Box<DisplayDispatch>,
_thread_unsafe: PhantomData<*mut ()>,
}
impl fmt::Debug for Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Display").finish_non_exhaustive()
}
}
impl From<DisplayDispatch> for Display {
fn from(dispatch: DisplayDispatch) -> Self {
Self {
dispatch: Box::new(dispatch),
_thread_unsafe: PhantomData,
}
}
}
impl Display {
pub fn builder() -> DisplayBuilder {
DisplayBuilder::new()
}
pub unsafe fn new(display: impl HasRawDisplayHandle) -> Result<Self, Error> {
Self::builder().build_from_raw(display.raw_display_handle())
}
pub async unsafe fn make_surface(
&mut self,
window: impl HasRawWindowHandle,
width: u32,
height: u32,
) -> Result<Surface, Error> {
self.make_surface_from_raw(window.raw_window_handle(), width, height)
.await
}
}
pub struct Surface {
dispatch: Box<SurfaceDispatch>,
_thread_unsafe: PhantomData<*mut ()>,
}
impl fmt::Debug for Surface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Surface").finish_non_exhaustive()
}
}
impl From<SurfaceDispatch> for Surface {
fn from(dispatch: SurfaceDispatch) -> Self {
Self {
dispatch: Box::new(dispatch),
_thread_unsafe: PhantomData,
}
}
}
pub struct RenderContext<'dsp, 'surf> {
dispatch: Box<ContextDispatch<'dsp, 'surf>>,
mismatch: Result<(), Error>,
check_context: bool,
_thread_unsafe: PhantomData<*mut ()>,
}
impl<'dsp, 'surf> RenderContext<'dsp, 'surf> {
fn from_dispatch(dispatch: ContextDispatch<'dsp, 'surf>, check_context: bool) -> Self {
Self {
dispatch: Box::new(dispatch),
mismatch: Ok(()),
check_context,
_thread_unsafe: PhantomData,
}
}
}
impl fmt::Debug for RenderContext<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RenderContext").finish_non_exhaustive()
}
}
impl Drop for RenderContext<'_, '_> {
fn drop(&mut self) {
if self.check_context {
HAS_CONTEXT
.try_with(|has_context| has_context.set(false))
.ok();
}
}
}
#[derive(Clone)]
pub struct Brush {
dispatch: Rc<BrushDispatch>,
_thread_unsafe: PhantomData<*mut ()>,
}
impl From<BrushDispatch> for Brush {
fn from(dispatch: BrushDispatch) -> Self {
Self {
dispatch: Rc::new(dispatch),
_thread_unsafe: PhantomData,
}
}
}
impl fmt::Debug for Brush {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Brush").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct Image {
dispatch: Rc<ImageDispatch>,
_thread_unsafe: PhantomData<*mut ()>,
}
impl From<ImageDispatch> for Image {
fn from(dispatch: ImageDispatch) -> Self {
Self {
dispatch: Rc::new(dispatch),
_thread_unsafe: PhantomData,
}
}
}
impl fmt::Debug for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Image").finish_non_exhaustive()
}
}
macro_rules! make_dispatch {
($($(#[$meta:meta])* $name:ident (
$display:ty,
$surface:ty,
$ctx:ty,
$brush:ty,
$image:ty
)),* $(,)?) => {
enum DisplayDispatch {
$(
$(#[$meta])*
$name($display),
)*
}
enum SurfaceDispatch {
$(
$(#[$meta])*
$name($surface),
)*
}
enum ContextDispatch<'dsp, 'surf> {
$(
$(#[$meta])*
$name($ctx),
)*
}
enum BrushDispatch {
$(
$(#[$meta])*
$name($brush),
)*
}
enum ImageDispatch {
$(
$(#[$meta])*
$name($image),
)*
}
impl DisplayBuilder {
#[allow(unused_assignments, unused_mut)]
pub unsafe fn build_from_raw(
mut self,
raw: RawDisplayHandle
) -> Result<Display, Error> {
let mut last_error;
$(
$(#[$meta])*
{
match <$display>::new(&mut self, raw) {
Ok(display) => {
tracing::trace!("Created `{}` display", stringify!($name));
return Ok(DisplayDispatch::$name(display).into());
},
Err(e) => {
tracing::warn!(
"Failed to create `{}` display: {}",
stringify!($name),
e
);
last_error = e;
}
}
}
)*
Err(last_error)
}
}
impl Display {
pub fn supports_transparency(&self) -> bool {
match &*self.dispatch {
$(
$(#[$meta])*
DisplayDispatch::$name(display) => display.supports_transparency(),
)*
}
}
pub fn x11_visual(&self) -> Option<std::ptr::NonNull<()>> {
match &*self.dispatch {
$(
$(#[$meta])*
DisplayDispatch::$name(display) => display.x11_visual(),
)*
}
}
pub async unsafe fn make_surface_from_raw(
&mut self,
window: RawWindowHandle,
width: u32,
height: u32,
) -> Result<Surface, Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
DisplayDispatch::$name(display) => {
let surface = display.make_surface(window, width, height).await?;
Ok(SurfaceDispatch::$name(surface).into())
},
)*
}
}
pub async fn present(&mut self) {
match &mut *self.dispatch {
$(
$(#[$meta])*
DisplayDispatch::$name(ctx) => ctx.present().await,
)*
}
}
}
impl<'dsp, 'surf> RenderContext<'dsp, 'surf> {
#[allow(unreachable_patterns)]
pub fn new(
display: &'dsp mut Display,
surface: &'surf mut Surface,
width: u32,
height: u32,
) -> Result<Self, Error> {
let prev = HAS_CONTEXT
.try_with(|has_context| has_context.replace(true))
.piet_err()?;
if prev {
return Err(Error::BackendError(
"Only one context can be active per thread.".into()
));
}
match (&mut *display.dispatch, &mut *surface.dispatch) {
$(
$(#[$meta])*
(DisplayDispatch::$name(display), SurfaceDispatch::$name(surface)) => {
let ctx = unsafe {
<$ctx>::new(display, surface, width, height)?
};
Ok(RenderContext::from_dispatch(
ContextDispatch::$name(ctx),
true
))
},
)*
_ => Err(Error::InvalidInput)
}
}
#[allow(unreachable_patterns)]
pub unsafe fn new_unchecked(
display: &'dsp mut Display,
surface: &'surf mut Surface,
width: u32,
height: u32,
) -> Result<Self, Error> {
match (&mut *display.dispatch, &mut *surface.dispatch) {
$(
$(#[$meta])*
(DisplayDispatch::$name(display), SurfaceDispatch::$name(surface)) => {
let ctx = <$ctx>::new_unchecked(display, surface, width, height)?;
Ok(RenderContext::from_dispatch(
ContextDispatch::$name(ctx),
false
))
},
)*
_ => Err(Error::InvalidInput)
}
}
}
impl piet::RenderContext for RenderContext<'_, '_> {
type Brush = Brush;
type Image = Image;
type Text = Text;
type TextLayout = TextLayout;
fn status(&mut self) -> Result<(), Error> {
let mismatch = std::mem::replace(&mut self.mismatch, Ok(()));
let status = match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.status(),
)*
};
status.and(mismatch)
}
fn solid_brush(&mut self, color: piet::Color) -> Self::Brush {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => {
let brush = ctx.solid_brush(color);
BrushDispatch::$name(brush).into()
},
)*
}
}
fn gradient(
&mut self,
gradient: impl Into<FixedGradient>
) -> Result<Self::Brush, Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => {
Ok(BrushDispatch::$name(ctx.gradient(gradient.into())?).into())
},
)*
}
}
fn clear(&mut self, region: impl Into<Option<Rect>>, color: piet::Color) {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.clear(region.into(), color),
)*
}
}
#[allow(unreachable_patterns)]
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
let brush = brush.make_brush(self, || shape.bounding_box());
match (&mut *self.dispatch, &*brush.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), BrushDispatch::$name(brush)) => {
ctx.stroke(shape, brush, width)
},
)*
_ => unreachable!(),
}
}
#[allow(unreachable_patterns)]
fn stroke_styled(
&mut self,
shape: impl Shape,
brush: &impl IntoBrush<Self>,
width: f64,
style: &StrokeStyle,
) {
let brush = brush.make_brush(self, || shape.bounding_box());
match (&mut *self.dispatch, &*brush.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), BrushDispatch::$name(brush)) => {
ctx.stroke_styled(shape, brush, width, style)
},
)*
_ => unreachable!(),
}
}
#[allow(unreachable_patterns)]
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box());
match (&mut *self.dispatch, &*brush.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), BrushDispatch::$name(brush)) => {
ctx.fill(shape, brush)
},
)*
_ => unreachable!(),
}
}
#[allow(unreachable_patterns)]
fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box());
match (&mut *self.dispatch, &*brush.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), BrushDispatch::$name(brush)) => {
ctx.fill_even_odd(shape, brush)
},
)*
_ => unreachable!(),
}
}
fn clip(&mut self, shape: impl Shape) {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.clip(shape),
)*
}
}
fn text(&mut self) -> &mut Self::Text {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => {
ctx.text()
},
)*
}
}
fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>) {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.draw_text(layout, pos.into()),
)*
}
}
fn save(&mut self) -> Result<(), Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.save(),
)*
}
}
fn restore(&mut self) -> Result<(), Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.restore(),
)*
}
}
fn finish(&mut self) -> Result<(), Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.finish(),
)*
}
}
fn transform(&mut self, transform: Affine) {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.transform(transform),
)*
}
}
fn make_image(
&mut self,
width: usize,
height: usize,
buf: &[u8],
format: ImageFormat,
) -> Result<Self::Image, Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => {
let img = ctx.make_image(width, height, buf, format)?;
Ok(ImageDispatch::$name(img).into())
}
)*
}
}
#[allow(unreachable_patterns)]
fn draw_image(
&mut self,
image: &Self::Image,
dst_rect: impl Into<Rect>,
interp: InterpolationMode,
) {
match (&mut *self.dispatch, &*image.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), ImageDispatch::$name(img)) => {
ctx.draw_image(img, dst_rect.into(), interp)
}
)*
_ => unreachable!(),
}
}
#[allow(unreachable_patterns)]
fn draw_image_area(
&mut self,
image: &Self::Image,
src_rect: impl Into<Rect>,
dst_rect: impl Into<Rect>,
interp: InterpolationMode,
) {
match (&mut *self.dispatch, &*image.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), ImageDispatch::$name(img)) => {
ctx.draw_image_area(
img,
src_rect.into(),
dst_rect.into(),
interp
)
}
)*
_ => unreachable!(),
}
}
fn capture_image_area(&mut self, src_rect: impl Into<Rect>) -> Result<Self::Image, Error> {
match &mut *self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => {
let img = ctx.capture_image_area(src_rect.into())?;
Ok(ImageDispatch::$name(img).into())
}
)*
}
}
#[allow(unreachable_patterns)]
fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || rect);
match (&mut *self.dispatch, &*brush.dispatch) {
$(
$(#[$meta])*
(ContextDispatch::$name(ctx), BrushDispatch::$name(brush)) => {
ctx.blurred_rect(rect, blur_radius, brush)
},
)*
_ => unreachable!(),
}
}
fn current_transform(&self) -> Affine {
match &*self.dispatch {
$(
$(#[$meta])*
ContextDispatch::$name(ctx) => ctx.current_transform(),
)*
}
}
}
impl piet::IntoBrush<RenderContext<'_, '_>> for Brush {
fn make_brush<'a>(
&'a self,
_piet: &mut RenderContext<'_, '_>,
_bbox: impl FnOnce() -> Rect,
) -> Cow<'a, <RenderContext<'_, '_> as piet::RenderContext>::Brush> {
Cow::Borrowed(self)
}
}
impl piet::Image for Image {
fn size(&self) -> Size {
match &*self.dispatch {
$(
$(#[$meta])*
ImageDispatch::$name(image) => image.size(),
)*
}
}
}
}
}
make_dispatch! {
#[cfg(feature = "wgpu")]
Wgpu(
wgpu_backend::Display,
wgpu_backend::Surface,
wgpu_backend::RenderContext<'dsp, 'surf>,
piet_wgpu::Brush,
piet_wgpu::Image
),
#[cfg(all(feature = "gl", not(target_arch = "wasm32")))]
DesktopGl(
desktop_gl::Display,
desktop_gl::Surface,
desktop_gl::RenderContext<'dsp, 'surf>,
piet_glow::Brush<glow::Context>,
piet_glow::Image<glow::Context>
),
#[cfg(all(feature = "gl", target_arch = "wasm32"))]
WebGl(
web_gl::Display,
web_gl::Surface,
web_gl::RenderContext<'dsp, 'surf>,
piet_glow::Brush<glow::Context>,
piet_glow::Image<glow::Context>
),
SwRast(
swrast::Display,
swrast::Surface,
swrast::RenderContext<'dsp, 'surf>,
swrast::Brush,
swrast::Image
),
}
struct LibraryError<E>(E);
impl<E: fmt::Debug> fmt::Debug for LibraryError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl<E: fmt::Display> fmt::Display for LibraryError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
trait ResultExt<T, E: std::error::Error + 'static> {
fn piet_err(self) -> Result<T, Error>;
}
impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
fn piet_err(self) -> Result<T, Error> {
self.map_err(|e| Error::BackendError(Box::new(LibraryError(e))))
}
}
trait OptionExt<T> {
fn piet_err(self, message: impl Into<String>) -> Result<T, Error>;
}
impl<T> OptionExt<T> for Option<T> {
fn piet_err(self, message: impl Into<String>) -> Result<T, Error> {
self.ok_or_else(|| Error::BackendError(message.into().into()))
}
}
#[derive(Debug)]
struct SwitchToSwrast;
impl fmt::Display for SwitchToSwrast {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Switching to software rendering, this may cause lower performance"
)
}
}
impl std::error::Error for SwitchToSwrast {}