Add texture pool to render cache node (#3804)
* Add texture pool to render cache node * Use direct texture copy instead of bitter and fix graphene_cli * Remove warnings * Fix wgpu import path * Code review fixes
This commit is contained in:
parent
2ac82a10b5
commit
116a4106c4
|
|
@ -11,13 +11,13 @@ pub(crate) struct RenderState {
|
||||||
executor: WgpuExecutor,
|
executor: WgpuExecutor,
|
||||||
config: wgpu::SurfaceConfiguration,
|
config: wgpu::SurfaceConfiguration,
|
||||||
render_pipeline: wgpu::RenderPipeline,
|
render_pipeline: wgpu::RenderPipeline,
|
||||||
transparent_texture: wgpu::Texture,
|
transparent_texture: std::sync::Arc<wgpu::Texture>,
|
||||||
sampler: wgpu::Sampler,
|
sampler: wgpu::Sampler,
|
||||||
desired_width: u32,
|
desired_width: u32,
|
||||||
desired_height: u32,
|
desired_height: u32,
|
||||||
viewport_scale: [f32; 2],
|
viewport_scale: [f32; 2],
|
||||||
viewport_offset: [f32; 2],
|
viewport_offset: [f32; 2],
|
||||||
viewport_texture: Option<wgpu::Texture>,
|
viewport_texture: Option<std::sync::Arc<wgpu::Texture>>,
|
||||||
overlays_texture: Option<TargetTexture>,
|
overlays_texture: Option<TargetTexture>,
|
||||||
ui_texture: Option<wgpu::Texture>,
|
ui_texture: Option<wgpu::Texture>,
|
||||||
bind_group: Option<wgpu::BindGroup>,
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
|
|
@ -50,7 +50,7 @@ impl RenderState {
|
||||||
|
|
||||||
surface.configure(&context.device, &config);
|
surface.configure(&context.device, &config);
|
||||||
|
|
||||||
let transparent_texture = context.device.create_texture(&wgpu::TextureDescriptor {
|
let transparent_texture = std::sync::Arc::new(context.device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: Some("Transparent Texture"),
|
label: Some("Transparent Texture"),
|
||||||
size: wgpu::Extent3d {
|
size: wgpu::Extent3d {
|
||||||
width: 1,
|
width: 1,
|
||||||
|
|
@ -63,7 +63,7 @@ impl RenderState {
|
||||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
});
|
}));
|
||||||
|
|
||||||
// Create shader module
|
// Create shader module
|
||||||
let shader = context.device.create_shader_module(wgpu::include_wgsl!("composite_shader.wgsl"));
|
let shader = context.device.create_shader_module(wgpu::include_wgsl!("composite_shader.wgsl"));
|
||||||
|
|
@ -207,7 +207,7 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: wgpu::Texture) {
|
pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: std::sync::Arc<wgpu::Texture>) {
|
||||||
self.viewport_texture = Some(viewport_texture);
|
self.viewport_texture = Some(viewport_texture);
|
||||||
self.update_bindgroup();
|
self.update_bindgroup();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ impl DesktopWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum NodeGraphExecutionResult {
|
pub enum NodeGraphExecutionResult {
|
||||||
HasRun(Option<wgpu::Texture>),
|
HasRun(Option<std::sync::Arc<wgpu::Texture>>),
|
||||||
NotRun,
|
NotRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,9 @@ pub struct NodeRuntime {
|
||||||
/// Cached surface for Wasm viewport rendering (reused across frames)
|
/// Cached surface for Wasm viewport rendering (reused across frames)
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
|
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
|
||||||
|
/// Currently displayed texture, the runtime keeps a reference to it to avoid the texture getting destroyed while it is still in use.
|
||||||
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
|
current_viewport_texture: Option<ImageTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages passed from the editor thread to the node runtime thread.
|
/// Messages passed from the editor thread to the node runtime thread.
|
||||||
|
|
@ -144,6 +147,8 @@ impl NodeRuntime {
|
||||||
inspect_state: None,
|
inspect_state: None,
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
wasm_viewport_surface: None,
|
wasm_viewport_surface: None,
|
||||||
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
|
current_viewport_texture: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,7 +280,7 @@ impl NodeRuntime {
|
||||||
.gpu_executor()
|
.gpu_executor()
|
||||||
.expect("GPU executor should be available when we receive a texture");
|
.expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
let raster_cpu = Raster::new_gpu(image_texture.texture).convert(Footprint::BOUNDLESS, executor).await;
|
let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
||||||
|
|
||||||
let (data, width, height) = raster_cpu.to_flat_u8();
|
let (data, width, height) = raster_cpu.to_flat_u8();
|
||||||
|
|
||||||
|
|
@ -299,7 +304,7 @@ impl NodeRuntime {
|
||||||
.gpu_executor()
|
.gpu_executor()
|
||||||
.expect("GPU executor should be available when we receive a texture");
|
.expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
let raster_cpu = Raster::new_gpu(image_texture.texture).convert(Footprint::BOUNDLESS, executor).await;
|
let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;
|
||||||
|
|
||||||
self.sender.send_eyedropper_preview(raster_cpu);
|
self.sender.send_eyedropper_preview(raster_cpu);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -354,13 +359,22 @@ impl NodeRuntime {
|
||||||
);
|
);
|
||||||
|
|
||||||
let surface_texture = surface_inner.get_current_texture().expect("Failed to get surface texture");
|
let surface_texture = surface_inner.get_current_texture().expect("Failed to get surface texture");
|
||||||
|
self.current_viewport_texture = Some(image_texture.clone());
|
||||||
|
|
||||||
// Blit the rendered texture to the surface
|
encoder.copy_texture_to_texture(
|
||||||
surface.surface.blitter.copy(
|
vello::wgpu::TexelCopyTextureInfoBase {
|
||||||
&executor.context.device,
|
texture: image_texture.texture.as_ref(),
|
||||||
&mut encoder,
|
mip_level: 0,
|
||||||
&image_texture.texture.create_view(&vello::wgpu::TextureViewDescriptor::default()),
|
origin: Default::default(),
|
||||||
&surface_texture.texture.create_view(&vello::wgpu::TextureViewDescriptor::default()),
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
vello::wgpu::TexelCopyTextureInfoBase {
|
||||||
|
texture: &surface_texture.texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Default::default(),
|
||||||
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
image_texture.texture.size(),
|
||||||
);
|
);
|
||||||
|
|
||||||
executor.context.queue.submit([encoder.finish()]);
|
executor.context.queue.submit([encoder.finish()]);
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pub async fn export_document(
|
||||||
}
|
}
|
||||||
RenderOutputType::Texture(image_texture) => {
|
RenderOutputType::Texture(image_texture) => {
|
||||||
// Convert GPU texture to CPU buffer
|
// Convert GPU texture to CPU buffer
|
||||||
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture);
|
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture.as_ref().clone());
|
||||||
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
||||||
let (data, width, height) = cpu_raster.to_flat_u8();
|
let (data, width, height) = cpu_raster.to_flat_u8();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ impl Size for web_sys::HtmlCanvasElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, DynAny)]
|
||||||
pub struct ImageTexture {
|
pub struct ImageTexture {
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub texture: wgpu::Texture,
|
pub texture: Arc<wgpu::Texture>,
|
||||||
#[cfg(not(feature = "wgpu"))]
|
#[cfg(not(feature = "wgpu"))]
|
||||||
pub texture: (),
|
pub texture: (),
|
||||||
}
|
}
|
||||||
|
|
@ -89,10 +89,6 @@ impl PartialEq for ImageTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl StaticType for ImageTexture {
|
|
||||||
type Static = ImageTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
impl Size for ImageTexture {
|
impl Size for ImageTexture {
|
||||||
fn size(&self) -> UVec2 {
|
fn size(&self) -> UVec2 {
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ pub async fn pixel_preview<'a: 'n>(
|
||||||
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
||||||
let resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform);
|
let resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform);
|
||||||
|
|
||||||
result.data = RenderOutputType::Texture(ImageTexture { texture: resampled });
|
result.data = RenderOutputType::Texture(ImageTexture { texture: resampled.into() });
|
||||||
|
|
||||||
result
|
result
|
||||||
.metadata
|
.metadata
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,12 @@ struct TileCacheImpl {
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
total_memory: usize,
|
total_memory: usize,
|
||||||
cache_key: CacheKey,
|
cache_key: CacheKey,
|
||||||
|
texture_cache_resolution: UVec2,
|
||||||
|
/// Pool of textures of the same size: `texture_cache_resolution`.
|
||||||
|
/// Reusing textures reduces the wgpu allocation pressure,
|
||||||
|
/// which is a problem on web since we have to wait for
|
||||||
|
/// the browser to garbage collect unused textures, eating up memory.
|
||||||
|
texture_cache: Vec<Arc<wgpu::Texture>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, dyn_any::DynAny, Debug)]
|
#[derive(Clone, Default, dyn_any::DynAny, Debug)]
|
||||||
|
|
@ -214,6 +220,36 @@ impl TileCacheImpl {
|
||||||
self.regions.clear();
|
self.regions.clear();
|
||||||
self.total_memory = 0;
|
self.total_memory = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn request_texture(&mut self, size: UVec2, device: &wgpu::Device) -> Arc<wgpu::Texture> {
|
||||||
|
if self.texture_cache_resolution != size {
|
||||||
|
self.texture_cache_resolution = size;
|
||||||
|
self.texture_cache.clear();
|
||||||
|
}
|
||||||
|
self.texture_cache.truncate(5);
|
||||||
|
for texture in &self.texture_cache {
|
||||||
|
if Arc::strong_count(texture) == 1 {
|
||||||
|
return Arc::clone(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let texture = Arc::new(device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("viewport_output"),
|
||||||
|
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,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
}));
|
||||||
|
self.texture_cache.push(texture.clone());
|
||||||
|
|
||||||
|
texture
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileCache {
|
impl TileCache {
|
||||||
|
|
@ -224,6 +260,10 @@ impl TileCache {
|
||||||
pub fn store_regions(&self, regions: Vec<CachedRegion>) {
|
pub fn store_regions(&self, regions: Vec<CachedRegion>) {
|
||||||
self.0.lock().unwrap().store_regions(regions);
|
self.0.lock().unwrap().store_regions(regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn request_texture(&self, size: UVec2, device: &wgpu::Device) -> Arc<wgpu::Texture> {
|
||||||
|
self.0.lock().unwrap().request_texture(size, device)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_into_regions(tiles: &[TileCoord], max_region_area: u32) -> Vec<RenderRegion> {
|
fn group_into_regions(tiles: &[TileCoord], max_region_area: u32) -> Vec<RenderRegion> {
|
||||||
|
|
@ -413,7 +453,11 @@ pub async fn render_output_cache<'a: 'n>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
||||||
let (output_texture, combined_metadata) = composite_cached_regions(&all_regions, physical_resolution, &device_origin_offset, &footprint.transform, exec);
|
|
||||||
|
let device = &exec.context.device;
|
||||||
|
let output_texture = tile_cache.request_texture(physical_resolution, device);
|
||||||
|
|
||||||
|
let combined_metadata = composite_cached_regions(&all_regions, output_texture.as_ref(), &device_origin_offset, &footprint.transform, exec);
|
||||||
|
|
||||||
RenderOutput {
|
RenderOutput {
|
||||||
data: RenderOutputType::Texture(ImageTexture { texture: output_texture }),
|
data: RenderOutputType::Texture(ImageTexture { texture: output_texture }),
|
||||||
|
|
@ -462,7 +506,7 @@ where
|
||||||
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
|
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
|
||||||
|
|
||||||
CachedRegion {
|
CachedRegion {
|
||||||
texture: rendered_texture.texture,
|
texture: rendered_texture.texture.as_ref().clone(),
|
||||||
texture_size: region_pixel_size,
|
texture_size: region_pixel_size,
|
||||||
tiles: region.tiles.clone(),
|
tiles: region.tiles.clone(),
|
||||||
metadata: result.metadata,
|
metadata: result.metadata,
|
||||||
|
|
@ -473,29 +517,14 @@ where
|
||||||
|
|
||||||
fn composite_cached_regions(
|
fn composite_cached_regions(
|
||||||
regions: &[CachedRegion],
|
regions: &[CachedRegion],
|
||||||
output_resolution: UVec2,
|
output_texture: &wgpu::Texture,
|
||||||
device_origin_offset: &DVec2,
|
device_origin_offset: &DVec2,
|
||||||
viewport_transform: &DAffine2,
|
viewport_transform: &DAffine2,
|
||||||
exec: &wgpu_executor::WgpuExecutor,
|
exec: &wgpu_executor::WgpuExecutor,
|
||||||
) -> (wgpu::Texture, rendering::RenderMetadata) {
|
) -> rendering::RenderMetadata {
|
||||||
let device = &exec.context.device;
|
let device = &exec.context.device;
|
||||||
let queue = &exec.context.queue;
|
let queue = &exec.context.queue;
|
||||||
|
let output_resolution = UVec2::new(output_texture.width(), output_texture.height());
|
||||||
// TODO: Use texture pool to reuse existing unused textures instead of allocating fresh ones every time
|
|
||||||
let output_texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
||||||
label: Some("viewport_output"),
|
|
||||||
size: wgpu::Extent3d {
|
|
||||||
width: output_resolution.x,
|
|
||||||
height: output_resolution.y,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: wgpu::TextureDimension::D2,
|
|
||||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
||||||
view_formats: &[],
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("composite") });
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("composite") });
|
||||||
let mut combined_metadata = rendering::RenderMetadata::default();
|
let mut combined_metadata = rendering::RenderMetadata::default();
|
||||||
|
|
@ -548,5 +577,5 @@ fn composite_cached_regions(
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.submit([encoder.finish()]);
|
queue.submit([encoder.finish()]);
|
||||||
(output_texture, combined_metadata)
|
combined_metadata
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,10 +196,11 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let texture = exec
|
let texture = Arc::new(
|
||||||
.render_vello_scene_to_texture(&scene, physical_resolution, context, background)
|
exec.render_vello_scene_to_texture(&scene, physical_resolution, context, background)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to render Vello scene");
|
.expect("Failed to render Vello scene"),
|
||||||
|
);
|
||||||
|
|
||||||
RenderOutputType::Texture(ImageTexture { texture })
|
RenderOutputType::Texture(ImageTexture { texture })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue