use std::collections::HashMap;
use vello_common::paint::{ImageSource, PaintType};
use vello_hybrid::{Resources, Scene};
use crate::backend::{convert, mesh, BackendError};
use crate::blend::BlendMode;
use crate::brush::{Brush, Image, Sampling};
use crate::geometry::Affine;
use crate::mesh::Mesh;
use crate::path::{FillRule, Path};
use crate::pick::{self, PickId};
use crate::scene::recording::RecordingScene;
use crate::scene::{GlyphRun, SceneBuilder};
use crate::stroke::Stroke;
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
mod webgl;
#[cfg(feature = "vello-hybrid")]
mod wgpu_renderer;
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
pub use webgl::HybridWebGlRenderer;
#[cfg(feature = "vello-hybrid")]
pub use wgpu_renderer::HybridRenderer;
const PICK_ALIASING_THRESHOLD: u8 = 128;
const MIN_PICK_STROKE_WIDTH: f64 = 2.0;
pub const MAX_DIMENSION: u32 = u16::MAX as u32;
#[derive(Debug, Default, Clone)]
pub struct HybridScene {
ops: RecordingScene,
}
impl HybridScene {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.ops.ops.len()
}
pub fn is_empty(&self) -> bool {
self.ops.ops.is_empty()
}
}
impl SceneBuilder for HybridScene {
fn clear(&mut self) {
self.ops.clear();
}
fn fill(
&mut self,
rule: FillRule,
transform: Affine,
brush: &Brush,
brush_transform: Option<Affine>,
path: &Path,
pick_id: PickId,
) {
self.ops
.fill(rule, transform, brush, brush_transform, path, pick_id);
}
fn stroke(
&mut self,
stroke: &Stroke,
transform: Affine,
brush: &Brush,
brush_transform: Option<Affine>,
path: &Path,
pick_id: PickId,
) {
self.ops
.stroke(stroke, transform, brush, brush_transform, path, pick_id);
}
fn draw_image(
&mut self,
image: &Image,
transform: Affine,
sampling: Sampling,
alpha: f32,
pick_id: PickId,
) {
self.ops
.draw_image(image, transform, sampling, alpha, pick_id);
}
fn draw_glyphs(&mut self, run: &GlyphRun<'_>, pick_id: PickId) {
self.ops.draw_glyphs(run, pick_id);
}
fn draw_mesh(&mut self, mesh: &Mesh, transform: Affine, pick_id: PickId) {
self.ops.draw_mesh(mesh, transform, pick_id);
}
fn push_layer(&mut self, blend: BlendMode, alpha: f32, transform: Affine, clip: &Path) {
self.ops.push_layer(blend, alpha, transform, clip);
}
fn pop_layer(&mut self) {
self.ops.pop_layer();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Pass {
Display,
Pick,
}
fn image_key(image: &Image) -> u64 {
image.data.id()
}
struct Writer<'a> {
scene: &'a mut Scene,
resources: &'a mut Resources,
pass: Pass,
images: &'a HashMap<u64, ImageSource>,
}
impl Writer<'_> {
fn paint(&self, brush: &Brush, pick_id: PickId) -> Option<PaintType> {
match self.pass {
Pass::Display => match brush {
Brush::Solid(color) => Some((*color).into()),
Brush::Gradient(gradient) => Some(gradient.clone().into()),
Brush::Image(image) => self.image_paint(
&image.image,
image.sampler.quality,
image.sampler.x_extend,
image.sampler.y_extend,
),
},
Pass::Pick => pick::raw_id(pick_id).map(|id| pick::id_to_color(id).into()),
}
}
fn image_paint(
&self,
image: &Image,
quality: peniko::ImageQuality,
x_extend: peniko::Extend,
y_extend: peniko::Extend,
) -> Option<PaintType> {
let source = self.images.get(&image_key(image))?;
Some(
vello_common::paint::Image {
image: source.clone(),
sampler: peniko::ImageSampler {
x_extend,
y_extend,
quality,
alpha: 1.0,
},
}
.into(),
)
}
fn set_placement(&mut self, transform: Affine, brush_transform: Option<Affine>) {
self.scene.set_transform(transform);
match brush_transform {
Some(bt) => self.scene.set_paint_transform(bt),
None => self.scene.reset_paint_transform(),
}
}
}
impl SceneBuilder for Writer<'_> {
fn clear(&mut self) {
self.scene.reset();
}
fn fill(
&mut self,
rule: FillRule,
transform: Affine,
brush: &Brush,
brush_transform: Option<Affine>,
path: &Path,
pick_id: PickId,
) {
let Some(paint) = self.paint(brush, pick_id) else {
return;
};
self.set_placement(transform, brush_transform);
self.scene.set_fill_rule(convert::fill_rule(rule));
self.scene.set_paint(paint);
self.scene.fill_path(path);
}
fn stroke(
&mut self,
stroke: &Stroke,
transform: Affine,
brush: &Brush,
brush_transform: Option<Affine>,
path: &Path,
pick_id: PickId,
) {
let Some(paint) = self.paint(brush, pick_id) else {
return;
};
let mut stroke = stroke.clone();
if self.pass == Pass::Pick && stroke.width < MIN_PICK_STROKE_WIDTH {
stroke.width = MIN_PICK_STROKE_WIDTH;
}
self.set_placement(transform, brush_transform);
self.scene.set_stroke(stroke);
self.scene.set_paint(paint);
self.scene.stroke_path(path);
}
fn draw_image(
&mut self,
image: &Image,
transform: Affine,
sampling: Sampling,
alpha: f32,
pick_id: PickId,
) {
let bounds = crate::geometry::Rect::new(0.0, 0.0, image.width.into(), image.height.into());
let paint = match self.pass {
Pass::Display => self.image_paint(
image,
convert::sampling_to_quality(sampling),
peniko::Extend::Pad,
peniko::Extend::Pad,
),
Pass::Pick => pick::raw_id(pick_id).map(|id| pick::id_to_color(id).into()),
};
let Some(paint) = paint else {
return;
};
let layered = self.pass == Pass::Display && alpha < 1.0;
if layered {
self.scene.push_opacity_layer(alpha);
}
self.set_placement(transform, None);
self.scene.set_fill_rule(peniko::Fill::NonZero);
self.scene.set_paint(paint);
self.scene.fill_rect(&bounds);
if layered {
self.scene.pop_layer();
}
}
fn draw_glyphs(&mut self, run: &GlyphRun<'_>, pick_id: PickId) {
let Some(paint) = self.paint(run.brush, pick_id) else {
return;
};
let layered = self.pass == Pass::Display && run.brush_alpha < 1.0;
if layered {
self.scene.push_opacity_layer(run.brush_alpha);
}
self.scene.set_transform(run.transform);
self.scene.reset_paint_transform();
self.scene.set_paint(paint);
let stroked = match (self.pass, run.style) {
(Pass::Display, Some(stroke)) => {
self.scene.set_stroke(stroke.clone());
true
}
_ => false,
};
let glyphs = run
.glyphs
.iter()
.map(|g| glifo::Glyph {
id: g.id,
x: g.x,
y: g.y,
})
.collect::<Vec<_>>();
let mut builder = self
.scene
.glyph_run(self.resources, run.font.data())
.font_size(run.font_size)
.hint(run.hint);
if let Some(gt) = run.glyph_transform {
builder = builder.glyph_transform(gt);
}
if stroked {
builder.stroke_glyphs(glyphs.into_iter());
} else {
builder.fill_glyphs(glyphs.into_iter());
}
if layered {
self.scene.pop_layer();
}
}
fn draw_mesh(&mut self, mesh_data: &Mesh, transform: Affine, pick_id: PickId) {
mesh::decompose(mesh_data, transform, pick_id, self);
}
fn push_layer(&mut self, blend: BlendMode, alpha: f32, transform: Affine, clip: &Path) {
self.scene.set_transform(transform);
match self.pass {
Pass::Display => self.scene.push_layer(
Some(clip),
Some(convert::blend_mode(blend)),
Some(alpha),
None,
None,
),
Pass::Pick => self.scene.push_layer(Some(clip), None, None, None, None),
}
}
fn pop_layer(&mut self) {
self.scene.pop_layer();
}
}
fn dimension(v: u32) -> Result<u16, BackendError> {
u16::try_from(v).map_err(|_| {
BackendError::Other(format!(
"frame dimension {v} exceeds the {MAX_DIMENSION} px the hybrid backend supports"
))
})
}
fn recorded_images(ops: &RecordingScene) -> Vec<&Image> {
use crate::scene::recording::Op;
let mut keys: Vec<u64> = Vec::new();
let mut images: Vec<&Image> = Vec::new();
for op in &ops.ops {
let image = match op {
Op::Fill { brush, .. } | Op::Stroke { brush, .. } => match brush {
Brush::Image(b) => Some(&b.image),
_ => None,
},
Op::DrawGlyphs(run) => match &run.brush {
Brush::Image(b) => Some(&b.image),
_ => None,
},
Op::DrawImage { image, .. } => Some(image),
_ => None,
};
if let Some(image) = image {
let key = image_key(image);
if !keys.contains(&key) {
keys.push(key);
images.push(image);
}
}
}
images
}
#[cfg(feature = "vello-hybrid")]
fn unpremultiply(buf: &mut [u8]) {
for px in buf.chunks_exact_mut(4) {
let a = px[3];
if a == 0 || a == 255 {
continue;
}
let a32 = u32::from(a);
for c in &mut px[..3] {
*c = ((u32::from(*c) * 255 + a32 / 2) / a32).min(255) as u8;
}
}
}