From d60f37fa445121115a263b7f72ad49d4a7300efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A2ris=20DOUADY?= Date: Sat, 13 Jul 2024 19:33:41 +0200 Subject: [PATCH] Add bindgroup cache in wgpu (#130) avoid recreating them every frame Co-authored-by: Lucien Greathouse --- crates/yakui-wgpu/src/bindgroup_cache.rs | 88 ++++++++++++++++++++++++ crates/yakui-wgpu/src/lib.rs | 77 ++++++++++++--------- 2 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 crates/yakui-wgpu/src/bindgroup_cache.rs diff --git a/crates/yakui-wgpu/src/bindgroup_cache.rs b/crates/yakui-wgpu/src/bindgroup_cache.rs new file mode 100644 index 00000000..3a177126 --- /dev/null +++ b/crates/yakui-wgpu/src/bindgroup_cache.rs @@ -0,0 +1,88 @@ +use crate::samplers::Samplers; +use std::collections::HashMap; +use yakui_core::TextureId; + +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub(crate) struct TextureBindgroupCacheEntry { + pub id: TextureId, + pub min_filter: wgpu::FilterMode, + pub mag_filter: wgpu::FilterMode, + pub mipmap_filter: wgpu::FilterMode, + pub address_mode: wgpu::AddressMode, +} + +pub(crate) struct TextureBindgroupCache { + cache: HashMap, + layout: wgpu::BindGroupLayout, + pub default: wgpu::BindGroup, +} + +impl TextureBindgroupCache { + pub fn new(layout: wgpu::BindGroupLayout, default: wgpu::BindGroup) -> Self { + Self { + cache: HashMap::new(), + layout, + default, + } + } + + pub fn clear(&mut self) { + self.cache.clear(); + } + + pub fn update( + &mut self, + device: &wgpu::Device, + entry: TextureBindgroupCacheEntry, + view: &wgpu::TextureView, + samplers: &Samplers, + ) { + self.cache.entry(entry).or_insert_with(|| { + bindgroup( + device, + &self.layout, + samplers, + view, + entry.min_filter, + entry.mag_filter, + entry.mipmap_filter, + entry.address_mode, + ) + }); + } + + pub fn get(&self, entry: &TextureBindgroupCacheEntry) -> &wgpu::BindGroup { + self.cache.get(entry).unwrap_or(&self.default) + } +} + +pub fn bindgroup( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + samplers: &Samplers, + view: &wgpu::TextureView, + min_filter: wgpu::FilterMode, + mag_filter: wgpu::FilterMode, + mipmap_filter: wgpu::FilterMode, + address_mode: wgpu::AddressMode, +) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("yakui Bind Group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(samplers.get( + min_filter, + mag_filter, + mipmap_filter, + address_mode, + )), + }, + ], + }) +} diff --git a/crates/yakui-wgpu/src/lib.rs b/crates/yakui-wgpu/src/lib.rs index 8442b648..a3494888 100644 --- a/crates/yakui-wgpu/src/lib.rs +++ b/crates/yakui-wgpu/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::new_without_default)] +mod bindgroup_cache; mod buffer; mod pipeline_cache; mod samplers; @@ -18,6 +19,8 @@ use yakui_core::geometry::{Rect, Vec2, Vec4}; use yakui_core::paint::{PaintDom, Pipeline, Texture, TextureChange, TextureFormat}; use yakui_core::{ManagedTextureId, TextureId}; +use self::bindgroup_cache::TextureBindgroupCache; +use self::bindgroup_cache::TextureBindgroupCacheEntry; use self::pipeline_cache::PipelineCache; use self::samplers::Samplers; use self::texture::{GpuManagedTexture, GpuTexture}; @@ -25,11 +28,10 @@ use self::texture::{GpuManagedTexture, GpuTexture}; pub struct YakuiWgpu { main_pipeline: PipelineCache, text_pipeline: PipelineCache, - layout: wgpu::BindGroupLayout, - default_texture: GpuManagedTexture, samplers: Samplers, textures: Arena, managed_textures: HashMap, + texture_bindgroup_cache: TextureBindgroupCache, vertices: Buffer, indices: Buffer, @@ -109,16 +111,25 @@ impl YakuiWgpu { let default_texture_data = Texture::new(TextureFormat::Rgba8Srgb, UVec2::new(1, 1), vec![255; 4]); let default_texture = GpuManagedTexture::new(device, queue, &default_texture_data); + let default_bindgroup = bindgroup_cache::bindgroup( + device, + &layout, + &samplers, + &default_texture.view, + default_texture.min_filter, + default_texture.mag_filter, + wgpu::FilterMode::Nearest, + wgpu::AddressMode::ClampToEdge, + ); Self { main_pipeline, text_pipeline, - layout, - default_texture, samplers, textures: Arena::new(), managed_textures: HashMap::new(), + texture_bindgroup_cache: TextureBindgroupCache::new(layout, default_bindgroup), vertices: Buffer::new(wgpu::BufferUsages::VERTEX), indices: Buffer::new(wgpu::BufferUsages::INDEX), commands: Vec::new(), @@ -281,7 +292,12 @@ impl YakuiWgpu { } } - render_pass.set_bind_group(0, &command.bind_group, &[]); + let bindgroup = command + .bind_group_entry + .map(|entry| self.texture_bindgroup_cache.get(&entry)) + .unwrap_or(&self.texture_bindgroup_cache.default); + + render_pass.set_bind_group(0, bindgroup, &[]); render_pass.draw_indexed(command.index_range.clone(), 0, 0..1); } } @@ -293,6 +309,7 @@ impl YakuiWgpu { self.vertices.clear(); self.indices.clear(); self.commands.clear(); + self.texture_bindgroup_cache.clear(); let commands = paint .layers() @@ -314,12 +331,13 @@ impl YakuiWgpu { self.vertices.extend(vertices); self.indices.extend(indices); - let (view, min_filter, mag_filter, mipmap_filter, address_mode) = call + let bind_group_entry = call .texture .and_then(|id| match id { TextureId::Managed(managed) => { let texture = self.managed_textures.get(&managed)?; Some(( + id, &texture.view, texture.min_filter, texture.mag_filter, @@ -331,6 +349,7 @@ impl YakuiWgpu { let index = Index::from_bits(bits)?; let texture = self.textures.get(index)?; Some(( + id, &texture.view, texture.min_filter, texture.mag_filter, @@ -339,36 +358,28 @@ impl YakuiWgpu { )) } }) - .unwrap_or(( - &self.default_texture.view, - self.default_texture.min_filter, - self.default_texture.mag_filter, - wgpu::FilterMode::Nearest, - self.default_texture.address_mode, - )); - - let sampler = - self.samplers - .get(min_filter, mag_filter, mipmap_filter, address_mode); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("yakui Bind Group"), - layout: &self.layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(sampler), + .map( + |(id, view, min_filter, mag_filter, mipmap_filter, address_mode)| { + let entry = TextureBindgroupCacheEntry { + id, + min_filter, + mag_filter, + mipmap_filter, + address_mode, + }; + self.texture_bindgroup_cache.update( + device, + entry, + view, + &self.samplers, + ); + entry }, - ], - }); + ); DrawCommand { index_range: start..end, - bind_group, + bind_group_entry, pipeline: call.pipeline, clip: call.clip, } @@ -411,7 +422,7 @@ impl YakuiWgpu { struct DrawCommand { index_range: Range, - bind_group: wgpu::BindGroup, + bind_group_entry: Option, pipeline: Pipeline, clip: Option, }