Added a Vello back-end with a custom winit viewer. My first PR! 🎉 You can get Rust with [Rustup](https://rustup.rs). Then run `cargo run --release` in the `vello/` directory. Things left to do before merging: - [x] remove `clap` - [x] add rustfmt & clippy checks to CI - [x] add text support - [x] add README - [ ] add CONTRIBUTING Diffs= abe5aab14 Added a Vello back-end with a custom winit viewer. (#5786) Co-authored-by: Dragoș Tiselice <dragos@rive.app> Co-authored-by: Dragoș Tiselice <dragostiselice@gmail.com>
diff --git a/.rive_head b/.rive_head index 0d382b8..498b00a 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -adeebb26a7ac58a1aaa18a40ed9d98af46735c97 +abe5aab1431c34a42458a9650831d6b3a7c39feb
diff --git a/vello/Cargo.toml b/vello/Cargo.toml new file mode 100644 index 0000000..4a4d533 --- /dev/null +++ b/vello/Cargo.toml
@@ -0,0 +1,17 @@ +[package] +name = "rive-vello" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +walkdir = "2.3.3" + +[dependencies] +clap = { version = "4.3.19", features = ["derive"] } +image = "0.24.6" +pollster = "0.3.0" +smallvec = "1.8.0" +vello = { git = "https://github.com/linebender/vello", rev = "3cb5462" } +wgpu = "0.17.0" +winit = "0.28.6"
diff --git a/vello/README.md b/vello/README.md new file mode 100644 index 0000000..a59755f --- /dev/null +++ b/vello/README.md
@@ -0,0 +1,31 @@ +# Vello back-end for Rive + +Small application for viewing `.riv` files rendered with Vello. It uses [winit] +for creating the window and [image] to decode images. + +## Build + +You will need clang, Cargo, and the Rust compiler to build. You can can install +Cargo and rustc using [rustup]. + +To use the application, run: + +```bash +$ cargo run --release +``` + +## Usage + +Drop any `.riv` file into the window to open it. Scroll to control the size of +the grid of copies. + +## Caveats + +The current implementation is a work-in-progress and might exhibit artifacts or +render incorrectly. + +Only tested on macOS for the time being. + +[winit]: https://github.com/rust-windowing/winit +[image]: https://github.com/image-rs/image +[rustup]: https://rustup.rs
diff --git a/vello/build.rs b/vello/build.rs new file mode 100644 index 0000000..c2105ca --- /dev/null +++ b/vello/build.rs
@@ -0,0 +1,118 @@ +use std::{ + env, + ffi::OsString, + path::{Path, PathBuf}, + process::Command, +}; + +use walkdir::WalkDir; + +const CHECKOUT_DIRECTORY: &str = "target"; + +struct Checkout { + path: PathBuf, +} + +impl Checkout { + pub fn new(repo: &str, tag: &str) -> Self { + let name = repo.rsplit_once('/').expect("URL format invalid").1; + let mut path = PathBuf::from(CHECKOUT_DIRECTORY); + + path.push(name); + + if !path.is_dir() { + Command::new("git") + .args([ + "clone", + "-b", + tag, + repo, + &path.as_os_str().to_string_lossy(), + ]) + .output() + .unwrap_or_else(|_| panic!("failed to clone {}; is git CLI available?", name)); + } + + Self { path } + } + + pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf { + self.path.join(path) + } +} + +fn all_files_with_extension<P: AsRef<Path>>( + path: P, + extension: &str, +) -> impl Iterator<Item = PathBuf> + '_ { + WalkDir::new(path).into_iter().filter_map(move |entry| { + entry + .ok() + .map(|entry| entry.into_path()) + .filter(|path| path.extension() == Some(&OsString::from(extension))) + }) +} + +fn main() { + println!("cargo:rerun-if-changed=src/vello_renderer.hpp"); + println!("cargo:rerun-if-changed=src/vello_renderer.cpp"); + println!("cargo:rerun-if-changed=src/winit_viewer.cpp"); + + let harfbuzz = Checkout::new("https://github.com/harfbuzz/harfbuzz", "8.1.1"); + let sheen_bidi = Checkout::new("https://github.com/Tehreer/SheenBidi", "v2.6"); + + let target = env::var("TARGET").unwrap(); + let profile = env::var("PROFILE").unwrap(); + + let mut cfg = cc::Build::new(); + cfg.compiler("clang") + .cpp(true) + .flag_if_supported("-std=c++11") // for unix + .warnings(false) + .file(harfbuzz.join("src/harfbuzz.cc")); + + if !target.contains("windows") { + cfg.define("HAVE_PTHREAD", "1"); + } + + if target.contains("apple") && profile.contains("release") { + cfg.define("HAVE_CORETEXT", "1"); + } + + if target.contains("windows") { + cfg.define("HAVE_DIRECTWRITE", "1"); + } + + if target.contains("windows-gnu") { + cfg.flag("-Wa,-mbig-obj"); + } + + cfg.compile("harfbuzz"); + + cc::Build::new() + .compiler("clang") + .files(all_files_with_extension(sheen_bidi.join("Source"), "c")) + .include(sheen_bidi.join("Headers")) + .compile("sheenbidi"); + + cc::Build::new() + .compiler("clang") + .cpp(true) + .files(all_files_with_extension("../src", "cpp")) + .files(all_files_with_extension( + "../viewer/src/viewer_content", + "cpp", + )) + .file("src/vello_renderer.cpp") + .file("src/winit_viewer.cpp") + .include("src") + .include("../include") + .include("../viewer/include") + .include(harfbuzz.join("src")) + .include(sheen_bidi.join("Headers")) + .flag("-std=c++14") + .flag("-Wno-everything") + .define("RIVE_SKIP_IMGUI", None) + .define("WITH_RIVE_TEXT", None) + .compile("rive"); +}
diff --git a/vello/src/lib.rs b/vello/src/lib.rs new file mode 100644 index 0000000..f50f50a --- /dev/null +++ b/vello/src/lib.rs
@@ -0,0 +1,564 @@ +#![allow(clippy::missing_safety_doc)] + +use core::slice; +use std::{io::Cursor, ptr::NonNull}; + +use image::io::Reader; +use smallvec::SmallVec; +use util::{ScaleFromOrigin, UnwrapAndDeref}; +use vello::{ + kurbo::{Affine, BezPath, Line, PathSeg, Point, Rect, Shape, Vec2}, + peniko::{ + BlendMode, Brush, BrushRef, Cap, Color, ColorStop, ColorStopsSource, Fill, Format, + Gradient, Image, Join, Mix, Stroke, + }, + SceneBuilder, SceneFragment, +}; + +mod rive; +mod util; +mod viewer; + +pub use viewer::ViewerContent; + +fn from_bgra8(color: u32) -> Color { + Color::rgba8( + (color >> 16) as u8, + (color >> 8) as u8, + color as u8, + (color >> 24) as u8, + ) +} + +#[derive(Debug)] +enum RenderStyle { + Fill, + Stroke(Stroke), +} + +#[derive(Debug)] +struct SliceStops<'s> { + colors: &'s [u32], + stops: &'s [f32], +} + +impl ColorStopsSource for SliceStops<'_> { + fn collect_stops(&self, vec: &mut SmallVec<[ColorStop; 4]>) { + vec.extend( + self.colors + .iter() + .zip(self.stops.iter()) + .map(|(&color, &offset)| ColorStop { + offset, + color: from_bgra8(color), + }), + ); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_gradient_new_linear( + sx: f32, + sy: f32, + ex: f32, + ey: f32, + colors_data: *const u32, + stops_data: *const f32, + len: usize, +) -> Option<NonNull<Gradient>> { + let colors = slice::from_raw_parts(colors_data, len); + let stops = slice::from_raw_parts(stops_data, len); + + let raw_stops = SliceStops { colors, stops }; + let gradient = + Gradient::new_linear((sx as f64, sy as f64), (ex as f64, ey as f64)).with_stops(raw_stops); + + NonNull::new(Box::into_raw(Box::new(gradient))) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_gradient_new_radial( + cx: f32, + cy: f32, + radius: f32, + colors_data: *const u32, + stops_data: *const f32, + len: usize, +) -> Option<NonNull<Gradient>> { + let colors = slice::from_raw_parts(colors_data, len); + let stops = slice::from_raw_parts(stops_data, len); + + let raw_stops = SliceStops { colors, stops }; + let gradient = Gradient::new_radial((cx as f64, cy as f64), radius).with_stops(raw_stops); + + NonNull::new(Box::into_raw(Box::new(gradient))) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_gradient_release(gradient: Option<NonNull<Gradient>>) { + gradient.map(|ptr| Box::from_raw(ptr.as_ptr())).unwrap(); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_image_new(data: *const u8, len: usize) -> Option<NonNull<Image>> { + let image = Reader::new(Cursor::new(slice::from_raw_parts(data, len))) + .with_guessed_format() + .ok()? + .decode() + .ok()? + .into_rgba8(); + let width = image.width(); + let height = image.height(); + + NonNull::new(Box::into_raw(Box::new(Image::new( + image.into_raw().into(), + Format::Rgba8, + width, + height, + )))) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_image_release(image: Option<NonNull<Image>>) { + image.map(|ptr| Box::from_raw(ptr.as_ptr())).unwrap(); +} + +#[derive(Debug)] +pub struct VelloPaint { + style: RenderStyle, + brush: Brush, + blend_mode: BlendMode, +} + +impl Default for VelloPaint { + fn default() -> Self { + Self { + style: RenderStyle::Fill, + brush: Brush::Solid(Color::TRANSPARENT), + blend_mode: Mix::Normal.into(), + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_new() -> Option<NonNull<VelloPaint>> { + NonNull::new(Box::into_raw(Box::default())) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_release(paint: Option<NonNull<VelloPaint>>) { + paint.map(|ptr| Box::from_raw(ptr.as_ptr())).unwrap(); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_style( + mut paint: Option<NonNull<VelloPaint>>, + style: rive::RenderPaint, +) { + paint.unwrap_and_deref().style = style.into(); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_color(mut paint: Option<NonNull<VelloPaint>>, color: u32) { + paint.unwrap_and_deref().brush = Brush::Solid(from_bgra8(color)); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_gradient( + mut paint: Option<NonNull<VelloPaint>>, + mut gradient: Option<NonNull<Gradient>>, +) { + paint.unwrap_and_deref().brush = Brush::Gradient(gradient.unwrap_and_deref().clone()); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_thickness( + mut paint: Option<NonNull<VelloPaint>>, + thickness: f32, +) { + let style = &mut paint.unwrap_and_deref().style; + loop { + if let RenderStyle::Stroke(stroke) = style { + stroke.width = thickness; + break; + } else { + *style = RenderStyle::Stroke(Stroke::new(0.0)); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_join( + mut paint: Option<NonNull<VelloPaint>>, + join: rive::StrokeJoin, +) { + let style = &mut paint.unwrap_and_deref().style; + loop { + if let RenderStyle::Stroke(stroke) = style { + stroke.join = match join { + rive::StrokeJoin::Miter => Join::Miter, + rive::StrokeJoin::Round => Join::Round, + rive::StrokeJoin::Bevel => Join::Bevel, + }; + break; + } else { + *style = RenderStyle::Stroke(Stroke::new(0.0)); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_cap( + mut paint: Option<NonNull<VelloPaint>>, + cap: rive::StrokeCap, +) { + let style = &mut paint.unwrap_and_deref().style; + loop { + if let RenderStyle::Stroke(stroke) = style { + stroke.start_cap = match cap { + rive::StrokeCap::Butt => Cap::Butt, + rive::StrokeCap::Round => Cap::Round, + rive::StrokeCap::Square => Cap::Square, + }; + stroke.end_cap = stroke.start_cap; + break; + } else { + *style = RenderStyle::Stroke(Stroke::new(0.0)); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_paint_set_blend_mode( + mut paint: Option<NonNull<VelloPaint>>, + blend_mode: rive::BlendMode, +) { + let mix: Mix = blend_mode.into(); + paint.unwrap_and_deref().blend_mode = mix.into(); +} + +#[derive(Debug)] +pub struct VelloPath { + path: BezPath, + fill: Fill, +} + +impl Default for VelloPath { + fn default() -> Self { + Self { + path: Default::default(), + fill: Fill::NonZero, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_new() -> Option<NonNull<VelloPath>> { + NonNull::new(Box::into_raw(Box::default())) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_release(path: Option<NonNull<VelloPath>>) { + path.map(|ptr| Box::from_raw(ptr.as_ptr())).unwrap(); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_set_fill_rule( + mut path: Option<NonNull<VelloPath>>, + fill_rule: rive::FillRule, +) { + path.unwrap_and_deref().fill = match fill_rule { + rive::FillRule::NonZero => Fill::NonZero, + rive::FillRule::EvenOdd => Fill::EvenOdd, + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_rewind(mut path: Option<NonNull<VelloPath>>) { + path.unwrap_and_deref().path.truncate(0); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_extend( + mut path: Option<NonNull<VelloPath>>, + mut from: Option<NonNull<VelloPath>>, + mut transform: Option<NonNull<[f32; 6]>>, +) { + let mut from = from.unwrap_and_deref().path.clone(); + from.apply_affine(Affine::new(transform.unwrap_and_deref().map(Into::into))); + + path.unwrap_and_deref() + .path + .extend(from.elements().iter().cloned()); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_move_to(mut path: Option<NonNull<VelloPath>>, x: f32, y: f32) { + path.unwrap_and_deref() + .path + .move_to(Point::new(x as f64, y as f64)); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_line_to(mut path: Option<NonNull<VelloPath>>, x: f32, y: f32) { + path.unwrap_and_deref() + .path + .line_to(Point::new(x as f64, y as f64)); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_cubic_to( + mut path: Option<NonNull<VelloPath>>, + ox: f32, + oy: f32, + ix: f32, + iy: f32, + x: f32, + y: f32, +) { + path.unwrap_and_deref().path.curve_to( + Point::new(ox as f64, oy as f64), + Point::new(ix as f64, iy as f64), + Point::new(x as f64, y as f64), + ); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_path_close(mut path: Option<NonNull<VelloPath>>) { + path.unwrap_and_deref().path.close_path(); +} + +pub struct VelloRenderer { + pub scene: Box<SceneFragment>, + builder: SceneBuilder<'static>, + transforms: Vec<Affine>, + clips: Vec<bool>, +} + +impl VelloRenderer { + fn last_transform(&mut self) -> &mut Affine { + self.transforms.last_mut().unwrap() + } + + fn last_clip(&mut self) -> &mut bool { + self.clips.last_mut().unwrap() + } +} + +impl Default for VelloRenderer { + fn default() -> Self { + let mut scene = Box::<SceneFragment>::default(); + let builder = { + let scene_mut: &mut SceneFragment = &mut scene; + SceneBuilder::for_fragment(unsafe { + // Quite a hack until we have a better way to do this in Vello. + // Pretend that the scene fragment pointer lives for 'static. + std::mem::transmute(scene_mut) + }) + }; + + Self { + scene, + builder, + transforms: vec![Affine::IDENTITY], + clips: vec![false], + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_save(mut renderer: Option<NonNull<VelloRenderer>>) { + let renderer = renderer.unwrap_and_deref(); + + let last_transform = *renderer.last_transform(); + + renderer.transforms.push(last_transform); + renderer.clips.push(false); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_restore(mut renderer: Option<NonNull<VelloRenderer>>) { + let renderer = renderer.unwrap_and_deref(); + + renderer.transforms.pop(); + if renderer.clips.pop().unwrap_or_default() { + renderer.builder.pop_layer(); + } + + if renderer.transforms.is_empty() { + renderer.transforms.push(Affine::IDENTITY); + renderer.clips.push(false); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_transform( + mut renderer: Option<NonNull<VelloRenderer>>, + transform: *const [f32; 6], +) { + let last_transform = renderer.unwrap_and_deref().last_transform(); + *last_transform *= Affine::new((*transform).map(Into::into)); +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_draw_path( + mut renderer: Option<NonNull<VelloRenderer>>, + mut path: Option<NonNull<VelloPath>>, + mut paint: Option<NonNull<VelloPaint>>, +) { + let renderer = renderer.unwrap_and_deref(); + let path = path.unwrap_and_deref(); + let paint = paint.unwrap_and_deref(); + + let transform = *renderer.last_transform(); + + let builder = &mut renderer.builder; + + let skip_blending = paint.blend_mode == Mix::Normal.into(); + + if !skip_blending { + builder.push_layer(paint.blend_mode, 1.0, transform, &path.path.bounding_box()); + } + + match &paint.style { + RenderStyle::Fill => builder.fill(path.fill, transform, &paint.brush, None, &path.path), + RenderStyle::Stroke(stroke) => { + builder.stroke(stroke, transform, &paint.brush, None, &path.path) + } + } + + if !skip_blending { + builder.pop_layer(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_clip_path( + mut renderer: Option<NonNull<VelloRenderer>>, + mut clip: Option<NonNull<VelloPath>>, +) { + let renderer = renderer.unwrap_and_deref(); + + let transform = *renderer.last_transform(); + + if *renderer.last_clip() { + renderer.builder.pop_layer(); + } + + renderer + .builder + .push_layer(Mix::Clip, 1.0, transform, &clip.unwrap_and_deref().path); + + *renderer.last_clip() = true; +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_draw_image( + mut renderer: Option<NonNull<VelloRenderer>>, + mut image: Option<NonNull<Image>>, + blend_mode: rive::BlendMode, + opacity: f32, +) { + let renderer = renderer.unwrap_and_deref(); + let image = image.unwrap_and_deref(); + let mix: Mix = blend_mode.into(); + + let transform = renderer.last_transform().pre_translate(Vec2::new( + image.width as f64 * -0.5, + image.height as f64 * -0.5, + )); + let rect = Rect::new(0.0, 0.0, image.width as f64, image.height as f64); + + let builder = &mut renderer.builder; + + let skip_blending = mix == Mix::Normal && opacity == 1.0; + + if skip_blending { + builder.push_layer(mix, opacity, transform, &rect); + } + + builder.draw_image(image, transform); + + if skip_blending { + builder.pop_layer(); + } +} + +fn triangle_path(points: [Point; 3]) -> BezPath { + BezPath::from_path_segments( + [ + PathSeg::Line(Line::new(points[0], points[1])), + PathSeg::Line(Line::new(points[1], points[2])), + PathSeg::Line(Line::new(points[2], points[0])), + ] + .into_iter(), + ) +} + +#[no_mangle] +pub unsafe extern "C" fn vello_renderer_draw_image_mesh( + mut renderer: Option<NonNull<VelloRenderer>>, + mut image: Option<NonNull<Image>>, + vertices_data: *const rive::Vec2D, + vertices_len: usize, + uvs_data: *const rive::Vec2D, + uvs_len: usize, + indices_data: *const u16, + indices_len: usize, + blend_mode: rive::BlendMode, + opacity: f32, +) { + let renderer = renderer.unwrap_and_deref(); + let image = image.unwrap_and_deref(); + + let vertices = slice::from_raw_parts(vertices_data, vertices_len); + let uvs = slice::from_raw_parts(uvs_data, uvs_len); + let indices = slice::from_raw_parts(indices_data, indices_len); + + let mix: Mix = blend_mode.into(); + + for triangle_indices in indices.chunks_exact(3) { + let points = [ + vertices[triangle_indices[0] as usize], + vertices[triangle_indices[1] as usize], + vertices[triangle_indices[2] as usize], + ]; + let uvs = [ + uvs[triangle_indices[0] as usize], + uvs[triangle_indices[1] as usize], + uvs[triangle_indices[2] as usize], + ]; + + let center = Point::new( + ((points[0].x + points[1].x + points[2].x) / 3.0) as f64, + ((points[0].y + points[1].y + points[2].y) / 3.0) as f64, + ); + + let path = triangle_path(points.map(|v| Point::new(v.x as f64, v.y as f64))); + + let transform = renderer + .last_transform() + .pre_scale_from_origin(1.03, center); + let brush_transform = util::map_uvs_to_triangle(&points, &uvs, image.width, image.height); + + let builder = &mut renderer.builder; + + let skip_blending = mix == Mix::Normal; + + if !skip_blending { + builder.push_layer(mix, opacity, transform, &path.bounding_box()); + } + + builder.fill( + Fill::NonZero, + transform, + BrushRef::Image(image), + Some(brush_transform), + &path, + ); + + if !skip_blending { + builder.pop_layer(); + } + } +}
diff --git a/vello/src/main.rs b/vello/src/main.rs new file mode 100644 index 0000000..fadb30f --- /dev/null +++ b/vello/src/main.rs
@@ -0,0 +1,229 @@ +use std::time::Instant; + +use rive_vello::{VelloRenderer, ViewerContent}; +use vello::{ + kurbo::{Affine, Rect, Vec2}, + peniko::{Color, Fill}, + util::{RenderContext, RenderSurface}, + Renderer, RendererOptions, Scene, SceneBuilder, +}; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, MouseButton, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, +}; + +struct RenderState { + surface: RenderSurface, + window: Window, +} + +const INITIAL_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(700, 700); +const FRAME_STATS_CAPACITY: usize = 30; +const SCROLL_FACTOR_THRESHOLD: f64 = 100.0; + +fn main() { + let mut viewer_content: Option<ViewerContent> = None; + + let event_loop = EventLoop::new(); + let mut cached_window: Option<Window> = None; + let mut renderer: Option<Renderer> = None; + let mut render_cx = RenderContext::new().unwrap(); + let mut render_state: Option<RenderState> = None; + + let mut mouse_pos = Vec2::default(); + let mut scroll_delta = 0.0; + let mut frame_start_time = Instant::now(); + let mut stats = Vec::with_capacity(FRAME_STATS_CAPACITY); + + event_loop.run(move |event, _event_loop, control_flow| match event { + Event::WindowEvent { ref event, .. } => { + let Some(render_state) = &mut render_state else { return }; + + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(size) => { + if let Some(viewer_content) = &viewer_content { + viewer_content.handle_resize(size.width, size.height); + } + + render_cx.resize_surface(&mut render_state.surface, size.width, size.height); + render_state.window.request_redraw(); + } + WindowEvent::MouseInput { + state, + button: MouseButton::Left, + .. + } => { + if let Some(viewer_content) = &viewer_content { + let handler = match state { + ElementState::Pressed => ViewerContent::handle_pointer_down, + ElementState::Released => ViewerContent::handle_pointer_up, + }; + + handler(viewer_content, mouse_pos); + } + } + WindowEvent::CursorMoved { position, .. } => { + mouse_pos = Vec2::new(position.x, position.y); + if let Some(viewer_content) = &viewer_content { + viewer_content.handle_pointer_move(mouse_pos); + } + } + WindowEvent::MouseWheel { delta, .. } => match delta { + winit::event::MouseScrollDelta::LineDelta(_, lines_y) => { + scroll_delta = (scroll_delta + - (*lines_y as f64).signum() * SCROLL_FACTOR_THRESHOLD) + .max(0.0); + } + winit::event::MouseScrollDelta::PixelDelta(pixels) => { + scroll_delta = (scroll_delta - pixels.y).max(0.0); + } + }, + WindowEvent::DroppedFile(path) => { + viewer_content = ViewerContent::new(path); + + if let Some(viewer_content) = &viewer_content { + let size = render_state.window.inner_size(); + viewer_content.handle_resize(size.width, size.height); + } + } + _ => {} + } + } + Event::MainEventsCleared => { + if let Some(render_state) = &mut render_state { + render_state.window.request_redraw(); + } + } + Event::RedrawRequested(_) => { + let mut vello_renderer = VelloRenderer::default(); + let factor = (scroll_delta / SCROLL_FACTOR_THRESHOLD).max(1.0) as u32; + + let elapsed = &frame_start_time.elapsed(); + stats.push(elapsed.as_secs_f64()); + + if stats.len() == FRAME_STATS_CAPACITY { + let average = stats.drain(..).sum::<f64>() / FRAME_STATS_CAPACITY as f64; + + if let Some(state) = &mut render_state { + let copies = (factor > 1) + .then(|| format!(" ({} copies)", factor.pow(2))) + .unwrap_or_default(); + state.window.set_title(&format!( + "Rive on Vello demo | {:.2}ms{}", + average * 1000.0, + copies + )); + } + } + + frame_start_time = Instant::now(); + + let Some(render_state) = &mut render_state else { return }; + let width = render_state.surface.config.width; + let height = render_state.surface.config.height; + let device_handle = &render_cx.devices[render_state.surface.dev_id]; + + let render_params = vello::RenderParams { + base_color: Color::DIM_GRAY, + width, + height, + }; + + let surface_texture = render_state + .surface + .surface + .get_current_texture() + .expect("failed to get surface texture"); + + let mut scene = Scene::default(); + let mut builder = SceneBuilder::for_scene(&mut scene); + + if let Some(viewer_content) = &mut viewer_content { + viewer_content.handle_draw(&mut vello_renderer, elapsed.as_secs_f64()); + + for i in 0..factor.pow(2) { + builder.append( + &vello_renderer.scene, + Some( + Affine::default() + .then_scale(1.0 / factor as f64) + .then_translate(Vec2::new( + (i % factor) as f64 * width as f64 / factor as f64, + (i / factor) as f64 * height as f64 / factor as f64, + )), + ), + ); + } + } else { + // Vello currently crashes when rendering an empty scene. + builder.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::TRANSPARENT, + None, + &Rect::new(0.0, 0.0, 0.0, 0.0), + ); + } + + vello::block_on_wgpu( + &device_handle.device, + renderer.as_mut().unwrap().render_to_surface_async( + &device_handle.device, + &device_handle.queue, + &scene, + &surface_texture, + &render_params, + ), + ) + .expect("failed to render to surface"); + + surface_texture.present(); + device_handle.device.poll(wgpu::Maintain::Poll); + } + Event::Suspended => { + if let Some(render_state) = render_state.take() { + cached_window = Some(render_state.window); + } + *control_flow = ControlFlow::Wait; + } + Event::Resumed => { + if render_state.is_some() { + return; + } + + let window = cached_window.take().unwrap_or_else(|| { + WindowBuilder::new() + .with_inner_size(INITIAL_WINDOW_SIZE) + .with_resizable(true) + .with_title("Rive on Vello demo") + .build(_event_loop) + .unwrap() + }); + let size = window.inner_size(); + let surface_future = render_cx.create_surface(&window, size.width, size.height); + + let surface = pollster::block_on(surface_future).expect("Error creating surface"); + render_state = { + let render_state = RenderState { window, surface }; + renderer = Some( + Renderer::new( + &render_cx.devices[render_state.surface.dev_id].device, + &RendererOptions { + surface_format: Some(render_state.surface.format), + timestamp_period: render_cx.devices[render_state.surface.dev_id] + .queue + .get_timestamp_period(), + }, + ) + .expect("Could create renderer"), + ); + Some(render_state) + }; + *control_flow = ControlFlow::Poll; + } + _ => {} + }); +}
diff --git a/vello/src/rive.rs b/vello/src/rive.rs new file mode 100644 index 0000000..7c6c222 --- /dev/null +++ b/vello/src/rive.rs
@@ -0,0 +1,98 @@ +use vello::peniko::{Mix, Stroke}; + +use crate::RenderStyle; + +#[repr(C)] +#[derive(Debug, Default)] +pub enum RenderPaint { + Stroke, + #[default] + Fill, +} + +impl From<RenderPaint> for RenderStyle { + fn from(value: RenderPaint) -> Self { + match value { + RenderPaint::Stroke => Self::Stroke(Stroke::new(0.0)), + RenderPaint::Fill => Self::Fill, + } + } +} + +#[repr(u32)] +#[derive(Debug, Default)] +pub enum StrokeJoin { + #[default] + Miter = 0, + Round = 1, + Bevel = 2, +} + +#[repr(u32)] +#[derive(Debug, Default)] +pub enum StrokeCap { + #[default] + Butt = 0, + Round = 1, + Square = 2, +} + +#[repr(u32)] +#[derive(Debug, Default)] +pub enum BlendMode { + #[default] + SrcOver = 3, + Screen = 14, + Overlay = 15, + Darken = 16, + Lighten = 17, + ColorDodge = 18, + ColorBurn = 19, + HardLight = 20, + SoftLight = 21, + Difference = 22, + Exclusion = 23, + Multiply = 24, + Hue = 25, + Saturation = 26, + Color = 27, + Luminosity = 28, +} + +impl From<BlendMode> for Mix { + fn from(value: BlendMode) -> Self { + match value { + BlendMode::SrcOver => Self::Normal, + BlendMode::Screen => Self::Screen, + BlendMode::Overlay => Self::Overlay, + BlendMode::Darken => Self::Darken, + BlendMode::Lighten => Self::Lighten, + BlendMode::ColorDodge => Self::ColorDodge, + BlendMode::ColorBurn => Self::ColorBurn, + BlendMode::HardLight => Self::HardLight, + BlendMode::SoftLight => Self::SoftLight, + BlendMode::Difference => Self::Difference, + BlendMode::Exclusion => Self::Exclusion, + BlendMode::Multiply => Self::Multiply, + BlendMode::Hue => Self::Hue, + BlendMode::Saturation => Self::Saturation, + BlendMode::Color => Self::Color, + BlendMode::Luminosity => Self::Luminosity, + } + } +} + +#[repr(C)] +#[derive(Debug, Default)] +pub enum FillRule { + #[default] + NonZero, + EvenOdd, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct Vec2D { + pub x: f32, + pub y: f32, +}
diff --git a/vello/src/util.rs b/vello/src/util.rs new file mode 100644 index 0000000..aba56d9 --- /dev/null +++ b/vello/src/util.rs
@@ -0,0 +1,157 @@ +use std::{ + ops::{Add, Mul, Neg, Sub}, + ptr::NonNull, +}; + +use vello::kurbo::{Affine, Point}; + +use crate::rive; + +pub trait UnwrapAndDeref { + type Deref; + + unsafe fn unwrap_and_deref(&mut self) -> &mut Self::Deref; +} + +impl<T> UnwrapAndDeref for Option<NonNull<T>> { + type Deref = T; + + unsafe fn unwrap_and_deref(&mut self) -> &mut Self::Deref { + self.map(|mut ptr| ptr.as_mut()).unwrap() + } +} + +#[derive(Clone, Copy, Debug)] +struct Vec2 { + x: f32, + y: f32, +} + +impl Vec2 { + pub fn new(x: f32, y: f32) -> Self { + Self { x, y } + } + + pub fn splat(val: f32) -> Self { + Self::new(val, val) + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::new(self.x + rhs.x, self.y + rhs.y) + } +} + +impl Sub for Vec2 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self::new(self.x - rhs.x, self.y - rhs.y) + } +} + +impl Mul for Vec2 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self::new(self.x * rhs.x, self.y * rhs.y) + } +} + +impl Mul<f32> for Vec2 { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + self * Self::splat(rhs) + } +} + +impl Neg for Vec2 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::new(-self.x, -self.y) + } +} + +/// Finds the affine transform that maps triangle `from` to triangle `to`. The algorithm is based +/// on the [Simplex Affine Mapping] method which has a [Swift implementation]. +/// +/// [Simplex Affine Mapping]: https://www.researchgate.net/publication/332410209_Beginner%27s_guide_to_mapping_simplexes_affinely +/// [Swift implementation]: https://rethunk.medium.com/finding-an-affine-transform-using-three-2d-point-correspondences-using-simplex-affine-mapping-255aeb4e8055 +fn simplex_affine_mapping(from: [Vec2; 3], to: [Vec2; 3]) -> Affine { + let [a, b, c] = from; + let [d, e, f] = to; + + let det_recip = (a.x * b.y + b.x * c.y + c.x * a.y - a.x * c.y - b.x * a.y - c.x * b.y).recip(); + + let p = (d * (b.y - c.y) - e * (a.y - c.y) + f * (a.y - b.y)) * det_recip; + + let q = (e * (a.x - c.x) - d * (b.x - c.x) - f * (a.x - b.x)) * det_recip; + + let t = (d * (b.x * c.y - b.y * c.x) - e * (a.x * c.y - a.y * c.x) + + f * (a.x * b.y - a.y * b.x)) + * det_recip; + + Affine::new([ + p.x as f64, p.y as f64, q.x as f64, q.y as f64, t.x as f64, t.y as f64, + ]) +} + +pub fn map_uvs_to_triangle( + points: &[rive::Vec2D; 3], + uvs: &[rive::Vec2D; 3], + width: u32, + height: u32, +) -> Affine { + simplex_affine_mapping( + uvs.map(|v| Vec2::new(v.x * width as f32, v.y * height as f32)), + points.map(|v| Vec2::new(v.x, v.y)), + ) +} + +pub trait ScaleFromOrigin { + fn pre_scale_from_origin(self, scale: f64, origin: Point) -> Self; +} + +impl ScaleFromOrigin for Affine { + fn pre_scale_from_origin(self, scale: f64, origin: Point) -> Self { + let origin = origin.to_vec2(); + self.pre_translate(origin) + .pre_scale(scale) + .pre_translate(-origin) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use vello::kurbo; + + #[test] + fn straightforward_sam() { + let from = [ + Vec2::new(1.0, 1.0), + Vec2::new(3.0, 1.0), + Vec2::new(1.0, 3.0), + ]; + let to = [ + Vec2::new(4.0, 4.0), + Vec2::new(4.0, -1.0), + Vec2::new(-2.0, 4.0), + ]; + + let mapping = simplex_affine_mapping(from, to); + + for (from, to) in from.into_iter().zip(to.into_iter()) { + let res = mapping * kurbo::Point::new(from.x as f64, from.y as f64); + + assert_eq!(res.x as f32, to.x); + assert_eq!(res.y as f32, to.y); + } + } +}
diff --git a/vello/src/vello_renderer.cpp b/vello/src/vello_renderer.cpp new file mode 100644 index 0000000..63c94e7 --- /dev/null +++ b/vello/src/vello_renderer.cpp
@@ -0,0 +1,181 @@ +#include "vello_renderer.hpp" + +VelloPath::~VelloPath() { vello_path_release(m_path); } + +void VelloPath::rewind() { vello_path_rewind(m_path); } + +void VelloPath::addRenderPath(RenderPath* path, const Mat2D& transform) +{ + vello_path_extend(m_path, static_cast<VelloPath*>(path)->m_path, transform.values()); +} + +void VelloPath::fillRule(FillRule value) { vello_path_set_fill_rule(m_path, value); } + +void VelloPath::moveTo(float x, float y) { vello_path_move_to(m_path, x, y); } + +void VelloPath::lineTo(float x, float y) { vello_path_line_to(m_path, x, y); } + +void VelloPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) +{ + vello_path_cubic_to(m_path, ox, oy, ix, iy, x, y); +} + +void VelloPath::close() { vello_path_close(m_path); } + +VelloGradient::~VelloGradient() { vello_gradient_release(m_gradient); } + +VelloImage::~VelloImage() { vello_image_release(m_image); } + +VelloPaint::~VelloPaint() { vello_paint_release(m_paint); } + +void VelloPaint::style(RenderPaintStyle style) { vello_paint_set_style(m_paint, style); } + +void VelloPaint::color(unsigned int value) { vello_paint_set_color(m_paint, value); } + +void VelloPaint::thickness(float value) { vello_paint_set_thickness(m_paint, value); } + +void VelloPaint::join(StrokeJoin value) { vello_paint_set_join(m_paint, value); } + +void VelloPaint::cap(StrokeCap value) { vello_paint_set_cap(m_paint, value); } + +void VelloPaint::blendMode(BlendMode value) { vello_paint_set_blend_mode(m_paint, value); } + +void VelloPaint::shader(rcp<RenderShader> shader) +{ + VelloGradient* gradient = (VelloGradient*)shader.get(); + + if (gradient != nullptr) + { + vello_paint_set_gradient(m_paint, gradient->gradient()); + } +} + +void VelloRenderer::save() { vello_renderer_save(m_renderer); } + +void VelloRenderer::restore() { vello_renderer_restore(m_renderer); } + +void VelloRenderer::transform(const Mat2D& transform) +{ + vello_renderer_transform(m_renderer, transform.values()); +} + +void VelloRenderer::clipPath(RenderPath* path) +{ + vello_renderer_clip_path(m_renderer, static_cast<VelloPath*>(path)->path()); +} + +void VelloRenderer::drawPath(RenderPath* path, RenderPaint* paint) +{ + vello_renderer_draw_path(m_renderer, + static_cast<VelloPath*>(path)->path(), + static_cast<VelloPaint*>(paint)->paint()); +} + +void VelloRenderer::drawImage(const RenderImage* image, BlendMode blend_mode, float opacity) +{ + vello_renderer_draw_image(m_renderer, + static_cast<const VelloImage*>(image)->image(), + blend_mode, + opacity); +} + +void VelloRenderer::drawImageMesh(const RenderImage* image, + rcp<RenderBuffer> vertices, + rcp<RenderBuffer> uvCoords, + rcp<RenderBuffer> indices, + BlendMode blendMode, + float opacity) +{ + vello_renderer_draw_image_mesh(m_renderer, + static_cast<const VelloImage*>(image)->image(), + DataRenderBuffer::Cast(vertices.get())->vecs(), + vertices->count(), + DataRenderBuffer::Cast(uvCoords.get())->vecs(), + uvCoords->count(), + DataRenderBuffer::Cast(indices.get())->u16s(), + indices->count(), + blendMode, + opacity); +} + +rcp<RenderBuffer> VelloFactory::makeBufferU16(Span<const uint16_t> data) +{ + return DataRenderBuffer::Make(data); +} + +rcp<RenderBuffer> VelloFactory::makeBufferU32(Span<const uint32_t> data) +{ + return DataRenderBuffer::Make(data); +} + +rcp<RenderBuffer> VelloFactory::makeBufferF32(Span<const float> data) +{ + return DataRenderBuffer::Make(data); +} + +rcp<RenderShader> VelloFactory::makeLinearGradient(float sx, + float sy, + float ex, + float ey, + const ColorInt colors[], // [count] + const float stops[], // [count] + size_t count) +{ + RawVelloGradient gradient = vello_gradient_new_linear(sx, sy, ex, ey, colors, stops, count); + return rcp<RenderShader>(new VelloGradient(std::move(gradient))); +} + +rcp<RenderShader> VelloFactory::makeRadialGradient(float cx, + float cy, + float radius, + const ColorInt colors[], // [count] + const float stops[], // [count] + size_t count) +{ + RawVelloGradient gradient = vello_gradient_new_radial(cx, cy, radius, colors, stops, count); + return rcp<RenderShader>(new VelloGradient(std::move(gradient))); +} + +std::unique_ptr<RenderPath> VelloFactory::makeRenderPath(RawPath& rawPath, FillRule fillRule) +{ + std::unique_ptr<VelloPath> path = std::make_unique<VelloPath>(); + + for (auto [verb, points] : rawPath) + { + switch (verb) + { + case PathVerb::move: + path->move(points[0]); + break; + case PathVerb::line: + path->line(points[1]); + break; + case PathVerb::cubic: + path->cubic(points[1], points[2], points[3]); + break; + case PathVerb::close: + path->close(); + break; + } + } + + return path; +} + +std::unique_ptr<RenderPath> VelloFactory::makeEmptyRenderPath() +{ + return std::make_unique<VelloPath>(); +} + +std::unique_ptr<RenderPaint> VelloFactory::makeRenderPaint() +{ + return std::make_unique<VelloPaint>(); +} + +std::unique_ptr<RenderImage> VelloFactory::decodeImage(Span<const uint8_t> encoded) +{ + return std::make_unique<VelloImage>(vello_image_new(encoded.data(), encoded.size())); +} + +static VelloFactory factory; +rive::Factory* ViewerContent::RiveFactory() { return &factory; }
diff --git a/vello/src/vello_renderer.hpp b/vello/src/vello_renderer.hpp new file mode 100644 index 0000000..5ef84d2 --- /dev/null +++ b/vello/src/vello_renderer.hpp
@@ -0,0 +1,222 @@ +#ifndef _RIVE_VELLO_RENDERER_HPP_ +#define _RIVE_VELLO_RENDERER_HPP_ + +#include "rive/factory.hpp" +#include "rive/math/mat2d.hpp" +#include "rive/math/path_types.hpp" +#include "rive/math/vec2d.hpp" +#include "rive/renderer.hpp" +#include "utils/factory_utils.hpp" +#include "viewer/viewer_content.hpp" + +#include "vello_renderer.hpp" + +using namespace rive; + +typedef void* RawVelloPath; +typedef void* RawVelloGradient; +typedef void* RawVelloImage; +typedef void* RawVelloPaint; +typedef void* RawVelloRenderer; + +extern "C" +{ + typedef void* RawVelloPath; + typedef void* RawVelloGradient; + typedef void* RawVelloImage; + typedef void* RawVelloPaint; + typedef void* RawVelloRenderer; + + const RawVelloPath vello_path_new(); + void vello_path_release(const RawVelloPath path); + void vello_path_rewind(const RawVelloPath path); + void vello_path_extend(const RawVelloPath path, void* other, const float* transform); + void vello_path_set_fill_rule(const RawVelloPath path, FillRule fill_rule); + void vello_path_move_to(const RawVelloPath path, float x, float y); + void vello_path_line_to(const RawVelloPath path, float x, float y); + void vello_path_cubic_to(const RawVelloPath path, + float ox, + float oy, + float ix, + float iy, + float x, + float y); + void vello_path_close(const RawVelloPath path); + + const RawVelloGradient vello_gradient_new_linear(float sx, + float sy, + float ex, + float ey, + const uint32_t* colors_data, + const float* stops_data, + size_t len); + const RawVelloGradient vello_gradient_new_radial(float cx, + float cy, + float radius, + const uint32_t* colors_data, + const float* stops_data, + size_t len); + void vello_gradient_release(const RawVelloGradient gradient); + + const RawVelloImage vello_image_new(const uint8_t* data, size_t len); + void vello_image_release(const RawVelloImage image); + + const RawVelloPaint vello_paint_new(); + void vello_paint_release(const RawVelloPaint paint); + void vello_paint_set_style(const RawVelloPaint paint, RenderPaintStyle style); + void vello_paint_set_color(const RawVelloPaint paint, uint32_t color); + void vello_paint_set_thickness(const RawVelloPaint paint, float thickness); + void vello_paint_set_join(const RawVelloPaint paint, StrokeJoin join); + void vello_paint_set_cap(const RawVelloPaint paint, StrokeCap cap); + void vello_paint_set_blend_mode(const RawVelloPaint paint, BlendMode blend_mode); + void vello_paint_set_gradient(const RawVelloPaint paint, const RawVelloGradient gradient); + + const RawVelloRenderer vello_renderer_new(); + void vello_renderer_release(const RawVelloRenderer renderer); + void vello_renderer_save(const RawVelloRenderer renderer); + void vello_renderer_restore(const RawVelloRenderer renderer); + void vello_renderer_transform(const RawVelloRenderer renderer, const float* transform); + void vello_renderer_clip_path(const RawVelloRenderer renderer, const RawVelloPath path); + void vello_renderer_draw_path(const RawVelloRenderer renderer, + const RawVelloPath path, + const RawVelloPaint paint); + void vello_renderer_draw_image(const RawVelloRenderer renderer, + const RawVelloImage image, + BlendMode blend_mode, + float opacity); + void vello_renderer_draw_image_mesh(const RawVelloRenderer renderer, + const RawVelloImage image, + const Vec2D* vertices_data, + size_t vertices_len, + const Vec2D* uvs_data, + size_t uvs_len, + const uint16_t* indices_data, + size_t indices_len, + BlendMode blend_mode, + float opacity); +} + +using namespace rive; + +class VelloPath : public RenderPath +{ +private: + RawVelloPath m_path; + +public: + VelloPath() : m_path(vello_path_new()) {} + VelloPath(RawVelloPath& path) : m_path(std::move(path)) {} + ~VelloPath() override; + + const RawVelloPath& path() const { return m_path; } + + void rewind() override; + void addRenderPath(RenderPath* path, const Mat2D& transform) override; + void fillRule(FillRule value) override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override; + virtual void close() override; +}; + +class VelloGradient : public RenderShader +{ +private: + RawVelloGradient m_gradient; + +public: + VelloGradient(RawVelloGradient gradient) : m_gradient(gradient) {} + ~VelloGradient() override; + + const RawVelloGradient& gradient() const { return m_gradient; } +}; + +class VelloImage : public RenderImage +{ +private: + RawVelloImage m_image; + +public: + VelloImage(RawVelloImage image) : m_image(image) {} + ~VelloImage() override; + + const RawVelloImage& image() const { return m_image; } +}; + +class VelloPaint : public RenderPaint +{ +private: + RawVelloPaint m_paint; + +public: + VelloPaint() : m_paint(vello_paint_new()) {} + ~VelloPaint() override; + + const RawVelloPaint& paint() const { return m_paint; } + + void style(RenderPaintStyle style) override; + void color(unsigned int value) override; + void thickness(float value) override; + void join(StrokeJoin value) override; + void cap(StrokeCap value) override; + void blendMode(BlendMode value) override; + void shader(rcp<RenderShader>) override; + void invalidateStroke() override {} +}; + +class VelloRenderer : public Renderer +{ +protected: + RawVelloRenderer m_renderer; + +public: + VelloRenderer() : m_renderer(vello_renderer_new()) {} + VelloRenderer(RawVelloRenderer renderer) : m_renderer(std::move(renderer)) {} + ~VelloRenderer() override {} + + void save() override; + void restore() override; + void transform(const Mat2D& transform) override; + void clipPath(RenderPath* path) override; + void drawPath(RenderPath* path, RenderPaint* paint) override; + void drawImage(const RenderImage*, BlendMode, float opacity) override; + void drawImageMesh(const RenderImage*, + rcp<RenderBuffer> vertices_f32, + rcp<RenderBuffer> uvCoords_f32, + rcp<RenderBuffer> indices_u16, + BlendMode, + float opacity) override; +}; + +class VelloFactory : public Factory +{ +public: + rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>) override; + rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>) override; + rcp<RenderBuffer> makeBufferF32(Span<const float>) override; + + rcp<RenderShader> makeLinearGradient(float sx, + float sy, + float ex, + float ey, + const ColorInt colors[], // [count] + const float stops[], // [count] + size_t count) override; + + rcp<RenderShader> makeRadialGradient(float cx, + float cy, + float radius, + const ColorInt colors[], // [count] + const float stops[], // [count] + size_t count) override; + + std::unique_ptr<RenderPath> makeRenderPath(RawPath&, FillRule) override; + + std::unique_ptr<RenderPath> makeEmptyRenderPath() override; + + std::unique_ptr<RenderPaint> makeRenderPaint() override; + + std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) override; +}; + +#endif
diff --git a/vello/src/viewer.rs b/vello/src/viewer.rs new file mode 100644 index 0000000..2b51a58 --- /dev/null +++ b/vello/src/viewer.rs
@@ -0,0 +1,113 @@ +use std::{ + ffi::CString, fs::File, io::Read, os::unix::prelude::OsStrExt, path::Path, ptr::NonNull, +}; + +use vello::kurbo::Vec2; + +use crate::VelloRenderer; + +#[derive(Debug)] +enum RawViewerContent {} + +extern "C" { + fn viewer_content_new(raw_path: *const i8) -> Option<NonNull<RawViewerContent>>; + fn viewer_content_release(raw_viewer_content: Option<NonNull<RawViewerContent>>); + fn viewer_content_handle_resize( + raw_viewer_content: Option<NonNull<RawViewerContent>>, + width: i32, + height: i32, + ); + // We're simply propagating the `VelloRender` pointer opaquely through the FFI. + #[allow(improper_ctypes)] + fn viewer_content_handle_draw( + raw_viewer_content: Option<NonNull<RawViewerContent>>, + raw_vello_renderer: Option<NonNull<VelloRenderer>>, + elapsed: f64, + ); + fn viewer_content_handle_pointer_move( + raw_viewer_content: Option<NonNull<RawViewerContent>>, + x: f32, + y: f32, + ); + fn viewer_content_handle_pointer_down( + raw_viewer_content: Option<NonNull<RawViewerContent>>, + x: f32, + y: f32, + ); + fn viewer_content_handle_pointer_up( + raw_viewer_content: Option<NonNull<RawViewerContent>>, + x: f32, + y: f32, + ); +} + +#[derive(Debug)] +pub struct ViewerContent { + raw_viewer_content: Option<NonNull<RawViewerContent>>, +} + +impl ViewerContent { + pub fn new<P: AsRef<Path>>(path: P) -> Option<Self> { + let path = path.as_ref(); + + let header = { + let mut file = File::open(path).ok()?; + let mut buffer = [0; 4]; + + file.read_exact(&mut buffer).ok()?; + + buffer + }; + + if &header != b"RIVE" { + return None; + } + + let c_str = CString::new(path.as_os_str().as_bytes()).unwrap(); + let raw_viewer_content = Some(unsafe { viewer_content_new(c_str.as_ptr())? }); + + Some(Self { raw_viewer_content }) + } + + pub fn handle_resize(&self, width: u32, height: u32) { + unsafe { + viewer_content_handle_resize(self.raw_viewer_content, width as i32, height as i32); + } + } + + pub fn handle_draw(&mut self, renderer: &mut VelloRenderer, elapsed: f64) { + unsafe { + viewer_content_handle_draw( + self.raw_viewer_content, + NonNull::new(renderer as *mut VelloRenderer), + elapsed, + ) + } + } + + pub fn handle_pointer_move(&self, pos: Vec2) { + unsafe { + viewer_content_handle_pointer_move(self.raw_viewer_content, pos.x as f32, pos.y as f32); + } + } + + pub fn handle_pointer_down(&self, pos: Vec2) { + unsafe { + viewer_content_handle_pointer_down(self.raw_viewer_content, pos.x as f32, pos.y as f32); + } + } + + pub fn handle_pointer_up(&self, pos: Vec2) { + unsafe { + viewer_content_handle_pointer_up(self.raw_viewer_content, pos.x as f32, pos.y as f32); + } + } +} + +impl Drop for ViewerContent { + fn drop(&mut self) { + unsafe { + viewer_content_release(self.raw_viewer_content); + } + } +}
diff --git a/vello/src/winit_viewer.cpp b/vello/src/winit_viewer.cpp new file mode 100644 index 0000000..9406af1 --- /dev/null +++ b/vello/src/winit_viewer.cpp
@@ -0,0 +1,48 @@ +#include "viewer/viewer_content.hpp" + +#include "vello_renderer.hpp" + +extern "C" +{ + typedef void* RawViewerContent; + + const RawViewerContent viewer_content_new(const char* filename) + { + return static_cast<void*>(ViewerContent::findHandler(filename).release()); + } + + void viewer_content_release(const RawViewerContent viewer_content) + { + std::unique_ptr<ViewerContent> val(std::move(static_cast<ViewerContent*>(viewer_content))); + } + + void viewer_content_handle_resize(const RawViewerContent viewer_content, + int32_t width, + int32_t height) + { + static_cast<ViewerContent*>(viewer_content)->handleResize(width, height); + } + + void viewer_content_handle_draw(const RawViewerContent viewer_content, + RawVelloRenderer raw_renderer, + double elapsed) + { + VelloRenderer renderer = VelloRenderer(raw_renderer); + static_cast<ViewerContent*>(viewer_content)->handleDraw(&renderer, elapsed); + } + + void viewer_content_handle_pointer_move(const RawViewerContent viewer_content, float x, float y) + { + static_cast<ViewerContent*>(viewer_content)->handlePointerMove(x, y); + } + + void viewer_content_handle_pointer_down(const RawViewerContent viewer_content, float x, float y) + { + static_cast<ViewerContent*>(viewer_content)->handlePointerDown(x, y); + } + + void viewer_content_handle_pointer_up(const RawViewerContent viewer_content, float x, float y) + { + static_cast<ViewerContent*>(viewer_content)->handlePointerUp(x, y); + } +}
diff --git a/viewer/include/viewer/viewer_content.hpp b/viewer/include/viewer/viewer_content.hpp index ad7ba60..2dde428 100644 --- a/viewer/include/viewer/viewer_content.hpp +++ b/viewer/include/viewer/viewer_content.hpp
@@ -5,10 +5,14 @@ #ifndef _RIVE_VIEWER_CONTENT_HPP_ #define _RIVE_VIEWER_CONTENT_HPP_ +#include <vector> + #include "rive/span.hpp" #include "rive/refcnt.hpp" +#ifndef RIVE_SKIP_IMGUI #include "imgui.h" +#endif namespace rive { @@ -24,7 +28,9 @@ virtual void handleResize(int width, int height) = 0; virtual void handleDraw(rive::Renderer* renderer, double elapsed) = 0; +#ifndef RIVE_SKIP_IMGUI virtual void handleImgui() = 0; +#endif virtual void handlePointerMove(float x, float y) {} virtual void handlePointerDown(float x, float y) {}
diff --git a/viewer/src/viewer_content/image_content.cpp b/viewer/src/viewer_content/image_content.cpp index ab24415..6bdb84d 100644 --- a/viewer/src/viewer_content/image_content.cpp +++ b/viewer/src/viewer_content/image_content.cpp
@@ -19,7 +19,9 @@ } void handleResize(int width, int height) override {} +#ifndef RIVE_SKIP_IMGUI void handleImgui() override {} +#endif }; std::unique_ptr<ViewerContent> ViewerContent::Image(const char filename[])
diff --git a/viewer/src/viewer_content/scene_content.cpp b/viewer/src/viewer_content/scene_content.cpp index 196000f..5b94ea3 100644 --- a/viewer/src/viewer_content/scene_content.cpp +++ b/viewer/src/viewer_content/scene_content.cpp
@@ -209,6 +209,7 @@ renderer->restore(); } +#ifndef RIVE_SKIP_IMGUI void handleImgui() override { // For now the atlas packer only works with tess as it compiles in our @@ -379,6 +380,7 @@ ImGui::Text("Drop a .riv file to preview."); } } +#endif }; std::unique_ptr<ViewerContent> ViewerContent::Scene(const char filename[]) @@ -389,4 +391,4 @@ return rivestd::make_unique<SceneContent>(filename, std::move(file)); } return nullptr; -} \ No newline at end of file +}
diff --git a/viewer/src/viewer_content/text_content.cpp b/viewer/src/viewer_content/text_content.cpp index 81f2637..d455b78 100644 --- a/viewer/src/viewer_content/text_content.cpp +++ b/viewer/src/viewer_content/text_content.cpp
@@ -338,6 +338,7 @@ } void handleResize(int width, int height) override {} +#ifndef RIVE_SKIP_IMGUI void handleImgui() override { const char* alignOptions[] = {"left", "right", "center"}; @@ -347,6 +348,7 @@ ImGui::Combo("combo", &m_align, alignOptions, IM_ARRAYSIZE(alignOptions)); ImGui::End(); } +#endif }; static bool ends_width(const char str[], const char suffix[])
diff --git a/viewer/src/viewer_content/textpath_content.cpp b/viewer/src/viewer_content/textpath_content.cpp index 7c9071b..351da53 100644 --- a/viewer/src/viewer_content/textpath_content.cpp +++ b/viewer/src/viewer_content/textpath_content.cpp
@@ -387,6 +387,7 @@ void handleResize(int width, int height) override {} +#ifndef RIVE_SKIP_IMGUI void handleImgui() override { ImGui::Begin("path", nullptr); @@ -398,6 +399,7 @@ ImGui::SliderFloat("Flare radius", &m_flareRadius, 10, 100); ImGui::End(); } +#endif }; std::unique_ptr<ViewerContent> ViewerContent::TextPath(const char filename[])
diff --git a/viewer/src/viewer_content/trimpath_content.cpp b/viewer/src/viewer_content/trimpath_content.cpp index 2f4dac2..d63a00b 100644 --- a/viewer/src/viewer_content/trimpath_content.cpp +++ b/viewer/src/viewer_content/trimpath_content.cpp
@@ -159,6 +159,7 @@ void handleResize(int width, int height) override {} +#ifndef RIVE_SKIP_IMGUI void handleImgui() override { ImGui::Begin("trim", nullptr); @@ -166,6 +167,7 @@ ImGui::SliderFloat("To", &m_trimTo, 0, 1); ImGui::End(); } +#endif }; std::unique_ptr<ViewerContent> ViewerContent::TrimPath(const char[])
diff --git a/viewer/src/viewer_content/viewer_content.cpp b/viewer/src/viewer_content/viewer_content.cpp index 44a63b3..a8ba142 100644 --- a/viewer/src/viewer_content/viewer_content.cpp +++ b/viewer/src/viewer_content/viewer_content.cpp
@@ -63,9 +63,11 @@ printf("\n"); } +#ifndef RIVE_SKIP_IMGUI #include "viewer/viewer_host.hpp" rive::Factory* ViewerContent::RiveFactory() { return ViewerHost::Factory(); } +#endif #include "rive/text/font_hb.hpp" rive::rcp<rive::Font> ViewerContent::DecodeFont(rive::Span<const uint8_t> span)