From 8c459499165eedc2a6a733cc8d9637a20b71d5f4 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 27 Sep 2025 18:13:51 +0200 Subject: [PATCH] Add implementation of rasterize node using vello --- .../node_graph/document_node_definitions.rs | 5 +- node-graph/gstd/src/wasm_application_io.rs | 75 +++++++++++++++++-- .../interpreted-executor/src/node_registry.rs | 4 + node-graph/wgpu-executor/src/lib.rs | 2 +- 4 files changed, 79 insertions(+), 7 deletions(-) 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 ff12d80e0d..eed2aa5610 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,7 +481,7 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Loads an image from a given URL"), properties: None, }, - #[cfg(all(feature = "gpu", target_family = "wasm"))] + #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Rasterize", category: "Raster", @@ -502,7 +502,10 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { + #[cfg(target_family = "wasm")] inputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], + #[cfg(not(target_family = "wasm"))] + inputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::scope("wgpu-executor")], implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER), ..Default::default() }, diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 9e6c25a172..f103afc19f 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -3,22 +3,19 @@ use base64::Engine; pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::wasm_application_io::*; use graphene_application_io::ApplicationIo; -#[cfg(target_family = "wasm")] use graphene_core::gradient::GradientStops; #[cfg(target_family = "wasm")] use graphene_core::math::bbox::Bbox; use graphene_core::raster::image::Image; use graphene_core::raster_types::{CPU, Raster}; use graphene_core::table::Table; -#[cfg(target_family = "wasm")] use graphene_core::transform::Footprint; -#[cfg(target_family = "wasm")] use graphene_core::vector::Vector; use graphene_core::{Color, Ctx}; -#[cfg(target_family = "wasm")] use graphene_core::{Graphic, WasmNotSend}; +use graphene_svg_renderer::Render; #[cfg(target_family = "wasm")] -use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender}; +use graphene_svg_renderer::{RenderParams, RenderSvgSegmentList, SvgRender}; use std::sync::Arc; #[cfg(target_family = "wasm")] use wasm_bindgen::JsCast; @@ -209,3 +206,71 @@ where ..Default::default() }) } + +#[cfg(not(target_family = "wasm"))] +#[node_macro::node(category(""))] +async fn rasterize<'a: 'n, T: WasmNotSend + 'n>( + _: impl Ctx, + #[implementations( + Table, + Table>, + Table, + Table, + Table, + )] + mut data: Table, + footprint: Footprint, + wgpu_executor: &'a wgpu_executor::WgpuExecutor, +) -> Table> +where + Table: graphene_svg_renderer::Render, +{ + use graphene_core::math::bbox::Bbox; + use graphene_core::table::TableRow; + use wgpu_executor::RenderContext; + + if footprint.transform.matrix2.determinant() == 0. { + log::trace!("Invalid footprint received for rasterization"); + return Table::new(); + } + + let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox(); + let resolution = footprint.resolution; + + // Adjust data transforms to account for bounding box offset + for row in data.iter_mut() { + *row.transform = glam::DAffine2::from_translation(-aabb.start) * *row.transform; + } + + // Create Vello scene and render context + let mut scene = vello::Scene::new(); + let mut context = RenderContext::default(); + + // Render data to Vello scene + let render_params = graphene_svg_renderer::RenderParams { + footprint: Footprint::default(), + for_export: true, + ..Default::default() + }; + data.render_to_vello(&mut scene, Default::default(), &mut context, &render_params); + + // Render scene to texture + let background = graphene_core::Color::TRANSPARENT; + let wgpu_texture = wgpu_executor + .render_vello_scene_to_texture(&scene, resolution, &context, background) + .await + .expect("Failed to render Vello scene to texture"); + + // Wrap the texture in a Raster + let gpu_raster = Raster::new_gpu(wgpu_texture); + + // Convert GPU raster to CPU raster using Convert trait + use graphene_core::ops::Convert; + let cpu_raster = gpu_raster.convert(Footprint::default(), wgpu_executor).await; + + Table::new_from_row(TableRow { + element: cpu_raster, + transform: footprint.transform, + ..Default::default() + }) +} diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 5a285f5298..101d52a387 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -148,6 +148,8 @@ fn node_registry() -> HashMap, 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]), + #[cfg(feature = "gpu")] + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &WgpuExecutor, Context => graphene_std::ContextFeatures]), // ========== // MEMO NODES // ========== @@ -183,6 +185,8 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => WgpuSurface]), #[cfg(feature = "gpu")] + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WgpuExecutor]), + #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Color]), diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 42fdd9e1a0..9fd7e43175 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -124,7 +124,7 @@ impl WgpuExecutor { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC, format: VELLO_SURFACE_FORMAT, view_formats: &[], });