diff options
author | 2024-04-05 23:59:21 +0200 | |
---|---|---|
committer | 2024-04-05 23:59:21 +0200 | |
commit | 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 (patch) | |
tree | b1a14b0ec7b2da4368d5c98850fe9e9eebc5490a /wgpu/src/text.rs | |
parent | 4a356cfc16f3b45d64826732009d9feeac016b28 (diff) | |
download | iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.tar.gz iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.tar.bz2 iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.zip |
Decouple caching from layering and simplify everything
Diffstat (limited to 'wgpu/src/text.rs')
-rw-r--r-- | wgpu/src/text.rs | 427 |
1 files changed, 225 insertions, 202 deletions
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a7695b74..e84e675d 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -4,245 +4,273 @@ use crate::graphics::color; use crate::graphics::text::cache::{self, Cache as BufferCache}; use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map; +use std::rc::Rc; +use std::sync::atomic::{self, AtomicU64}; use std::sync::Arc; pub use crate::graphics::Text; -pub type Batch = Vec<Text>; +const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION { + glyphon::ColorMode::Accurate +} else { + glyphon::ColorMode::Web +}; -#[allow(missing_debug_implementations)] -pub struct Pipeline { - format: wgpu::TextureFormat, - atlas: glyphon::TextAtlas, - renderers: Vec<glyphon::TextRenderer>, - prepare_layer: usize, - cache: BufferCache, -} +pub type Batch = Vec<Item>; -pub enum Cache { - Staged(Batch), - Uploaded { - batch: Batch, - renderer: glyphon::TextRenderer, - atlas: Option<glyphon::TextAtlas>, - buffer_cache: Option<BufferCache>, +#[derive(Debug)] +pub enum Item { + Group { transformation: Transformation, - target_size: Size<u32>, - needs_reupload: bool, + text: Vec<Text>, }, + Cached { + transformation: Transformation, + cache: Cache, + }, +} + +#[derive(Debug, Clone)] +pub struct Cache { + id: Id, + text: Rc<[Text]>, + version: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + impl Cache { - pub fn is_empty(&self) -> bool { - match self { - Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { - batch.is_empty() - } + pub fn new(text: Vec<Text>) -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + + Self { + id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + text: Rc::from(text), + version: 0, } } - pub fn update(&mut self, new_batch: Batch) { - match self { - Self::Staged(batch) => { - *batch = new_batch; - } - Self::Uploaded { - batch, - needs_reupload, - .. - } => { - *batch = new_batch; - *needs_reupload = true; - } - } + pub fn update(&mut self, text: Vec<Text>) { + self.text = Rc::from(text); + self.version += 1; } } -impl Default for Cache { - fn default() -> Self { - Self::Staged(Batch::default()) - } +struct Upload { + renderer: glyphon::TextRenderer, + atlas: glyphon::TextAtlas, + buffer_cache: BufferCache, + transformation: Transformation, + version: usize, } -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self { - Pipeline { - format, - renderers: Vec::new(), - atlas: glyphon::TextAtlas::with_color_mode( - device, - queue, - format, - if color::GAMMA_CORRECTION { - glyphon::ColorMode::Accurate - } else { - glyphon::ColorMode::Web - }, - ), - prepare_layer: 0, - cache: BufferCache::new(), - } +#[derive(Default)] +pub struct Storage { + uploads: FxHashMap<Id, Upload>, + recently_used: FxHashSet<Id>, +} + +impl Storage { + pub fn new() -> Self { + Self::default() + } + + fn get(&self, id: Id) -> Option<&Upload> { + self.uploads.get(&id) } - pub fn prepare_batch( + fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, - sections: &Batch, - layer_bounds: Rectangle, - layer_transformation: Transformation, + format: wgpu::TextureFormat, + cache: &Cache, + new_transformation: Transformation, + bounds: Rectangle, target_size: Size<u32>, ) { - if self.renderers.len() <= self.prepare_layer { - self.renderers.push(glyphon::TextRenderer::new( - &mut self.atlas, - device, - wgpu::MultisampleState::default(), - None, - )); - } + match self.uploads.entry(cache.id) { + hash_map::Entry::Occupied(entry) => { + let upload = entry.into_mut(); - let renderer = &mut self.renderers[self.prepare_layer]; - let result = prepare( - device, - queue, - encoder, - renderer, - &mut self.atlas, - &mut self.cache, - sections, - layer_bounds, - layer_transformation, - target_size, - ); + if upload.version != cache.version + || upload.transformation != new_transformation + { + let _ = prepare( + device, + queue, + encoder, + &mut upload.renderer, + &mut upload.atlas, + &mut upload.buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); - match result { - Ok(()) => { - self.prepare_layer += 1; - } - Err(glyphon::PrepareError::AtlasFull) => { - // If the atlas cannot grow, then all bets are off. - // Instead of panicking, we will just pray that the result - // will be somewhat readable... - } - } - } + upload.version = cache.version; + upload.transformation = new_transformation; - pub fn prepare_cache( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - cache: &mut Cache, - layer_bounds: Rectangle, - new_transformation: Transformation, - new_target_size: Size<u32>, - ) { - match cache { - Cache::Staged(_) => { - let Cache::Staged(batch) = - std::mem::replace(cache, Cache::Staged(Batch::default())) - else { - unreachable!() - }; - - // TODO: Find a better heuristic (?) - let (mut atlas, mut buffer_cache) = if batch.len() > 10 { - ( - Some(glyphon::TextAtlas::with_color_mode( - device, - queue, - self.format, - if color::GAMMA_CORRECTION { - glyphon::ColorMode::Accurate - } else { - glyphon::ColorMode::Web - }, - )), - Some(BufferCache::new()), - ) - } else { - (None, None) - }; + upload.buffer_cache.trim(); + upload.atlas.trim(); + } + } + hash_map::Entry::Vacant(entry) => { + let mut atlas = glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ); let mut renderer = glyphon::TextRenderer::new( - atlas.as_mut().unwrap_or(&mut self.atlas), + &mut atlas, device, wgpu::MultisampleState::default(), None, ); + let mut buffer_cache = BufferCache::new(); + let _ = prepare( device, queue, encoder, &mut renderer, - atlas.as_mut().unwrap_or(&mut self.atlas), - buffer_cache.as_mut().unwrap_or(&mut self.cache), - &batch, - layer_bounds, + &mut atlas, + &mut buffer_cache, + &cache.text, + bounds, new_transformation, - new_target_size, + target_size, ); - *cache = Cache::Uploaded { - batch, - needs_reupload: false, + let _ = entry.insert(Upload { renderer, atlas, buffer_cache, transformation: new_transformation, - target_size: new_target_size, - } + version: 0, + }); } - Cache::Uploaded { - batch, - needs_reupload, - renderer, - atlas, - buffer_cache, - transformation, - target_size, - } => { - if *needs_reupload - || atlas.is_none() - || buffer_cache.is_none() - || new_transformation != *transformation - || new_target_size != *target_size - { - let _ = prepare( + } + + let _ = self.recently_used.insert(cache.id); + } + + pub fn trim(&mut self) { + self.uploads.retain(|id, _| self.recently_used.contains(id)); + self.recently_used.clear(); + } +} + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + format: wgpu::TextureFormat, + atlas: glyphon::TextAtlas, + renderers: Vec<glyphon::TextRenderer>, + prepare_layer: usize, + cache: BufferCache, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + ) -> Self { + Pipeline { + format, + renderers: Vec::new(), + atlas: glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ), + prepare_layer: 0, + cache: BufferCache::new(), + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + storage: &mut Storage, + batch: &Batch, + layer_bounds: Rectangle, + layer_transformation: Transformation, + target_size: Size<u32>, + ) { + for item in batch { + match item { + Item::Group { + transformation, + text, + } => { + if self.renderers.len() <= self.prepare_layer { + self.renderers.push(glyphon::TextRenderer::new( + &mut self.atlas, + device, + wgpu::MultisampleState::default(), + None, + )); + } + + let renderer = &mut self.renderers[self.prepare_layer]; + let result = prepare( device, queue, encoder, renderer, - atlas.as_mut().unwrap_or(&mut self.atlas), - buffer_cache.as_mut().unwrap_or(&mut self.cache), - batch, + &mut self.atlas, + &mut self.cache, + text, layer_bounds, - new_transformation, - new_target_size, + layer_transformation * *transformation, + target_size, ); - *transformation = new_transformation; - *target_size = new_target_size; - *needs_reupload = false; + match result { + Ok(()) => { + self.prepare_layer += 1; + } + Err(glyphon::PrepareError::AtlasFull) => { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... + } + } + } + Item::Cached { + transformation, + cache, + } => { + storage.prepare( + device, + queue, + encoder, + self.format, + cache, + layer_transformation * *transformation, + layer_bounds, + target_size, + ); } } } } - pub fn render_batch<'a>( + pub fn render<'a>( &'a self, - layer: usize, + storage: &'a Storage, + start: usize, + batch: &'a Batch, bounds: Rectangle<u32>, render_pass: &mut wgpu::RenderPass<'a>, - ) { - let renderer = &self.renderers[layer]; + ) -> usize { + let mut layer_count = 0; render_pass.set_scissor_rect( bounds.x, @@ -251,34 +279,29 @@ impl Pipeline { bounds.height, ); - renderer - .render(&self.atlas, render_pass) - .expect("Render text"); - } + for item in batch { + match item { + Item::Group { .. } => { + let renderer = &self.renderers[start + layer_count]; - pub fn render_cache<'a>( - &'a self, - cache: &'a Cache, - bounds: Rectangle<u32>, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - let Cache::Uploaded { - renderer, atlas, .. - } = cache - else { - return; - }; + renderer + .render(&self.atlas, render_pass) + .expect("Render text"); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); + layer_count += 1; + } + Item::Cached { cache, .. } => { + if let Some(upload) = storage.get(cache.id) { + upload + .renderer + .render(&upload.atlas, render_pass) + .expect("Render cached text"); + } + } + } + } - renderer - .render(atlas.as_ref().unwrap_or(&self.atlas), render_pass) - .expect("Render text"); + layer_count } pub fn end_frame(&mut self) { @@ -296,7 +319,7 @@ fn prepare( renderer: &mut glyphon::TextRenderer, atlas: &mut glyphon::TextAtlas, buffer_cache: &mut BufferCache, - sections: &Batch, + sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, target_size: Size<u32>, @@ -333,8 +356,8 @@ fn prepare( font_system, cache::Key { content, - size: (*size).into(), - line_height: f32::from(line_height.to_absolute(*size)), + size: f32::from(*size), + line_height: f32::from(*line_height), font: *font, bounds: Size { width: bounds.width, |