diff --git a/Cargo.lock b/Cargo.lock index 62956752..aec9abb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2235,6 +2235,7 @@ dependencies = [ "reqwest", "tokio", "vello", + "vello_encoding", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index f0eff7d1..5ea89efb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,6 +136,7 @@ winit = { version = "0.30", features = ["wayland", "rwh_06"] } url = "2.5" tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] } vello = { git = "https://github.com/linebender/vello.git", rev = "87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" } # TODO switch back to stable when a release is made +vello_encoding = { git = "https://github.com/linebender/vello.git", rev = "87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" } # TODO switch back to stable when a release is made resvg = "0.45" usvg = "0.45" rand = { version = "0.9", default-features = false, features = ["std_rng"] } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index d845ac54..a110c34d 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -481,71 +481,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Loads an image from a given URL"), properties: None, }, - #[cfg(feature = "gpu")] - DocumentNodeDefinition { - identifier: "Create Canvas", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), - skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - output_names: vec!["Image".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("Creates a new canvas object."), - properties: None, - }, #[cfg(all(feature = "gpu", target_family = "wasm"))] DocumentNodeDefinition { identifier: "Rasterize", diff --git a/node-graph/gapplication-io/src/lib.rs b/node-graph/gapplication-io/src/lib.rs index e55bc9a1..5f0b8ee5 100644 --- a/node-graph/gapplication-io/src/lib.rs +++ b/node-graph/gapplication-io/src/lib.rs @@ -227,6 +227,7 @@ pub enum ExportFormat { }, Jpeg, Canvas, + Texture, } #[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 7f634c96..4f7373d8 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" [features] -default = ["wasm"] +default = ["wasm", "wgpu"] gpu = [] wgpu = ["gpu", "graph-craft/wgpu", "graphene-application-io/wgpu"] wasm = [ @@ -18,7 +18,7 @@ wasm = [ "image/png", ] image-compare = [] -vello = ["dep:vello", "gpu"] +vello = ["gpu"] resvg = [] wayland = ["graph-craft/wayland"] shader-nodes = ["graphene-raster-nodes/shader-nodes"] @@ -48,7 +48,8 @@ base64 = { workspace = true } wasm-bindgen = { workspace = true, optional = true } wasm-bindgen-futures = { workspace = true, optional = true } tokio = { workspace = true, optional = true } -vello = { workspace = true, optional = true } +vello = { workspace = true } +vello_encoding = { workspace = true } web-sys = { workspace = true, optional = true, features = [ "Window", "CanvasRenderingContext2d", diff --git a/node-graph/gstd/src/lib.rs b/node-graph/gstd/src/lib.rs index d21d4c9c..41807a47 100644 --- a/node-graph/gstd/src/lib.rs +++ b/node-graph/gstd/src/lib.rs @@ -1,4 +1,5 @@ pub mod any; +pub mod render_node; pub mod text; #[cfg(feature = "wasm")] pub mod wasm_application_io; diff --git a/node-graph/gstd/src/render_node.rs b/node-graph/gstd/src/render_node.rs new file mode 100644 index 00000000..5966d801 --- /dev/null +++ b/node-graph/gstd/src/render_node.rs @@ -0,0 +1,225 @@ +use graph_craft::document::value::RenderOutput; +pub use graph_craft::document::value::RenderOutputType; +pub use graph_craft::wasm_application_io::*; +use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig, SurfaceFrame}; +use graphene_core::gradient::GradientStops; +use graphene_core::raster::image::Image; +use graphene_core::raster_types::{CPU, Raster}; +use graphene_core::table::Table; +use graphene_core::transform::Footprint; +use graphene_core::vector::Vector; +use graphene_core::{Artboard, CloneVarArgs, ExtractAll, ExtractVarArgs}; +use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend}; +use graphene_svg_renderer::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; +use graphene_svg_renderer::{RenderMetadata, SvgSegment}; +use std::sync::Arc; +use wgpu_executor::RenderContext; + +/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string. +type ImageData = Vec<(u64, Image)>; + +#[derive(Clone, dyn_any::DynAny)] +pub enum RenderIntermediateType { + Vello(Arc<(vello::Scene, RenderContext)>), + Svg(Arc<(String, ImageData, String)>), +} +#[derive(Clone, dyn_any::DynAny)] +pub struct RenderIntermediate { + ty: RenderIntermediateType, + metadata: RenderMetadata, + contains_artboard: bool, +} + +#[node_macro::node(category(""))] +async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>( + ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs, + #[implementations( + Context -> Table, + Context -> Table, + Context -> Table, + Context -> Table>, + Context -> Table, + Context -> Table, + )] + data: impl Node, Output = T>, + editor_api: impl Node, Output = &'a WasmEditorApi>, +) -> RenderIntermediate { + let mut render = SvgRender::new(); + let render_params = ctx + .vararg(0) + .expect("Did not find var args") + .downcast_ref::() + .expect("Downcasting render params yielded invalid type"); + + let ctx = OwnedContextImpl::from(ctx.clone()).into_context(); + let data = data.eval(ctx).await; + + let footprint = Footprint::default(); + let mut metadata = RenderMetadata::default(); + data.collect_metadata(&mut metadata, footprint, None); + let contains_artboard = data.contains_artboard(); + + let editor_api = editor_api.eval(None).await; + + if !render_params.for_export && editor_api.editor_preferences.use_vello() && matches!(render_params.render_output_type, graphene_svg_renderer::RenderOutputType::Vello) { + let mut scene = vello::Scene::new(); + + let mut context = wgpu_executor::RenderContext::default(); + data.render_to_vello(&mut scene, Default::default(), &mut context, render_params); + + RenderIntermediate { + ty: RenderIntermediateType::Vello(Arc::new((scene, context))), + metadata, + contains_artboard, + } + } else { + data.render_svg(&mut render, render_params); + + RenderIntermediate { + ty: RenderIntermediateType::Svg(Arc::new((render.svg.to_svg_string(), render.image_data, render.svg_defs.clone()))), + metadata, + contains_artboard, + } + } +} + +#[node_macro::node(category(""))] +async fn create_context<'a: 'n>( + // Context injections are defined in the wrap_network_in_scope function + render_config: RenderConfig, + data: impl Node, Output = RenderOutput>, +) -> RenderOutput { + let footprint = render_config.viewport; + + let render_output_type = match render_config.export_format { + ExportFormat::Svg => RenderOutputTypeRequest::Svg, + ExportFormat::Png { .. } => todo!(), + ExportFormat::Jpeg => todo!(), + ExportFormat::Canvas => RenderOutputTypeRequest::Vello, + ExportFormat::Texture => RenderOutputTypeRequest::Vello, + }; + let render_params = RenderParams { + render_mode: render_config.render_mode, + hide_artboards: render_config.hide_artboards, + for_export: render_config.for_export, + render_output_type, + footprint: Footprint::default(), + ..Default::default() + }; + let ctx = OwnedContextImpl::default() + .with_footprint(footprint) + .with_real_time(render_config.time.time) + .with_animation_time(render_config.time.animation_time.as_secs_f64()) + .with_vararg(Box::new(render_params)) + .into_context(); + + data.eval(ctx).await +} + +#[node_macro::node(category(""))] +async fn render<'a: 'n>( + ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, + editor_api: &'a WasmEditorApi, + data: RenderIntermediate, + _surface_handle: impl Node, Output = Option>, +) -> RenderOutput { + let footprint = ctx.footprint(); + let render_params = ctx + .vararg(0) + .expect("Did not find var args") + .downcast_ref::() + .expect("Downcasting render params yielded invalid type"); + let mut render_params = render_params.clone(); + render_params.footprint = *footprint; + let render_params = &render_params; + + let RenderIntermediate { ty, mut metadata, contains_artboard } = data; + metadata.apply_transform(footprint.transform); + + let surface_handle = if cfg!(all(feature = "vello", target_family = "wasm")) { + _surface_handle.eval(None).await + } else { + None + }; + + let mut output_format = render_params.render_output_type; + // TODO: Actually request the right thing upfront + if let RenderIntermediateType::Svg(_) = ty { + output_format = RenderOutputTypeRequest::Svg; + } + let data = match (output_format, &ty) { + (RenderOutputTypeRequest::Svg, RenderIntermediateType::Svg(svg_data)) => { + let mut svg_renderer = SvgRender::new(); + if !contains_artboard && !render_params.hide_artboards { + svg_renderer.leaf_tag("rect", |attributes| { + attributes.push("x", "0"); + attributes.push("y", "0"); + attributes.push("width", footprint.resolution.x.to_string()); + attributes.push("height", footprint.resolution.y.to_string()); + let matrix = format_transform_matrix(footprint.transform.inverse()); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } + attributes.push("fill", "white"); + }); + } + svg_renderer.svg.push(SvgSegment::from(svg_data.0.clone())); + svg_renderer.image_data = svg_data.1.clone(); + svg_renderer.svg_defs = svg_data.2.clone(); + + svg_renderer.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2())); + RenderOutputType::Svg { + svg: svg_renderer.svg.to_svg_string(), + image_data: svg_renderer.image_data, + } + } + (RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => { + let Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() else { + unreachable!("Attempted to render with Vello when no GPU executor is available"); + }; + let (child, context) = Arc::as_ref(vello_data); + let footprint_transform = vello::kurbo::Affine::new(footprint.transform.to_cols_array()); + + let mut scene = vello::Scene::new(); + scene.append(child, Some(footprint_transform)); + + let encoding = scene.encoding_mut(); + + // We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport + // See for more detail + + for transform in encoding.transforms.iter_mut() { + if transform.matrix[0] == f32::INFINITY { + *transform = vello_encoding::Transform::from_kurbo(&(vello::kurbo::Affine::scale_non_uniform(footprint.resolution.x as f64, footprint.resolution.y as f64))) + } + } + + let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22); + if !contains_artboard && !render_params.hide_artboards { + background = Color::WHITE; + } + if let Some(surface_handle) = surface_handle { + exec.render_vello_scene(&scene, &surface_handle, footprint.resolution, context, background) + .await + .expect("Failed to render Vello scene"); + + let frame = SurfaceFrame { + surface_id: surface_handle.window_id, + resolution: footprint.resolution, + transform: glam::DAffine2::IDENTITY, + }; + + RenderOutputType::CanvasFrame(frame) + } else { + let texture = exec + .render_vello_scene_to_texture(&scene, footprint.resolution, context, background) + .await + .expect("Failed to render Vello scene"); + + RenderOutputType::Texture(ImageTexture { texture }) + } + } + _ => unreachable!("Render node did not receive its requested data type"), + }; + RenderOutput { data, metadata } +} diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 6aeddf4d..9e6c25a1 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,8 +1,9 @@ -use graph_craft::document::value::RenderOutput; +#[cfg(target_family = "wasm")] +use base64::Engine; pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::wasm_application_io::*; -use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig}; -use graphene_core::Artboard; +use graphene_application_io::ApplicationIo; +#[cfg(target_family = "wasm")] use graphene_core::gradient::GradientStops; #[cfg(target_family = "wasm")] use graphene_core::math::bbox::Bbox; @@ -11,13 +12,13 @@ use graphene_core::raster_types::{CPU, Raster}; use graphene_core::table::Table; #[cfg(target_family = "wasm")] use graphene_core::transform::Footprint; -use graphene_core::vector::Vector; -use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend}; -use graphene_svg_renderer::RenderMetadata; -use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; - #[cfg(target_family = "wasm")] -use base64::Engine; +use graphene_core::vector::Vector; +use graphene_core::{Color, Ctx}; +#[cfg(target_family = "wasm")] +use graphene_core::{Graphic, WasmNotSend}; +#[cfg(target_family = "wasm")] +use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender}; use std::sync::Arc; #[cfg(target_family = "wasm")] use wasm_bindgen::JsCast; @@ -139,80 +140,6 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table> { Table::new_from_element(Raster::new_cpu(image)) } -fn render_svg(data: impl Render, mut render: SvgRender, render_params: RenderParams) -> RenderOutputType { - let footprint = render_params.footprint; - - if !data.contains_artboard() && !render_params.hide_artboards { - render.leaf_tag("rect", |attributes| { - attributes.push("x", "0"); - attributes.push("y", "0"); - attributes.push("width", footprint.resolution.x.to_string()); - attributes.push("height", footprint.resolution.y.to_string()); - let matrix = format_transform_matrix(footprint.transform.inverse()); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } - attributes.push("fill", "white"); - }); - } - - data.render_svg(&mut render, &render_params); - - render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2())); - - RenderOutputType::Svg { - svg: render.svg.to_svg_string(), - image_data: render.image_data, - } -} - -#[cfg(feature = "vello")] -#[cfg_attr(not(target_family = "wasm"), allow(dead_code))] -async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: &WasmEditorApi, surface_handle: Option, render_params: RenderParams) -> RenderOutputType { - use graphene_application_io::{ImageTexture, SurfaceFrame}; - - let mut footprint = render_config.viewport; - footprint.resolution = footprint.resolution.max(glam::UVec2::splat(1)); - let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else { - unreachable!("Attempted to render with Vello when no GPU executor is available"); - }; - use vello::*; - - let mut scene = Scene::new(); - let mut child = Scene::new(); - - let mut context = wgpu_executor::RenderContext::default(); - data.render_to_vello(&mut child, Default::default(), &mut context, &render_params); - - // TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(n) cost - scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array()))); - - let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22); - if !data.contains_artboard() && !render_config.hide_artboards { - background = Color::WHITE; - } - if let Some(surface_handle) = surface_handle { - exec.render_vello_scene(&scene, &surface_handle, footprint.resolution, &context, background) - .await - .expect("Failed to render Vello scene"); - - let frame = SurfaceFrame { - surface_id: surface_handle.window_id, - resolution: render_config.viewport.resolution, - transform: glam::DAffine2::IDENTITY, - }; - - RenderOutputType::CanvasFrame(frame) - } else { - let texture = exec - .render_vello_scene_to_texture(&scene, footprint.resolution, &context, background) - .await - .expect("Failed to render Vello scene"); - - RenderOutputType::Texture(ImageTexture { texture }) - } -} - #[cfg(target_family = "wasm")] #[node_macro::node(category(""))] async fn rasterize( @@ -282,70 +209,3 @@ where ..Default::default() }) } - -#[node_macro::node(category(""))] -async fn render<'a: 'n, T: 'n + Render + WasmNotSend>( - render_config: RenderConfig, - editor_api: impl Node, Output = &'a WasmEditorApi>, - #[implementations( - Context -> Table, - Context -> Table, - Context -> Table, - Context -> Table>, - Context -> Table, - Context -> Table, - )] - data: impl Node, Output = T>, - _surface_handle: impl Node, Output = Option>, -) -> RenderOutput { - let footprint = render_config.viewport; - let ctx = OwnedContextImpl::default() - .with_footprint(footprint) - .with_real_time(render_config.time.time) - .with_animation_time(render_config.time.animation_time.as_secs_f64()) - .into_context(); - ctx.footprint(); - - let render_params = RenderParams { - render_mode: render_config.render_mode, - hide_artboards: render_config.hide_artboards, - for_export: render_config.for_export, - footprint, - ..Default::default() - }; - - let data = data.eval(ctx.clone()).await; - let editor_api = editor_api.eval(None).await; - - #[cfg(all(feature = "vello", not(test), target_family = "wasm"))] - let _surface_handle = _surface_handle.eval(None).await; - #[cfg(not(target_family = "wasm"))] - let _surface_handle: Option = None; - - let use_vello = editor_api.editor_preferences.use_vello(); - #[cfg(all(feature = "vello", not(test), target_family = "wasm"))] - let use_vello = use_vello && _surface_handle.is_some(); - - let mut metadata = RenderMetadata::default(); - data.collect_metadata(&mut metadata, footprint, None); - - let output_format = render_config.export_format; - let data = match output_format { - ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params), - ExportFormat::Canvas => { - if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() { - #[cfg(all(feature = "vello", not(test)))] - return RenderOutput { - data: render_canvas(render_config, data, editor_api, _surface_handle, render_params).await, - metadata, - }; - #[cfg(any(not(feature = "vello"), test))] - render_svg(data, SvgRender::new(), render_params) - } else { - render_svg(data, SvgRender::new(), render_params) - } - } - _ => todo!("Non-SVG render output for {output_format:?}"), - }; - RenderOutput { data, metadata } -} diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 322bd7b1..d67b3442 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -156,11 +156,19 @@ pub struct RenderContext { pub resource_overrides: Vec<(peniko::Image, wgpu::Texture)>, } +#[derive(Default, Clone, Copy, Hash)] +pub enum RenderOutputType { + #[default] + Svg, + Vello, +} + /// Static state used whilst rendering #[derive(Default, Clone)] pub struct RenderParams { pub render_mode: RenderMode, pub footprint: Footprint, + pub render_output_type: RenderOutputType, pub thumbnail: bool, /// Don't render the rectangle for an artboard to allow exporting with a transparent background. pub hide_artboards: bool, @@ -174,6 +182,23 @@ pub struct RenderParams { pub override_paint_order: bool, } +impl Hash for RenderParams { + fn hash(&self, state: &mut H) { + self.render_mode.hash(state); + self.footprint.hash(state); + self.render_output_type.hash(state); + self.thumbnail.hash(state); + self.hide_artboards.hash(state); + self.for_export.hash(state); + self.for_mask.hash(state); + if let Some(x) = self.alignment_parent_transform { + x.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) + } + self.aligned_strokes.hash(state); + self.override_paint_order.hash(state); + } +} + impl RenderParams { pub fn for_clipper(&self) -> Self { Self { for_mask: true, ..*self } @@ -224,6 +249,14 @@ pub struct RenderMetadata { pub clip_targets: HashSet, } +impl RenderMetadata { + pub fn apply_transform(&mut self, transform: DAffine2) { + for value in self.upstream_footprints.values_mut() { + value.transform = transform * value.transform; + } + } +} + // TODO: Rename to "Graphical" pub trait Render: BoundingBox + RenderComplexity { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); @@ -719,7 +752,7 @@ impl Render for Table { applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, - &render_params, + render_params, ); attributes.push_val(fill_and_stroke); }); @@ -817,7 +850,7 @@ impl Render for Table { applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, - &render_params, + render_params, ); attributes.push_val(fill_and_stroke); }); @@ -1352,17 +1385,23 @@ impl Render for Table> { } } +const ALMOST_INF: f64 = 2_000_000_000.; +const ALMOST_INF_OFFSET: f64 = ALMOST_INF / -2.; + +// Since colors and gradients are technically infinitely big, we have to implement +// workarounds for rendering them correctly in a way which still allows us +// to cache the intermediate render data (SVG string/Vello scene). +// For SVG, this is is achived by creating a truly giant rectangle. +// For Vello, we create a layer with a placeholder transform which we +// later replace with the current viewport transform before each render. impl Render for Table { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for row in self.iter() { render.leaf_tag("rect", |attributes| { - attributes.push("width", render_params.footprint.resolution.x.to_string()); - attributes.push("height", render_params.footprint.resolution.y.to_string()); - - let matrix = format_transform_matrix(render_params.footprint.transform.inverse()); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } + attributes.push("width", ALMOST_INF.to_string()); + attributes.push("height", ALMOST_INF.to_string()); + attributes.push("x", ALMOST_INF_OFFSET.to_string()); + attributes.push("y", ALMOST_INF_OFFSET.to_string()); let color = row.element; attributes.push("fill", format!("#{}", color.to_rgb_hex_srgb_from_gamma())); @@ -1383,7 +1422,7 @@ impl Render for Table { } #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { + fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; for row in self.iter() { @@ -1391,23 +1430,19 @@ impl Render for Table { let blend_mode = alpha_blending.blend_mode.to_peniko(); let opacity = alpha_blending.opacity(render_params.for_mask); - let transform = parent_transform * render_params.footprint.transform.inverse(); let color = row.element; let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]); - let rect = kurbo::Rect::from_origin_size( - kurbo::Point::ZERO, - kurbo::Size::new(render_params.footprint.resolution.x as f64, render_params.footprint.resolution.y as f64), - ); + let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)); let mut layer = false; if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() { let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver); - scene.push_layer(blending, opacity, kurbo::Affine::IDENTITY, &rect); + scene.push_layer(blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect); layer = true; } - scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), vello_color, None, &rect); + scene.fill(peniko::Fill::NonZero, kurbo::Affine::scale(f64::INFINITY), vello_color, None, &rect); if layer { scene.pop_layer(); @@ -1420,13 +1455,10 @@ impl Render for Table { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for row in self.iter() { render.leaf_tag("rect", |attributes| { - attributes.push("width", render_params.footprint.resolution.x.to_string()); - attributes.push("height", render_params.footprint.resolution.y.to_string()); - - let matrix = format_transform_matrix(render_params.footprint.transform.inverse()); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } + attributes.push("width", ALMOST_INF.to_string()); + attributes.push("height", ALMOST_INF.to_string()); + attributes.push("x", ALMOST_INF_OFFSET.to_string()); + attributes.push("y", ALMOST_INF_OFFSET.to_string()); let mut stop_string = String::new(); for (position, color) in row.element.0.iter() { @@ -1483,7 +1515,7 @@ impl Render for Table { } #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { + fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; for row in self.iter() { @@ -1491,23 +1523,20 @@ impl Render for Table { let blend_mode = alpha_blending.blend_mode.to_peniko(); let opacity = alpha_blending.opacity(render_params.for_mask); - let transform = parent_transform * render_params.footprint.transform.inverse(); let color = row.element.0.first().map(|stop| stop.1).unwrap_or(Color::MAGENTA); let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]); - let rect = kurbo::Rect::from_origin_size( - kurbo::Point::ZERO, - kurbo::Size::new(render_params.footprint.resolution.x as f64, render_params.footprint.resolution.y as f64), - ); + let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)); let mut layer = false; if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() { let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver); - scene.push_layer(blending, opacity, kurbo::Affine::IDENTITY, &rect); + // See implemenation in `Table` for more detail + scene.push_layer(blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect); layer = true; } - scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), vello_color, None, &rect); + scene.fill(peniko::Fill::NonZero, kurbo::Affine::scale(f64::INFINITY), vello_color, None, &rect); if layer { scene.pop_layer(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 2bd69b8b..65ed9439 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -21,12 +21,14 @@ use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; +use graphene_std::render_node::RenderIntermediate; use graphene_std::table::Table; use graphene_std::transform::Footprint; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; +use graphene_std::wasm_application_io::WasmEditorApi; #[cfg(feature = "gpu")] -use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; +use graphene_std::wasm_application_io::WasmSurfaceHandle; use node_registry_macros::{async_node, convert_node, into_node}; use once_cell::sync::Lazy; use std::collections::HashMap; @@ -130,6 +132,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => &WasmEditorApi, Context => graphene_std::ContextFeatures]), #[cfg(feature = "gpu")] async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Arc, Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WgpuSurface, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WindowHandle, Context => graphene_std::ContextFeatures]), @@ -164,7 +167,6 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => DAffine2]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), - #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]), @@ -215,6 +217,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => graphene_core::vector::misc::CentroidType]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_path_bool::BooleanOperation]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::text::TextAlign]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]), // ================= // IMPURE MEMO NODES // ================= diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index b1a71608..76da9a21 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -28,7 +28,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> Result<()> { + let size = size.max(UVec2::ONE); let target_texture = if let Some(target_texture) = output && target_texture.size == size {