diff --git a/src/lib.rs b/src/lib.rs index 2e86f274..2202f4d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -411,6 +411,7 @@ struct State { text_bind_group_layout: wgpu::BindGroupLayout, fonts: HashMap, + font_textures: HashMap, write_textures: HashMap, @@ -693,6 +694,30 @@ impl State { multiview: None, }); + let inconsolata = include_bytes!("./Inconsolata-Regular.ttf") as &[u8]; + let inconsolata = text::FontCache::new(&device, inconsolata, 16.0); + + let mut font_textures = HashMap::new(); + { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &text_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&inconsolata.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&inconsolata.sampler), + }, + ], + label: Some("inconsolata"), + }); + font_textures.insert(0, bind_group); + } + let mut fonts = HashMap::new(); + fonts.insert(0, inconsolata); + let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Sprite Vertex Buffer"), contents: bytemuck::cast_slice(SPRITE_VERTICES), @@ -721,7 +746,8 @@ impl State { text_instance_buffers: VertexBufferPool::new(), text_bind_group_layout, - fonts: HashMap::new(), + fonts, + font_textures, write_textures: HashMap::new(), screen_uniform, @@ -852,6 +878,7 @@ impl State { enum DrawMode { Sprites, Circles, + Text, } trait DrawModeInstance: Sized { @@ -885,6 +912,17 @@ impl DrawModeInstance for CircleInstance { } } +impl DrawModeInstance for GlyphInstance { + const MODE: DrawMode = DrawMode::Text; + + fn get_vertex_buffer<'a>( + state: &'a mut State, + vb: &VertexBufferHandle, + ) -> &'a mut VertexBuffer { + state.text_instance_buffers.get_mut(&vb) + } +} + #[derive(Debug)] struct DrawCall { mode: DrawMode, @@ -947,6 +985,17 @@ impl DrawCall { .buffer } + DrawMode::Text => { + match self.texture_id { + Some(id) => { + let bind_group = state.font_textures.get(&id).unwrap(); + pass.set_bind_group(1, bind_group, &[]); + } + None => (), + } + &state.text_instance_buffers.get(&self.vertex_buffer).buffer + } + DrawMode::Circles => { &state .circle_instance_buffers @@ -1060,7 +1109,7 @@ impl<'a> FrameBuilder<'a> { GraphicsCommand::StrokeColor(c) => { self.stroke_color = c; } - GraphicsCommand::Print(pc) => println!("{}", pc.text), + GraphicsCommand::Print(pc) => self.push_text(pc), GraphicsCommand::Sprite(si) => self.push_sprite(si), GraphicsCommand::Circle(cc) => self.push_circle(cc), GraphicsCommand::UseTexture(id) => self.use_texture(id), @@ -1140,6 +1189,11 @@ impl<'a> FrameBuilder<'a> { .state .circle_instance_buffers .get_a_buffer(&self.state.device), + + DrawMode::Text => self + .state + .text_instance_buffers + .get_a_buffer(&self.state.device), } } @@ -1186,6 +1240,37 @@ impl<'a> FrameBuilder<'a> { }); } + fn push_text(&mut self, pc: script::graphics::PrintCommand) { + println!("{}", pc.text); + let mut cursor_x = 0.0; + let cursor_y = 0.0; + + self.mode = DrawMode::Text; // ? + self.use_texture(0); // ? + + let font_id: u32 = 0; + let font = self.state.fonts.get_mut(&font_id).unwrap(); + let mut glyphs = vec![]; + for char in pc.text.chars() { + let color = self.fill_color.clone(); + let glyph = font.get_char(&self.state.queue, char); + glyphs.push(GlyphInstance { + src_top_left: [glyph.x, glyph.y], + src_dims: [glyph.w, glyph.h], + dest_top_left: [cursor_x, cursor_y], + dest_dims: [glyph.w, glyph.h], + color, + }); + cursor_x += glyph.w; + } + + for glyph in glyphs { + // TODO: Get new instance buffer when we don't have space for + // this glyph instead of doing this over and over. + self.get_instance_buffer().vec.push(glyph); + } + } + fn flush(&mut self) { if self.draw_calls.len() > 0 { let mut pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -1220,6 +1305,7 @@ impl<'a> FrameBuilder<'a> { match call.mode { DrawMode::Sprites => pass.set_pipeline(&self.state.sprite_pipeline), DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline), + DrawMode::Text => pass.set_pipeline(&self.state.text_pipeline), } last_mode = Some(call.mode); } diff --git a/src/text.rs b/src/text.rs index 271c754c..25eec3f9 100644 --- a/src/text.rs +++ b/src/text.rs @@ -45,12 +45,9 @@ enum CellCacheKey { pub struct FontCache { font: Font, size: f32, - atlas_width: u16, - atlas_height: u16, - char_width: u16, - char_height: u16, texture: wgpu::Texture, - view: wgpu::TextureView, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, cells: Vec, cell_cache: LruCache, @@ -58,7 +55,6 @@ pub struct FontCache { enum SlotState { Empty, - Allocated(u16, fontdue::Metrics), Rendered(u16, fontdue::Metrics), } @@ -70,8 +66,15 @@ struct GlyphCell { state: SlotState, } +pub struct Glyph { + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, +} + impl FontCache { - fn new(device: &wgpu::Device, bytes: &[u8], size: f32) -> Self { + pub fn new(device: &wgpu::Device, bytes: &[u8], size: f32) -> Self { let font = fontdue::Font::from_bytes(bytes, fontdue::FontSettings::default()) .expect("Could not parse font"); @@ -90,10 +93,21 @@ impl FontCache { sample_count: 4, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); // Measure the font to figure out the size of a cell in the cache. // NOTE: This metric nonsense is bad, probably. @@ -133,59 +147,71 @@ impl FontCache { ); } - // Set up the binding group and pipeline and whatnot. - FontCache { font, size, - atlas_width, - atlas_height, - char_width: char_width as u16, - char_height: char_height as u16, texture, view, + sampler, cells, cell_cache, } } - fn rasterize_character(&mut self, queue: &wgpu::Queue, index: u16, slot: &GlyphCell) { - let (metrics, bitmap) = self.font.rasterize_indexed(index, self.size); - - let mut texture = self.texture.as_image_copy(); - texture.origin.x = slot.x.into(); - texture.origin.y = slot.y.into(); - - queue.write_texture( - texture, - &bitmap, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(metrics.width as u32), - rows_per_image: None, - }, - wgpu::Extent3d { - width: metrics.width as u32, - height: metrics.height as u32, - depth_or_array_layers: 1, - }, - ); - } - - fn get_glyph_slot(&mut self, index: u16) -> &mut GlyphCell { + pub fn get_char(&mut self, queue: &wgpu::Queue, c: char) -> Glyph { + let index = self.font.lookup_glyph_index(c); let key = CellCacheKey::GlyphIndex(index); - if let Some(cell_index) = self.cell_cache.get(&key) { - return &mut self.cells[*cell_index]; - } + let cell = match self.cell_cache.get(&key) { + Some(cell_index) => &mut self.cells[*cell_index], + None => { + let (_, cell_index) = self + .cell_cache + .pop_lru() + .expect("did not put all available things in the LRU cache"); + self.cell_cache.put(key, cell_index); + let cell = &mut self.cells[cell_index]; + cell.state = SlotState::Empty; // This isn't what it used to be. + cell + } + }; - if let Some((_, cell_index)) = self.cell_cache.pop_lru() { - self.cell_cache.put(key, cell_index); - let cell = &mut self.cells[cell_index]; - cell.state = SlotState::Empty; - return cell; - } + // I mean, technically if we got an LRU hit here it's rendered, but + // convincing the compiler of that is a pain. + let metrics = match cell.state { + SlotState::Rendered(_, metrics) => metrics, + SlotState::Empty => { + let (metrics, bitmap) = self.font.rasterize_indexed(index, self.size); - panic!("Did not put enough things in the LRU cache, why is it empty here?"); + let mut texture = self.texture.as_image_copy(); + texture.origin.x = cell.x.into(); + texture.origin.y = cell.y.into(); + + queue.write_texture( + texture, + &bitmap, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(metrics.width as u32), + rows_per_image: None, + }, + wgpu::Extent3d { + width: metrics.width as u32, + height: metrics.height as u32, + depth_or_array_layers: 1, + }, + ); + + cell.state = SlotState::Rendered(index, metrics.clone()); + metrics + } + }; + + Glyph { + x: cell.x as f32 + metrics.bounds.xmin, + y: cell.y as f32 + metrics.bounds.ymin, + w: metrics.bounds.width, + h: metrics.bounds.height, + } } }