From e32fc6de19a191ad5bad9cc68eff167a54a64a6f Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Tue, 28 Feb 2023 21:24:05 +0100 Subject: [PATCH] Improve font cache Always write the information for touched glyphs every frame, to try and avoid accidently depending on those being stable across atlas updates. When the font cache is out of space, perform an emergency repack, recreating the atlas with only the glyphs touched in the current frame. This should address both changing requirements on the font cache over time from the application, as well as inefficient packing from incrementally building the atlas. --- bins/narcissus/src/main.rs | 45 +-- bins/narcissus/src/pipelines/text.rs | 4 +- bins/narcissus/src/shaders/text.vert.glsl | 2 +- bins/narcissus/src/shaders/text.vert.spv | Bin 3076 -> 3076 bytes libs/narcissus-font/src/cache.rs | 425 +++++++++++++--------- libs/narcissus-font/src/font.rs | 2 +- libs/narcissus-font/src/lib.rs | 2 +- 7 files changed, 276 insertions(+), 204 deletions(-) diff --git a/bins/narcissus/src/main.rs b/bins/narcissus/src/main.rs index fb282d7..0f046e8 100644 --- a/bins/narcissus/src/main.rs +++ b/bins/narcissus/src/main.rs @@ -8,7 +8,7 @@ use helpers::{create_buffer_with_data, create_image_with_data, load_image, load_ use mapped_buffer::MappedBuffer; use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc}; use narcissus_core::{default, rand::Pcg64}; -use narcissus_font::{CachedGlyph, FontCollection, GlyphCache}; +use narcissus_font::{FontCollection, GlyphCache, TouchedGlyph, TouchedGlyphInfo}; use narcissus_gpu::{ create_device, Access, BufferImageCopy, BufferUsageFlags, ClearValue, Extent2d, Extent3d, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, @@ -41,7 +41,7 @@ pub unsafe trait Blittable: Sized {} unsafe impl Blittable for u8 {} unsafe impl Blittable for u16 {} unsafe impl Blittable for Affine3 {} -unsafe impl Blittable for CachedGlyph {} +unsafe impl Blittable for TouchedGlyph {} pub fn main() { let app = create_app(); @@ -110,7 +110,7 @@ pub fn main() { let mut glyph_buffer = MappedBuffer::new( device.as_ref(), BufferUsageFlags::STORAGE, - std::mem::size_of::() * MAX_GLYPHS, + std::mem::size_of::() * MAX_GLYPHS, ); let glyph_atlas = device.create_image(&ImageDesc { @@ -163,7 +163,7 @@ pub fn main() { } } - let mut num_chars = 0; + let mut space_count = 0; let start_time = Instant::now(); 'main: loop { @@ -181,9 +181,8 @@ pub fn main() { if key == Key::Escape { break 'main; } - if key == Key::Space && pressed == PressedState::Released { - num_chars += 1; - println!("{num_chars}"); + if key == Key::Space && pressed == PressedState::Pressed { + space_count += 1; } } Quit => { @@ -271,7 +270,6 @@ pub fn main() { let line1 = "加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して"; let mut glyph_instances = Vec::new(); - let mut glyph_indices = Vec::new(); let mut x; let mut y = 0.0; @@ -294,33 +292,29 @@ pub fn main() { y += (font.ascent() - font.descent() + font.line_gap()) * scale; y = y.trunc(); - glyph_indices.clear(); - glyph_indices.extend(text.chars().take(num_chars).map(|c| { - font.glyph_index(c) - .unwrap_or_else(|| font.glyph_index('□').unwrap()) - })); - let mut prev_glyph_index = None; - for glyph_index in glyph_indices.iter().copied() { + for c in text.chars().skip(space_count).take(1) { + let TouchedGlyphInfo { + touched_glyph_index, + glyph_index, + advance_width, + } = glyph_cache.touch_glyph(font_family, c, font_size_px); + if let Some(prev_glyph_index) = prev_glyph_index.replace(glyph_index) { x += font.kerning_advance(prev_glyph_index, glyph_index) * scale; } - let cached_glyph_index = - glyph_cache.cache_glyph(font_family, glyph_index, font_size_px); - const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]; let color = COLOR_SERIES[rng.next_bound_u64(4) as usize]; glyph_instances.push(GlyphInstance { - cached_glyph_index, x, y, + touched_glyph_index, color, }); - let h_metrics = font.horizontal_metrics(glyph_index); - x += h_metrics.advance_width * scale; + x += advance_width * scale; } } @@ -336,9 +330,10 @@ pub fn main() { glyph_instance_buffer.write_slice(&glyph_instances); // If the atlas has been updated, we need to upload it to the GPU - if let Some((cached_glyphs, texture)) = glyph_cache.update_atlas() { - glyph_buffer.write_slice(cached_glyphs); + let (touched_glyphs, texture) = glyph_cache.update_atlas(); + glyph_buffer.write_slice(touched_glyphs); + if let Some(texture) = texture { // upload atlas { let width = atlas_width; @@ -391,9 +386,7 @@ pub fn main() { ); device.destroy_buffer(&frame, buffer); - - image - }; + } } device.cmd_begin_rendering( diff --git a/bins/narcissus/src/pipelines/text.rs b/bins/narcissus/src/pipelines/text.rs index 48e5f71..df23b09 100644 --- a/bins/narcissus/src/pipelines/text.rs +++ b/bins/narcissus/src/pipelines/text.rs @@ -1,5 +1,5 @@ use narcissus_core::{cstr, default, include_bytes_align}; -use narcissus_font::CachedGlyphIndex; +use narcissus_font::TouchedGlyphIndex; use narcissus_gpu::{ Bind, BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, Buffer, CmdBuffer, CompareOp, CullingMode, Device, Frame, FrontFace, GraphicsPipelineDesc, @@ -25,9 +25,9 @@ pub struct TextUniforms { #[allow(unused)] #[repr(C)] pub struct GlyphInstance { - pub cached_glyph_index: CachedGlyphIndex, pub x: f32, pub y: f32, + pub touched_glyph_index: TouchedGlyphIndex, pub color: u32, } diff --git a/bins/narcissus/src/shaders/text.vert.glsl b/bins/narcissus/src/shaders/text.vert.glsl index 146096e..26b1e08 100644 --- a/bins/narcissus/src/shaders/text.vert.glsl +++ b/bins/narcissus/src/shaders/text.vert.glsl @@ -13,9 +13,9 @@ struct CachedGlyph { }; struct GlyphInstance { - uint index; float x; float y; + uint index; uint color; }; diff --git a/bins/narcissus/src/shaders/text.vert.spv b/bins/narcissus/src/shaders/text.vert.spv index ed9eb1ce047c2bebecc5218d1c9b6250a55cebbd..c4b15a6324e1b860c7e9c8dd73debeaa2b6b0bba 100644 GIT binary patch literal 3076 zcmZve$%~v-6vcntRo$)W491v5A!!qXV`6Y&LcK~LO=x#&C!M!I=-m~}Geb?x*o>4jKvw?N>J_e56{lFtGHUOJ|9l*oDQ^2dhyTDh#GBeCq z6zs}(W9AeyUt`n3&KY1b&~GiU#JIj^f%D2|99D2)VkRvAtGC3cFmoF(=S#w+*E?aw zZa;F~oqXH=hrifr{$i{7iw*hPr$qnidC#h zDqLQPtu-YVF}a8}XEC{mS-<^!7jyn{X+LYJ*`Ad)^}9YieAj0FO-xtros24sxM;w)c{ktwUSQ3NF5AcNw)eMvsa17rU(VCDbuUg` zF?qSR5Busq`i$y-X>8`&%9txt&%My!`fA2~-p07jvOZ$Q?@3>t@y>rVTIuy2gSp%v ze{;BRYIxYI@8*3dy3=kSuGO@>v>(g6{yl#+-uvaOp0E8nb-=z}OkeiVeZ3Ta`+7Nc zF?p%yj?w1Fx@XgycI&$Kp7uPL*SfC_bn;eg<}&`BtnJ@;*Pa*SUF+`|alMC`^7}o# z*iYB^V^ZTl`}_%oiC@P|&B~+o$7g!+0ycs{6={O zwCSg|x7L}3vs2?bo8FaTN0_Nue4RVjr?GE>xmRND;kUuu-^RWRCVzRT)yry*6c1zM zp>{8iO&*@LD}r^d?}IfDYyJ>y$m7RgLmoc`lSlXPb1-Mz*e}8S{b}siVCtf=--5~E zaQ1i~R8IXH+aFB-#ulOK4E=5nHu0OQv$zD6(;UX=b0eeLcWuX`)mxxu)9YKIX3+Hx zLHUTc&uvgMX`kEUBi=rDX1?un7Zhfm#_kPf+~M%P52~g)jnPNm>QmbyGj;a;)8u`i z9@T)ed=v_A4#$Ia%wvi1KI)jqq41750fjZklhqT2KbO6F0t)xcwcnFaT36fV_IKe_ z{>NG0d+&74owz>U0Z#`%o!rj`GvAqDXP|KFJ`?PPvQu|M>*{qVyfv=o-Uz0~ow4`O zn^3;mysNxdfBxH-#xrn{{bewbr0`D;XUUMGG~8hdd?q0;XUV%ps T^YZb`o5wp@pMRL*dZzdn-+R5V literal 3076 zcmZve+pAS&6vfBhd+mM9<0bPN=utB@Q=|urP)vQOBPD7G74#5-c+JujdWy6QD4H5w z)aY)$SWlI&YGri!i+l;>H}@E}*tp=Z);H#D%=wM)+o#uEHl{&GXTo7C2_e^8b31>J>(A;^F*= zxn#Xmx@V^!IIniTh4qkjDO24ntki66DlY-WWG{mw8CzOj*iwz`#jsd|-< zI&;eW6m%Lxri&+H|8#~MnL&FnF=1kM#Yhzloi%Mvr-p%$`1}xyt*xP)+FtA29NBmf?YpGM5*uk*s%FD_OtN zn#+F2laqZz!|5|`>Kt-T?DWAJO~VuWVSf{o`J?gd7aUL5ex2AvpJ!bcy{6BP>`$NP zYzKoEdQJ>B{-}Gwy)s z_d-9Z@%OOCruh6L6$bv4`ZXtd1{boJs^+s!*yAwTc(V(^r3;;=&L(#y*o*4d-u&!4mj{gb{9=r~3ihip_BXNDjlmxtXm@$dQN@GY z@IcEeY=Z}9ZKbi~x?wCl(9KO_;Q@BbSewUh#^4dYzZ+x5!TvDD?@zGX#>gG)Ph-qt zr}ucBR5|evw%!>2!8S`(r|tKwv7z5soz3T@%1I7l@OehP+IMV6tkvhGnoZW*J%iNS zA(an!d|r}jCh>XMKH%|r)$@(dYf@q43AWc5aXZa>pHwx;Nen*lMxWTWs9$HFf10=t z&?6dPzHdr}hrlz)Co8N^K z{y&cT+^1DZA=!h^{`B3XjIo*JsAiI5XxR z`dlhsY}{4cD{$oQx#atdxiWXw_@{?2>vw$ETYC6PDm-)k+H>Z2A#?slDm-)kRw^tp z-#Lcgj>LQ~6`q(YQem0-4^rg;&#bOW#RnVwxm&@eX;8gi^PSI`;C|Vm2MYgDDjRmr UKJ((?%#(*Z8K3{u(bMYaE-qTV#{d8T diff --git a/libs/narcissus-font/src/cache.rs b/libs/narcissus-font/src/cache.rs index ce93343..e42230f 100644 --- a/libs/narcissus-font/src/cache.rs +++ b/libs/narcissus-font/src/cache.rs @@ -1,4 +1,10 @@ -use crate::{font::GlyphBitmapBox, FontCollection, GlyphIndex, Oversample, Packer}; +use std::collections::hash_map::Entry; + +use crate::{ + font::{GlyphBitmapBox, HorizontalMetrics}, + FontCollection, GlyphIndex, Oversample, Packer, +}; +use narcissus_core::default; use rustc_hash::FxHashMap; use stb_truetype_sys::rectpack::Rect; @@ -7,12 +13,12 @@ pub use narcissus_core::FiniteF32; /// An index into the CachedGlyph slice. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(transparent)] -pub struct CachedGlyphIndex(u32); +pub struct TouchedGlyphIndex(u32); /// Holds data required to draw a glyph from the glyph atlas. #[derive(Clone, Copy, Default)] #[repr(C)] -pub struct CachedGlyph { +pub struct TouchedGlyph { // Bitmap coordinates in texture atlas. pub x0: i32, pub x1: i32, @@ -26,19 +32,30 @@ pub struct CachedGlyph { pub offset_y1: f32, } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] struct GlyphKey { family: Family, - glyph_index: GlyphIndex, + c: char, size_px: FiniteF32, } -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct Glyph { - family: F, +#[derive(Clone, Copy)] +pub struct TouchedGlyphInfo { + pub touched_glyph_index: TouchedGlyphIndex, + pub glyph_index: GlyphIndex, + pub advance_width: f32, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct CachedGlyphIndex(u32); + +struct CachedGlyph { + glyph_key: GlyphKey, glyph_index: GlyphIndex, - size_px: FiniteF32, - cached_glyph_index: CachedGlyphIndex, + offset_x0: f32, + offset_y0: f32, + offset_x1: f32, + offset_y1: f32, } pub struct GlyphCache<'a, F> @@ -48,20 +65,19 @@ where fonts: &'a F, padding: usize, + width: usize, + height: usize, + texture: Box<[u8]>, - next_cached_glyph_index: u32, - cached_glyph_lookup: FxHashMap, CachedGlyphIndex>, - - glyphs: Vec>, + next_touched_glyph_index: u32, + touched_glyph_lookup: FxHashMap, TouchedGlyphInfo>, + touched_glyphs: Vec, - packer: Packer, + cached_glyph_indices_sorted: Vec, + cached_glyphs: Vec>, rects: Vec, - cached_glyphs: Vec, - - width: usize, - height: usize, - texture: Box<[u8]>, + packer: Packer, } impl<'a, F> GlyphCache<'a, F> @@ -73,20 +89,19 @@ where fonts, padding, + width, + height, + texture: vec![0; width * height].into_boxed_slice(), - next_cached_glyph_index: 0, - cached_glyph_lookup: Default::default(), + next_touched_glyph_index: 0, + touched_glyph_lookup: default(), + touched_glyphs: default(), - glyphs: Vec::new(), + cached_glyph_indices_sorted: default(), + cached_glyphs: default(), + rects: default(), packer: Packer::new(width - padding, height - padding), - rects: Vec::new(), - - cached_glyphs: Vec::new(), - - width, - height, - texture: vec![0; width * height].into_boxed_slice(), } } @@ -106,167 +121,231 @@ where } } - pub fn cache_glyph( - &mut self, - family: F::Family, - glyph_index: GlyphIndex, - size_px: f32, - ) -> CachedGlyphIndex { + pub fn touch_glyph(&mut self, family: F::Family, c: char, size_px: f32) -> TouchedGlyphInfo { let key = GlyphKey { family, - glyph_index, + c, size_px: FiniteF32::new(size_px).unwrap(), }; - *self.cached_glyph_lookup.entry(key).or_insert_with(|| { - let cached_glyph_index = CachedGlyphIndex(self.next_cached_glyph_index); - self.next_cached_glyph_index += 1; - cached_glyph_index - }) - } - - pub fn update_atlas(&mut self) -> Option<(&[CachedGlyph], &[u8])> { - let glyphs_len = self.cached_glyphs.len(); - - // If we have the same number of glyphs as we have cached, then there's nothing to do. - if glyphs_len == self.cached_glyph_lookup.len() { - return None; + match self.touched_glyph_lookup.entry(key) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + let touched_glyph_index = TouchedGlyphIndex(self.next_touched_glyph_index); + self.next_touched_glyph_index += 1; + + let font = self.fonts.font(family); + let glyph_index = font + .glyph_index(c) + .unwrap_or_else(|| font.glyph_index('□').unwrap()); + + let HorizontalMetrics { + advance_width, + left_side_bearing: _, + } = font.horizontal_metrics(glyph_index); + + *entry.insert(TouchedGlyphInfo { + touched_glyph_index, + glyph_index, + advance_width, + }) + } } + } - // Extend the glyphs list with the new glyphs. - self.glyphs - .extend(self.cached_glyph_lookup.iter().filter_map( - |(glyph_key, &cached_glyph_index)| { - if cached_glyph_index.0 < glyphs_len as u32 { - None - } else { - Some(Glyph { - family: glyph_key.family, - glyph_index: glyph_key.glyph_index, - size_px: glyph_key.size_px, - cached_glyph_index, - }) - } - }, - )); - - // The new glyphs might not be in the right order, because HashMap doesn't gaurantee - // iteration order. So we need to sort them here by their cached_glyph_index. - self.glyphs[glyphs_len..].sort_by_key( - |Glyph { - family: _, - glyph_index: _, - size_px: _, - cached_glyph_index, - }| *cached_glyph_index, + pub fn update_atlas(&mut self) -> (&[TouchedGlyph], Option<&[u8]>) { + debug_assert_eq!( + self.touched_glyph_lookup.len(), + self.next_touched_glyph_index as usize ); + self.touched_glyphs + .resize(self.touched_glyph_lookup.len(), TouchedGlyph::default()); + self.next_touched_glyph_index = 0; + + let mut is_emergency_repack = false; + + 'emergency_repack: loop { + let cached_glyphs_len = self.cached_glyphs.len(); + + // For each touched glyph, try and find it in our cached glyph list. + for (&glyph_key, touched_glyph_info) in self.touched_glyph_lookup.iter() { + match self + .cached_glyph_indices_sorted + .binary_search_by_key(&glyph_key, |&cached_glyph_index| { + self.cached_glyphs[cached_glyph_index].glyph_key + }) { + // We've already cached this glyph. So we just need to write the `touched_glyphs` + // information. + Ok(index) => { + let cached_glyph_index = self.cached_glyph_indices_sorted[index]; + + let touched_glyph = &mut self.touched_glyphs + [touched_glyph_info.touched_glyph_index.0 as usize]; + + let rect = &self.rects[cached_glyph_index]; + touched_glyph.x0 = rect.x; + touched_glyph.x1 = rect.x + rect.w; + touched_glyph.y0 = rect.y; + touched_glyph.y1 = rect.y + rect.h; + + let cached_glyph = &self.cached_glyphs[cached_glyph_index]; + touched_glyph.offset_x0 = cached_glyph.offset_x0; + touched_glyph.offset_y0 = cached_glyph.offset_y0; + touched_glyph.offset_x1 = cached_glyph.offset_x1; + touched_glyph.offset_y1 = cached_glyph.offset_y1; + } + // This glyph isn't cached, so we must prepare to pack and render it. + Err(_) => { + let font = self.fonts.font(glyph_key.family); + let size_px = glyph_key.size_px.get(); + let scale = font.scale_for_size_px(size_px); + let oversample = Self::oversample_for_size(size_px); + + let bitmap_box = font.glyph_bitmap_box( + touched_glyph_info.glyph_index, + scale * oversample.as_f32(), + scale * oversample.as_f32(), + 0.0, + 0.0, + ); + + let w = bitmap_box.x1 - bitmap_box.x0 + + self.padding as i32 + + oversample.as_i32() + - 1; + let h = bitmap_box.y1 - bitmap_box.y0 + + self.padding as i32 + + oversample.as_i32() + - 1; + + self.cached_glyphs.push(CachedGlyph { + glyph_key, + glyph_index: touched_glyph_info.glyph_index, + offset_x0: 0.0, + offset_y0: 0.0, + offset_x1: 0.0, + offset_y1: 0.0, + }); + + self.rects.push(Rect { + id: touched_glyph_info.touched_glyph_index.0 as i32, + w, + h, + x: 0, + y: 0, + was_packed: 0, + }); + } + } + } + + // If we haven't added anything new, we're done here. + if self.cached_glyphs.len() == cached_glyphs_len { + self.touched_glyph_lookup.clear(); + return (&self.touched_glyphs, None); + } + + // Pack any new glyphs we might have. + // + // If packing fails, wipe the cache and try again with a full repack and render of the + // touched_glyphs this iteration. + if !self.packer.pack(&mut self.rects[cached_glyphs_len..]) { + assert!( + !is_emergency_repack, + "emergency repack failed, texture atlas exhausted" + ); - debug_assert!(self - .glyphs - .iter() - .enumerate() - .all(|(i, cached_glyph)| cached_glyph.cached_glyph_index.0 as usize == i)); - - let padding = self.padding as i32; - - // Add new rects for the new cached glyphs. - self.rects - .extend(self.glyphs[glyphs_len..].iter().map(|glyph| { - let font = self.fonts.font(glyph.family); - let size_px = glyph.size_px.get(); + self.cached_glyph_indices_sorted.clear(); + self.cached_glyphs.clear(); + self.rects.clear(); + self.packer.clear(); + self.texture.fill(0); + + is_emergency_repack = true; + continue 'emergency_repack; + } + + // Render any new glyphs we might have. + for (cached_glyph, rect) in self.cached_glyphs[cached_glyphs_len..] + .iter_mut() + .zip(self.rects[cached_glyphs_len..].iter_mut()) + { + let font = self.fonts.font(cached_glyph.glyph_key.family); + + // Pad on left and top. + let padding = self.padding as i32; + rect.x += padding; + rect.y += padding; + rect.w -= padding; + rect.h -= padding; + + let size_px = cached_glyph.glyph_key.size_px.get(); let scale = font.scale_for_size_px(size_px); let oversample = Self::oversample_for_size(size_px); - let bitmap_box = font.glyph_bitmap_box( - glyph.glyph_index, + let scale_x = scale * oversample.as_f32(); + let scale_y = scale * oversample.as_f32(); + + let (sub_x, sub_y) = font.render_glyph_bitmap( + &mut self.texture, + rect.x, + rect.y, + rect.w, + rect.h, + self.width as i32, + scale_x, + scale_y, + 0.0, + 0.0, + oversample, + oversample, + cached_glyph.glyph_index, + ); + + let GlyphBitmapBox { + x0, + x1: _, + y0, + y1: _, + } = font.glyph_bitmap_box( + cached_glyph.glyph_index, scale * oversample.as_f32(), scale * oversample.as_f32(), 0.0, 0.0, ); - let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample.as_i32() - 1; - let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample.as_i32() - 1; - - Rect { - id: glyph.cached_glyph_index.0 as i32, - w, - h, - x: 0, - y: 0, - was_packed: 0, - } - })); - - // TODO: Emergency re-pack when this fails, dropping glyphs unused this frame. - assert!(self.packer.pack(&mut self.rects[glyphs_len..])); - - self.cached_glyphs - .resize(self.glyphs.len(), CachedGlyph::default()); - - // Render the new glyphs. - for (glyph, rect) in self.glyphs[glyphs_len..] - .iter() - .zip(self.rects[glyphs_len..].iter_mut()) - { - let font = self.fonts.font(glyph.family); - - // Pad on left and top. - let padding = self.padding as i32; - rect.x += padding; - rect.y += padding; - rect.w -= padding; - rect.h -= padding; - - let size_px = glyph.size_px.get(); - let scale = font.scale_for_size_px(size_px); - let oversample = Self::oversample_for_size(size_px); - - let scale_x = scale * oversample.as_f32(); - let scale_y = scale * oversample.as_f32(); - - let (sub_x, sub_y) = font.render_glyph_bitmap( - &mut self.texture, - rect.x, - rect.y, - rect.w, - rect.h, - self.width as i32, - scale_x, - scale_y, - 0.0, - 0.0, - oversample, - oversample, - glyph.glyph_index, - ); - - let cached_glyph = &mut self.cached_glyphs[rect.id as usize]; - - cached_glyph.x0 = rect.x; - cached_glyph.x1 = rect.x + rect.w; - cached_glyph.y0 = rect.y; - cached_glyph.y1 = rect.y + rect.h; - - let GlyphBitmapBox { - x0, - x1: _, - y0, - y1: _, - } = font.glyph_bitmap_box( - glyph.glyph_index, - scale * oversample.as_f32(), - scale * oversample.as_f32(), - 0.0, - 0.0, - ); - - cached_glyph.offset_x0 = x0 as f32 / oversample.as_f32() + sub_x; - cached_glyph.offset_y0 = y0 as f32 / oversample.as_f32() + sub_y; - cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x; - cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y; + let offset_x0 = x0 as f32 / oversample.as_f32() + sub_x; + let offset_y0 = y0 as f32 / oversample.as_f32() + sub_y; + let offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x; + let offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y; + + cached_glyph.offset_x0 = offset_x0; + cached_glyph.offset_y0 = offset_y0; + cached_glyph.offset_x1 = offset_x1; + cached_glyph.offset_y1 = offset_y1; + + let touched_glyph = &mut self.touched_glyphs[rect.id as usize]; + + touched_glyph.x0 = rect.x; + touched_glyph.x1 = rect.x + rect.w; + touched_glyph.y0 = rect.y; + touched_glyph.y1 = rect.y + rect.h; + + touched_glyph.offset_x0 = offset_x0; + touched_glyph.offset_y0 = offset_y0; + touched_glyph.offset_x1 = offset_x1; + touched_glyph.offset_y1 = offset_y1; + } + + self.cached_glyph_indices_sorted.clear(); + self.cached_glyph_indices_sorted + .extend(0..self.cached_glyphs.len()); + self.cached_glyph_indices_sorted + .sort_unstable_by_key(|&index| self.cached_glyphs[index].glyph_key); + + self.touched_glyph_lookup.clear(); + return (&self.touched_glyphs, Some(&self.texture)); } - - Some((&self.cached_glyphs, &self.texture)) } } diff --git a/libs/narcissus-font/src/font.rs b/libs/narcissus-font/src/font.rs index 26aa063..9a326e0 100644 --- a/libs/narcissus-font/src/font.rs +++ b/libs/narcissus-font/src/font.rs @@ -237,5 +237,5 @@ impl<'a> Font<'a> { pub trait FontCollection<'a> { type Family: Copy + Eq + Ord + std::hash::Hash; - fn font(&self, font_family: Self::Family) -> &Font<'a>; + fn font(&self, family: Self::Family) -> &Font<'a>; } diff --git a/libs/narcissus-font/src/lib.rs b/libs/narcissus-font/src/lib.rs index e91386b..75eb07e 100644 --- a/libs/narcissus-font/src/lib.rs +++ b/libs/narcissus-font/src/lib.rs @@ -2,6 +2,6 @@ mod cache; mod font; mod packer; -pub use cache::{CachedGlyph, CachedGlyphIndex, GlyphCache}; +pub use cache::{GlyphCache, TouchedGlyph, TouchedGlyphIndex, TouchedGlyphInfo}; pub use font::{Font, FontCollection, GlyphIndex, Oversample}; pub use packer::{Packer, Rect}; -- 2.49.0