use std::{path::Path, time::Instant};
-use narcissus_app::{create_app, Event, Key, WindowDesc};
+use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc};
use narcissus_core::{default, obj, rand::Pcg64};
use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache};
use narcissus_gpu::{
const MAX_SHARKS: usize = 262_144;
const NUM_SHARKS: usize = 50;
+const GLYPH_CACHE_WIDTH: usize = 1024;
+const GLYPH_CACHE_HEIGHT: usize = 512;
const MAX_GLYPH_INSTANCES: usize = 262_144;
const MAX_GLYPHS: usize = 8192;
let text_pipeline = TextPipeline::new(device.as_ref());
let fonts = Fonts::new();
- let mut glyph_cache = GlyphCache::new(&fonts, 1024, 512, 1);
+ let mut glyph_cache = GlyphCache::new(&fonts, GLYPH_CACHE_WIDTH, GLYPH_CACHE_HEIGHT, 1);
let blåhaj_image = load_image("bins/narcissus/data/blåhaj.png");
let (blåhaj_vertices, blåhaj_indices) = load_obj("bins/narcissus/data/blåhaj.obj");
std::mem::size_of::<CachedGlyph>() * MAX_GLYPHS,
);
+ let glyph_atlas = device.create_image(&ImageDesc {
+ location: MemoryLocation::Device,
+ usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST,
+ dimension: ImageDimension::Type2d,
+ format: ImageFormat::R8_UNORM,
+ initial_layout: ImageLayout::Optimal,
+ width: glyph_cache.width() as u32,
+ height: glyph_cache.height() as u32,
+ depth: 1,
+ layer_count: 1,
+ mip_levels: 1,
+ });
+
+ {
+ let frame = device.begin_frame();
+ let mut cmd_buffer = device.create_cmd_buffer(&frame, &thread_token);
+ device.cmd_barrier(
+ &mut cmd_buffer,
+ None,
+ &[ImageBarrier::layout_optimal(
+ &[Access::None],
+ &[Access::ShaderSampledImageRead],
+ glyph_atlas,
+ ImageAspectFlags::COLOR,
+ )],
+ );
+ device.submit(&frame, cmd_buffer);
+ device.end_frame(frame);
+ }
+
let text_sampler = device.create_sampler(&SamplerDesc {
filter: SamplerFilter::Bilinear,
address_mode: SamplerAddressMode::Clamp,
}
}
+ let mut num_chars = 0;
+
let start_time = Instant::now();
'main: loop {
let frame = device.begin_frame();
KeyPress {
window_id: _,
key,
- pressed: _,
+ pressed,
modifiers: _,
} => {
if key == Key::Escape {
break 'main;
}
+ if key == Key::Space && pressed == PressedState::Released {
+ num_chars += 1;
+ println!("{num_chars}");
+ }
}
Quit => {
break 'main;
y = y.trunc();
glyph_indices.clear();
- glyph_indices.extend(text.chars().map(|c| {
+ glyph_indices.extend(text.chars().take(num_chars).map(|c| {
font.glyph_index(c)
.unwrap_or_else(|| font.glyph_index('□').unwrap())
}));
let atlas_width = glyph_cache.width() as u32;
let atlas_height = glyph_cache.height() as u32;
- let (cached_glyphs, texture) = glyph_cache.update_atlas();
-
text_uniforms.write(TextUniforms {
screen_width: width,
screen_height: height,
atlas_width,
atlas_height,
});
- cached_glyph_buffer.write_slice(cached_glyphs);
glyph_instance_buffer.write_slice(&glyph_instances);
- // upload atlas
- let glyph_atlas = {
- let width = atlas_width;
- let height = atlas_height;
- let data = texture;
-
- let buffer =
- create_buffer_with_data(device.as_ref(), BufferUsageFlags::TRANSFER_SRC, data);
-
- let image = device.create_image(&ImageDesc {
- location: MemoryLocation::Device,
- usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST,
- dimension: ImageDimension::Type2d,
- format: ImageFormat::R8_UNORM,
- initial_layout: ImageLayout::Optimal,
- width,
- height,
- depth: 1,
- layer_count: 1,
- mip_levels: 1,
- });
-
- device.cmd_barrier(
- &mut cmd_buffer,
- None,
- &[ImageBarrier::layout_optimal(
- &[Access::None],
- &[Access::TransferWrite],
- image,
- ImageAspectFlags::COLOR,
- )],
- );
-
- device.cmd_copy_buffer_to_image(
- &mut cmd_buffer,
- buffer,
- image,
- ImageLayout::Optimal,
- &[BufferImageCopy {
- buffer_offset: 0,
- buffer_row_length: 0,
- buffer_image_height: 0,
- image_subresource: default(),
- image_offset: Offset3d { x: 0, y: 0, z: 0 },
- image_extent: Extent3d {
- width,
- height,
- depth: 1,
- },
- }],
- );
-
- device.cmd_barrier(
- &mut cmd_buffer,
- None,
- &[ImageBarrier::layout_optimal(
- &[Access::TransferWrite],
- &[Access::FragmentShaderSampledImageRead],
+ if let Some((cached_glyphs, texture)) = glyph_cache.update_atlas() {
+ cached_glyph_buffer.write_slice(cached_glyphs);
+
+ // upload atlas
+ {
+ let width = atlas_width;
+ let height = atlas_height;
+ let image = glyph_atlas;
+ let data = texture;
+
+ let buffer =
+ create_buffer_with_data(device.as_ref(), BufferUsageFlags::TRANSFER_SRC, data);
+
+ device.cmd_barrier(
+ &mut cmd_buffer,
+ None,
+ &[ImageBarrier::layout_optimal(
+ &[Access::ShaderSampledImageRead],
+ &[Access::TransferWrite],
+ image,
+ ImageAspectFlags::COLOR,
+ )],
+ );
+
+ device.cmd_copy_buffer_to_image(
+ &mut cmd_buffer,
+ buffer,
image,
- ImageAspectFlags::COLOR,
- )],
- );
-
- device.destroy_buffer(&frame, buffer);
-
- image
- };
+ ImageLayout::Optimal,
+ &[BufferImageCopy {
+ buffer_offset: 0,
+ buffer_row_length: 0,
+ buffer_image_height: 0,
+ image_subresource: default(),
+ image_offset: Offset3d { x: 0, y: 0, z: 0 },
+ image_extent: Extent3d {
+ width,
+ height,
+ depth: 1,
+ },
+ }],
+ );
+
+ device.cmd_barrier(
+ &mut cmd_buffer,
+ None,
+ &[ImageBarrier::layout_optimal(
+ &[Access::TransferWrite],
+ &[Access::FragmentShaderSampledImageRead],
+ image,
+ ImageAspectFlags::COLOR,
+ )],
+ );
+
+ device.destroy_buffer(&frame, buffer);
+
+ image
+ };
+ }
device.cmd_begin_rendering(
&mut cmd_buffer,
device.cmd_end_rendering(&mut cmd_buffer);
- device.destroy_image(&frame, glyph_atlas);
-
device.submit(&frame, cmd_buffer);
device.end_frame(frame);
padding,
- glyphs: Vec::new(),
-
next_cached_glyph_index: 0,
cached_glyph_lookup: Default::default(),
+ glyphs: Vec::new(),
+
packer: Packer::new(width - padding, height - padding),
rects: Vec::new(),
})
}
- pub fn update_atlas(&mut self) -> (&[CachedGlyph], &[u8]) {
- // We recreate the CachedGlyphs structure completely every update, so reset the index here.
- self.next_cached_glyph_index = 0;
+ pub fn update_atlas(&mut self) -> Option<(&[CachedGlyph], &[u8])> {
+ let glyphs_len = self.cached_glyphs.len();
- self.glyphs.clear();
- self.glyphs.extend(self.cached_glyph_lookup.iter().map(
- |(glyph_key, &cached_glyph_index)| Glyph {
- family: glyph_key.family,
- glyph_index: glyph_key.glyph_index,
- size_px: glyph_key.size_px,
- cached_glyph_index,
- },
- ));
+ // 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;
+ }
- // Sort just so we avoid ping-ponging between fonts during rendering.
- self.glyphs.sort_unstable();
+ // 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,
+ );
+
+ 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;
- self.rects.clear();
- self.rects.extend(self.glyphs.iter().map(|glyph| {
- let font = self.fonts.font(glyph.family);
- let size_px = glyph.size_px.get();
- let scale = font.scale_for_size_px(size_px);
- let oversample = Self::oversample_for_size(size_px);
+ // 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();
+ 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,
+ 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..]));
- let bitmap_box = font.glyph_bitmap_box(
- 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,
- }
- }));
-
- self.packer.clear();
- assert!(self.packer.pack(&mut self.rects));
-
- self.texture.fill(0);
self.cached_glyphs
.resize(self.glyphs.len(), CachedGlyph::default());
- let padding = self.padding as i32;
-
- for (glyph, rect) in self.glyphs.iter().zip(self.rects.iter_mut()) {
+ // 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;
cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y;
}
- (&self.cached_glyphs, &self.texture)
+ Some((&self.cached_glyphs, &self.texture))
}
}