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,
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();
let mut glyph_buffer = MappedBuffer::new(
device.as_ref(),
BufferUsageFlags::STORAGE,
- std::mem::size_of::<CachedGlyph>() * MAX_GLYPHS,
+ std::mem::size_of::<TouchedGlyph>() * MAX_GLYPHS,
);
let glyph_atlas = device.create_image(&ImageDesc {
}
}
- let mut num_chars = 0;
+ let mut space_count = 0;
let start_time = Instant::now();
'main: loop {
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 => {
let line1 = "加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して";
let mut glyph_instances = Vec::new();
- let mut glyph_indices = Vec::new();
let mut x;
let mut y = 0.0;
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;
}
}
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;
);
device.destroy_buffer(&frame, buffer);
-
- image
- };
+ }
}
device.cmd_begin_rendering(
-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;
/// 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,
pub offset_y1: f32,
}
-#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
struct GlyphKey<Family> {
family: Family,
- glyph_index: GlyphIndex,
+ c: char,
size_px: FiniteF32,
}
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
-struct Glyph<F> {
- 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<F> {
+ glyph_key: GlyphKey<F>,
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>
fonts: &'a F,
padding: usize,
+ width: usize,
+ height: usize,
+ texture: Box<[u8]>,
- next_cached_glyph_index: u32,
- cached_glyph_lookup: FxHashMap<GlyphKey<F::Family>, CachedGlyphIndex>,
-
- glyphs: Vec<Glyph<F::Family>>,
+ next_touched_glyph_index: u32,
+ touched_glyph_lookup: FxHashMap<GlyphKey<F::Family>, TouchedGlyphInfo>,
+ touched_glyphs: Vec<TouchedGlyph>,
- packer: Packer,
+ cached_glyph_indices_sorted: Vec<usize>,
+ cached_glyphs: Vec<CachedGlyph<F::Family>>,
rects: Vec<Rect>,
- cached_glyphs: Vec<CachedGlyph>,
-
- width: usize,
- height: usize,
- texture: Box<[u8]>,
+ packer: Packer,
}
impl<'a, F> GlyphCache<'a, F>
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(),
}
}
}
}
- 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))
}
}