Improve texture caching by allowing weak refs and more formats (#4447)

* Destroy GPU textures when the last reference drops

* Request pooled textures synchronously

* Pool cached textures by format

* Add the owned GPU buffer type

* Route the remaining GPU allocations through the executor

* Add weak texture parking to the texture cache

* Raise the texture cache budget to 1GB for native and 512MB for wasm
This commit is contained in:
Timon
2026-08-27 15:19:00 +00:00
committed by GitHub
parent 8c48d5acce
commit 96cc520c4b
13 changed files with 213 additions and 169 deletions

View File

@@ -1,40 +1,33 @@
use crate::WgpuExecutor;
use crate::{Buffer, WgpuExecutor};
use core_types::Color;
use core_types::color::SRGBA8;
use core_types::list::{Item, List};
use core_types::ops::Convert;
use core_types::transform::Footprint;
use raster_types::Image;
use raster_types::{CPU, GPU, Raster};
use wgpu::util::{DeviceExt, TextureDataOrder};
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
use raster_types::{CPU, GPU, Raster, Texture};
use wgpu::{Extent3d, TextureFormat};
/// Uploads CPU image data to a GPU texture
///
/// Creates a new WGPU texture with RGBA8UnormSrgb format and uploads the provided
/// image data. The texture is configured for binding, copying, and source operations.
fn upload_to_texture(device: &wgpu::Device, queue: &wgpu::Queue, image: &Raster<CPU>) -> wgpu::Texture {
fn upload_to_texture(executor: &WgpuExecutor, queue: &wgpu::Queue, image: &Raster<CPU>) -> Texture {
let rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
device.create_texture_with_data(
queue,
&TextureDescriptor {
label: Some("upload_to_texture staging texture"),
size: Extent3d {
width: image.width,
height: image.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
view_formats: &[],
},
TextureDataOrder::LayerMajor,
let texture = executor.request_texture_with_format(glam::UVec2::new(image.width, image.height), TextureFormat::Rgba8UnormSrgb);
queue.write_texture(
texture.as_image_copy(),
bytemuck::cast_slice(rgba8_data.as_slice()),
)
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * image.width),
rows_per_image: Some(image.height),
},
Extent3d {
width: image.width,
height: image.height,
depth_or_array_layers: 1,
},
);
texture
}
/// Converts a Raster<GPU> texture to Raster<CPU> by downloading the underlying texture data.
@@ -44,7 +37,7 @@ fn upload_to_texture(device: &wgpu::Device, queue: &wgpu::Queue, image: &Raster<
/// - 4 bytes-per-pixel RGBA8
/// - Texture has COPY_SRC usage
struct RasterGpuToRasterCpuConverter {
buffer: wgpu::Buffer,
buffer: Buffer,
width: u32,
height: u32,
unpadded_bytes_per_row: u32,
@@ -52,7 +45,7 @@ struct RasterGpuToRasterCpuConverter {
_source: raster_types::Texture,
}
impl RasterGpuToRasterCpuConverter {
fn new(device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, data_gpu: Raster<GPU>) -> Self {
fn new(executor: &WgpuExecutor, encoder: &mut wgpu::CommandEncoder, data_gpu: Raster<GPU>) -> Self {
let texture = data_gpu.data();
let width = texture.width();
let height = texture.height();
@@ -62,7 +55,7 @@ impl RasterGpuToRasterCpuConverter {
let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
let buffer_size = padded_bytes_per_row as u64 * height as u64;
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
let buffer = executor.create_buffer(&wgpu::BufferDescriptor {
label: Some("texture_download_buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
@@ -151,13 +144,12 @@ impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
/// Converts a `List<Raster<CPU>>` to `List<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<GPU>> {
let device = &executor.context().device;
let queue = executor.context().queue.lock();
let list = self
.into_iter()
.map(|row| {
let (image, attributes) = row.into_parts();
let texture = upload_to_texture(device, &queue, &image);
let texture = upload_to_texture(executor, &queue, &image);
Item::from_parts(Raster::new_gpu(texture), attributes)
})
@@ -171,9 +163,8 @@ impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
/// Converts single CPU raster to GPU by uploading to texture
impl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster<GPU> {
let device = &executor.context().device;
let queue = executor.context().queue.lock();
let texture = upload_to_texture(device, &queue, &self);
let texture = upload_to_texture(executor, &queue, &self);
queue.submit([]);
Raster::new_gpu(texture)
@@ -202,7 +193,7 @@ impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
for row in self {
let (element, attributes) = row.into_parts();
converters.push(RasterGpuToRasterCpuConverter::new(device, &mut encoder, element));
converters.push(RasterGpuToRasterCpuConverter::new(executor, &mut encoder, element));
rows_meta.push(Item::from_parts((), attributes));
}
@@ -239,7 +230,7 @@ impl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
label: Some("single_texture_download_encoder"),
});
let converter = RasterGpuToRasterCpuConverter::new(device, &mut encoder, self);
let converter = RasterGpuToRasterCpuConverter::new(executor, &mut encoder, self);
queue.submit([encoder.finish()]);