Skip to content

Commit c071243

Browse files
afrdbaig7timon-schellingTrueDoctor
authored
Fix GPU out-of-memory crash by reusing overlay textures (#3614)
* Refactor TargetTexture into proper abstraction with ensure_size() method * Remove redundant overlays_texture field, use view() directly * Use if-let syntax in render_vello_scene_to_target_texture to avoid explicit unwrap * Implement TargetTexture::new() constructor to avoid dummy textures * fix compile error * cleanup * Avoid cloning texture view --------- Co-authored-by: Timon <me@timon.zip> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
1 parent a88342b commit c071243

3 files changed

Lines changed: 96 additions & 63 deletions

File tree

desktop/src/render/state.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::window::Window;
1+
use std::borrow::Cow;
22

3-
use crate::wrapper::{Color, WgpuContext, WgpuExecutor};
3+
use crate::window::Window;
4+
use crate::wrapper::{Color, TargetTexture, WgpuContext, WgpuExecutor};
45

56
#[derive(derivative::Derivative)]
67
#[derivative(Debug)]
@@ -17,7 +18,7 @@ pub(crate) struct RenderState {
1718
viewport_scale: [f32; 2],
1819
viewport_offset: [f32; 2],
1920
viewport_texture: Option<wgpu::Texture>,
20-
overlays_texture: Option<wgpu::Texture>,
21+
overlays_texture: Option<TargetTexture>,
2122
ui_texture: Option<wgpu::Texture>,
2223
bind_group: Option<wgpu::BindGroup>,
2324
#[derivative(Debug = "ignore")]
@@ -208,11 +209,6 @@ impl RenderState {
208209
self.update_bindgroup();
209210
}
210211

211-
pub(crate) fn bind_overlays_texture(&mut self, overlays_texture: wgpu::Texture) {
212-
self.overlays_texture = Some(overlays_texture);
213-
self.update_bindgroup();
214-
}
215-
216212
pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) {
217213
self.ui_texture = Some(bind_ui_texture);
218214
self.update_bindgroup();
@@ -236,12 +232,15 @@ impl RenderState {
236232
return;
237233
};
238234
let size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height());
239-
let texture = futures::executor::block_on(self.executor.render_vello_scene_to_texture(&scene, size, &Default::default(), Color::TRANSPARENT));
240-
let Ok(texture) = texture else {
241-
tracing::error!("Error rendering overlays");
235+
let result = futures::executor::block_on(
236+
self.executor
237+
.render_vello_scene_to_target_texture(&scene, size, &Default::default(), Color::TRANSPARENT, &mut self.overlays_texture),
238+
);
239+
if let Err(e) = result {
240+
tracing::error!("Error rendering overlays: {:?}", e);
242241
return;
243-
};
244-
self.bind_overlays_texture(texture);
242+
}
243+
self.update_bindgroup();
245244
}
246245

247246
pub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> {
@@ -312,7 +311,11 @@ impl RenderState {
312311

313312
fn update_bindgroup(&mut self) {
314313
let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
315-
let overlays_texture_view = self.overlays_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
314+
let overlays_texture_view = self
315+
.overlays_texture
316+
.as_ref()
317+
.map(|target| Cow::Borrowed(target.view()))
318+
.unwrap_or_else(|| Cow::Owned(self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())));
316319
let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
317320

318321
let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
@@ -324,7 +327,7 @@ impl RenderState {
324327
},
325328
wgpu::BindGroupEntry {
326329
binding: 1,
327-
resource: wgpu::BindingResource::TextureView(&overlays_texture_view),
330+
resource: wgpu::BindingResource::TextureView(&overlays_texture_view.as_ref()),
328331
},
329332
wgpu::BindGroupEntry {
330333
binding: 2,

desktop/wrapper/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub use graphite_editor::consts::FILE_EXTENSION;
66
// TODO: Remove usage of this reexport in desktop create and remove this line
77
pub use graphene_std::Color;
88

9+
pub use wgpu_executor::TargetTexture;
910
pub use wgpu_executor::WgpuContext;
1011
pub use wgpu_executor::WgpuContextBuilder;
1112
pub use wgpu_executor::WgpuExecutor;

node-graph/libraries/wgpu-executor/src/lib.rs

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,58 @@ pub struct Surface {
4848
pub blitter: TextureBlitter,
4949
}
5050

51+
#[derive(Clone, Debug)]
5152
pub struct TargetTexture {
5253
texture: wgpu::Texture,
5354
view: wgpu::TextureView,
5455
size: UVec2,
5556
}
5657

58+
impl TargetTexture {
59+
/// Creates a new TargetTexture with the specified size.
60+
pub fn new(device: &wgpu::Device, size: UVec2) -> Self {
61+
let size = size.max(UVec2::ONE);
62+
let texture = device.create_texture(&wgpu::TextureDescriptor {
63+
label: None,
64+
size: wgpu::Extent3d {
65+
width: size.x,
66+
height: size.y,
67+
depth_or_array_layers: 1,
68+
},
69+
mip_level_count: 1,
70+
sample_count: 1,
71+
dimension: wgpu::TextureDimension::D2,
72+
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
73+
format: VELLO_SURFACE_FORMAT,
74+
view_formats: &[],
75+
});
76+
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
77+
78+
Self { texture, view, size }
79+
}
80+
81+
/// Ensures the texture has the specified size, creating a new one if needed.
82+
/// This allows reusing the same texture across frames when the size hasn't changed.
83+
pub fn ensure_size(&mut self, device: &wgpu::Device, size: UVec2) {
84+
let size = size.max(UVec2::ONE);
85+
if self.size == size {
86+
return;
87+
}
88+
89+
*self = Self::new(device, size);
90+
}
91+
92+
/// Returns a reference to the texture view for rendering.
93+
pub fn view(&self) -> &wgpu::TextureView {
94+
&self.view
95+
}
96+
97+
/// Returns a reference to the underlying texture.
98+
pub fn texture(&self) -> &wgpu::Texture {
99+
&self.texture
100+
}
101+
}
102+
57103
#[cfg(target_family = "wasm")]
58104
pub type Window = web_sys::HtmlCanvasElement;
59105
#[cfg(not(target_family = "wasm"))]
@@ -71,55 +117,38 @@ impl WgpuExecutor {
71117
self.render_vello_scene_to_target_texture(scene, size, context, background, &mut output).await?;
72118
Ok(output.unwrap().texture)
73119
}
120+
pub async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option<TargetTexture>) -> Result<()> {
121+
// Initialize (lazily) if this is the first call
122+
if output.is_none() {
123+
*output = Some(TargetTexture::new(&self.context.device, size));
124+
}
74125

75-
async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option<TargetTexture>) -> Result<()> {
76-
let size = size.max(UVec2::ONE);
77-
let target_texture = if let Some(target_texture) = output
78-
&& target_texture.size == size
79-
{
80-
target_texture
81-
} else {
82-
let texture = self.context.device.create_texture(&wgpu::TextureDescriptor {
83-
label: None,
84-
size: wgpu::Extent3d {
85-
width: size.x,
86-
height: size.y,
87-
depth_or_array_layers: 1,
88-
},
89-
mip_level_count: 1,
90-
sample_count: 1,
91-
dimension: wgpu::TextureDimension::D2,
92-
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
93-
format: VELLO_SURFACE_FORMAT,
94-
view_formats: &[],
95-
});
96-
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
97-
*output = Some(TargetTexture { texture, view, size });
98-
output.as_mut().unwrap()
99-
};
100-
101-
let [r, g, b, a] = background.to_rgba8_srgb();
102-
let render_params = RenderParams {
103-
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
104-
width: size.x,
105-
height: size.y,
106-
antialiasing_method: AaConfig::Msaa16,
107-
};
108-
109-
{
110-
let mut renderer = self.vello_renderer.lock().await;
111-
for (image_brush, texture) in context.resource_overrides.iter() {
112-
let texture_view = wgpu::TexelCopyTextureInfoBase {
113-
texture: texture.clone(),
114-
mip_level: 0,
115-
origin: Origin3d::ZERO,
116-
aspect: TextureAspect::All,
117-
};
118-
renderer.override_image(&image_brush.image, Some(texture_view));
119-
}
120-
renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?;
121-
for (image_brush, _) in context.resource_overrides.iter() {
122-
renderer.override_image(&image_brush.image, None);
126+
if let Some(target_texture) = output.as_mut() {
127+
target_texture.ensure_size(&self.context.device, size);
128+
129+
let [r, g, b, a] = background.to_rgba8_srgb();
130+
let render_params = RenderParams {
131+
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
132+
width: size.x,
133+
height: size.y,
134+
antialiasing_method: AaConfig::Msaa16,
135+
};
136+
137+
{
138+
let mut renderer = self.vello_renderer.lock().await;
139+
for (image_brush, texture) in context.resource_overrides.iter() {
140+
let texture_view = wgpu::TexelCopyTextureInfoBase {
141+
texture: texture.clone(),
142+
mip_level: 0,
143+
origin: Origin3d::ZERO,
144+
aspect: TextureAspect::All,
145+
};
146+
renderer.override_image(&image_brush.image, Some(texture_view));
147+
}
148+
renderer.render_to_texture(&self.context.device, &self.context.queue, scene, target_texture.view(), &render_params)?;
149+
for (image_brush, _) in context.resource_overrides.iter() {
150+
renderer.override_image(&image_brush.image, None);
151+
}
123152
}
124153
}
125154
Ok(())

0 commit comments

Comments
 (0)