diff options
Diffstat (limited to '')
| -rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
| -rw-r--r-- | wgpu/src/backend.rs | 6 | ||||
| -rw-r--r-- | wgpu/src/text.rs | 470 | 
3 files changed, 273 insertions, 204 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1a94c6a3..dffbbab0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -36,6 +36,7 @@ bitflags = "1.2"  once_cell = "1.0"  rustc-hash = "1.1"  twox-hash = "1.6" +ouroboros = "0.15"  [dependencies.bytemuck]  version = "1.9" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 874edb96..5a275c8a 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -14,6 +14,8 @@ use tracing::info_span;  #[cfg(any(feature = "image", feature = "svg"))]  use crate::image; +use std::borrow::Cow; +  /// A [`wgpu`] graphics backend for [`iced`].  ///  /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -234,6 +236,10 @@ impl backend::Text for Backend {              nearest_only,          )      } + +    fn load_font(&mut self, font: Cow<'static, [u8]>) { +        self.text_pipeline.load_font(font); +    }  }  #[cfg(feature = "image")] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 083d9d2b..cdfcd576 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -5,108 +5,37 @@ use iced_native::alignment;  use iced_native::{Color, Font, Rectangle, Size};  use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow;  use std::cell::RefCell;  use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc;  use twox_hash::RandomXxHashBuilder64;  #[allow(missing_debug_implementations)]  pub struct Pipeline { +    system: Option<System>,      renderers: Vec<glyphon::TextRenderer>,      atlas: glyphon::TextAtlas, -    cache: glyphon::SwashCache<'static>, -    measurement_cache: RefCell<Cache>, -    render_cache: Cache,      layer: usize,  } -struct Cache { -    entries: FxHashMap<KeyHash, glyphon::Buffer<'static>>, -    recently_used: FxHashSet<KeyHash>, -    hasher: RandomXxHashBuilder64, -} - -impl Cache { -    fn new() -> Self { -        Self { -            entries: FxHashMap::default(), -            recently_used: FxHashSet::default(), -            hasher: RandomXxHashBuilder64::default(), -        } -    } - -    fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { -        self.entries.get(key) -    } - -    fn allocate( -        &mut self, -        key: Key<'_>, -    ) -> (KeyHash, &mut glyphon::Buffer<'static>) { -        let hash = { -            let mut hasher = self.hasher.build_hasher(); - -            key.content.hash(&mut hasher); -            (key.size as i32).hash(&mut hasher); -            key.font.hash(&mut hasher); -            (key.bounds.width as i32).hash(&mut hasher); -            (key.bounds.height as i32).hash(&mut hasher); -            key.color.into_rgba8().hash(&mut hasher); - -            hasher.finish() -        }; +#[ouroboros::self_referencing] +struct System { +    fonts: glyphon::FontSystem, -        if !self.entries.contains_key(&hash) { -            let metrics = -                glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); +    #[borrows(fonts)] +    #[not_covariant] +    cache: glyphon::SwashCache<'this>, -            let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); +    #[borrows(fonts)] +    #[not_covariant] +    measurement_cache: RefCell<Cache<'this>>, -            buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); -            buffer.set_text( -                key.content, -                glyphon::Attrs::new().family(to_family(key.font)).color({ -                    let [r, g, b, a] = key.color.into_linear(); - -                    glyphon::Color::rgba( -                        (r * 255.0) as u8, -                        (g * 255.0) as u8, -                        (b * 255.0) as u8, -                        (a * 255.0) as u8, -                    ) -                }), -            ); - -            let _ = self.entries.insert(hash, buffer); -        } - -        let _ = self.recently_used.insert(hash); - -        (hash, self.entries.get_mut(&hash).unwrap()) -    } - -    fn trim(&mut self) { -        self.entries -            .retain(|key, _| self.recently_used.contains(key)); - -        self.recently_used.clear(); -    } +    #[borrows(fonts)] +    #[not_covariant] +    render_cache: Cache<'this>,  } -#[derive(Debug, Clone, Copy)] -struct Key<'a> { -    content: &'a str, -    size: f32, -    font: Font, -    bounds: Size, -    color: Color, -} - -type KeyHash = u64; - -// TODO: Share with `iced_graphics` -static FONT_SYSTEM: once_cell::sync::Lazy<glyphon::FontSystem> = -    once_cell::sync::Lazy::new(glyphon::FontSystem::new); -  impl Pipeline {      pub fn new(          device: &wgpu::Device, @@ -114,15 +43,41 @@ impl Pipeline {          format: wgpu::TextureFormat,      ) -> Self {          Pipeline { +            system: Some( +                SystemBuilder { +                    fonts: glyphon::FontSystem::new(), +                    cache_builder: |fonts| glyphon::SwashCache::new(fonts), +                    measurement_cache_builder: |_| RefCell::new(Cache::new()), +                    render_cache_builder: |_| Cache::new(), +                } +                .build(), +            ),              renderers: Vec::new(),              atlas: glyphon::TextAtlas::new(device, queue, format), -            cache: glyphon::SwashCache::new(&FONT_SYSTEM), -            measurement_cache: RefCell::new(Cache::new()), -            render_cache: Cache::new(),              layer: 0,          }      } +    pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { +        let heads = self.system.take().unwrap().into_heads(); + +        let (locale, mut db) = heads.fonts.into_locale_and_db(); + +        db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( +            bytes.to_owned(), +        ))); + +        self.system = Some( +            SystemBuilder { +                fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), +                cache_builder: |fonts| glyphon::SwashCache::new(fonts), +                measurement_cache_builder: |_| RefCell::new(Cache::new()), +                render_cache_builder: |_| Cache::new(), +            } +            .build(), +        ); +    } +      pub fn prepare(          &mut self,          device: &wgpu::Device, @@ -132,93 +87,100 @@ impl Pipeline {          scale_factor: f32,          target_size: Size<u32>,      ) { -        if self.renderers.len() <= self.layer { -            self.renderers -                .push(glyphon::TextRenderer::new(device, queue)); -        } - -        let renderer = &mut self.renderers[self.layer]; - -        let keys: Vec<_> = sections -            .iter() -            .map(|section| { -                let (key, _) = self.render_cache.allocate(Key { -                    content: section.content, -                    size: section.size * scale_factor, -                    font: section.font, -                    bounds: Size { -                        width: section.bounds.width * scale_factor, -                        height: section.bounds.height * scale_factor, +        self.system.as_mut().unwrap().with_mut(|fields| { +            if self.renderers.len() <= self.layer { +                self.renderers +                    .push(glyphon::TextRenderer::new(device, queue)); +            } + +            let renderer = &mut self.renderers[self.layer]; + +            let keys: Vec<_> = sections +                .iter() +                .map(|section| { +                    let (key, _) = fields.render_cache.allocate( +                        fields.fonts, +                        Key { +                            content: section.content, +                            size: section.size * scale_factor, +                            font: section.font, +                            bounds: Size { +                                width: section.bounds.width * scale_factor, +                                height: section.bounds.height * scale_factor, +                            }, +                            color: section.color, +                        }, +                    ); + +                    key +                }) +                .collect(); + +            let bounds = glyphon::TextBounds { +                left: (bounds.x * scale_factor) as i32, +                top: (bounds.y * scale_factor) as i32, +                right: ((bounds.x + bounds.width) * scale_factor) as i32, +                bottom: ((bounds.y + bounds.height) * scale_factor) as i32, +            }; + +            let text_areas: Vec<_> = sections +                .iter() +                .zip(keys.iter()) +                .map(|(section, key)| { +                    let buffer = fields +                        .render_cache +                        .get(key) +                        .expect("Get cached buffer"); + +                    let x = section.bounds.x * scale_factor; +                    let y = section.bounds.y * scale_factor; + +                    let (total_lines, max_width) = buffer +                        .layout_runs() +                        .enumerate() +                        .fold((0, 0.0), |(_, max), (i, buffer)| { +                            (i + 1, buffer.line_w.max(max)) +                        }); + +                    let total_height = +                        total_lines as f32 * section.size * 1.2 * scale_factor; + +                    let left = match section.horizontal_alignment { +                        alignment::Horizontal::Left => x, +                        alignment::Horizontal::Center => x - max_width / 2.0, +                        alignment::Horizontal::Right => x - max_width, +                    }; + +                    let top = match section.vertical_alignment { +                        alignment::Vertical::Top => y, +                        alignment::Vertical::Center => y - total_height / 2.0, +                        alignment::Vertical::Bottom => y - total_height, +                    }; + +                    glyphon::TextArea { +                        buffer, +                        left: left as i32, +                        top: top as i32, +                        bounds, +                    } +                }) +                .collect(); + +            renderer +                .prepare( +                    device, +                    queue, +                    &mut self.atlas, +                    glyphon::Resolution { +                        width: target_size.width, +                        height: target_size.height,                      }, -                    color: section.color, -                }); - -                key -            }) -            .collect(); - -        let bounds = glyphon::TextBounds { -            left: (bounds.x * scale_factor) as i32, -            top: (bounds.y * scale_factor) as i32, -            right: ((bounds.x + bounds.width) * scale_factor) as i32, -            bottom: ((bounds.y + bounds.height) * scale_factor) as i32, -        }; - -        let text_areas: Vec<_> = sections -            .iter() -            .zip(keys.iter()) -            .map(|(section, key)| { -                let buffer = -                    self.render_cache.get(key).expect("Get cached buffer"); - -                let x = section.bounds.x * scale_factor; -                let y = section.bounds.y * scale_factor; - -                let (total_lines, max_width) = buffer -                    .layout_runs() -                    .enumerate() -                    .fold((0, 0.0), |(_, max), (i, buffer)| { -                        (i + 1, buffer.line_w.max(max)) -                    }); - -                let total_height = -                    total_lines as f32 * section.size * 1.2 * scale_factor; - -                let left = match section.horizontal_alignment { -                    alignment::Horizontal::Left => x, -                    alignment::Horizontal::Center => x - max_width / 2.0, -                    alignment::Horizontal::Right => x - max_width, -                }; - -                let top = match section.vertical_alignment { -                    alignment::Vertical::Top => y, -                    alignment::Vertical::Center => y - total_height / 2.0, -                    alignment::Vertical::Bottom => y - total_height, -                }; - -                glyphon::TextArea { -                    buffer, -                    left: left as i32, -                    top: top as i32, -                    bounds, -                } -            }) -            .collect(); - -        renderer -            .prepare( -                device, -                queue, -                &mut self.atlas, -                glyphon::Resolution { -                    width: target_size.width, -                    height: target_size.height, -                }, -                &text_areas, -                glyphon::Color::rgb(0, 0, 0), -                &mut self.cache, -            ) -            .expect("Prepare text sections"); +                    &text_areas, +                    glyphon::Color::rgb(0, 0, 0), +                    fields.cache, +                ) +                .expect("Prepare text sections"); +        });      }      pub fn render( @@ -251,7 +213,10 @@ impl Pipeline {      pub fn end_frame(&mut self) {          self.renderers.truncate(self.layer); -        self.render_cache.trim(); +        self.system +            .as_mut() +            .unwrap() +            .with_render_cache_mut(|cache| cache.trim());          self.layer = 0;      } @@ -263,24 +228,29 @@ impl Pipeline {          font: Font,          bounds: Size,      ) -> (f32, f32) { -        let mut measurement_cache = self.measurement_cache.borrow_mut(); - -        let (_, paragraph) = measurement_cache.allocate(Key { -            content, -            size: size, -            font, -            bounds, -            color: Color::BLACK, -        }); +        self.system.as_ref().unwrap().with(|fields| { +            let mut measurement_cache = fields.measurement_cache.borrow_mut(); + +            let (_, paragraph) = measurement_cache.allocate( +                fields.fonts, +                Key { +                    content, +                    size: size, +                    font, +                    bounds, +                    color: Color::BLACK, +                }, +            ); -        let (total_lines, max_width) = paragraph -            .layout_runs() -            .enumerate() -            .fold((0, 0.0), |(_, max), (i, buffer)| { -                (i + 1, buffer.line_w.max(max)) -            }); +            let (total_lines, max_width) = paragraph +                .layout_runs() +                .enumerate() +                .fold((0, 0.0), |(_, max), (i, buffer)| { +                    (i + 1, buffer.line_w.max(max)) +                }); -        (max_width, size * 1.2 * total_lines as f32) +            (max_width, size * 1.2 * total_lines as f32) +        })      }      pub fn hit_test( @@ -292,23 +262,31 @@ impl Pipeline {          point: iced_native::Point,          _nearest_only: bool,      ) -> Option<Hit> { -        let mut measurement_cache = self.measurement_cache.borrow_mut(); - -        let (_, paragraph) = measurement_cache.allocate(Key { -            content, -            size: size, -            font, -            bounds, -            color: Color::BLACK, -        }); +        self.system.as_ref().unwrap().with(|fields| { +            let mut measurement_cache = fields.measurement_cache.borrow_mut(); + +            let (_, paragraph) = measurement_cache.allocate( +                fields.fonts, +                Key { +                    content, +                    size: size, +                    font, +                    bounds, +                    color: Color::BLACK, +                }, +            ); -        let cursor = paragraph.hit(point.x as i32, point.y as i32)?; +            let cursor = paragraph.hit(point.x as i32, point.y as i32)?; -        Some(Hit::CharOffset(cursor.index)) +            Some(Hit::CharOffset(cursor.index)) +        })      }      pub fn trim_measurement_cache(&mut self) { -        self.measurement_cache.borrow_mut().trim(); +        self.system +            .as_mut() +            .unwrap() +            .with_measurement_cache_mut(|cache| cache.borrow_mut().trim());      }  } @@ -322,3 +300,87 @@ fn to_family(font: Font) -> glyphon::Family<'static> {          Font::Monospace => glyphon::Family::Monospace,      }  } + +struct Cache<'a> { +    entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>, +    recently_used: FxHashSet<KeyHash>, +    hasher: RandomXxHashBuilder64, +} + +impl<'a> Cache<'a> { +    fn new() -> Self { +        Self { +            entries: FxHashMap::default(), +            recently_used: FxHashSet::default(), +            hasher: RandomXxHashBuilder64::default(), +        } +    } + +    fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> { +        self.entries.get(key) +    } + +    fn allocate( +        &mut self, +        fonts: &'a glyphon::FontSystem, +        key: Key<'_>, +    ) -> (KeyHash, &mut glyphon::Buffer<'a>) { +        let hash = { +            let mut hasher = self.hasher.build_hasher(); + +            key.content.hash(&mut hasher); +            (key.size as i32).hash(&mut hasher); +            key.font.hash(&mut hasher); +            (key.bounds.width as i32).hash(&mut hasher); +            (key.bounds.height as i32).hash(&mut hasher); +            key.color.into_rgba8().hash(&mut hasher); + +            hasher.finish() +        }; + +        if !self.entries.contains_key(&hash) { +            let metrics = +                glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); +            let mut buffer = glyphon::Buffer::new(&fonts, metrics); + +            buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); +            buffer.set_text( +                key.content, +                glyphon::Attrs::new().family(to_family(key.font)).color({ +                    let [r, g, b, a] = key.color.into_linear(); + +                    glyphon::Color::rgba( +                        (r * 255.0) as u8, +                        (g * 255.0) as u8, +                        (b * 255.0) as u8, +                        (a * 255.0) as u8, +                    ) +                }), +            ); + +            let _ = self.entries.insert(hash, buffer); +        } + +        let _ = self.recently_used.insert(hash); + +        (hash, self.entries.get_mut(&hash).unwrap()) +    } + +    fn trim(&mut self) { +        self.entries +            .retain(|key, _| self.recently_used.contains(key)); + +        self.recently_used.clear(); +    } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { +    content: &'a str, +    size: f32, +    font: Font, +    bounds: Size, +    color: Color, +} + +type KeyHash = u64;  | 
