Integrate Vello for vector rendering (#1802)

* Start integrating vello into render pipeline

Cache vello render creation

Implement viewport navigation

Close vello path

Add transform parameter to vello render pass

* Fix render node types

* Fix a bunch of bugs in the path translation

* Avoid panic on empty document

* Fix rendering of holes

* Implement image rendering

* Implement graph recompilation afer editor api change

* Implement preferences toggle for using vello as the renderer

* Make surface creation optional

* Feature gate vello usages

* Implement skeleton for radial gradient

* Rename vello preference

* Fix some gradients

* Only update monitor nodes on graph recompile

* Fix warnings + remove dead code

* Update everything except for thumbnails after a node graph evaluation

* Fix missing click targets for Image frames

* Improve perfamance by removing unecessary widget updates

* Fix node graph paning

* Fix thumbnail loading

* Implement proper hash for vector modification

* Fix test and warnings

* Code review

* Fix dep

* Remove warning

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-07-22 10:56:29 +02:00
committed by GitHub
parent 8e774efe9d
commit ab71d26d84
41 changed files with 807 additions and 760 deletions

View File

@@ -14,6 +14,7 @@ nightly = []
alloc = ["dyn-any", "bezier-rs"]
type_id_logging = []
wasm = ["web-sys"]
vello = ["dep:vello", "bezier-rs/kurbo"]
std = [
"dyn-any",
"dyn-any/std",
@@ -57,6 +58,7 @@ rand_chacha = { workspace = true, optional = true }
bezier-rs = { workspace = true, optional = true }
kurbo = { workspace = true, optional = true }
base64 = { workspace = true, optional = true }
vello = { workspace = true, optional = true }
specta = { workspace = true, optional = true }
rustybuzz = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }

View File

@@ -10,7 +10,7 @@ use core::future::Future;
use core::hash::{Hash, Hasher};
use core::pin::Pin;
use core::ptr::addr_of;
use glam::DAffine2;
use glam::{DAffine2, UVec2};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -26,6 +26,7 @@ impl core::fmt::Display for SurfaceId {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SurfaceFrame {
pub surface_id: SurfaceId,
pub resolution: UVec2,
pub transform: DAffine2,
}
@@ -51,11 +52,23 @@ unsafe impl StaticType for SurfaceFrame {
type Static = SurfaceFrame;
}
impl<S> From<SurfaceHandleFrame<S>> for SurfaceFrame {
pub trait Size {
fn size(&self) -> UVec2;
}
#[cfg(target_arch = "wasm32")]
impl Size for web_sys::HtmlCanvasElement {
fn size(&self) -> UVec2 {
UVec2::new(self.width(), self.height())
}
}
impl<S: Size> From<SurfaceHandleFrame<S>> for SurfaceFrame {
fn from(x: SurfaceHandleFrame<S>) -> Self {
Self {
surface_id: x.surface_handle.surface_id,
transform: x.transform,
resolution: x.surface_handle.surface.size(),
}
}
}
@@ -70,6 +83,12 @@ pub struct SurfaceHandle<Surface> {
// #[cfg(target_arch = "wasm32")]
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
impl<S: Size> Size for SurfaceHandle<S> {
fn size(&self) -> UVec2 {
self.surface.size()
}
}
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
type Static = SurfaceHandle<T>;
}
@@ -162,8 +181,9 @@ impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
}
}
pub trait GetImaginatePreferences {
fn get_host_name(&self) -> &str;
pub trait GetEditorPreferences {
fn hostname(&self) -> &str;
fn use_vello(&self) -> bool;
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
@@ -196,10 +216,14 @@ impl NodeGraphUpdateSender for Logger {
struct DummyPreferences;
impl GetImaginatePreferences for DummyPreferences {
fn get_host_name(&self) -> &str {
impl GetEditorPreferences for DummyPreferences {
fn hostname(&self) -> &str {
"dummy_endpoint"
}
fn use_vello(&self) -> bool {
false
}
}
pub struct EditorApi<Io> {
@@ -208,8 +232,8 @@ pub struct EditorApi<Io> {
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
pub application_io: Option<Arc<Io>>,
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
/// Imaginate preferences made available to the graph through the [`WasmEditorApi`].
pub imaginate_preferences: Box<dyn GetImaginatePreferences + Send + Sync>,
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
}
impl<Io> Eq for EditorApi<Io> {}
@@ -220,7 +244,7 @@ impl<Io: Default> Default for EditorApi<Io> {
font_cache: FontCache::default(),
application_io: None,
node_graph_message_sender: Box::new(Logger),
imaginate_preferences: Box::new(DummyPreferences),
editor_preferences: Box::new(DummyPreferences),
}
}
}
@@ -230,7 +254,7 @@ impl<Io> Hash for EditorApi<Io> {
self.font_cache.hash(state);
self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);
(self.imaginate_preferences.as_ref() as *const dyn GetImaginatePreferences).hash(state);
(self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state);
}
}
@@ -239,7 +263,7 @@ impl<Io> PartialEq for EditorApi<Io> {
self.font_cache == other.font_cache
&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)
&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)
&& std::ptr::eq(self.imaginate_preferences.as_ref() as *const _, other.imaginate_preferences.as_ref() as *const _)
&& std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _)
}
}

View File

@@ -1,6 +1,5 @@
use crate::application_io::SurfaceHandleFrame;
use crate::raster::{BlendMode, ImageFrame};
use crate::renderer::GraphicElementRendered;
use crate::transform::Footprint;
use crate::vector::VectorData;
use crate::{Color, Node, SurfaceFrame};
@@ -9,7 +8,7 @@ use dyn_any::{DynAny, StaticType};
use node_macro::node_fn;
use core::ops::{Deref, DerefMut};
use glam::{DAffine2, DVec2, IVec2, UVec2};
use glam::{DAffine2, IVec2, UVec2};
use web_sys::HtmlCanvasElement;
pub mod renderer;
@@ -202,19 +201,7 @@ async fn add_artboard<Data: Into<Artboard> + Send>(footprint: Footprint, artboar
}
impl From<ImageFrame<Color>> for GraphicElement {
fn from(mut image_frame: ImageFrame<Color>) -> Self {
use base64::Engine;
let image = &image_frame.image;
if !image.data.is_empty() {
let output = image.to_png();
let preamble = "data:image/png;base64,";
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
image_frame.image.base64_string = Some(base64_string);
}
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::ImageFrame(image_frame)
}
}
@@ -237,14 +224,28 @@ impl From<alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>> for GraphicEl
fn from(surface: alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>) -> Self {
let surface_id = surface.surface_handle.surface_id;
let transform = surface.transform;
GraphicElement::Surface(SurfaceFrame { surface_id, transform })
GraphicElement::Surface(SurfaceFrame {
surface_id,
transform,
resolution: UVec2 {
x: surface.surface_handle.surface.width(),
y: surface.surface_handle.surface.height(),
},
})
}
}
impl From<SurfaceHandleFrame<HtmlCanvasElement>> for GraphicElement {
fn from(surface: SurfaceHandleFrame<HtmlCanvasElement>) -> Self {
let surface_id = surface.surface_handle.surface_id;
let transform = surface.transform;
GraphicElement::Surface(SurfaceFrame { surface_id, transform })
GraphicElement::Surface(SurfaceFrame {
surface_id,
transform,
resolution: UVec2 {
x: surface.surface_handle.surface.width(),
y: surface.surface_handle.surface.height(),
},
})
}
}
@@ -287,21 +288,4 @@ impl GraphicGroup {
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
};
pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
let mut root_node = usvg::Group::default();
let tree = usvg::Tree {
size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(),
view_box: usvg::ViewBox {
rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(),
aspect: usvg::AspectRatio::default(),
},
root: root_node.clone(),
};
for element in self.iter() {
root_node.children.push(element.to_usvg_node());
}
tree
}
}

View File

@@ -4,6 +4,7 @@ use crate::raster::bbox::Bbox;
use crate::raster::{BlendMode, Image, ImageFrame};
use crate::transform::Transform;
use crate::uuid::generate_uuid;
use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::PointId;
use crate::SurfaceFrame;
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
@@ -13,6 +14,8 @@ use bezier_rs::Subpath;
use base64::Engine;
use glam::{DAffine2, DVec2};
#[cfg(feature = "vello")]
use vello::*;
/// Represents a clickable target for the layer
#[derive(Clone, Debug)]
@@ -166,7 +169,7 @@ pub enum ImageRenderMode {
/// Static state used whilst rendering
#[derive(Default)]
pub struct RenderParams {
pub view_mode: crate::vector::style::ViewMode,
pub view_mode: ViewMode,
pub image_render_mode: ImageRenderMode,
pub culling_bounds: Option<[DVec2; 2]>,
pub thumbnail: bool,
@@ -177,7 +180,7 @@ pub struct RenderParams {
}
impl RenderParams {
pub fn new(view_mode: crate::vector::style::ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self {
pub fn new(view_mode: ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self {
Self {
view_mode,
image_render_mode,
@@ -203,7 +206,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String {
result
}
fn to_transform(transform: DAffine2) -> usvg::Transform {
pub fn to_transform(transform: DAffine2) -> usvg::Transform {
let cols = transform.to_cols_array();
usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32)
}
@@ -212,33 +215,14 @@ pub trait GraphicElementRendered {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
fn to_usvg_node(&self) -> usvg::Node {
let mut render = SvgRender::new();
let render_params = RenderParams::new(crate::vector::style::ViewMode::Normal, ImageRenderMode::Base64, None, false, false, false);
self.render_svg(&mut render, &render_params);
render.format_svg(DVec2::ZERO, DVec2::ONE);
let svg = render.svg.to_svg_string();
let opt = usvg::Options::default();
let tree = usvg::Tree::from_str(&svg, &opt).expect("Failed to parse SVG");
usvg::Node::Group(Box::new(tree.root.clone()))
}
fn to_usvg_tree(&self, resolution: glam::UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
let root = match self.to_usvg_node() {
usvg::Node::Group(root_node) => *root_node,
_ => usvg::Group::default(),
};
usvg::Tree {
size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(),
view_box: usvg::ViewBox {
rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(),
aspect: usvg::AspectRatio::default(),
},
root,
}
#[cfg(feature = "vello")]
fn to_vello_scene(&self, transform: DAffine2) -> Scene {
let mut scene = vello::Scene::new();
self.render_to_vello(&mut scene, transform);
scene
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) {}
fn contains_artboard(&self) -> bool {
false
@@ -283,12 +267,22 @@ impl GraphicElementRendered for GraphicGroup {
}
}
fn to_usvg_node(&self) -> usvg::Node {
let mut root_node = usvg::Group::default();
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
let kurbo_transform = kurbo::Affine::new((transform * self.transform).to_cols_array());
// TODO: We make the bounding box bigger to accommodate for the stroke width. This should be done in a better way.
let Some(bounds) = self.bounding_box(DAffine2::from_scale(DVec2::splat(1.05))) else { return };
let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
scene.push_layer(
blending,
self.alpha_blending.opacity,
kurbo_transform,
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
);
for element in self.iter() {
root_node.children.push(element.to_usvg_node());
element.render_to_vello(scene, transform * self.transform);
}
usvg::Node::Group(Box::new(root_node))
scene.pop_layer();
}
fn contains_artboard(&self) -> bool {
@@ -335,8 +329,8 @@ impl GraphicElementRendered for VectorData {
}
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
let filled = self.style.fill() != &crate::vector::style::Fill::None;
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = self.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
@@ -346,38 +340,81 @@ impl GraphicElementRendered for VectorData {
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
}
fn to_usvg_node(&self) -> usvg::Node {
use bezier_rs::BezierHandles;
use usvg::tiny_skia_path::PathBuilder;
let mut builder = PathBuilder::new();
let vector_data = self;
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
use crate::vector::style::GradientType;
use vello::peniko;
let transform = to_transform(vector_data.transform);
for subpath in vector_data.stroke_bezier_paths() {
let start = vector_data.transform.transform_point2(subpath[0].anchor);
builder.move_to(start.x as f32, start.y as f32);
for bezier in subpath.iter() {
bezier.apply_transformation(|pos| vector_data.transform.transform_point2(pos));
let end = bezier.end;
match bezier.handles {
BezierHandles::Linear => builder.line_to(end.x as f32, end.y as f32),
BezierHandles::Quadratic { handle } => builder.quad_to(handle.x as f32, handle.y as f32, end.x as f32, end.y as f32),
BezierHandles::Cubic { handle_start, handle_end } => {
builder.cubic_to(handle_start.x as f32, handle_start.y as f32, handle_end.x as f32, handle_end.y as f32, end.x as f32, end.y as f32)
}
}
}
if subpath.closed {
builder.close()
}
let kurbo_transform = kurbo::Affine::new(transform.to_cols_array());
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new();
for (_, subpath) in self.region_bezier_paths() {
subpath.to_vello_path(self.transform, &mut path);
}
match self.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64));
scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path);
}
Fill::Gradient(gradient) => {
let mut stops = peniko::ColorStops::new();
for &(offset, color) in &gradient.stops.0 {
stops.push(peniko::ColorStop {
offset: offset as f32,
color: peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64),
});
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = self.bounding_box().unwrap_or_default();
let lerp_bounds = |p: DVec2| bounds[0] + (bounds[1] - bounds[0]) * p;
let start = lerp_bounds(gradient.start);
let end = lerp_bounds(gradient.end);
let transform = self.transform * gradient.transform;
let start = transform.transform_point2(start);
let end = transform.transform_point2(end);
let fill = peniko::Brush::Gradient(peniko::Gradient {
kind: match gradient.gradient_type {
GradientType::Linear => peniko::GradientKind::Linear {
start: to_point(start),
end: to_point(end),
},
GradientType::Radial => {
let radius = start.distance(end);
peniko::GradientKind::Radial {
start_center: to_point(start),
start_radius: 0.,
end_center: to_point(end),
end_radius: radius as f32,
}
}
},
stops,
..Default::default()
});
scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path);
}
Fill::None => (),
};
let mut path = kurbo::BezPath::new();
for (_, subpath) in self.region_bezier_paths() {
subpath.to_vello_path(self.transform, &mut path);
}
if let Some(stroke) = self.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64),
None => peniko::Color::TRANSPARENT,
};
let stroke = kurbo::Stroke {
width: stroke.weight,
miter_limit: stroke.line_join_miter_limit,
..Default::default()
};
scene.stroke(&stroke, kurbo_transform, color, None, &path);
}
let path = builder.finish().unwrap();
let mut path = usvg::Path::new(path.into());
path.abs_transform = transform;
// TODO: use proper style
path.fill = None;
path.stroke = Some(usvg::Stroke::default());
usvg::Node::Path(Box::new(path))
}
}
@@ -458,6 +495,28 @@ impl GraphicElementRendered for Artboard {
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
use vello::peniko;
// Render background
let color = peniko::Color::rgba(self.background.r() as f64, self.background.g() as f64, self.background.b() as f64, self.background.a() as f64);
let rect = kurbo::Rect::new(self.location.x as f64, self.location.y as f64, self.dimensions.x as f64, self.dimensions.y as f64);
let blend_mode = peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcOver);
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect);
scene.pop_layer();
if self.clip {
scene.push_layer(blend_mode, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
}
self.graphic_group.render_to_vello(scene, transform * self.transform());
if self.clip {
scene.pop_layer();
}
}
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
subpath.apply_transform(self.graphic_group.transform.inverse());
@@ -486,6 +545,13 @@ impl GraphicElementRendered for crate::ArtboardGroup {
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
for artboard in &self.artboards {
artboard.render_to_vello(scene, transform)
}
}
fn contains_artboard(&self) -> bool {
!self.artboards.is_empty()
}
@@ -511,6 +577,11 @@ impl GraphicElementRendered for SurfaceFrame {
render.svg.push(canvas.into())
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) {
todo!()
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let bbox = Bbox::from_transform(transform);
let aabb = bbox.to_axis_aligned_bbox();
@@ -567,24 +638,24 @@ impl GraphicElementRendered for ImageFrame<Color> {
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
}
fn to_usvg_node(&self) -> usvg::Node {
let image_frame = self;
if image_frame.image.width * image_frame.image.height == 0 {
return usvg::Node::Group(Box::default());
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
use vello::peniko;
let image = &self.image;
if image.data.is_empty() {
return;
}
let png = image_frame.image.to_png();
usvg::Node::Image(Box::new(usvg::Image {
id: String::new(),
abs_transform: to_transform(image_frame.transform),
visibility: usvg::Visibility::Visible,
view_box: usvg::ViewBox {
rect: usvg::NonZeroRect::from_xywh(0., 0., 1., 1.).unwrap(),
aspect: usvg::AspectRatio::default(),
},
rendering_mode: usvg::ImageRendering::OptimizeSpeed,
kind: usvg::ImageKind::PNG(png.into()),
bounding_box: None,
}))
let image = vello::peniko::Image {
data: image.to_flat_u8().0.into(),
width: image.width,
height: image.height,
format: peniko::Format::Rgba8,
extend: peniko::Extend::Repeat,
};
let transform = transform * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
}
}
@@ -616,12 +687,13 @@ impl GraphicElementRendered for GraphicElement {
}
}
fn to_usvg_node(&self) -> usvg::Node {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.to_usvg_node(),
GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(),
GraphicElement::Surface(surface) => surface.to_usvg_node(),
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform),
GraphicElement::ImageFrame(image_frame) => image_frame.render_to_vello(scene, transform),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform),
GraphicElement::Surface(surface) => surface.render_to_vello(scene, transform),
}
}
@@ -659,32 +731,6 @@ impl<T: Primitive> GraphicElementRendered for T {
}
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
fn to_usvg_node(&self) -> usvg::Node {
let text = self;
usvg::Node::Text(Box::new(usvg::Text {
id: String::new(),
abs_transform: usvg::Transform::identity(),
rendering_mode: usvg::TextRendering::OptimizeSpeed,
writing_mode: usvg::WritingMode::LeftToRight,
chunks: vec![usvg::TextChunk {
text: text.to_string(),
x: None,
y: None,
anchor: usvg::TextAnchor::Start,
spans: vec![],
text_flow: usvg::TextFlow::Linear,
}],
dx: Vec::new(),
dy: Vec::new(),
rotate: Vec::new(),
bounding_box: None,
abs_bounding_box: None,
stroke_bounding_box: None,
abs_stroke_bounding_box: None,
flattened: None,
}))
}
}
impl GraphicElementRendered for Option<Color> {

View File

@@ -233,6 +233,37 @@ impl core::fmt::Display for BlendMode {
}
}
#[cfg(feature = "vello")]
impl From<BlendMode> for vello::peniko::Mix {
fn from(val: BlendMode) -> Self {
match val {
// Normal group
BlendMode::Normal => vello::peniko::Mix::Normal,
// Darken group
BlendMode::Darken => vello::peniko::Mix::Darken,
BlendMode::Multiply => vello::peniko::Mix::Multiply,
BlendMode::ColorBurn => vello::peniko::Mix::ColorBurn,
// Lighten group
BlendMode::Lighten => vello::peniko::Mix::Lighten,
BlendMode::Screen => vello::peniko::Mix::Screen,
BlendMode::ColorDodge => vello::peniko::Mix::ColorDodge,
// Contrast group
BlendMode::Overlay => vello::peniko::Mix::Overlay,
BlendMode::SoftLight => vello::peniko::Mix::SoftLight,
BlendMode::HardLight => vello::peniko::Mix::HardLight,
// Inversion group
BlendMode::Difference => vello::peniko::Mix::Difference,
BlendMode::Exclusion => vello::peniko::Mix::Exclusion,
// Component group
BlendMode::Hue => vello::peniko::Mix::Hue,
BlendMode::Saturation => vello::peniko::Mix::Saturation,
BlendMode::Color => vello::peniko::Mix::Color,
BlendMode::Luminosity => vello::peniko::Mix::Luminosity,
_ => todo!(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LuminanceNode<LuminanceCalculation> {
luminance_calc: LuminanceCalculation,

View File

@@ -277,7 +277,7 @@ impl ManipulatorPointId {
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
@@ -287,7 +287,7 @@ pub enum HandleType {
}
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HandleId {
pub ty: HandleType,

View File

@@ -9,7 +9,7 @@ use std::collections::HashMap;
macro_rules! create_ids {
($($id:ident),*) => {
$(
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DynAny)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// A strongly typed ID
pub struct $id(u64);

View File

@@ -16,6 +16,20 @@ pub struct PointModification {
delta: HashMap<PointId, DVec2>,
}
impl Hash for PointModification {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.add.hash(state);
let mut remove = self.remove.iter().collect::<Vec<_>>();
remove.sort_unstable();
remove.hash(state);
let mut delta = self.delta.iter().map(|(&a, &b)| (a, [b.x.to_bits(), b.y.to_bits()])).collect::<Vec<_>>();
delta.sort_unstable();
delta.hash(state);
}
}
impl PointModification {
/// Apply this modification to the specified [`PointDomain`].
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
@@ -90,6 +104,36 @@ pub struct SegmentModification {
stroke: HashMap<SegmentId, StrokeId>,
}
impl Hash for SegmentModification {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.add.hash(state);
let mut remove = self.remove.iter().collect::<Vec<_>>();
remove.sort_unstable();
remove.hash(state);
let mut start_point = self.start_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
start_point.sort_unstable();
start_point.hash(state);
let mut end_point = self.end_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
end_point.sort_unstable();
end_point.hash(state);
let mut handle_primary = self.handle_primary.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
handle_primary.sort_unstable();
handle_primary.hash(state);
let mut handle_end = self.handle_end.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
handle_end.sort_unstable();
handle_end.hash(state);
let mut stroke = self.stroke.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
stroke.sort_unstable();
stroke.hash(state);
}
}
impl SegmentModification {
/// Apply this modification to the specified [`SegmentDomain`].
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
@@ -245,6 +289,24 @@ pub struct RegionModification {
fill: HashMap<RegionId, FillId>,
}
impl Hash for RegionModification {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.add.hash(state);
let mut remove = self.remove.iter().collect::<Vec<_>>();
remove.sort_unstable();
remove.hash(state);
let mut segment_range = self.segment_range.iter().map(|(&a, b)| (a, (*b.start(), *b.end()))).collect::<Vec<_>>();
segment_range.sort_unstable();
segment_range.hash(state);
let mut fill = self.fill.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
fill.sort_unstable();
fill.hash(state);
}
}
impl RegionModification {
/// Apply this modification to the specified [`RegionDomain`].
pub fn apply(&self, region_domain: &mut RegionDomain) {
@@ -398,8 +460,19 @@ impl VectorModification {
impl core::hash::Hash for VectorModification {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
// TODO: properly implement (hashing a hashset is difficult because ordering is unstable)
PointId::generate().hash(state);
self.points.hash(state);
self.segments.hash(state);
self.regions.hash(state);
let mut add_g1_continuous = self.add_g1_continuous.iter().copied().collect::<Vec<_>>();
add_g1_continuous.sort_unstable();
add_g1_continuous.hash(state);
let mut remove_g1_continuous = self.remove_g1_continuous.iter().copied().collect::<Vec<_>>();
remove_g1_continuous.sort_unstable();
remove_g1_continuous.hash(state);
}
}

View File

@@ -248,9 +248,6 @@ pub struct DocumentNode {
/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.
#[serde(default)]
pub skip_deduplication: bool,
/// Used as a hash of the graph input where applicable. This ensures that proto nodes that depend on the graph's input are always regenerated.
#[serde(default)]
pub world_state_hash: u64,
/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.
#[serde(skip)]
pub original_location: OriginalLocation,
@@ -293,7 +290,6 @@ impl Default for DocumentNode {
locked: Default::default(),
metadata: DocumentNodeMetadata::default(),
skip_deduplication: Default::default(),
world_state_hash: Default::default(),
original_location: OriginalLocation::default(),
}
}
@@ -392,7 +388,6 @@ impl DocumentNode {
construction_args: args,
original_location: self.original_location,
skip_deduplication: self.skip_deduplication,
world_state_hash: self.world_state_hash,
}
}

View File

@@ -76,10 +76,6 @@ macro_rules! tagged_value {
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
Ok(TaggedValue::SurfaceFrame(frame.into()))
}
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
}

View File

@@ -286,27 +286,3 @@ impl std::fmt::Display for ImaginateSamplingMethod {
}
}
}
#[derive(Clone, Debug, PartialEq, Hash, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImaginatePreferences {
pub host_name: String,
}
impl graphene_core::application_io::GetImaginatePreferences for ImaginatePreferences {
fn get_host_name(&self) -> &str {
&self.host_name
}
}
impl Default for ImaginatePreferences {
fn default() -> Self {
Self {
host_name: "http://localhost:7860/".into(),
}
}
}
unsafe impl dyn_any::StaticType for ImaginatePreferences {
type Static = ImaginatePreferences;
}

View File

@@ -224,9 +224,6 @@ pub struct ProtoNode {
pub identifier: ProtoNodeIdentifier,
pub original_location: OriginalLocation,
pub skip_deduplication: bool,
// TODO: This is a hack, figure out a proper solution
/// Represents a global state on which the node depends.
pub world_state_hash: u64,
}
impl Default for ProtoNode {
@@ -237,7 +234,6 @@ impl Default for ProtoNode {
input: ProtoNodeInput::None,
original_location: OriginalLocation::default(),
skip_deduplication: false,
world_state_hash: 0,
}
}
}
@@ -288,7 +284,6 @@ impl ProtoNode {
if self.skip_deduplication {
self.original_location.path.hash(&mut hasher);
}
self.world_state_hash.hash(&mut hasher);
std::mem::discriminant(&self.input).hash(&mut hasher);
match self.input {
ProtoNodeInput::None => (),
@@ -317,7 +312,6 @@ impl ProtoNode {
..Default::default()
},
skip_deduplication: false,
world_state_hash: 0,
}
}
@@ -451,7 +445,6 @@ impl ProtoNetwork {
input,
original_location: OriginalLocation { path, ..Default::default() },
skip_deduplication: false,
world_state_hash: 0,
},
));
@@ -947,12 +940,12 @@ mod test {
assert_eq!(
ids,
vec![
NodeId(8751908307531981068),
NodeId(3279077344149194814),
NodeId(532186116905587629),
NodeId(10764326338085309082),
NodeId(18015434340620913446),
NodeId(11801333199647382191)
NodeId(5686040524603683634),
NodeId(13787140740513543798),
NodeId(1280393769237740322),
NodeId(3100442468152897091),
NodeId(14834729712909816752),
NodeId(8678825113056010444)
]
);
}

View File

@@ -220,3 +220,32 @@ impl ApplicationIo for WasmApplicationIo {
pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<wgpu_executor::Window>;
#[derive(Clone, Debug, PartialEq, Hash, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EditorPreferences {
pub imaginate_hostname: String,
pub use_vello: bool,
}
impl graphene_core::application_io::GetEditorPreferences for EditorPreferences {
fn hostname(&self) -> &str {
&self.imaginate_hostname
}
fn use_vello(&self) -> bool {
self.use_vello
}
}
impl Default for EditorPreferences {
fn default() -> Self {
Self {
imaginate_hostname: "http://localhost:7860/".into(),
use_vello: false,
}
}
}
unsafe impl dyn_any::StaticType for EditorPreferences {
type Static = EditorPreferences;
}

View File

@@ -1,6 +1,6 @@
use graph_craft::document::value::TaggedValue;
use graph_craft::graphene_compiler::{Compiler, Executor};
use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{concrete, ProtoNodeIdentifier};
use graph_craft::{document::*, generic};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateSender};
@@ -47,7 +47,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
font_cache: FontCache::default(),
application_io: Some(application_io.into()),
node_graph_message_sender: Box::new(UpdateLogger {}),
imaginate_preferences: Box::new(ImaginatePreferences::default()),
editor_preferences: Box::new(EditorPreferences::default()),
});
let executor = create_executor(document_string, editor_api)?;
let render_config = graphene_core::application_io::RenderConfig::default();
@@ -126,7 +126,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _, _>")),
..Default::default()
},
]
@@ -174,7 +174,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
// font_cache: &FontCache::default(),
// application_io: &block_on(WasmApplicationIo::new()),
// node_graph_message_sender: &UpdateLogger {},
// imaginate_preferences: &ImaginatePreferences::default(),
// editor_preferences: &EditorPreferences::default(),
// render_config: graphene_core::application_io::RenderConfig::default(),
// };
// let result = (&executor).execute(editor_api.clone()).await.unwrap();
@@ -191,7 +191,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
// font_cache: &FontCache::default(),
// application_io: &block_on(WasmApplicationIo::new()),
// node_graph_message_sender: &UpdateLogger {},
// imaginate_preferences: &ImaginatePreferences::default(),
// editor_preferences: &EditorPreferences::default(),
// render_config: graphene_core::application_io::RenderConfig::default(),
// };
// let result = (&executor).execute(editor_api.clone()).await.unwrap();

View File

@@ -290,7 +290,6 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
let canvas = editor_api.application_io.create_surface();
let surface = unsafe { executor.create_surface(canvas) }.unwrap();
// log::debug!("id: {surface:?}");
let surface_id = surface.surface_id;
let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap();

View File

@@ -3,7 +3,8 @@ use core::any::TypeId;
use core::future::Future;
use futures::{future::Either, TryFutureExt};
use glam::{DVec2, U64Vec2};
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginatePreferences, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle};
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle};
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::application_io::NodeGraphUpdateMessage;
use graphene_core::raster::{Color, Image, Luma, Pixel};
use image::{DynamicImage, ImageBuffer, ImageFormat};
@@ -50,17 +51,19 @@ impl core::fmt::Debug for ImaginatePersistentData {
impl Default for ImaginatePersistentData {
fn default() -> Self {
let mut status = ImaginateServerStatus::default();
let server_status = ImaginateServerStatus::default();
#[cfg(not(miri))]
let client = new_client().map_err(|err| status = ImaginateServerStatus::Failed(err.to_string())).ok();
let mut server_status = server_status;
#[cfg(not(miri))]
let client = new_client().map_err(|err| server_status = ImaginateServerStatus::Failed(err.to_string())).ok();
#[cfg(miri)]
let client = None;
let ImaginatePreferences { host_name } = Default::default();
let EditorPreferences { imaginate_hostname: host_name, .. } = Default::default();
Self {
pending_server_check: None,
host_name: parse_url(&host_name).unwrap(),
client,
server_status: status,
server_status,
}
}
}
@@ -285,14 +288,14 @@ pub async fn imaginate<'a, P: Pixel>(
) -> Image<P> {
let WasmEditorApi {
node_graph_message_sender,
imaginate_preferences,
editor_preferences,
..
} = editor_api.await;
let set_progress = |progress: ImaginateStatus| {
controller.set_status(progress);
node_graph_message_sender.send(NodeGraphUpdateMessage::ImaginateStatusUpdate);
};
let host_name = imaginate_preferences.get_host_name();
let host_name = editor_preferences.hostname();
imaginate_maybe_fail(
image,
host_name,

View File

@@ -225,10 +225,11 @@ fn to_svg_string(vector: &VectorData, transform: DAffine2) -> String {
fn from_svg_string(svg_string: &str) -> VectorData {
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><path d="{}"></path></svg>"#, svg_string);
let Some(tree) = usvg::Tree::from_str(&svg, &Default::default()).ok() else {
let fontdb = usvg::fontdb::Database::new();
let Some(tree) = usvg::Tree::from_str(&svg, &Default::default(), &fontdb).ok() else {
return VectorData::empty();
};
let Some(usvg::Node::Path(path)) = tree.root.children.first() else {
let Some(usvg::Node::Path(path)) = tree.root().children().first() else {
return VectorData::empty();
};
@@ -239,10 +240,10 @@ pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();
let mut points = path.data.points().iter();
let mut points = path.data().points().iter();
let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);
for verb in path.data.verbs() {
for verb in path.data().verbs() {
match verb {
usvg::tiny_skia_path::PathVerb::Move => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), false));

View File

@@ -1,4 +1,4 @@
use dyn_any::DynFuture;
pub use graph_craft::document::value::RenderOutput;
pub use graph_craft::wasm_application_io::*;
#[cfg(target_arch = "wasm32")]
use graphene_core::application_io::SurfaceHandle;
@@ -14,10 +14,8 @@ use graphene_core::{Color, WasmNotSend};
#[cfg(target_arch = "wasm32")]
use base64::Engine;
use core::future::Future;
#[cfg(target_arch = "wasm32")]
use glam::DAffine2;
use std::marker::PhantomData;
use std::sync::Arc;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::Clamped;
@@ -87,15 +85,6 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
};
image
}
pub use graph_craft::document::value::RenderOutput;
pub struct RenderNode<Data, Surface, Parameter> {
data: Data,
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
surface_handle: Surface,
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
surface_handle: PhantomData<Surface>,
parameter: PhantomData<Parameter>,
}
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput {
if !data.contains_artboard() && !render_params.hide_artboards {
@@ -115,50 +104,28 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
RenderOutput::Svg(render.svg.to_svg_string())
}
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
fn render_canvas(
data: impl GraphicElementRendered,
mut render: SvgRender,
render_params: RenderParams,
footprint: Footprint,
editor: &'_ WasmEditorApi,
surface_handle: wgpu_executor::WindowHandle,
) -> RenderOutput {
let resolution = footprint.resolution;
data.render_svg(&mut render, &render_params);
// TODO: reenable once we switch to full node graph
let min = footprint.transform.inverse().transform_point2((0., 0.).into());
let max = footprint.transform.inverse().transform_point2(resolution.as_dvec2());
render.format_svg(min, max);
let string = render.svg.to_svg_string();
let _array = string.as_bytes();
let canvas = &surface_handle.surface;
canvas.set_width(resolution.x);
canvas.set_height(resolution.y);
let usvg_tree = data.to_usvg_tree(resolution, [min, max]);
#[cfg(feature = "vello")]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutput {
if let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() {
use vello::*;
if let Some(_exec) = editor.application_io.as_ref().unwrap().gpu_executor() {
todo!()
let footprint = render_config.viewport;
let mut scene = Scene::new();
let mut child = Scene::new();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y)
.await
.expect("Failed to render Vello scene");
} else {
let pixmap_size = usvg_tree.size.to_int_size();
let mut pixmap = resvg::tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
resvg::render(&usvg_tree, resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut());
let array: Clamped<&[u8]> = Clamped(pixmap.data());
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, pixmap_size.width(), pixmap_size.height()).expect("Failed to construct ImageData");
context.put_image_data(&image_data, 0.0, 0.0).unwrap();
unreachable!("Attempted to render with Vello when no GPU executor is available");
}
/*
let preamble = "data:image/svg+xml;base64,";
let mut base64_string = String::with_capacity(preamble.len() + array.len() * 4);
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(array, &mut base64_string);
let image_data = web_sys::HtmlImageElement::new().unwrap();
image_data.set_src(base64_string.as_str());
wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap();
context.draw_image_with_html_image_element(&image_data, 0.0, 0.0).unwrap();
*/
let frame = graphene_core::application_io::SurfaceHandleFrame {
surface_handle,
transform: glam::DAffine2::IDENTITY,
@@ -226,90 +193,44 @@ async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::Transf
}
}
// Render with the data node taking in Footprint.
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, Footprint>
where
for<'a> Data: Node<'a, Footprint, Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = DynFuture<'input, RenderOutput>;
#[inline]
fn eval(&'input self, render_config: RenderConfig) -> Self::Output {
let footprint = render_config.viewport;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let RenderConfig { hide_artboards, for_export, .. } = render_config;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let data_fut = self.data.eval(footprint);
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let surface_fut = self.surface_handle.eval(());
Box::pin(async move {
let data = data_fut.await;
let footprint = render_config.viewport;
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let output_format = render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
})
}
pub struct RenderNode<EditorApi, Data, Surface> {
editor_api: EditorApi,
data: Data,
_surface_handle: Surface,
}
// Render with the data node taking in ().
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, ()>
where
for<'a> Data: Node<'a, (), Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = DynFuture<'input, RenderOutput>;
#[node_macro::node_fn(RenderNode)]
async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSend>(
render_config: RenderConfig,
editor_api: &'a WasmEditorApi,
data: impl Node<Footprint, Output = T>,
_surface_handle: impl Node<Footprint, Output = Option<wgpu_executor::WgpuSurface>>,
) -> RenderOutput {
let footprint = render_config.viewport;
#[inline]
fn eval(&'input self, render_config: RenderConfig) -> Self::Output {
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let RenderConfig { hide_artboards, for_export, .. } = render_config;
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let data_fut = self.data.eval(());
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
let surface_fut = self.surface_handle.eval(());
Box::pin(async move {
let data = data_fut.await;
let footprint = render_config.viewport;
let data = self.data.eval(footprint).await;
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
let surface_handle = self._surface_handle.eval(footprint).await;
let use_vello = editor_api.editor_preferences.use_vello();
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
let use_vello = use_vello && surface_handle.is_some();
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let output_format = render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await),
_ => todo!("Non-SVG render output for {output_format:?}"),
let output_format = render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
ExportFormat::Canvas => {
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
return render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await;
#[cfg(not(all(feature = "vello", target_arch = "wasm32")))]
render_svg(data, SvgRender::new(), render_params, footprint)
} else {
render_svg(data, SvgRender::new(), render_params, footprint)
}
})
}
}
#[automatically_derived]
impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
pub fn new(data: Data, _surface_handle: Surface) -> Self {
Self {
data,
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
surface_handle: _surface_handle,
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
surface_handle: PhantomData,
parameter: PhantomData,
}
_ => todo!("Non-SVG render output for {output_format:?}"),
}
}

View File

@@ -20,9 +20,9 @@ use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureW
use graphene_std::application_io::RenderConfig;
use graphene_std::raster::*;
use graphene_std::wasm_application_io::*;
use wgpu_executor::WindowHandle;
#[cfg(feature = "gpu")]
use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput};
use wgpu_executor::{WgpuSurface, WindowHandle};
use dyn_any::StaticType;
use glam::{DAffine2, DVec2, UVec2};
@@ -381,9 +381,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::ReadOutputBufferNode<_, _>, input: Arc<WgpuShaderInput>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: wgpu_executor::WgpuSurface, params: []),
async_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Footprint, output: Option<wgpu_executor::WgpuSurface>, params: [&WasmEditorApi]),
#[cfg(feature = "gpu")]
async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => wgpu_executor::WgpuSurface, () =>&WgpuExecutor]),
async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => Option<wgpu_executor::WgpuSurface>, () =>&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
wgpu_executor::UploadTextureNode<_>,
@@ -612,6 +612,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame, params: [ShaderInputFrame]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Option<wgpu_executor::WgpuSurface>, params: [Option<wgpu_executor::WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WindowHandle, params: [wgpu_executor::WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: graphene_std::SurfaceFrame, params: [graphene_std::SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]),
@@ -619,6 +620,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: ShaderInputFrame, fn_params: [Footprint => ShaderInputFrame]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: WgpuSurface, fn_params: [Footprint => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: Option<WgpuSurface>, fn_params: [Footprint => Option<WgpuSurface>]),
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: (), params: [UVec2, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]),
@@ -627,23 +630,17 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ImageFrame<Color>, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => VectorData, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => GraphicGroup, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Artboard, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ArtboardGroup, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Option<Color>, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Vec<Color>, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => bool, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f32, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f64, Footprint => Option<WgpuSurface>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => String, Footprint => Option<WgpuSurface>]),
#[cfg(target_arch = "wasm32")]
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
#[cfg(target_arch = "wasm32")]

View File

@@ -33,6 +33,7 @@ futures = { workspace = true }
web-sys = { workspace = true, features = ["HtmlCanvasElement"] }
winit = { workspace = true }
serde = { workspace = true }
vello = { workspace = true }
# Required dependencies
futures-intrusive = { version = "0.5.0", features = ["alloc"] }

View File

@@ -18,8 +18,13 @@ impl Context {
};
let instance = wgpu::Instance::new(instance_descriptor);
let adapter_options = wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
};
// `request_adapter` instantiates the general connection to the GPU
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?;
let adapter = instance.request_adapter(&adapter_options).await?;
let required_limits = adapter.limits();
// `request_device` instantiates the feature specific connection to the GPU, defining some parameters,

View File

@@ -1,5 +1,6 @@
mod context;
mod executor;
pub use context::Context;
pub use executor::GpuExecutor;
@@ -14,9 +15,10 @@ use graphene_core::{Color, Cow, Node, SurfaceFrame};
use anyhow::{bail, Result};
use futures::Future;
use glam::DAffine2;
use glam::{DAffine2, UVec2};
use std::pin::Pin;
use std::sync::Arc;
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
use wgpu::util::DeviceExt;
use wgpu::{Buffer, BufferDescriptor, ShaderModule, SurfaceConfiguration, SurfaceError, Texture, TextureView};
@@ -27,6 +29,7 @@ use web_sys::HtmlCanvasElement;
pub struct WgpuExecutor {
pub context: Context,
render_configuration: RenderConfiguration,
vello_renderer: std::sync::Mutex<vello::Renderer>,
}
impl std::fmt::Debug for WgpuExecutor {
@@ -47,6 +50,12 @@ impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
pub type WgpuSurface = Arc<SurfaceHandle<Surface>>;
pub type WgpuWindow = Arc<SurfaceHandle<WindowHandle>>;
impl graphene_core::application_io::Size for Surface {
fn size(&self) -> UVec2 {
self.resolution
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
@@ -107,7 +116,10 @@ pub struct ShaderModuleWrapper(ShaderModule);
pub type ShaderHandle = ShaderModuleWrapper;
pub type BufferHandle = Buffer;
pub type TextureHandle = Texture;
pub struct Surface(wgpu::Surface<'static>);
pub struct Surface {
pub inner: wgpu::Surface<'static>,
resolution: UVec2,
}
#[cfg(target_arch = "wasm32")]
pub type Window = HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))]
@@ -122,6 +134,44 @@ unsafe impl StaticType for Surface {
// }
impl WgpuExecutor {
pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, width: u32, height: u32) -> Result<()> {
let surface = &surface.surface.inner;
let surface_caps = surface.get_capabilities(&self.context.adapter);
surface.configure(
&self.context.device,
&SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING,
format: wgpu::TextureFormat::Rgba8Unorm,
width,
height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
},
);
let surface_texture = surface.get_current_texture()?;
let render_params = RenderParams {
base_color: vello::peniko::Color::TRANSPARENT,
width,
height,
antialiasing_method: AaConfig::Area,
};
{
let mut renderer = self.vello_renderer.lock().unwrap();
renderer
.render_to_surface_async(&self.context.device, &self.context.queue, scene, &surface_texture, &render_params)
.await
.unwrap();
}
surface_texture.present();
Ok(())
}
pub fn load_shader(&self, shader: Shader) -> Result<ShaderHandle> {
#[cfg(not(feature = "passthrough"))]
let shader_module = self.context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
@@ -300,11 +350,11 @@ impl WgpuExecutor {
..Default::default()
});
let surface = &canvas.as_ref().surface.0;
let surface = &canvas.as_ref().surface.inner;
let surface_caps = surface.get_capabilities(&self.context.adapter);
if surface_caps.formats.is_empty() {
log::warn!("No surface formats available");
// return Ok(());
return Ok(());
}
// TODO:
let resolution = transform.decompose_scale().as_uvec2();
@@ -455,8 +505,9 @@ impl WgpuExecutor {
}
#[cfg(target_arch = "wasm32")]
fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
pub fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle, resolution: Option<UVec2>) -> Result<SurfaceHandle<Surface>> {
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;
let resolution = resolution.unwrap_or(UVec2::new(1920, 1080));
// let surface_caps = surface.get_capabilities(&self.context.adapter);
// let surface_format = wgpu::TextureFormat::Rgba16Float;
@@ -474,12 +525,13 @@ impl WgpuExecutor {
// self.surface_config.set(Some(config));
Ok(SurfaceHandle {
surface_id: canvas.surface_id,
surface: Surface(surface),
surface: Surface { inner: surface, resolution },
})
}
#[cfg(not(target_arch = "wasm32"))]
fn create_surface(&self, window: SurfaceHandle<Window>) -> Result<SurfaceHandle<Surface>> {
pub fn create_surface(&self, window: SurfaceHandle<Window>, resolution: Option<UVec2>) -> Result<SurfaceHandle<Surface>> {
let size = window.surface.inner_size();
let resolution = resolution.unwrap_or(UVec2 { x: size.width, y: size.height });
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?;
let surface_caps = surface.get_capabilities(&self.context.adapter);
@@ -488,8 +540,8 @@ impl WgpuExecutor {
let _config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
width: resolution.x,
height: resolution.y,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
@@ -500,7 +552,7 @@ impl WgpuExecutor {
let surface_id = window.surface_id;
Ok(SurfaceHandle {
surface_id,
surface: Surface(surface),
surface: Surface { inner: surface, resolution },
})
}
}
@@ -619,7 +671,23 @@ impl WgpuExecutor {
sampler,
};
Some(Self { context, render_configuration })
let vello_renderer = Renderer::new(
&context.device,
RendererOptions {
surface_format: Some(wgpu::TextureFormat::Rgba8Unorm),
use_cpu: false,
antialiasing_support: AaSupport::all(),
num_init_threads: std::num::NonZeroUsize::new(1),
},
)
.map_err(|e| anyhow::anyhow!("Failed to create Vello renderer: {:?}", e))
.ok()?;
Some(Self {
context,
render_configuration,
vello_renderer: vello_renderer.into(),
})
}
}
@@ -861,15 +929,17 @@ async fn read_output_buffer_node<'a: 'input>(buffer: Arc<WgpuShaderInput>, execu
executor.read_output_buffer(buffer).await.unwrap()
}
pub struct CreateGpuSurfaceNode {}
pub struct CreateGpuSurfaceNode<EditorApi> {
editor_api: EditorApi,
}
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
#[node_macro::node_fn(CreateGpuSurfaceNode)]
async fn create_gpu_surface<'a: 'input, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + Send + Sync>(editor_api: &'a EditorApi<Io>) -> WgpuSurface {
let canvas = editor_api.application_io.as_ref().unwrap().create_surface();
let executor = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
Arc::new(executor.create_surface(canvas).unwrap())
async fn create_gpu_surface<'a: 'input, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(footprint: Footprint, editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
let canvas = editor_api.application_io.as_ref()?.create_surface();
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
Some(Arc::new(executor.create_surface(canvas, Some(footprint.resolution)).ok()?))
}
pub struct RenderTextureNode<Image, Surface, EditorApi> {
@@ -885,14 +955,19 @@ pub struct ShaderInputFrame {
}
#[node_macro::node_fn(RenderTextureNode)]
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: WgpuSurface, executor: &'a WgpuExecutor) -> SurfaceFrame {
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: Option<WgpuSurface>, executor: &'a WgpuExecutor) -> SurfaceFrame {
let surface = surface.unwrap();
let surface_id = surface.surface_id;
let image = self.image.eval(footprint).await;
let transform = image.transform;
executor.create_render_pass(footprint, image, surface).unwrap();
SurfaceFrame { surface_id, transform }
SurfaceFrame {
surface_id,
transform,
resolution: footprint.resolution,
}
}
pub struct UploadTextureNode<Executor> {