Fix lifetime of cached textures (#4333)

* Fix in-use textures being destroyed

* Remove ImageTexture refs

* Review
This commit is contained in:
Timon
2026-07-14 13:23:31 +02:00
committed by Keavon Chambers
parent ba0a97aefe
commit 7e29fe9a31
18 changed files with 100 additions and 79 deletions

View File

@@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
default = ["serde"]
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "text-nodes/serde", "graphene-resource/serde"]
wasm = ["dep:web-sys"]
wgpu = ["dep:wgpu"]
wgpu = ["dep:raster-types", "raster-types/wgpu"]
[dependencies]
# Local dependencies
@@ -20,6 +20,9 @@ vector-types = { workspace = true }
text-nodes = { workspace = true }
graphene-resource = { workspace = true }
# Optional local dependencies
raster-types = { workspace = true, optional = true }
# Workspace dependencies
blake3 = { workspace = true }
glam = { workspace = true }
@@ -27,7 +30,4 @@ log = { workspace = true }
# Optional workspace dependencies
serde = { workspace = true, optional = true }
# Optional workspace dependencies
web-sys = { workspace = true, optional = true }
wgpu = { workspace = true, optional = true }

View File

@@ -11,35 +11,10 @@ use vector_types::vector::style::RenderMode;
pub use graphene_resource as resource;
#[cfg(feature = "wgpu")]
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]
pub struct ImageTexture(Arc<wgpu::Texture>);
#[cfg(feature = "wgpu")]
impl AsRef<wgpu::Texture> for ImageTexture {
fn as_ref(&self) -> &wgpu::Texture {
&self.0
}
}
#[cfg(feature = "wgpu")]
impl From<wgpu::Texture> for ImageTexture {
fn from(texture: wgpu::Texture) -> Self {
Self(Arc::new(texture))
}
}
#[cfg(feature = "wgpu")]
impl From<Arc<wgpu::Texture>> for ImageTexture {
fn from(texture: Arc<wgpu::Texture>) -> Self {
Self(texture)
}
}
#[cfg(feature = "wgpu")]
impl From<ImageTexture> for Arc<wgpu::Texture> {
fn from(image_texture: ImageTexture) -> Self {
image_texture.0
}
}
pub use raster_types::Texture;
#[cfg(not(feature = "wgpu"))]
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]
pub struct ImageTexture;
pub struct Texture; // TODO: Consider removing this
pub trait ApplicationIo {
type Executor;

View File

@@ -1,6 +1,6 @@
use dyn_any::DynAny;
#[cfg(feature = "wgpu")]
use graphene_application_io::ImageTexture;
use graphene_application_io::Texture;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use web_sys::js_sys::{Object, Reflect};
@@ -23,7 +23,7 @@ pub trait Canvas {
#[cfg(feature = "wgpu")]
pub trait CanvasSurface: Canvas {
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor);
fn present(&mut self, texture: &Texture, executor: &WgpuExecutor);
}
#[derive(Clone, DynAny)]
@@ -85,10 +85,10 @@ impl Canvas for CanvasSurfaceHandle {
}
#[cfg(feature = "wgpu")]
impl CanvasSurface for CanvasSurfaceHandle {
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) {
fn present(&mut self, texture: &Texture, executor: &WgpuExecutor) {
let context = executor.context();
let source_texture: &wgpu::Texture = image_texture.as_ref();
let source_texture: &wgpu::Texture = texture.as_ref();
let surface = self.surface(executor);

View File

@@ -139,15 +139,60 @@ mod cpu {
}
pub use gpu::GPU;
#[cfg(feature = "wgpu")]
pub use gpu::Texture;
#[cfg(feature = "wgpu")]
mod gpu {
use super::*;
use crate::raster_types::__private::Sealed;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq, Hash, DynAny)]
pub struct Texture(Arc<wgpu::Texture>);
impl Deref for Texture {
type Target = wgpu::Texture;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<wgpu::Texture> for Texture {
fn as_ref(&self) -> &wgpu::Texture {
&self.0
}
}
impl From<Arc<wgpu::Texture>> for Texture {
fn from(texture: Arc<wgpu::Texture>) -> Self {
Self(texture)
}
}
impl From<wgpu::Texture> for Texture {
fn from(texture: wgpu::Texture) -> Self {
Self(Arc::new(texture))
}
}
impl From<Texture> for Arc<wgpu::Texture> {
fn from(texture: Texture) -> Self {
texture.0
}
}
impl core_types::CacheHash for Texture {
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
use ::core::hash::Hash;
self.hash(state);
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct GPU {
pub texture: wgpu::Texture,
pub texture: Texture,
}
impl core_types::CacheHash for GPU {
@@ -166,8 +211,8 @@ mod gpu {
}
impl Raster<GPU> {
pub fn new_gpu(texture: wgpu::Texture) -> Self {
Self::new(GPU { texture })
pub fn new_gpu(texture: impl Into<Texture>) -> Self {
Self::new(GPU { texture: texture.into() })
}
pub fn data(&self) -> &wgpu::Texture {

View File

@@ -22,7 +22,7 @@ use glam::{DAffine2, DMat2, DVec2};
use graphene_hash::CacheHashWrapper;
use graphene_resource::Resource;
use graphic_types::graphic::{graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute};
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
use graphic_types::vector_types::subpath::Subpath;
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
@@ -198,7 +198,7 @@ impl Default for SvgRender {
#[derive(Clone, Debug, Default)]
pub struct RenderContext {
pub resource_overrides: Vec<(peniko::ImageBrush, wgpu::Texture)>,
pub resource_overrides: Vec<(peniko::ImageBrush, Texture)>,
}
#[derive(Default, Clone, Copy, Hash, graphene_hash::CacheHash)]
@@ -1945,7 +1945,7 @@ impl Render for List<Raster<GPU>> {
.with_extend(peniko::Extend::Repeat);
let image_transform = transform * transform_attribute * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
context.resource_overrides.push((image, raster.data().clone()));
context.resource_overrides.push((image, raster.texture.clone()));
if layer {
scene.pop_layer()

View File

@@ -12,6 +12,7 @@ use core_types::color::SRGBA8;
use futures::lock::Mutex;
use glam::UVec2;
use graphene_application_io::{ApplicationIo, EditorApi};
use raster_types::Texture;
use std::sync::Arc;
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
use wgpu::{Origin3d, TextureAspect};
@@ -67,7 +68,7 @@ impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
}
impl WgpuExecutor {
pub async fn render_vello_scene(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>) -> Result<Arc<wgpu::Texture>> {
pub async fn render_vello_scene(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>) -> Result<Texture> {
let texture = self.request_texture(size).await;
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
@@ -84,7 +85,7 @@ impl WgpuExecutor {
let mut renderer = self.inner.vello_renderer.lock().await;
for (image_brush, texture) in context.resource_overrides.iter() {
let texture_view = wgpu::TexelCopyTextureInfoBase {
texture: texture.clone(),
texture: (**texture).clone(),
mip_level: 0,
origin: Origin3d::ZERO,
aspect: TextureAspect::All,
@@ -108,7 +109,7 @@ impl WgpuExecutor {
pipeline.init::<P>(self);
}
pub async fn request_texture(&self, size: UVec2) -> Arc<wgpu::Texture> {
pub async fn request_texture(&self, size: UVec2) -> Texture {
self.inner.texture_cache.lock().await.request_texture(&self.context().device, size)
}
}

View File

@@ -234,7 +234,7 @@ impl PerPixelAdjustGraphicsPipeline {
rp.draw(0..3, 0..1);
let attributes = textures.clone_item_attributes(index);
Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
Item::from_parts(Raster::new_gpu(tex_out), attributes)
})
.collect::<List<_>>();
context.queue.submit([cmd.finish()]);

View File

@@ -1,4 +1,5 @@
use glam::UVec2;
use raster_types::Texture;
use std::collections::VecDeque;
use std::sync::Arc;
@@ -16,7 +17,7 @@ impl TextureCache {
}
}
pub fn request_texture(&mut self, device: &wgpu::Device, size: UVec2) -> Arc<wgpu::Texture> {
pub fn request_texture(&mut self, device: &wgpu::Device, size: UVec2) -> Texture {
let size = size.max(UVec2::ONE);
if let Some(pos) = self
@@ -27,7 +28,7 @@ impl TextureCache {
let entry = self.textures.remove(pos).unwrap();
let texture = entry.clone();
self.textures.push_back(entry);
return texture;
return texture.into();
}
let incoming_bytes = size.x as u64 * size.y as u64 * 4;
@@ -50,7 +51,7 @@ impl TextureCache {
self.textures.push_back(texture.clone());
texture
texture.into()
}
fn total_free_bytes(&self) -> u64 {

View File

@@ -50,6 +50,7 @@ struct RasterGpuToRasterCpuConverter {
height: u32,
unpadded_bytes_per_row: u32,
padded_bytes_per_row: u32,
_source: raster_types::Texture,
}
impl RasterGpuToRasterCpuConverter {
fn new(device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, data_gpu: Raster<GPU>) -> Self {
@@ -97,6 +98,8 @@ impl RasterGpuToRasterCpuConverter {
height,
unpadded_bytes_per_row,
padded_bytes_per_row,
// Keep source texture alive
_source: data_gpu.texture.clone(),
}
}