From 2bb53ad6e7ea2689f2f56662e5840a8d363b3108 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 29 Mar 2024 04:02:24 +0100 Subject: Use a `StagingBelt` in `iced_wgpu` for regular buffer uploads --- wgpu/src/text.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6fa1922d..97ff77f5 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -53,6 +53,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, sections: &[Text<'_>], layer_bounds: Rectangle, scale_factor: f32, @@ -262,6 +263,7 @@ impl Pipeline { let result = renderer.prepare( device, queue, + encoder, font_system, &mut self.atlas, glyphon::Resolution { -- cgit From b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 21:07:54 +0200 Subject: Redesign `iced_wgpu` layering architecture --- wgpu/src/text.rs | 628 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 409 insertions(+), 219 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 97ff77f5..016ac92a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,20 +1,67 @@ use crate::core::alignment; use crate::core::{Rectangle, Size, Transformation}; use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::cache::{self, Cache as BufferCache}; use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; -use crate::layer::Text; -use std::borrow::Cow; -use std::cell::RefCell; use std::sync::Arc; +pub use crate::graphics::Text; + +pub type Batch = Vec; + #[allow(missing_debug_implementations)] pub struct Pipeline { - renderers: Vec, + format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, + renderers: Vec, prepare_layer: usize, - cache: RefCell, + cache: BufferCache, +} + +pub enum Cache { + Staged(Batch), + Uploaded { + batch: Batch, + renderer: glyphon::TextRenderer, + atlas: Option, + buffer_cache: Option, + scale_factor: f32, + target_size: Size, + needs_reupload: bool, + }, +} + +impl Cache { + pub fn is_empty(&self) -> bool { + match self { + Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { + batch.is_empty() + } + } + } + + 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; + } + } + } +} + +impl Default for Cache { + fn default() -> Self { + Self::Staged(Batch::default()) + } } impl Pipeline { @@ -24,6 +71,7 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { + format, renderers: Vec::new(), atlas: glyphon::TextAtlas::with_color_mode( device, @@ -36,25 +84,16 @@ impl Pipeline { }, ), prepare_layer: 0, - cache: RefCell::new(Cache::new()), + cache: BufferCache::new(), } } - pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - font_system() - .write() - .expect("Write font system") - .load_font(bytes); - - self.cache = RefCell::new(Cache::new()); - } - - pub fn prepare( + pub fn prepare_batch( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, - sections: &[Text<'_>], + sections: &Batch, layer_bounds: Rectangle, scale_factor: f32, target_size: Size, @@ -68,210 +107,18 @@ impl Pipeline { )); } - let mut font_system = font_system().write().expect("Write font system"); - let font_system = font_system.raw(); - let renderer = &mut self.renderers[self.prepare_layer]; - let cache = self.cache.get_mut(); - - enum Allocation { - Paragraph(Paragraph), - Editor(Editor), - Cache(cache::KeyHash), - Raw(Arc), - } - - let allocations: Vec<_> = sections - .iter() - .map(|section| match section { - Text::Paragraph { paragraph, .. } => { - paragraph.upgrade().map(Allocation::Paragraph) - } - Text::Editor { editor, .. } => { - editor.upgrade().map(Allocation::Editor) - } - Text::Cached(text) => { - let (key, _) = cache.allocate( - font_system, - cache::Key { - content: text.content, - size: text.size.into(), - line_height: f32::from( - text.line_height.to_absolute(text.size), - ), - font: text.font, - bounds: Size { - width: text.bounds.width, - height: text.bounds.height, - }, - shaping: text.shaping, - }, - ); - - Some(Allocation::Cache(key)) - } - Text::Raw { raw, .. } => { - raw.buffer.upgrade().map(Allocation::Raw) - } - }) - .collect(); - - let layer_bounds = layer_bounds * scale_factor; - - let text_areas = sections.iter().zip(allocations.iter()).filter_map( - |(section, allocation)| { - let ( - buffer, - bounds, - horizontal_alignment, - vertical_alignment, - color, - clip_bounds, - transformation, - ) = match section { - Text::Paragraph { - position, - color, - clip_bounds, - transformation, - .. - } => { - use crate::core::text::Paragraph as _; - - let Some(Allocation::Paragraph(paragraph)) = allocation - else { - return None; - }; - - ( - paragraph.buffer(), - Rectangle::new(*position, paragraph.min_bounds()), - paragraph.horizontal_alignment(), - paragraph.vertical_alignment(), - *color, - *clip_bounds, - *transformation, - ) - } - Text::Editor { - position, - color, - clip_bounds, - transformation, - .. - } => { - use crate::core::text::Editor as _; - - let Some(Allocation::Editor(editor)) = allocation - else { - return None; - }; - - ( - editor.buffer(), - Rectangle::new(*position, editor.bounds()), - alignment::Horizontal::Left, - alignment::Vertical::Top, - *color, - *clip_bounds, - *transformation, - ) - } - Text::Cached(text) => { - let Some(Allocation::Cache(key)) = allocation else { - return None; - }; - - let entry = cache.get(key).expect("Get cached buffer"); - - ( - &entry.buffer, - Rectangle::new( - text.bounds.position(), - entry.min_bounds, - ), - text.horizontal_alignment, - text.vertical_alignment, - text.color, - text.clip_bounds, - Transformation::IDENTITY, - ) - } - Text::Raw { - raw, - transformation, - } => { - let Some(Allocation::Raw(buffer)) = allocation else { - return None; - }; - - let (width, height) = buffer.size(); - - ( - buffer.as_ref(), - Rectangle::new( - raw.position, - Size::new(width, height), - ), - alignment::Horizontal::Left, - alignment::Vertical::Top, - raw.color, - raw.clip_bounds, - *transformation, - ) - } - }; - - let bounds = bounds * transformation * scale_factor; - - let left = match horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => { - bounds.x - bounds.width / 2.0 - } - alignment::Horizontal::Right => bounds.x - bounds.width, - }; - - let top = match vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => { - bounds.y - bounds.height / 2.0 - } - alignment::Vertical::Bottom => bounds.y - bounds.height, - }; - - let clip_bounds = layer_bounds.intersection( - &(clip_bounds * transformation * scale_factor), - )?; - - Some(glyphon::TextArea { - buffer, - left, - top, - scale: scale_factor * transformation.scale_factor(), - bounds: glyphon::TextBounds { - left: clip_bounds.x as i32, - top: clip_bounds.y as i32, - right: (clip_bounds.x + clip_bounds.width) as i32, - bottom: (clip_bounds.y + clip_bounds.height) as i32, - }, - default_color: to_color(color), - }) - }, - ); - - let result = renderer.prepare( + let result = prepare( device, queue, encoder, - font_system, + renderer, &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - text_areas, - &mut glyphon::SwashCache::new(), + &mut self.cache, + sections, + layer_bounds, + scale_factor, + target_size, ); match result { @@ -286,7 +133,109 @@ impl Pipeline { } } - pub fn render<'a>( + pub fn prepare_cache( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + cache: &mut Cache, + layer_bounds: Rectangle, + new_scale_factor: f32, + new_target_size: Size, + ) { + 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) + }; + + let mut renderer = glyphon::TextRenderer::new( + atlas.as_mut().unwrap_or(&mut self.atlas), + device, + wgpu::MultisampleState::default(), + None, + ); + + 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, + new_scale_factor, + new_target_size, + ); + + *cache = Cache::Uploaded { + batch, + needs_reupload: false, + renderer, + atlas, + buffer_cache, + scale_factor: new_scale_factor, + target_size: new_target_size, + } + } + Cache::Uploaded { + batch, + needs_reupload, + renderer, + atlas, + buffer_cache, + scale_factor, + target_size, + } => { + if *needs_reupload + || atlas.is_none() + || buffer_cache.is_none() + || new_scale_factor != *scale_factor + || new_target_size != *target_size + { + let _ = prepare( + device, + queue, + encoder, + renderer, + atlas.as_mut().unwrap_or(&mut self.atlas), + buffer_cache.as_mut().unwrap_or(&mut self.cache), + batch, + layer_bounds, + *scale_factor, + *target_size, + ); + + *scale_factor = new_scale_factor; + *target_size = new_target_size; + } + } + } + } + + pub fn render_batch<'a>( &'a self, layer: usize, bounds: Rectangle, @@ -306,10 +255,251 @@ impl Pipeline { .expect("Render text"); } + pub fn render_cache<'a>( + &'a self, + cache: &'a Cache, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + let Cache::Uploaded { + renderer, atlas, .. + } = cache + else { + return; + }; + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + renderer + .render(atlas.as_ref().unwrap_or(&self.atlas), render_pass) + .expect("Render text"); + } + pub fn end_frame(&mut self) { self.atlas.trim(); - self.cache.get_mut().trim(); + self.cache.trim(); self.prepare_layer = 0; } } + +fn prepare( + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + renderer: &mut glyphon::TextRenderer, + atlas: &mut glyphon::TextAtlas, + buffer_cache: &mut BufferCache, + sections: &Batch, + layer_bounds: Rectangle, + scale_factor: f32, + target_size: Size, +) -> Result<(), glyphon::PrepareError> { + let mut font_system = font_system().write().expect("Write font system"); + let font_system = font_system.raw(); + + enum Allocation { + Paragraph(Paragraph), + Editor(Editor), + Cache(cache::KeyHash), + Raw(Arc), + } + + let allocations: Vec<_> = sections + .iter() + .map(|section| match section { + Text::Paragraph { paragraph, .. } => { + paragraph.upgrade().map(Allocation::Paragraph) + } + Text::Editor { editor, .. } => { + editor.upgrade().map(Allocation::Editor) + } + Text::Cached { + content, + bounds, + size, + line_height, + font, + shaping, + .. + } => { + let (key, _) = buffer_cache.allocate( + font_system, + cache::Key { + content, + size: (*size).into(), + line_height: f32::from(line_height.to_absolute(*size)), + font: *font, + bounds: Size { + width: bounds.width, + height: bounds.height, + }, + shaping: *shaping, + }, + ); + + Some(Allocation::Cache(key)) + } + Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw), + }) + .collect(); + + let layer_bounds = layer_bounds * scale_factor; + + let text_areas = sections.iter().zip(allocations.iter()).filter_map( + |(section, allocation)| { + let ( + buffer, + bounds, + horizontal_alignment, + vertical_alignment, + color, + clip_bounds, + transformation, + ) = match section { + Text::Paragraph { + position, + color, + clip_bounds, + transformation, + .. + } => { + use crate::core::text::Paragraph as _; + + let Some(Allocation::Paragraph(paragraph)) = allocation + else { + return None; + }; + + ( + paragraph.buffer(), + Rectangle::new(*position, paragraph.min_bounds()), + paragraph.horizontal_alignment(), + paragraph.vertical_alignment(), + *color, + *clip_bounds, + *transformation, + ) + } + Text::Editor { + position, + color, + clip_bounds, + transformation, + .. + } => { + use crate::core::text::Editor as _; + + let Some(Allocation::Editor(editor)) = allocation else { + return None; + }; + + ( + editor.buffer(), + Rectangle::new(*position, editor.bounds()), + alignment::Horizontal::Left, + alignment::Vertical::Top, + *color, + *clip_bounds, + *transformation, + ) + } + Text::Cached { + bounds, + horizontal_alignment, + vertical_alignment, + color, + clip_bounds, + .. + } => { + let Some(Allocation::Cache(key)) = allocation else { + return None; + }; + + let entry = + buffer_cache.get(key).expect("Get cached buffer"); + + ( + &entry.buffer, + Rectangle::new(bounds.position(), entry.min_bounds), + *horizontal_alignment, + *vertical_alignment, + *color, + *clip_bounds, + Transformation::IDENTITY, + ) + } + Text::Raw { + raw, + transformation, + } => { + let Some(Allocation::Raw(buffer)) = allocation else { + return None; + }; + + let (width, height) = buffer.size(); + + ( + buffer.as_ref(), + Rectangle::new(raw.position, Size::new(width, height)), + alignment::Horizontal::Left, + alignment::Vertical::Top, + raw.color, + raw.clip_bounds, + *transformation, + ) + } + }; + + let bounds = bounds * transformation * scale_factor; + + let left = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - bounds.width / 2.0, + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + let top = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - bounds.height / 2.0, + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + let clip_bounds = layer_bounds + .intersection(&(clip_bounds * transformation * scale_factor))?; + + Some(glyphon::TextArea { + buffer, + left, + top, + scale: scale_factor * transformation.scale_factor(), + bounds: glyphon::TextBounds { + left: clip_bounds.x as i32, + top: clip_bounds.y as i32, + right: (clip_bounds.x + clip_bounds.width) as i32, + bottom: (clip_bounds.y + clip_bounds.height) as i32, + }, + default_color: to_color(color), + }) + }, + ); + + renderer.prepare( + device, + queue, + encoder, + font_system, + atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + text_areas, + &mut glyphon::SwashCache::new(), + ) +} -- cgit From 394e599c3a096b036aabdd6f850c4a7c518d44fa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 00:40:39 +0200 Subject: Fix layer transformations --- wgpu/src/text.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 016ac92a..e0a5355c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -26,7 +26,7 @@ pub enum Cache { renderer: glyphon::TextRenderer, atlas: Option, buffer_cache: Option, - scale_factor: f32, + transformation: Transformation, target_size: Size, needs_reupload: bool, }, @@ -95,7 +95,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, sections: &Batch, layer_bounds: Rectangle, - scale_factor: f32, + layer_transformation: Transformation, target_size: Size, ) { if self.renderers.len() <= self.prepare_layer { @@ -117,7 +117,7 @@ impl Pipeline { &mut self.cache, sections, layer_bounds, - scale_factor, + layer_transformation, target_size, ); @@ -140,7 +140,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, cache: &mut Cache, layer_bounds: Rectangle, - new_scale_factor: f32, + new_transformation: Transformation, new_target_size: Size, ) { match cache { @@ -186,7 +186,7 @@ impl Pipeline { buffer_cache.as_mut().unwrap_or(&mut self.cache), &batch, layer_bounds, - new_scale_factor, + new_transformation, new_target_size, ); @@ -196,7 +196,7 @@ impl Pipeline { renderer, atlas, buffer_cache, - scale_factor: new_scale_factor, + transformation: new_transformation, target_size: new_target_size, } } @@ -206,13 +206,13 @@ impl Pipeline { renderer, atlas, buffer_cache, - scale_factor, + transformation, target_size, } => { if *needs_reupload || atlas.is_none() || buffer_cache.is_none() - || new_scale_factor != *scale_factor + || new_transformation != *transformation || new_target_size != *target_size { let _ = prepare( @@ -224,11 +224,11 @@ impl Pipeline { buffer_cache.as_mut().unwrap_or(&mut self.cache), batch, layer_bounds, - *scale_factor, - *target_size, + new_transformation, + new_target_size, ); - *scale_factor = new_scale_factor; + *transformation = new_transformation; *target_size = new_target_size; } } @@ -297,7 +297,7 @@ fn prepare( buffer_cache: &mut BufferCache, sections: &Batch, layer_bounds: Rectangle, - scale_factor: f32, + layer_transformation: Transformation, target_size: Size, ) -> Result<(), glyphon::PrepareError> { let mut font_system = font_system().write().expect("Write font system"); @@ -349,7 +349,7 @@ fn prepare( }) .collect(); - let layer_bounds = layer_bounds * scale_factor; + let layer_bounds = layer_bounds * layer_transformation; let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { @@ -456,7 +456,7 @@ fn prepare( } }; - let bounds = bounds * transformation * scale_factor; + let bounds = bounds * transformation * layer_transformation; let left = match horizontal_alignment { alignment::Horizontal::Left => bounds.x, @@ -470,14 +470,16 @@ fn prepare( alignment::Vertical::Bottom => bounds.y - bounds.height, }; - let clip_bounds = layer_bounds - .intersection(&(clip_bounds * transformation * scale_factor))?; + let clip_bounds = layer_bounds.intersection( + &(clip_bounds * transformation * layer_transformation), + )?; Some(glyphon::TextArea { buffer, left, top, - scale: scale_factor * transformation.scale_factor(), + scale: transformation.scale_factor() + * layer_transformation.scale_factor(), bounds: glyphon::TextBounds { left: clip_bounds.x as i32, top: clip_bounds.y as i32, -- cgit From 4a356cfc16f3b45d64826732009d9feeac016b28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 01:24:34 +0200 Subject: Enable clipping and disable v-sync for now --- wgpu/src/text.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e0a5355c..a7695b74 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -230,6 +230,7 @@ impl Pipeline { *transformation = new_transformation; *target_size = new_target_size; + *needs_reupload = false; } } } -- cgit From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 23:59:21 +0200 Subject: Decouple caching from layering and simplify everything --- wgpu/src/text.rs | 427 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 225 insertions(+), 202 deletions(-) (limited to 'wgpu/src/text.rs') 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; +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, - prepare_layer: usize, - cache: BufferCache, -} +pub type Batch = Vec; -pub enum Cache { - Staged(Batch), - Uploaded { - batch: Batch, - renderer: glyphon::TextRenderer, - atlas: Option, - buffer_cache: Option, +#[derive(Debug)] +pub enum Item { + Group { transformation: Transformation, - target_size: Size, - needs_reupload: bool, + text: Vec, }, + 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) -> 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) { + 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, + recently_used: FxHashSet, +} + +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, ) { - 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, - ) { - 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, + 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, + ) { + 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, 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, - 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, @@ -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, -- cgit From 441aac25995290a83162a4728f22492ff69a5f4d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 03:06:40 +0200 Subject: Avoid generating empty caches in `iced_wgpu` --- wgpu/src/text.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e84e675d..1b21bb1c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -43,14 +43,18 @@ pub struct Cache { pub struct Id(u64); impl Cache { - pub fn new(text: Vec) -> Self { + pub fn new(text: Vec) -> Option { static NEXT_ID: AtomicU64 = AtomicU64::new(0); - Self { + if text.is_empty() { + return None; + } + + Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), text: Rc::from(text), version: 0, - } + }) } pub fn update(&mut self, text: Vec) { @@ -78,8 +82,12 @@ impl Storage { Self::default() } - fn get(&self, id: Id) -> Option<&Upload> { - self.uploads.get(&id) + fn get(&self, cache: &Cache) -> Option<&Upload> { + if cache.text.is_empty() { + return None; + } + + self.uploads.get(&cache.id) } fn prepare( @@ -97,8 +105,9 @@ impl Storage { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if upload.version != cache.version - || upload.transformation != new_transformation + if !cache.text.is_empty() + && (upload.version != cache.version + || upload.transformation != new_transformation) { let _ = prepare( device, @@ -154,6 +163,12 @@ impl Storage { transformation: new_transformation, version: 0, }); + + log::info!( + "New text upload: {} (total: {})", + cache.id.0, + self.uploads.len() + ); } } @@ -291,7 +306,7 @@ impl Pipeline { layer_count += 1; } Item::Cached { cache, .. } => { - if let Some(upload) = storage.get(cache.id) { + if let Some(upload) = storage.get(cache) { upload .renderer .render(&upload.atlas, render_pass) -- cgit From d0233da8a25c48a17942a3652cf3775f37c1420f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 12 Apr 2024 18:37:38 +0200 Subject: Fix applying local transformation to `layer_bounds` in `iced_wgpu::text` --- wgpu/src/text.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1b21bb1c..0d01faca 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -242,7 +242,7 @@ impl Pipeline { &mut self.atlas, &mut self.cache, text, - layer_bounds, + layer_bounds * layer_transformation, layer_transformation * *transformation, target_size, ); @@ -269,7 +269,7 @@ impl Pipeline { self.format, cache, layer_transformation * *transformation, - layer_bounds, + layer_bounds * layer_transformation, target_size, ); } @@ -388,8 +388,6 @@ fn prepare( }) .collect(); - let layer_bounds = layer_bounds * layer_transformation; - let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { let ( -- cgit From 2dcd4f916e0ea71f925212c8277498c6f995155b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 27 Apr 2024 14:16:12 +0200 Subject: Retain caches in `iced_wgpu` as long as `Rc` values are alive This allows reusing a `canvas::Cache` at no cost even if it is not presented every frame. --- wgpu/src/text.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0d01faca..f20db026 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -4,9 +4,9 @@ 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 rustc_hash::FxHashMap; use std::collections::hash_map; -use std::rc::Rc; +use std::rc::{self, Rc}; use std::sync::atomic::{self, AtomicU64}; use std::sync::Arc; @@ -69,12 +69,12 @@ struct Upload { buffer_cache: BufferCache, transformation: Transformation, version: usize, + text: rc::Weak<[Text]>, } #[derive(Default)] pub struct Storage { uploads: FxHashMap, - recently_used: FxHashSet, } impl Storage { @@ -162,6 +162,7 @@ impl Storage { buffer_cache, transformation: new_transformation, version: 0, + text: Rc::downgrade(&cache.text), }); log::info!( @@ -171,13 +172,11 @@ impl Storage { ); } } - - 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(); + self.uploads + .retain(|_id, upload| upload.text.strong_count() > 0); } } -- cgit From 24501fd73b5ae884367a2d112ff44625058b876b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 05:13:24 +0200 Subject: Fix `text` and `triangle` uploads being dropped on `canvas` cache clears --- wgpu/src/text.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f20db026..38712660 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -122,6 +122,7 @@ impl Storage { target_size, ); + upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; upload.transformation = new_transformation; -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- wgpu/src/text.rs | 74 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 18 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 38712660..5806863c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,7 +1,8 @@ use crate::core::alignment; use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::cache; use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache as BufferCache}; +use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache}; use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; use rustc_hash::FxHashMap; @@ -35,6 +36,7 @@ pub enum Item { #[derive(Debug, Clone)] pub struct Cache { id: Id, + group: cache::Group, text: Rc<[Text]>, version: usize, } @@ -43,7 +45,7 @@ pub struct Cache { pub struct Id(u64); impl Cache { - pub fn new(text: Vec) -> Option { + pub fn new(group: cache::Group, text: Vec) -> Option { static NEXT_ID: AtomicU64 = AtomicU64::new(0); if text.is_empty() { @@ -52,6 +54,7 @@ impl Cache { Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + group, text: Rc::from(text), version: 0, }) @@ -65,29 +68,39 @@ impl Cache { struct Upload { renderer: glyphon::TextRenderer, - atlas: glyphon::TextAtlas, buffer_cache: BufferCache, transformation: Transformation, version: usize, text: rc::Weak<[Text]>, + _atlas: rc::Weak<()>, } #[derive(Default)] pub struct Storage { + groups: FxHashMap, uploads: FxHashMap, } +struct Group { + atlas: glyphon::TextAtlas, + previous_uploads: usize, + handle: Rc<()>, +} + impl Storage { pub fn new() -> Self { Self::default() } - fn get(&self, cache: &Cache) -> Option<&Upload> { + fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> { if cache.text.is_empty() { return None; } - self.uploads.get(&cache.id) + self.groups + .get(&cache.group) + .map(|group| &group.atlas) + .zip(self.uploads.get(&cache.id)) } fn prepare( @@ -101,6 +114,20 @@ impl Storage { bounds: Rectangle, target_size: Size, ) { + let group_count = self.groups.len(); + + let group = self.groups.entry(cache.group).or_insert_with(|| { + log::info!("New text atlas created (total: {})", group_count + 1); + + Group { + atlas: glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ), + previous_uploads: 0, + handle: Rc::new(()), + } + }); + match self.uploads.entry(cache.id) { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); @@ -114,7 +141,7 @@ impl Storage { queue, encoder, &mut upload.renderer, - &mut upload.atlas, + &mut group.atlas, &mut upload.buffer_cache, &cache.text, bounds, @@ -127,16 +154,11 @@ impl Storage { upload.transformation = new_transformation; 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( - &mut atlas, + &mut group.atlas, device, wgpu::MultisampleState::default(), None, @@ -149,7 +171,7 @@ impl Storage { queue, encoder, &mut renderer, - &mut atlas, + &mut group.atlas, &mut buffer_cache, &cache.text, bounds, @@ -159,11 +181,11 @@ impl Storage { let _ = entry.insert(Upload { renderer, - atlas, buffer_cache, transformation: new_transformation, version: 0, text: Rc::downgrade(&cache.text), + _atlas: Rc::downgrade(&group.handle), }); log::info!( @@ -178,6 +200,22 @@ impl Storage { pub fn trim(&mut self) { self.uploads .retain(|_id, upload| upload.text.strong_count() > 0); + + self.groups.retain(|_id, group| { + let uploads_alive = Rc::weak_count(&group.handle); + + if uploads_alive == 0 { + return false; + } + + if uploads_alive < group.previous_uploads { + group.atlas.trim(); + } + + group.previous_uploads = uploads_alive; + + true + }); } } @@ -306,10 +344,10 @@ impl Pipeline { layer_count += 1; } Item::Cached { cache, .. } => { - if let Some(upload) = storage.get(cache) { + if let Some((atlas, upload)) = storage.get(cache) { upload .renderer - .render(&upload.atlas, render_pass) + .render(atlas, render_pass) .expect("Render cached text"); } } @@ -345,7 +383,7 @@ fn prepare( enum Allocation { Paragraph(Paragraph), Editor(Editor), - Cache(cache::KeyHash), + Cache(text_cache::KeyHash), Raw(Arc), } @@ -369,7 +407,7 @@ fn prepare( } => { let (key, _) = buffer_cache.allocate( font_system, - cache::Key { + text_cache::Key { content, size: f32::from(*size), line_height: f32::from(*line_height), -- cgit From c51b85e7ab067f5e7411eccd10a5ae192e6ee0a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 21:59:46 +0200 Subject: Invalidate text uploads after atlas trimming --- wgpu/src/text.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 5806863c..8ec1d56a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -71,6 +71,7 @@ struct Upload { buffer_cache: BufferCache, transformation: Transformation, version: usize, + group_version: usize, text: rc::Weak<[Text]>, _atlas: rc::Weak<()>, } @@ -83,8 +84,9 @@ pub struct Storage { struct Group { atlas: glyphon::TextAtlas, - previous_uploads: usize, - handle: Rc<()>, + version: usize, + should_trim: bool, + handle: Rc<()>, // Keeps track of active uploads } impl Storage { @@ -117,13 +119,18 @@ impl Storage { let group_count = self.groups.len(); let group = self.groups.entry(cache.group).or_insert_with(|| { - log::info!("New text atlas created (total: {})", group_count + 1); + log::debug!( + "New text atlas: {:?} (total: {})", + cache.group, + group_count + 1 + ); Group { atlas: glyphon::TextAtlas::with_color_mode( device, queue, format, COLOR_MODE, ), - previous_uploads: 0, + version: 0, + should_trim: false, handle: Rc::new(()), } }); @@ -134,6 +141,7 @@ impl Storage { if !cache.text.is_empty() && (upload.version != cache.version + || upload.group_version != group.version || upload.transformation != new_transformation) { let _ = prepare( @@ -151,9 +159,11 @@ impl Storage { upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; + upload.group_version = group.version; upload.transformation = new_transformation; upload.buffer_cache.trim(); + group.should_trim = true; } } hash_map::Entry::Vacant(entry) => { @@ -184,11 +194,12 @@ impl Storage { buffer_cache, transformation: new_transformation, version: 0, + group_version: group.version, text: Rc::downgrade(&cache.text), _atlas: Rc::downgrade(&group.handle), }); - log::info!( + log::debug!( "New text upload: {} (total: {})", cache.id.0, self.uploads.len() @@ -201,18 +212,21 @@ impl Storage { self.uploads .retain(|_id, upload| upload.text.strong_count() > 0); - self.groups.retain(|_id, group| { - let uploads_alive = Rc::weak_count(&group.handle); + self.groups.retain(|id, group| { + if Rc::weak_count(&group.handle) == 0 { + log::debug!("Dropping text atlas: {id:?}"); - if uploads_alive == 0 { return false; } - if uploads_alive < group.previous_uploads { + if group.should_trim { + log::debug!("Trimming text atlas: {id:?}"); + group.atlas.trim(); - } - group.previous_uploads = uploads_alive; + group.version += 1; + group.should_trim = false; + } true }); -- cgit From b276a603a17eda219b32f207aa53e2b6a1321a9f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:15:04 +0200 Subject: Fix cache trimming loop in `iced_wgpu::text` --- wgpu/src/text.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 8ec1d56a..2508906f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -157,13 +157,16 @@ impl Storage { target_size, ); + // Only trim if glyphs have changed + group.should_trim = + group.should_trim || upload.version != cache.version; + upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; upload.group_version = group.version; upload.transformation = new_transformation; upload.buffer_cache.trim(); - group.should_trim = true; } } hash_map::Entry::Vacant(entry) => { @@ -213,19 +216,28 @@ impl Storage { .retain(|_id, upload| upload.text.strong_count() > 0); self.groups.retain(|id, group| { - if Rc::weak_count(&group.handle) == 0 { + let active_uploads = Rc::weak_count(&group.handle); + + if active_uploads == 0 { log::debug!("Dropping text atlas: {id:?}"); return false; } - if group.should_trim { - log::debug!("Trimming text atlas: {id:?}"); + if id.is_singleton() || group.should_trim { + log::debug!( + "Trimming text atlas: {id:?} (uploads: {active_uploads})" + ); group.atlas.trim(); - - group.version += 1; group.should_trim = false; + + // We only need to worry about glyph fighting + // when the atlas may be shared by multiple + // uploads. + if !id.is_singleton() { + group.version += 1; + } } true -- cgit From 7e2d0dc931a4409e661851a1c7dffdcfd5b2f93a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:51:00 +0200 Subject: Keep text atlases alive during temporary empty uploads --- wgpu/src/text.rs | 76 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 32 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2508906f..7e683c77 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -61,6 +61,10 @@ impl Cache { } pub fn update(&mut self, text: Vec) { + if self.text.is_empty() && text.is_empty() { + return; + } + self.text = Rc::from(text); self.version += 1; } @@ -139,23 +143,24 @@ impl Storage { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if !cache.text.is_empty() - && (upload.version != cache.version - || upload.group_version != group.version - || upload.transformation != new_transformation) + if upload.version != cache.version + || upload.group_version != group.version + || upload.transformation != new_transformation { - let _ = prepare( - device, - queue, - encoder, - &mut upload.renderer, - &mut group.atlas, - &mut upload.buffer_cache, - &cache.text, - bounds, - new_transformation, - target_size, - ); + if !cache.text.is_empty() { + let _ = prepare( + device, + queue, + encoder, + &mut upload.renderer, + &mut group.atlas, + &mut upload.buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); + } // Only trim if glyphs have changed group.should_trim = @@ -179,18 +184,20 @@ impl Storage { let mut buffer_cache = BufferCache::new(); - let _ = prepare( - device, - queue, - encoder, - &mut renderer, - &mut group.atlas, - &mut buffer_cache, - &cache.text, - bounds, - new_transformation, - target_size, - ); + if !cache.text.is_empty() { + let _ = prepare( + device, + queue, + encoder, + &mut renderer, + &mut group.atlas, + &mut buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); + } let _ = entry.insert(Upload { renderer, @@ -202,6 +209,8 @@ impl Storage { _atlas: Rc::downgrade(&group.handle), }); + group.should_trim = cache.group.is_singleton(); + log::debug!( "New text upload: {} (total: {})", cache.id.0, @@ -224,10 +233,8 @@ impl Storage { return false; } - if id.is_singleton() || group.should_trim { - log::debug!( - "Trimming text atlas: {id:?} (uploads: {active_uploads})" - ); + if group.should_trim { + log::trace!("Trimming text atlas: {id:?}"); group.atlas.trim(); group.should_trim = false; @@ -236,6 +243,11 @@ impl Storage { // when the atlas may be shared by multiple // uploads. if !id.is_singleton() { + log::debug!( + "Invalidating text atlas: {id:?} \ + (uploads: {active_uploads})" + ); + group.version += 1; } } -- cgit From 447f3a2d14ab1c4bc20e232df1aa2375623af2de Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 12:29:17 +0200 Subject: Reuse `glyphon::Pipeline` state in `iced_wgpu` --- wgpu/src/text.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 7e683c77..68741461 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -115,6 +115,7 @@ impl Storage { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, + pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -131,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, + device, queue, pipeline, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -259,6 +260,7 @@ impl Storage { #[allow(missing_debug_implementations)] pub struct Pipeline { + state: glyphon::Pipeline, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -272,12 +274,16 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { + let state = glyphon::Pipeline::new(device); + let atlas = glyphon::TextAtlas::with_color_mode( + device, queue, &state, format, COLOR_MODE, + ); + Pipeline { + state, format, renderers: Vec::new(), - atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, - ), + atlas, prepare_layer: 0, cache: BufferCache::new(), } @@ -343,6 +349,7 @@ impl Pipeline { queue, encoder, self.format, + &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, -- cgit From bed53f81436c595e479009427e1fa4ff4a1048d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 13:22:22 +0200 Subject: Reuse `glyphon::Viewport` explicitly --- wgpu/src/text.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 68741461..9ecd5c99 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -113,13 +113,13 @@ impl Storage { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, - target_size: Size, ) { let group_count = self.groups.len(); @@ -152,6 +152,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut upload.renderer, &mut group.atlas, @@ -159,7 +160,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -189,6 +189,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut renderer, &mut group.atlas, @@ -196,7 +197,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -258,6 +258,20 @@ impl Storage { } } +pub struct Viewport(glyphon::Viewport); + +impl Viewport { + pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size) { + self.0.update( + queue, + glyphon::Resolution { + width: resolution.width, + height: resolution.height, + }, + ); + } +} + #[allow(missing_debug_implementations)] pub struct Pipeline { state: glyphon::Pipeline, @@ -293,12 +307,12 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &Viewport, encoder: &mut wgpu::CommandEncoder, storage: &mut Storage, batch: &Batch, layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) { for item in batch { match item { @@ -319,6 +333,7 @@ impl Pipeline { let result = prepare( device, queue, + &viewport.0, encoder, renderer, &mut self.atlas, @@ -326,7 +341,6 @@ impl Pipeline { text, layer_bounds * layer_transformation, layer_transformation * *transformation, - target_size, ); match result { @@ -347,13 +361,13 @@ impl Pipeline { storage.prepare( device, queue, + &viewport.0, encoder, self.format, &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, - target_size, ); } } @@ -362,6 +376,7 @@ impl Pipeline { pub fn render<'a>( &'a self, + viewport: &'a Viewport, storage: &'a Storage, start: usize, batch: &'a Batch, @@ -383,7 +398,7 @@ impl Pipeline { let renderer = &self.renderers[start + layer_count]; renderer - .render(&self.atlas, render_pass) + .render(&self.atlas, &viewport.0, render_pass) .expect("Render text"); layer_count += 1; @@ -392,7 +407,7 @@ impl Pipeline { if let Some((atlas, upload)) = storage.get(cache) { upload .renderer - .render(atlas, render_pass) + .render(atlas, &viewport.0, render_pass) .expect("Render cached text"); } } @@ -402,6 +417,10 @@ impl Pipeline { layer_count } + pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { + Viewport(glyphon::Viewport::new(device, &self.state)) + } + pub fn end_frame(&mut self) { self.atlas.trim(); self.cache.trim(); @@ -413,6 +432,7 @@ impl Pipeline { fn prepare( device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, renderer: &mut glyphon::TextRenderer, atlas: &mut glyphon::TextAtlas, @@ -420,7 +440,6 @@ fn prepare( sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) -> Result<(), glyphon::PrepareError> { let mut font_system = font_system().write().expect("Write font system"); let font_system = font_system.raw(); @@ -617,10 +636,7 @@ fn prepare( encoder, font_system, atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, + viewport, text_areas, &mut glyphon::SwashCache::new(), ) -- cgit From 99c1464cc16979381b3a531d293ee34fd6697480 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 19:34:43 +0200 Subject: Update `glyphon` fork to a cleaner branch --- wgpu/src/text.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'wgpu/src/text.rs') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 9ecd5c99..05db5f80 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -116,7 +116,7 @@ impl Storage { viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, - pipeline: &glyphon::Pipeline, + state: &glyphon::Cache, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -132,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, pipeline, format, COLOR_MODE, + device, queue, state, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -274,7 +274,7 @@ impl Viewport { #[allow(missing_debug_implementations)] pub struct Pipeline { - state: glyphon::Pipeline, + state: glyphon::Cache, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -288,7 +288,7 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { - let state = glyphon::Pipeline::new(device); + let state = glyphon::Cache::new(device); let atlas = glyphon::TextAtlas::with_color_mode( device, queue, &state, format, COLOR_MODE, ); -- cgit