From 605da5e8755c8a0d3817eec67f993cd72acecdfb Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 17:59:03 +0000 Subject: [PATCH 01/10] Pipeline trait --- node-graph/libraries/wgpu-executor/src/lib.rs | 31 +++++++++++++++- .../libraries/wgpu-executor/src/pipeline.rs | 37 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 node-graph/libraries/wgpu-executor/src/pipeline.rs diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 930612bec3..597ee4ca07 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -1,11 +1,14 @@ mod background; // TODO: Think about where to place this. Likely inlined in the node. Requires refactor of wgpu pipline usage. mod context; +mod pipeline; mod resample; pub mod shader_runtime; mod texture_cache; pub mod texture_conversion; -use std::sync::Arc; +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; use crate::background::BackgroundCompositor; use crate::resample::Resampler; @@ -22,6 +25,7 @@ use wgpu::{Origin3d, TextureAspect}; pub use context::Context as WgpuContext; pub use context::ContextBuilder as WgpuContextBuilder; +pub use pipeline::Pipeline; pub use rendering::RenderContext; pub use wgpu::Backends as WgpuBackends; pub use wgpu::Features as WgpuFeatures; @@ -36,6 +40,7 @@ pub struct WgpuExecutor { resampler: Resampler, background_compositor: BackgroundCompositor, pub shader_runtime: ShaderRuntime, + pipelines: RwLock>>, } impl std::fmt::Debug for WgpuExecutor { @@ -100,6 +105,29 @@ impl WgpuExecutor { pub async fn request_texture(&self, size: UVec2) -> Arc { self.texture_cache.lock().await.request_texture(&self.context.device, size) } + + fn pipeline(&self) -> Arc

{ + let key = TypeId::of::

(); + + if let Some(arc) = self.pipelines.read().unwrap().get(&key) { + return arc.clone().downcast::

().expect("TypeId

guarantees this downcast"); + } + + let built: Arc = Arc::new(P::create(&self.context)); + + self.pipelines + .write() + .unwrap() + .entry(key) + .or_insert(built) + .clone() + .downcast::

() + .expect("TypeId

guarantees this downcast") + } + + pub async fn run_pipeline(&self, args: &P::Args<'_>) -> P::Out { + self.pipeline::

().run(self, args).await + } } impl WgpuExecutor { @@ -133,6 +161,7 @@ impl WgpuExecutor { resampler, background_compositor, shader_runtime, + pipelines: RwLock::new(HashMap::new()), }) } } diff --git a/node-graph/libraries/wgpu-executor/src/pipeline.rs b/node-graph/libraries/wgpu-executor/src/pipeline.rs new file mode 100644 index 0000000000..d36399277a --- /dev/null +++ b/node-graph/libraries/wgpu-executor/src/pipeline.rs @@ -0,0 +1,37 @@ +use std::future::Future; +use std::pin::Pin; + +use crate::{WgpuContext, WgpuExecutor}; + +pub type PipelineFuture<'a, T> = Pin + Send + 'a>>; + +pub trait Pipeline: std::any::Any + Send + Sync + Sized { + type Args<'a>; + type Out: Send; + + fn create(context: &WgpuContext) -> Self; + + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out>; +} + +pub trait AsyncPipeline: std::any::Any + Send + Sync + Sized { + type Args<'a>; + type Out: Send; + + fn create(context: &WgpuContext) -> Self; + + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> impl Future + Send + 'a; +} + +impl Pipeline for P { + type Args<'a> =

::Args<'a>; + type Out =

::Out; + + fn create(context: &WgpuContext) -> Self { +

::create(context) + } + + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out> { + Box::pin(

::run(self, executor, args)) + } +} From 8b8fff05f061ae735264cbd43f2d78e8d8c0f55f Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 18:55:57 +0000 Subject: [PATCH 02/10] Impl Resampler pipeline --- node-graph/libraries/wgpu-executor/src/lib.rs | 7 +---- .../libraries/wgpu-executor/src/resample.rs | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 597ee4ca07..9004863fc8 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -37,7 +37,6 @@ pub struct WgpuExecutor { pub context: WgpuContext, texture_cache: Mutex, vello_renderer: Mutex, - resampler: Resampler, background_compositor: BackgroundCompositor, pub shader_runtime: ShaderRuntime, pipelines: RwLock>>, @@ -90,9 +89,7 @@ impl WgpuExecutor { } pub async fn resample_texture(&self, source: &wgpu::Texture, size: UVec2, transform: &glam::DAffine2) -> Arc { - let out = self.request_texture(size).await; - self.resampler.resample(&self.context, source, transform, &out); - out + self.run_pipeline::(&crate::resample::ResamplerArgs { source, transform, size }).await } pub async fn composite_background(&self, foreground: &wgpu::Texture, backgrounds: &[rendering::Background], document_to_screen: Affine2, zoom: f32) -> Arc { @@ -150,7 +147,6 @@ impl WgpuExecutor { let texture_cache = TextureCache::new(TEXTURE_CACHE_SIZE); - let resampler = Resampler::new(&context.device); let background_compositor = BackgroundCompositor::new(&context.device); let shader_runtime = ShaderRuntime::new(&context); @@ -158,7 +154,6 @@ impl WgpuExecutor { context, texture_cache: texture_cache.into(), vello_renderer: vello_renderer.into(), - resampler, background_compositor, shader_runtime, pipelines: RwLock::new(HashMap::new()), diff --git a/node-graph/libraries/wgpu-executor/src/resample.rs b/node-graph/libraries/wgpu-executor/src/resample.rs index 15a5dc99a9..8f219f334d 100644 --- a/node-graph/libraries/wgpu-executor/src/resample.rs +++ b/node-graph/libraries/wgpu-executor/src/resample.rs @@ -1,13 +1,26 @@ -use crate::WgpuContext; -use glam::{DAffine2, Vec2}; +use std::sync::Arc; + +use crate::pipeline::AsyncPipeline; +use crate::{WgpuContext, WgpuExecutor}; +use glam::{DAffine2, UVec2, Vec2}; pub struct Resampler { pipeline: wgpu::RenderPipeline, bind_group_layout: wgpu::BindGroupLayout, } -impl Resampler { - pub fn new(device: &wgpu::Device) -> Self { +pub struct ResamplerArgs<'a> { + pub source: &'a wgpu::Texture, + pub transform: &'a DAffine2, + pub size: UVec2, +} + +impl AsyncPipeline for Resampler { + type Args<'a> = ResamplerArgs<'a>; + type Out = Arc; + + fn create(context: &WgpuContext) -> Self { + let device = &context.device; let shader = device.create_shader_module(wgpu::include_wgsl!("resample_shader.wgsl")); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -74,7 +87,12 @@ impl Resampler { Resampler { pipeline, bind_group_layout } } - pub fn resample(&self, context: &WgpuContext, source: &wgpu::Texture, transform: &DAffine2, output: &wgpu::Texture) { + async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { + let context = &executor.context; + let &ResamplerArgs { source, transform, size } = args; + + let output = executor.request_texture(size).await; + let source_view = source.create_view(&wgpu::TextureViewDescriptor::default()); let output_view = output.create_view(&wgpu::TextureViewDescriptor::default()); @@ -126,5 +144,7 @@ impl Resampler { } context.queue.submit([encoder.finish()]); + + output } } From 7038200b4f53561ca030f15664d1b90f75ee4db0 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 19:19:32 +0000 Subject: [PATCH 03/10] Impl BackgroundCompositor pipeline --- .../wgpu-executor/src/background/mod.rs | 39 ++++++++++++++++--- node-graph/libraries/wgpu-executor/src/lib.rs | 14 +++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/background/mod.rs b/node-graph/libraries/wgpu-executor/src/background/mod.rs index dd4df445ad..3438ca0be5 100644 --- a/node-graph/libraries/wgpu-executor/src/background/mod.rs +++ b/node-graph/libraries/wgpu-executor/src/background/mod.rs @@ -1,4 +1,7 @@ -use glam::{Affine2, Vec2}; +use crate::pipeline::AsyncPipeline; +use crate::{WgpuContext, WgpuExecutor}; +use glam::{Affine2, UVec2, Vec2}; +use std::sync::Arc; use wgpu::util::DeviceExt; pub struct BackgroundCompositor { @@ -10,8 +13,19 @@ pub struct BackgroundCompositor { sampler: wgpu::Sampler, } -impl BackgroundCompositor { - pub fn new(device: &wgpu::Device) -> Self { +pub struct BackgroundCompositorArgs<'a> { + pub foreground: &'a wgpu::Texture, + pub backgrounds: &'a [rendering::Background], + pub document_to_screen: Affine2, + pub zoom: f32, +} + +impl AsyncPipeline for BackgroundCompositor { + type Args<'a> = BackgroundCompositorArgs<'a>; + type Out = Arc; + + fn create(context: &WgpuContext) -> Self { + let device = &context.device; let format = wgpu::TextureFormat::Rgba8Unorm; let checker_rect_shader = device.create_shader_module(wgpu::include_wgsl!("checker_rect.wgsl")); let checker_viewport_shader = device.create_shader_module(wgpu::include_wgsl!("checker_viewport.wgsl")); @@ -189,9 +203,20 @@ impl BackgroundCompositor { } } - pub fn composite(&self, context: &crate::WgpuContext, foreground: &wgpu::Texture, output: &wgpu::Texture, backgrounds: &[rendering::Background], document_to_screen: Affine2, zoom: f32) { + async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { + let context = &executor.context; + let &BackgroundCompositorArgs { + foreground, + backgrounds, + document_to_screen, + zoom, + } = args; + + let foreground_size = foreground.size(); + let output = executor.request_texture(UVec2::new(foreground_size.width, foreground_size.height)).await; + if zoom <= 0.0 { - return; + return output; } let device = &context.device; @@ -285,8 +310,12 @@ impl BackgroundCompositor { } queue.submit(std::iter::once(encoder.finish())); + + output } +} +impl BackgroundCompositor { fn create_checker_bind_group(&self, device: &wgpu::Device, uniforms: CompositeUniforms) -> wgpu::BindGroup { let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("background_checker_uniforms"), diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 9004863fc8..226cf4cbd8 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -37,7 +37,6 @@ pub struct WgpuExecutor { pub context: WgpuContext, texture_cache: Mutex, vello_renderer: Mutex, - background_compositor: BackgroundCompositor, pub shader_runtime: ShaderRuntime, pipelines: RwLock>>, } @@ -93,10 +92,13 @@ impl WgpuExecutor { } pub async fn composite_background(&self, foreground: &wgpu::Texture, backgrounds: &[rendering::Background], document_to_screen: Affine2, zoom: f32) -> Arc { - let size = foreground.size(); - let output = self.request_texture(UVec2::new(size.width, size.height)).await; - self.background_compositor.composite(&self.context, foreground, &output, backgrounds, document_to_screen, zoom); - output + self.run_pipeline::(&crate::background::BackgroundCompositorArgs { + foreground, + backgrounds, + document_to_screen, + zoom, + }) + .await } pub async fn request_texture(&self, size: UVec2) -> Arc { @@ -147,14 +149,12 @@ impl WgpuExecutor { let texture_cache = TextureCache::new(TEXTURE_CACHE_SIZE); - let background_compositor = BackgroundCompositor::new(&context.device); let shader_runtime = ShaderRuntime::new(&context); Some(Self { context, texture_cache: texture_cache.into(), vello_renderer: vello_renderer.into(), - background_compositor, shader_runtime, pipelines: RwLock::new(HashMap::new()), }) From e350c7e7c2c95120b9d1306e0057fb21ac43b268 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 22:27:13 +0000 Subject: [PATCH 04/10] Move Pipelines to only consumer --- Cargo.lock | 1 + node-graph/interpreted-executor/src/util.rs | 4 +- node-graph/libraries/wgpu-executor/src/lib.rs | 27 +--- node-graph/nodes/gstd/Cargo.toml | 1 + node-graph/nodes/gstd/src/lib.rs | 3 +- node-graph/nodes/gstd/src/pixel_preview.rs | 71 ---------- .../gstd/src/render_background.rs} | 134 ++++++++++++++++-- .../src/render_background_checker_rect.wgsl} | 0 .../render_background_checker_viewport.wgsl} | 0 .../src/render_background_fullscreen.wgsl} | 0 node-graph/nodes/gstd/src/render_node.rs | 106 -------------- .../gstd/src/render_pixel_preview.rs} | 96 +++++++++++-- .../gstd/src/render_pixel_preview.wgsl} | 0 13 files changed, 221 insertions(+), 222 deletions(-) delete mode 100644 node-graph/nodes/gstd/src/pixel_preview.rs rename node-graph/{libraries/wgpu-executor/src/background/mod.rs => nodes/gstd/src/render_background.rs} (69%) rename node-graph/{libraries/wgpu-executor/src/background/checker_rect.wgsl => nodes/gstd/src/render_background_checker_rect.wgsl} (100%) rename node-graph/{libraries/wgpu-executor/src/background/checker_viewport.wgsl => nodes/gstd/src/render_background_checker_viewport.wgsl} (100%) rename node-graph/{libraries/wgpu-executor/src/background/fullscreen.wgsl => nodes/gstd/src/render_background_fullscreen.wgsl} (100%) rename node-graph/{libraries/wgpu-executor/src/resample.rs => nodes/gstd/src/render_pixel_preview.rs} (55%) rename node-graph/{libraries/wgpu-executor/src/resample_shader.wgsl => nodes/gstd/src/render_pixel_preview.wgsl} (100%) diff --git a/Cargo.lock b/Cargo.lock index f3878680c1..53ff801050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2027,6 +2027,7 @@ dependencies = [ "base64", "blending-nodes", "brush-nodes", + "bytemuck", "core-types", "dyn-any", "glam", diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 4d39e124f6..f4e909f57d 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -63,7 +63,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Arc { - self.run_pipeline::(&crate::resample::ResamplerArgs { source, transform, size }).await - } - - pub async fn composite_background(&self, foreground: &wgpu::Texture, backgrounds: &[rendering::Background], document_to_screen: Affine2, zoom: f32) -> Arc { - self.run_pipeline::(&crate::background::BackgroundCompositorArgs { - foreground, - backgrounds, - document_to_screen, - zoom, - }) - .await - } - pub async fn request_texture(&self, size: UVec2) -> Arc { self.texture_cache.lock().await.request_texture(&self.context.device, size) } - fn pipeline(&self) -> Arc

{ + fn pipeline(&self) -> Arc

{ let key = TypeId::of::

(); if let Some(arc) = self.pipelines.read().unwrap().get(&key) { @@ -124,7 +107,7 @@ impl WgpuExecutor { .expect("TypeId

guarantees this downcast") } - pub async fn run_pipeline(&self, args: &P::Args<'_>) -> P::Out { + pub async fn run_pipeline(&self, args: &P::Args<'_>) -> P::Out { self.pipeline::

().run(self, args).await } } diff --git a/node-graph/nodes/gstd/Cargo.toml b/node-graph/nodes/gstd/Cargo.toml index e28b78251f..2385c9a9b6 100644 --- a/node-graph/nodes/gstd/Cargo.toml +++ b/node-graph/nodes/gstd/Cargo.toml @@ -61,6 +61,7 @@ reqwest = { workspace = true } image = { workspace = true } base64 = { workspace = true } wgpu = { workspace = true } +bytemuck = { workspace = true } # Optional local dependencies graphene-canvas-utils = { workspace = true, optional = true } diff --git a/node-graph/nodes/gstd/src/lib.rs b/node-graph/nodes/gstd/src/lib.rs index f17c1c336c..7236bb8c63 100644 --- a/node-graph/nodes/gstd/src/lib.rs +++ b/node-graph/nodes/gstd/src/lib.rs @@ -1,8 +1,9 @@ pub mod any; -pub mod pixel_preview; pub mod platform_application_io; +pub mod render_background; pub mod render_cache; pub mod render_node; +pub mod render_pixel_preview; pub mod text; pub use blending_nodes; pub use brush_nodes as brush; diff --git a/node-graph/nodes/gstd/src/pixel_preview.rs b/node-graph/nodes/gstd/src/pixel_preview.rs deleted file mode 100644 index d27b0cb8a7..0000000000 --- a/node-graph/nodes/gstd/src/pixel_preview.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::render_node::RenderOutputType; -use core_types::transform::{Footprint, Transform}; -use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; -use glam::{DAffine2, DVec2, UVec2}; -use graph_craft::application_io::PlatformEditorApi; -use graph_craft::document::value::RenderOutput; -use graphene_application_io::ApplicationIo; -use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; -use vector_types::vector::style::RenderMode; - -#[node_macro::node(category(""))] -pub async fn pixel_preview<'a: 'n>( - ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, - editor_api: &'a PlatformEditorApi, - data: impl Node, Output = RenderOutput> + Send + Sync, -) -> RenderOutput { - let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::()).cloned() else { - log::error!("invalid render params for pixel preview"); - let context = OwnedContextImpl::from(ctx).into_context(); - return data.eval(context).await; - }; - let physical_scale = render_params.scale; - - let footprint = *ctx.footprint(); - let viewport_zoom = footprint.scale_magnitudes().x * physical_scale; - - if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. { - let context = OwnedContextImpl::from(ctx).into_context(); - return data.eval(context).await; - } - - let physical_resolution = footprint.resolution; - let logical_resolution = physical_resolution.as_dvec2() / physical_scale; - - let logical_footprint = Footprint { - resolution: logical_resolution.as_uvec2().max(UVec2::ONE), - ..footprint - }; - - let bounds = logical_footprint.viewport_bounds_in_local_space(); - - let upstream_min = bounds.start.floor(); - let upstream_max = bounds.end.ceil(); - - let upstream_size = (upstream_max - upstream_min).max(DVec2::ONE); - let upstream_resolution = upstream_size.as_uvec2().max(UVec2::ONE); - - let upstream_footprint = Footprint { - transform: DAffine2::from_scale(DVec2::splat(1.0 / physical_scale)) * DAffine2::from_translation(-upstream_min), - resolution: upstream_resolution, - quality: footprint.quality, - }; - - let new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context(); - let mut result = data.eval(new_ctx).await; - - let RenderOutputType::Texture(ref source_texture) = result.data else { return result }; - - let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution); - - let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); - let resampled = exec.resample_texture(source_texture.as_ref(), physical_resolution, &transform).await; - - result.data = RenderOutputType::Texture(resampled.into()); - - result - .metadata - .apply_transform(footprint.transform * DAffine2::from_translation(upstream_min) * DAffine2::from_scale(DVec2::splat(physical_scale))); - - result -} diff --git a/node-graph/libraries/wgpu-executor/src/background/mod.rs b/node-graph/nodes/gstd/src/render_background.rs similarity index 69% rename from node-graph/libraries/wgpu-executor/src/background/mod.rs rename to node-graph/nodes/gstd/src/render_background.rs index 3438ca0be5..4a7852a829 100644 --- a/node-graph/libraries/wgpu-executor/src/background/mod.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -1,10 +1,126 @@ -use crate::pipeline::AsyncPipeline; -use crate::{WgpuContext, WgpuExecutor}; +use core_types::ExtractVarArgs; +use core_types::color::Linear; +use core_types::transform::Footprint; +use core_types::uuid::generate_uuid; +use core_types::{Ctx, ExtractFootprint}; use glam::{Affine2, UVec2, Vec2}; +use graph_craft::application_io::PlatformEditorApi; +use graph_craft::document::value::RenderOutput; +pub use graph_craft::document::value::RenderOutputType; +use graphene_application_io::ApplicationIo; +use rendering::{RenderParams, SvgRender, SvgRenderOutput}; +use std::fmt::Write; use std::sync::Arc; use wgpu::util::DeviceExt; +use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor, WgpuPipeline}; + +#[node_macro::node(category(""))] +async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderOutput) -> 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"); + + if !render_params.to_canvas() { + return data; + } + + let RenderOutput { data: foreground_data, metadata } = data; + let mut render_params = render_params.clone(); + render_params.footprint = *footprint; + + let data = match foreground_data { + RenderOutputType::Texture(foreground_texture) => { + if let Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() { + let doc_to_screen = (glam::DAffine2::from_scale(glam::DVec2::splat(render_params.scale)) * render_params.footprint.transform).as_affine2(); + let blended = exec + .run_pipeline::(&BackgroundCompositorArgs { + foreground: foreground_texture.as_ref(), + backgrounds: &metadata.backgrounds, + document_to_screen: doc_to_screen, + zoom: render_params.viewport_zoom.to_f32(), + }) + .await; + + RenderOutputType::Texture(blended.into()) + } else { + RenderOutputType::Texture(foreground_texture) + } + } + RenderOutputType::Svg { + svg: foreground_svg, + image_data: foreground_images, + } => { + let mut render = SvgRender::new(); + + if render_params.viewport_zoom > 0. { + let draw_checkerboard = |render: &mut SvgRender, rect: vello::kurbo::Rect, pattern_origin: glam::DVec2, checker_id_prefix: &str| { + let checker_id = format!("{checker_id_prefix}-{}", generate_uuid()); + let cell_size = 8. / render_params.viewport_zoom; + let pattern_size = cell_size * 2.; + + write!( + &mut render.svg_defs, + r##""##, + pattern_origin.x, + pattern_origin.y, + ) + .unwrap(); + + render.leaf_tag("rect", |attributes| { + attributes.push("x", rect.x0.to_string()); + attributes.push("y", rect.y0.to_string()); + attributes.push("width", rect.width().to_string()); + attributes.push("height", rect.height().to_string()); + attributes.push("fill", format!("url(#{checker_id})")); + }); + }; + + if metadata.backgrounds.is_empty() { + if render_params.scale > 0. { + let logical_resolution = render_params.footprint.resolution.as_dvec2() / render_params.scale; + let logical_footprint = Footprint { + resolution: logical_resolution.round().as_uvec2().max(glam::UVec2::ONE), + ..render_params.footprint + }; + let bounds = logical_footprint.viewport_bounds_in_local_space(); + let min = bounds.start.floor(); + let max = bounds.end.ceil(); + + if min.is_finite() && max.is_finite() { + let rect = vello::kurbo::Rect::new(min.x, min.y, max.x, max.y); + draw_checkerboard(&mut render, rect, glam::DVec2::ZERO, "checkered-viewport"); + } + } + } else { + for background in &metadata.backgrounds { + let [a, b] = [background.location, background.location + background.dimensions]; + let rect = vello::kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y)); + draw_checkerboard(&mut render, rect, glam::DVec2::new(rect.x0, rect.y0), "checkered-artboard"); + } + } + } + + let logical_resolution = render_params.footprint.resolution.as_dvec2() / render_params.scale; + render.wrap_with_transform(render_params.footprint.transform, Some(logical_resolution)); + + let background = SvgRenderOutput::from(render); + assert!(background.svg_defs.is_empty()); + + let svg = format!("{}{}", background.svg, foreground_svg); + let image_data = foreground_images; + + RenderOutputType::Svg { svg, image_data } + } + _ => unreachable!("Render background node received unsupported render output type"), + }; + + RenderOutput { data, metadata } +} -pub struct BackgroundCompositor { +struct BackgroundCompositor { checker_rect_pipeline: wgpu::RenderPipeline, checker_viewport_pipeline: wgpu::RenderPipeline, fullscreen_pipeline: wgpu::RenderPipeline, @@ -13,23 +129,23 @@ pub struct BackgroundCompositor { sampler: wgpu::Sampler, } -pub struct BackgroundCompositorArgs<'a> { +struct BackgroundCompositorArgs<'a> { pub foreground: &'a wgpu::Texture, pub backgrounds: &'a [rendering::Background], pub document_to_screen: Affine2, pub zoom: f32, } -impl AsyncPipeline for BackgroundCompositor { +impl AsyncWgpuPipeline for BackgroundCompositor { type Args<'a> = BackgroundCompositorArgs<'a>; type Out = Arc; fn create(context: &WgpuContext) -> Self { let device = &context.device; let format = wgpu::TextureFormat::Rgba8Unorm; - let checker_rect_shader = device.create_shader_module(wgpu::include_wgsl!("checker_rect.wgsl")); - let checker_viewport_shader = device.create_shader_module(wgpu::include_wgsl!("checker_viewport.wgsl")); - let fullscreen_shader = device.create_shader_module(wgpu::include_wgsl!("fullscreen.wgsl")); + let checker_rect_shader = device.create_shader_module(wgpu::include_wgsl!("render_background_checker_rect.wgsl")); + let checker_viewport_shader = device.create_shader_module(wgpu::include_wgsl!("render_background_checker_viewport.wgsl")); + let fullscreen_shader = device.create_shader_module(wgpu::include_wgsl!("render_background_fullscreen.wgsl")); let checker_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("background_checker_bind_group_layout"), @@ -203,7 +319,7 @@ impl AsyncPipeline for BackgroundCompositor { } } - async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { + async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a ::Args<'_>) -> Self::Out { let context = &executor.context; let &BackgroundCompositorArgs { foreground, diff --git a/node-graph/libraries/wgpu-executor/src/background/checker_rect.wgsl b/node-graph/nodes/gstd/src/render_background_checker_rect.wgsl similarity index 100% rename from node-graph/libraries/wgpu-executor/src/background/checker_rect.wgsl rename to node-graph/nodes/gstd/src/render_background_checker_rect.wgsl diff --git a/node-graph/libraries/wgpu-executor/src/background/checker_viewport.wgsl b/node-graph/nodes/gstd/src/render_background_checker_viewport.wgsl similarity index 100% rename from node-graph/libraries/wgpu-executor/src/background/checker_viewport.wgsl rename to node-graph/nodes/gstd/src/render_background_checker_viewport.wgsl diff --git a/node-graph/libraries/wgpu-executor/src/background/fullscreen.wgsl b/node-graph/nodes/gstd/src/render_background_fullscreen.wgsl similarity index 100% rename from node-graph/libraries/wgpu-executor/src/background/fullscreen.wgsl rename to node-graph/nodes/gstd/src/render_background_fullscreen.wgsl diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 121131a062..b77ac5f8a8 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -1,6 +1,5 @@ use core_types::list::List; use core_types::transform::{Footprint, Transform}; -use core_types::uuid::generate_uuid; use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs}; use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend}; use graph_craft::application_io::PlatformEditorApi; @@ -10,14 +9,10 @@ use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig}; use graphic_types::raster_types::{CPU, Raster}; use graphic_types::{Artboard, Graphic, Vector}; use rendering::{Render, RenderMetadata, RenderOutputType as RenderOutputTypeRequest, RenderParams, SvgRender, SvgRenderOutput}; -use std::fmt::Write; use std::sync::Arc; use vector_types::GradientStops; use wgpu_executor::RenderContext; -// Re-export render_output_cache from render_cache module -pub use crate::render_cache::render_output_cache; - #[derive(Clone, dyn_any::DynAny)] pub enum RenderIntermediateType { Vello(Arc<(vello::Scene, RenderContext)>), @@ -150,107 +145,6 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito RenderOutput { data, metadata } } -#[node_macro::node(category(""))] -async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderOutput) -> 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"); - - if !render_params.to_canvas() { - return data; - } - - let RenderOutput { data: foreground_data, metadata } = data; - let mut render_params = render_params.clone(); - render_params.footprint = *footprint; - - let data = match foreground_data { - RenderOutputType::Texture(foreground_texture) => { - if let Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() { - let doc_to_screen = (glam::DAffine2::from_scale(glam::DVec2::splat(render_params.scale)) * render_params.footprint.transform).as_affine2(); - let blended = exec - .composite_background(foreground_texture.as_ref(), &metadata.backgrounds, doc_to_screen, render_params.viewport_zoom as f32) - .await; - - RenderOutputType::Texture(blended.into()) - } else { - RenderOutputType::Texture(foreground_texture) - } - } - RenderOutputType::Svg { - svg: foreground_svg, - image_data: foreground_images, - } => { - let mut render = SvgRender::new(); - - if render_params.viewport_zoom > 0. { - let draw_checkerboard = |render: &mut SvgRender, rect: vello::kurbo::Rect, pattern_origin: glam::DVec2, checker_id_prefix: &str| { - let checker_id = format!("{checker_id_prefix}-{}", generate_uuid()); - let cell_size = 8. / render_params.viewport_zoom; - let pattern_size = cell_size * 2.; - - write!( - &mut render.svg_defs, - r##""##, - pattern_origin.x, - pattern_origin.y, - ) - .unwrap(); - - render.leaf_tag("rect", |attributes| { - attributes.push("x", rect.x0.to_string()); - attributes.push("y", rect.y0.to_string()); - attributes.push("width", rect.width().to_string()); - attributes.push("height", rect.height().to_string()); - attributes.push("fill", format!("url(#{checker_id})")); - }); - }; - - if metadata.backgrounds.is_empty() { - if render_params.scale > 0. { - let logical_resolution = render_params.footprint.resolution.as_dvec2() / render_params.scale; - let logical_footprint = Footprint { - resolution: logical_resolution.round().as_uvec2().max(glam::UVec2::ONE), - ..render_params.footprint - }; - let bounds = logical_footprint.viewport_bounds_in_local_space(); - let min = bounds.start.floor(); - let max = bounds.end.ceil(); - - if min.is_finite() && max.is_finite() { - let rect = vello::kurbo::Rect::new(min.x, min.y, max.x, max.y); - draw_checkerboard(&mut render, rect, glam::DVec2::ZERO, "checkered-viewport"); - } - } - } else { - for background in &metadata.backgrounds { - let [a, b] = [background.location, background.location + background.dimensions]; - let rect = vello::kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y)); - draw_checkerboard(&mut render, rect, glam::DVec2::new(rect.x0, rect.y0), "checkered-artboard"); - } - } - } - - let logical_resolution = render_params.footprint.resolution.as_dvec2() / render_params.scale; - render.wrap_with_transform(render_params.footprint.transform, Some(logical_resolution)); - - let background = SvgRenderOutput::from(render); - assert!(background.svg_defs.is_empty()); - - let svg = format!("{}{}", background.svg, foreground_svg); - let image_data = foreground_images; - - RenderOutputType::Svg { svg, image_data } - } - _ => unreachable!("Render background node received unsupported render output type"), - }; - - RenderOutput { data, metadata } -} - #[node_macro::node(category(""))] async fn create_context<'a: 'n>( // Context injections are defined in the wrap_network_in_scope function diff --git a/node-graph/libraries/wgpu-executor/src/resample.rs b/node-graph/nodes/gstd/src/render_pixel_preview.rs similarity index 55% rename from node-graph/libraries/wgpu-executor/src/resample.rs rename to node-graph/nodes/gstd/src/render_pixel_preview.rs index 8f219f334d..614ff40ed1 100644 --- a/node-graph/libraries/wgpu-executor/src/resample.rs +++ b/node-graph/nodes/gstd/src/render_pixel_preview.rs @@ -1,27 +1,101 @@ +use crate::render_node::RenderOutputType; +use core_types::transform::{Footprint, Transform}; +use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; +use glam::{DAffine2, DVec2, UVec2, Vec2}; +use graph_craft::application_io::PlatformEditorApi; +use graph_craft::document::value::RenderOutput; +use graphene_application_io::ApplicationIo; +use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use std::sync::Arc; +use vector_types::vector::style::RenderMode; +use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor}; + +#[node_macro::node(category(""))] +pub async fn render_pixel_preview<'a: 'n>( + ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, + editor_api: &'a PlatformEditorApi, + data: impl Node, Output = RenderOutput> + Send + Sync, +) -> RenderOutput { + let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::()).cloned() else { + log::error!("invalid render params for pixel preview"); + let context = OwnedContextImpl::from(ctx).into_context(); + return data.eval(context).await; + }; + let physical_scale = render_params.scale; + + let footprint = *ctx.footprint(); + let viewport_zoom = footprint.scale_magnitudes().x * physical_scale; + + if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. { + let context = OwnedContextImpl::from(ctx).into_context(); + return data.eval(context).await; + } + + let physical_resolution = footprint.resolution; + let logical_resolution = physical_resolution.as_dvec2() / physical_scale; + + let logical_footprint = Footprint { + resolution: logical_resolution.as_uvec2().max(UVec2::ONE), + ..footprint + }; + + let bounds = logical_footprint.viewport_bounds_in_local_space(); + + let upstream_min = bounds.start.floor(); + let upstream_max = bounds.end.ceil(); + + let upstream_size = (upstream_max - upstream_min).max(DVec2::ONE); + let upstream_resolution = upstream_size.as_uvec2().max(UVec2::ONE); + + let upstream_footprint = Footprint { + transform: DAffine2::from_scale(DVec2::splat(1.0 / physical_scale)) * DAffine2::from_translation(-upstream_min), + resolution: upstream_resolution, + quality: footprint.quality, + }; -use crate::pipeline::AsyncPipeline; -use crate::{WgpuContext, WgpuExecutor}; -use glam::{DAffine2, UVec2, Vec2}; + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context(); + let mut result = data.eval(new_ctx).await; + + let RenderOutputType::Texture(ref source_texture) = result.data else { return result }; + + let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution); + + let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); + let resampled = exec + .run_pipeline::(&ResamplerArgs { + source: source_texture.as_ref(), + transform: &transform, + size: logical_resolution.as_uvec2(), + }) + .await; + + result.data = RenderOutputType::Texture(resampled.into()); + + result + .metadata + .apply_transform(footprint.transform * DAffine2::from_translation(upstream_min) * DAffine2::from_scale(DVec2::splat(physical_scale))); + + result +} -pub struct Resampler { +struct PixelPreviewPipeline { pipeline: wgpu::RenderPipeline, bind_group_layout: wgpu::BindGroupLayout, } -pub struct ResamplerArgs<'a> { - pub source: &'a wgpu::Texture, - pub transform: &'a DAffine2, - pub size: UVec2, +struct ResamplerArgs<'a> { + source: &'a wgpu::Texture, + transform: &'a DAffine2, + size: UVec2, } -impl AsyncPipeline for Resampler { +impl AsyncWgpuPipeline for PixelPreviewPipeline { type Args<'a> = ResamplerArgs<'a>; type Out = Arc; fn create(context: &WgpuContext) -> Self { let device = &context.device; - let shader = device.create_shader_module(wgpu::include_wgsl!("resample_shader.wgsl")); + let shader = device.create_shader_module(wgpu::include_wgsl!("render_pixel_preview.wgsl")); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("resample_bind_group_layout"), @@ -84,7 +158,7 @@ impl AsyncPipeline for Resampler { cache: None, }); - Resampler { pipeline, bind_group_layout } + PixelPreviewPipeline { pipeline, bind_group_layout } } async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { diff --git a/node-graph/libraries/wgpu-executor/src/resample_shader.wgsl b/node-graph/nodes/gstd/src/render_pixel_preview.wgsl similarity index 100% rename from node-graph/libraries/wgpu-executor/src/resample_shader.wgsl rename to node-graph/nodes/gstd/src/render_pixel_preview.wgsl From 07e3e411ceb92fbebf42f1af7e1b2a9cc64e6342 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 22:44:15 +0000 Subject: [PATCH 05/10] Fixup --- node-graph/libraries/wgpu-executor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 8d31962bb7..ebc1104494 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -34,8 +34,8 @@ pub struct WgpuExecutor { pub context: WgpuContext, texture_cache: Mutex, vello_renderer: Mutex, - pub shader_runtime: ShaderRuntime, pipelines: RwLock>>, + pub shader_runtime: ShaderRuntime, } impl std::fmt::Debug for WgpuExecutor { From e941bec09134846e5d3edac314e0a7f3d14e4186 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 23:01:17 +0000 Subject: [PATCH 06/10] Refactor pipeline trait --- node-graph/libraries/wgpu-executor/src/lib.rs | 1 - .../libraries/wgpu-executor/src/pipeline.rs | 25 ------------------- .../nodes/gstd/src/render_background.rs | 4 +-- .../nodes/gstd/src/render_pixel_preview.rs | 4 +-- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index ebc1104494..f5e5d1fa6d 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -21,7 +21,6 @@ use wgpu::{Origin3d, TextureAspect}; pub use context::Context as WgpuContext; pub use context::ContextBuilder as WgpuContextBuilder; -pub use pipeline::AsyncPipeline as AsyncWgpuPipeline; pub use pipeline::Pipeline as WgpuPipeline; pub use rendering::RenderContext; pub use wgpu::Backends as WgpuBackends; diff --git a/node-graph/libraries/wgpu-executor/src/pipeline.rs b/node-graph/libraries/wgpu-executor/src/pipeline.rs index d36399277a..959b8157be 100644 --- a/node-graph/libraries/wgpu-executor/src/pipeline.rs +++ b/node-graph/libraries/wgpu-executor/src/pipeline.rs @@ -1,37 +1,12 @@ use std::future::Future; -use std::pin::Pin; use crate::{WgpuContext, WgpuExecutor}; -pub type PipelineFuture<'a, T> = Pin + Send + 'a>>; - pub trait Pipeline: std::any::Any + Send + Sync + Sized { type Args<'a>; type Out: Send; fn create(context: &WgpuContext) -> Self; - fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out>; -} - -pub trait AsyncPipeline: std::any::Any + Send + Sync + Sized { - type Args<'a>; - type Out: Send; - - fn create(context: &WgpuContext) -> Self; - fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> impl Future + Send + 'a; } - -impl Pipeline for P { - type Args<'a> =

::Args<'a>; - type Out =

::Out; - - fn create(context: &WgpuContext) -> Self { -

::create(context) - } - - fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out> { - Box::pin(

::run(self, executor, args)) - } -} diff --git a/node-graph/nodes/gstd/src/render_background.rs b/node-graph/nodes/gstd/src/render_background.rs index 4a7852a829..e93f084789 100644 --- a/node-graph/nodes/gstd/src/render_background.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -12,7 +12,7 @@ use rendering::{RenderParams, SvgRender, SvgRenderOutput}; use std::fmt::Write; use std::sync::Arc; use wgpu::util::DeviceExt; -use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor, WgpuPipeline}; +use wgpu_executor::{WgpuContext, WgpuExecutor, WgpuPipeline}; #[node_macro::node(category(""))] async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderOutput) -> RenderOutput { @@ -136,7 +136,7 @@ struct BackgroundCompositorArgs<'a> { pub zoom: f32, } -impl AsyncWgpuPipeline for BackgroundCompositor { +impl WgpuPipeline for BackgroundCompositor { type Args<'a> = BackgroundCompositorArgs<'a>; type Out = Arc; diff --git a/node-graph/nodes/gstd/src/render_pixel_preview.rs b/node-graph/nodes/gstd/src/render_pixel_preview.rs index 614ff40ed1..77a16b2fae 100644 --- a/node-graph/nodes/gstd/src/render_pixel_preview.rs +++ b/node-graph/nodes/gstd/src/render_pixel_preview.rs @@ -8,7 +8,7 @@ use graphene_application_io::ApplicationIo; use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use std::sync::Arc; use vector_types::vector::style::RenderMode; -use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor}; +use wgpu_executor::{WgpuContext, WgpuExecutor, WgpuPipeline}; #[node_macro::node(category(""))] pub async fn render_pixel_preview<'a: 'n>( @@ -89,7 +89,7 @@ struct ResamplerArgs<'a> { size: UVec2, } -impl AsyncWgpuPipeline for PixelPreviewPipeline { +impl WgpuPipeline for PixelPreviewPipeline { type Args<'a> = ResamplerArgs<'a>; type Out = Arc; From 1cd9e746f198bac02909cdaa241e85077ec90d16 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 23:12:20 +0000 Subject: [PATCH 07/10] Fix background offset --- node-graph/nodes/gstd/src/render_pixel_preview.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/nodes/gstd/src/render_pixel_preview.rs b/node-graph/nodes/gstd/src/render_pixel_preview.rs index 77a16b2fae..8d44c72f40 100644 --- a/node-graph/nodes/gstd/src/render_pixel_preview.rs +++ b/node-graph/nodes/gstd/src/render_pixel_preview.rs @@ -65,7 +65,7 @@ pub async fn render_pixel_preview<'a: 'n>( .run_pipeline::(&ResamplerArgs { source: source_texture.as_ref(), transform: &transform, - size: logical_resolution.as_uvec2(), + size: physical_resolution, }) .await; From 5dd5a35656d5e18bb68a940b2e63fa0a88b880c8 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 7 Jun 2026 23:27:16 +0000 Subject: [PATCH 08/10] Revert "Refactor pipeline trait" This reverts commit e941bec09134846e5d3edac314e0a7f3d14e4186. --- node-graph/libraries/wgpu-executor/src/lib.rs | 1 + .../libraries/wgpu-executor/src/pipeline.rs | 25 +++++++++++++++++++ .../nodes/gstd/src/render_background.rs | 4 +-- .../nodes/gstd/src/render_pixel_preview.rs | 4 +-- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index f5e5d1fa6d..ebc1104494 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -21,6 +21,7 @@ use wgpu::{Origin3d, TextureAspect}; pub use context::Context as WgpuContext; pub use context::ContextBuilder as WgpuContextBuilder; +pub use pipeline::AsyncPipeline as AsyncWgpuPipeline; pub use pipeline::Pipeline as WgpuPipeline; pub use rendering::RenderContext; pub use wgpu::Backends as WgpuBackends; diff --git a/node-graph/libraries/wgpu-executor/src/pipeline.rs b/node-graph/libraries/wgpu-executor/src/pipeline.rs index 959b8157be..d36399277a 100644 --- a/node-graph/libraries/wgpu-executor/src/pipeline.rs +++ b/node-graph/libraries/wgpu-executor/src/pipeline.rs @@ -1,12 +1,37 @@ use std::future::Future; +use std::pin::Pin; use crate::{WgpuContext, WgpuExecutor}; +pub type PipelineFuture<'a, T> = Pin + Send + 'a>>; + pub trait Pipeline: std::any::Any + Send + Sync + Sized { type Args<'a>; type Out: Send; fn create(context: &WgpuContext) -> Self; + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out>; +} + +pub trait AsyncPipeline: std::any::Any + Send + Sync + Sized { + type Args<'a>; + type Out: Send; + + fn create(context: &WgpuContext) -> Self; + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> impl Future + Send + 'a; } + +impl Pipeline for P { + type Args<'a> =

::Args<'a>; + type Out =

::Out; + + fn create(context: &WgpuContext) -> Self { +

::create(context) + } + + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out> { + Box::pin(

::run(self, executor, args)) + } +} diff --git a/node-graph/nodes/gstd/src/render_background.rs b/node-graph/nodes/gstd/src/render_background.rs index e93f084789..4a7852a829 100644 --- a/node-graph/nodes/gstd/src/render_background.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -12,7 +12,7 @@ use rendering::{RenderParams, SvgRender, SvgRenderOutput}; use std::fmt::Write; use std::sync::Arc; use wgpu::util::DeviceExt; -use wgpu_executor::{WgpuContext, WgpuExecutor, WgpuPipeline}; +use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor, WgpuPipeline}; #[node_macro::node(category(""))] async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderOutput) -> RenderOutput { @@ -136,7 +136,7 @@ struct BackgroundCompositorArgs<'a> { pub zoom: f32, } -impl WgpuPipeline for BackgroundCompositor { +impl AsyncWgpuPipeline for BackgroundCompositor { type Args<'a> = BackgroundCompositorArgs<'a>; type Out = Arc; diff --git a/node-graph/nodes/gstd/src/render_pixel_preview.rs b/node-graph/nodes/gstd/src/render_pixel_preview.rs index 8d44c72f40..12bfe46e25 100644 --- a/node-graph/nodes/gstd/src/render_pixel_preview.rs +++ b/node-graph/nodes/gstd/src/render_pixel_preview.rs @@ -8,7 +8,7 @@ use graphene_application_io::ApplicationIo; use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use std::sync::Arc; use vector_types::vector::style::RenderMode; -use wgpu_executor::{WgpuContext, WgpuExecutor, WgpuPipeline}; +use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor}; #[node_macro::node(category(""))] pub async fn render_pixel_preview<'a: 'n>( @@ -89,7 +89,7 @@ struct ResamplerArgs<'a> { size: UVec2, } -impl WgpuPipeline for PixelPreviewPipeline { +impl AsyncWgpuPipeline for PixelPreviewPipeline { type Args<'a> = ResamplerArgs<'a>; type Out = Arc; From 48bff5bfe59f0c01ea732c0fc02d6ad36a718f7a Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 8 Jun 2026 00:06:40 +0000 Subject: [PATCH 09/10] Review --- node-graph/libraries/wgpu-executor/src/lib.rs | 9 ++++----- node-graph/nodes/gstd/src/render_background.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index ebc1104494..08463dfc1c 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -91,17 +91,16 @@ impl WgpuExecutor { fn pipeline(&self) -> Arc

{ let key = TypeId::of::

(); - if let Some(arc) = self.pipelines.read().unwrap().get(&key) { - return arc.clone().downcast::

().expect("TypeId

guarantees this downcast"); + let cached = self.pipelines.read().unwrap().get(&key).cloned(); + if let Some(arc) = cached { + return arc.downcast::

().expect("TypeId

guarantees this downcast"); } - let built: Arc = Arc::new(P::create(&self.context)); - self.pipelines .write() .unwrap() .entry(key) - .or_insert(built) + .or_insert_with(|| Arc::new(P::create(&self.context))) .clone() .downcast::

() .expect("TypeId

guarantees this downcast") diff --git a/node-graph/nodes/gstd/src/render_background.rs b/node-graph/nodes/gstd/src/render_background.rs index 4a7852a829..fbb13d4dba 100644 --- a/node-graph/nodes/gstd/src/render_background.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -23,7 +23,7 @@ async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVar .downcast_ref::() .expect("Downcasting render params yielded invalid type"); - if !render_params.to_canvas() { + if !render_params.to_canvas() || render_params.viewport_zoom <= 0.0 { return data; } @@ -319,7 +319,7 @@ impl AsyncWgpuPipeline for BackgroundCompositor { } } - async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a ::Args<'_>) -> Self::Out { + async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { let context = &executor.context; let &BackgroundCompositorArgs { foreground, From a3efae02ba5de444b6bfe1f245d3eee551569081 Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 8 Jun 2026 00:09:15 +0000 Subject: [PATCH 10/10] Review --- node-graph/nodes/gstd/src/render_background.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/nodes/gstd/src/render_background.rs b/node-graph/nodes/gstd/src/render_background.rs index fbb13d4dba..96dd98711f 100644 --- a/node-graph/nodes/gstd/src/render_background.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -12,7 +12,7 @@ use rendering::{RenderParams, SvgRender, SvgRenderOutput}; use std::fmt::Write; use std::sync::Arc; use wgpu::util::DeviceExt; -use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor, WgpuPipeline}; +use wgpu_executor::{AsyncWgpuPipeline, WgpuContext, WgpuExecutor}; #[node_macro::node(category(""))] async fn render_background<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a PlatformEditorApi, data: RenderOutput) -> RenderOutput {