From f17644e0993b752e6484c963535cc74e860c7c10 Mon Sep 17 00:00:00 2001 From: afrdbaig7 Date: Wed, 14 Jan 2026 23:15:59 +0530 Subject: [PATCH 1/7] Refactor TargetTexture into proper abstraction with ensure_size() method --- desktop/src/render/state.rs | 19 ++++-- node-graph/libraries/wgpu-executor/src/lib.rs | 66 +++++++++++++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 46a919b8b5..6e718e5dcd 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -1,6 +1,7 @@ use crate::window::Window; use crate::wrapper::{Color, WgpuContext, WgpuExecutor}; +use wgpu_executor::TargetTexture; #[derive(derivative::Derivative)] #[derivative(Debug)] @@ -18,6 +19,7 @@ pub(crate) struct RenderState { viewport_offset: [f32; 2], viewport_texture: Option, overlays_texture: Option, + overlays_target_texture: Option, ui_texture: Option, bind_group: Option, #[derivative(Debug = "ignore")] @@ -182,6 +184,7 @@ impl RenderState { viewport_offset: [0.0, 0.0], viewport_texture: None, overlays_texture: None, + overlays_target_texture: None, ui_texture: None, bind_group: None, overlays_scene: None, @@ -236,12 +239,18 @@ impl RenderState { return; }; let size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height()); - let texture = futures::executor::block_on(self.executor.render_vello_scene_to_texture(&scene, size, &Default::default(), Color::TRANSPARENT)); - let Ok(texture) = texture else { - tracing::error!("Error rendering overlays"); + let result = futures::executor::block_on( + self.executor + .render_vello_scene_to_target_texture(&scene, size, &Default::default(), Color::TRANSPARENT, &mut self.overlays_target_texture), + ); + if let Err(e) = result { + tracing::error!("Error rendering overlays: {:?}", e); return; - }; - self.bind_overlays_texture(texture); + } + if let Some(target_texture) = &self.overlays_target_texture { + self.overlays_texture = Some(target_texture.texture().clone()); + self.update_bindgroup(); + } } pub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> { diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 868c73f60e..ffa4cb154d 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -54,6 +54,47 @@ pub struct TargetTexture { size: UVec2, } +impl TargetTexture { + /// Ensures the texture has the specified size, creating a new one if needed. + /// This allows reusing the same texture across frames when the size hasn't changed. + pub fn ensure_size(&mut self, device: &wgpu::Device, size: UVec2) { + let size = size.max(UVec2::ONE); + if self.size == size { + return; + } + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC, + format: VELLO_SURFACE_FORMAT, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + self.texture = texture; + self.view = view; + self.size = size; + } + + /// Returns a reference to the texture view for rendering. + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + /// Returns a reference to the underlying texture. + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } +} + #[cfg(target_family = "wasm")] pub type Window = web_sys::HtmlCanvasElement; #[cfg(not(target_family = "wasm"))] @@ -71,19 +112,14 @@ impl WgpuExecutor { self.render_vello_scene_to_target_texture(scene, size, context, background, &mut output).await?; Ok(output.unwrap().texture) } - - async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option) -> Result<()> { - let size = size.max(UVec2::ONE); - let target_texture = if let Some(target_texture) = output - && target_texture.size == size - { - target_texture - } else { + pub async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option) -> Result<()> { + // Initialize with a minimal texture if this is the first call + if output.is_none() { let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { - width: size.x, - height: size.y, + width: 1, + height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, @@ -94,9 +130,11 @@ impl WgpuExecutor { view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - *output = Some(TargetTexture { texture, view, size }); - output.as_mut().unwrap() - }; + *output = Some(TargetTexture { texture, view, size: UVec2::ONE }); + } + + let target_texture = output.as_mut().unwrap(); + target_texture.ensure_size(&self.context.device, size); let [r, g, b, a] = background.to_rgba8_srgb(); let render_params = RenderParams { @@ -117,7 +155,7 @@ impl WgpuExecutor { }; renderer.override_image(&image_brush.image, Some(texture_view)); } - renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?; + renderer.render_to_texture(&self.context.device, &self.context.queue, scene, target_texture.view(), &render_params)?; for (image_brush, _) in context.resource_overrides.iter() { renderer.override_image(&image_brush.image, None); } From 38d3d9edd90be9c55bfeeb2b0fca12647ecc04a4 Mon Sep 17 00:00:00 2001 From: afrdbaig7 Date: Fri, 16 Jan 2026 11:26:24 +0530 Subject: [PATCH 2/7] Remove redundant overlays_texture field, use view() directly --- desktop/src/render/state.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 6e718e5dcd..04f4bafc28 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -18,7 +18,6 @@ pub(crate) struct RenderState { viewport_scale: [f32; 2], viewport_offset: [f32; 2], viewport_texture: Option, - overlays_texture: Option, overlays_target_texture: Option, ui_texture: Option, bind_group: Option, @@ -183,7 +182,6 @@ impl RenderState { viewport_scale: [1.0, 1.0], viewport_offset: [0.0, 0.0], viewport_texture: None, - overlays_texture: None, overlays_target_texture: None, ui_texture: None, bind_group: None, @@ -211,11 +209,6 @@ impl RenderState { self.update_bindgroup(); } - pub(crate) fn bind_overlays_texture(&mut self, overlays_texture: wgpu::Texture) { - self.overlays_texture = Some(overlays_texture); - self.update_bindgroup(); - } - pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) { self.ui_texture = Some(bind_ui_texture); self.update_bindgroup(); @@ -247,10 +240,7 @@ impl RenderState { tracing::error!("Error rendering overlays: {:?}", e); return; } - if let Some(target_texture) = &self.overlays_target_texture { - self.overlays_texture = Some(target_texture.texture().clone()); - self.update_bindgroup(); - } + self.update_bindgroup(); } pub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> { @@ -321,7 +311,11 @@ impl RenderState { fn update_bindgroup(&mut self) { let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); - let overlays_texture_view = self.overlays_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); + let overlays_texture_view = self + .overlays_target_texture + .as_ref() + .map(|target| target.view()) + .unwrap_or_else(|| &self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())); let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor { From 9d30db8634cf29b260ac3009f3d56589a78466b3 Mon Sep 17 00:00:00 2001 From: afrdbaig7 Date: Sun, 25 Jan 2026 21:36:52 +0530 Subject: [PATCH 3/7] Use if-let syntax in render_vello_scene_to_target_texture to avoid explicit unwrap --- node-graph/libraries/wgpu-executor/src/lib.rs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index ffa4cb154d..3f267e494a 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -133,31 +133,32 @@ impl WgpuExecutor { *output = Some(TargetTexture { texture, view, size: UVec2::ONE }); } - let target_texture = output.as_mut().unwrap(); - target_texture.ensure_size(&self.context.device, size); - - let [r, g, b, a] = background.to_rgba8_srgb(); - let render_params = RenderParams { - base_color: vello::peniko::Color::from_rgba8(r, g, b, a), - width: size.x, - height: size.y, - antialiasing_method: AaConfig::Msaa16, - }; - - { - let mut renderer = self.vello_renderer.lock().await; - for (image_brush, texture) in context.resource_overrides.iter() { - let texture_view = wgpu::TexelCopyTextureInfoBase { - texture: texture.clone(), - mip_level: 0, - origin: Origin3d::ZERO, - aspect: TextureAspect::All, - }; - renderer.override_image(&image_brush.image, Some(texture_view)); - } - renderer.render_to_texture(&self.context.device, &self.context.queue, scene, target_texture.view(), &render_params)?; - for (image_brush, _) in context.resource_overrides.iter() { - renderer.override_image(&image_brush.image, None); + if let Some(target_texture) = output.as_mut() { + target_texture.ensure_size(&self.context.device, size); + + let [r, g, b, a] = background.to_rgba8_srgb(); + let render_params = RenderParams { + base_color: vello::peniko::Color::from_rgba8(r, g, b, a), + width: size.x, + height: size.y, + antialiasing_method: AaConfig::Msaa16, + }; + + { + let mut renderer = self.vello_renderer.lock().await; + for (image_brush, texture) in context.resource_overrides.iter() { + let texture_view = wgpu::TexelCopyTextureInfoBase { + texture: texture.clone(), + mip_level: 0, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }; + renderer.override_image(&image_brush.image, Some(texture_view)); + } + renderer.render_to_texture(&self.context.device, &self.context.queue, scene, target_texture.view(), &render_params)?; + for (image_brush, _) in context.resource_overrides.iter() { + renderer.override_image(&image_brush.image, None); + } } } Ok(()) From ba691961786eed25a574c629bdaefa30d08a975f Mon Sep 17 00:00:00 2001 From: afrdbaig7 Date: Sun, 25 Jan 2026 21:43:09 +0530 Subject: [PATCH 4/7] Implement TargetTexture::new() constructor to avoid dummy textures --- node-graph/libraries/wgpu-executor/src/lib.rs | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 3f267e494a..5de7873bf3 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -55,14 +55,9 @@ pub struct TargetTexture { } impl TargetTexture { - /// Ensures the texture has the specified size, creating a new one if needed. - /// This allows reusing the same texture across frames when the size hasn't changed. - pub fn ensure_size(&mut self, device: &wgpu::Device, size: UVec2) { + /// Creates a new TargetTexture with the specified size. + pub fn new(device: &wgpu::Device, size: UVec2) -> Self { let size = size.max(UVec2::ONE); - if self.size == size { - return; - } - let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { @@ -79,9 +74,18 @@ impl TargetTexture { }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - self.texture = texture; - self.view = view; - self.size = size; + Self { texture, view, size } + } + + /// Ensures the texture has the specified size, creating a new one if needed. + /// This allows reusing the same texture across frames when the size hasn't changed. + pub fn ensure_size(&mut self, device: &wgpu::Device, size: UVec2) { + let size = size.max(UVec2::ONE); + if self.size == size { + return; + } + + *self = Self::new(device, size); } /// Returns a reference to the texture view for rendering. @@ -113,24 +117,9 @@ impl WgpuExecutor { Ok(output.unwrap().texture) } pub async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option) -> Result<()> { - // Initialize with a minimal texture if this is the first call + // Initialize (lazily) if this is the first call if output.is_none() { - let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC, - format: VELLO_SURFACE_FORMAT, - view_formats: &[], - }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - *output = Some(TargetTexture { texture, view, size: UVec2::ONE }); + *output = Some(TargetTexture::new(&self.context.device, size)); } if let Some(target_texture) = output.as_mut() { From dbefeceb08ed10786e16033112978c0dac5d3d4d Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 25 Jan 2026 18:37:08 +0000 Subject: [PATCH 5/7] fix compile error --- desktop/src/render/state.rs | 22 +++++++++---------- desktop/wrapper/src/lib.rs | 1 + node-graph/libraries/wgpu-executor/src/lib.rs | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 04f4bafc28..3e55c0ef90 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -1,7 +1,5 @@ use crate::window::Window; - -use crate::wrapper::{Color, WgpuContext, WgpuExecutor}; -use wgpu_executor::TargetTexture; +use crate::wrapper::{Color, TargetTexture, WgpuContext, WgpuExecutor}; #[derive(derivative::Derivative)] #[derivative(Debug)] @@ -18,7 +16,7 @@ pub(crate) struct RenderState { viewport_scale: [f32; 2], viewport_offset: [f32; 2], viewport_texture: Option, - overlays_target_texture: Option, + overlays_texture: Option, ui_texture: Option, bind_group: Option, #[derivative(Debug = "ignore")] @@ -182,7 +180,7 @@ impl RenderState { viewport_scale: [1.0, 1.0], viewport_offset: [0.0, 0.0], viewport_texture: None, - overlays_target_texture: None, + overlays_texture: None, ui_texture: None, bind_group: None, overlays_scene: None, @@ -234,7 +232,7 @@ impl RenderState { let size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height()); let result = futures::executor::block_on( self.executor - .render_vello_scene_to_target_texture(&scene, size, &Default::default(), Color::TRANSPARENT, &mut self.overlays_target_texture), + .render_vello_scene_to_target_texture(&scene, size, &Default::default(), Color::TRANSPARENT, &mut self.overlays_texture), ); if let Err(e) = result { tracing::error!("Error rendering overlays: {:?}", e); @@ -311,11 +309,11 @@ impl RenderState { fn update_bindgroup(&mut self) { let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); - let overlays_texture_view = self - .overlays_target_texture - .as_ref() - .map(|target| target.view()) - .unwrap_or_else(|| &self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())); + let overlays_texture_view = if let Some(overlays_texture_view) = self.overlays_texture.as_ref().map(|target| target.view()) { + overlays_texture_view + } else { + &self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default()) + }; let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -327,7 +325,7 @@ impl RenderState { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&overlays_texture_view), + resource: wgpu::BindingResource::TextureView(overlays_texture_view), }, wgpu::BindGroupEntry { binding: 2, diff --git a/desktop/wrapper/src/lib.rs b/desktop/wrapper/src/lib.rs index bdb9978e61..4a9af68c0d 100644 --- a/desktop/wrapper/src/lib.rs +++ b/desktop/wrapper/src/lib.rs @@ -5,6 +5,7 @@ use graphite_editor::messages::prelude::{FrontendMessage, Message}; // TODO: Remove usage of this reexport in desktop create and remove this line pub use graphene_std::Color; +pub use wgpu_executor::TargetTexture; pub use wgpu_executor::WgpuContext; pub use wgpu_executor::WgpuContextBuilder; pub use wgpu_executor::WgpuExecutor; diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 5de7873bf3..3cbf47b92a 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -48,6 +48,7 @@ pub struct Surface { pub blitter: TextureBlitter, } +#[derive(Clone, Debug)] pub struct TargetTexture { texture: wgpu::Texture, view: wgpu::TextureView, From b176ace3ee31e4429a4e96d206990ab37c51830f Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 25 Jan 2026 18:42:28 +0000 Subject: [PATCH 6/7] cleanup --- desktop/src/render/state.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 3e55c0ef90..2506230eaa 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -309,11 +309,11 @@ impl RenderState { fn update_bindgroup(&mut self) { let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); - let overlays_texture_view = if let Some(overlays_texture_view) = self.overlays_texture.as_ref().map(|target| target.view()) { - overlays_texture_view - } else { - &self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default()) - }; + let overlays_texture_view = self + .overlays_texture + .as_ref() + .map(|target| target.view().clone()) + .unwrap_or_else(|| self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())); let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -325,7 +325,7 @@ impl RenderState { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(overlays_texture_view), + resource: wgpu::BindingResource::TextureView(&overlays_texture_view), }, wgpu::BindGroupEntry { binding: 2, From 80b1fa0885cc11da21b9c407399af6663d686af1 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 26 Jan 2026 13:04:11 +0100 Subject: [PATCH 7/7] Avoid cloning texture view --- desktop/src/render/state.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 2506230eaa..0933be079f 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::window::Window; use crate::wrapper::{Color, TargetTexture, WgpuContext, WgpuExecutor}; @@ -312,8 +314,8 @@ impl RenderState { let overlays_texture_view = self .overlays_texture .as_ref() - .map(|target| target.view().clone()) - .unwrap_or_else(|| self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())); + .map(|target| Cow::Borrowed(target.view())) + .unwrap_or_else(|| Cow::Owned(self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default()))); let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -325,7 +327,7 @@ impl RenderState { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&overlays_texture_view), + resource: wgpu::BindingResource::TextureView(&overlays_texture_view.as_ref()), }, wgpu::BindGroupEntry { binding: 2,