[oden] It doesn't crash but it doesn't work either

This commit is contained in:
John Doty 2023-08-31 14:46:04 -07:00
parent 71aa3c39f7
commit 8914b1795f
2 changed files with 162 additions and 50 deletions

View file

@ -411,6 +411,7 @@ struct State {
text_bind_group_layout: wgpu::BindGroupLayout, text_bind_group_layout: wgpu::BindGroupLayout,
fonts: HashMap<u32, text::FontCache>, fonts: HashMap<u32, text::FontCache>,
font_textures: HashMap<u32, wgpu::BindGroup>,
write_textures: HashMap<u32, texture::Texture>, write_textures: HashMap<u32, texture::Texture>,
@ -693,6 +694,30 @@ impl State {
multiview: None, 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 { let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Vertex Buffer"), label: Some("Sprite Vertex Buffer"),
contents: bytemuck::cast_slice(SPRITE_VERTICES), contents: bytemuck::cast_slice(SPRITE_VERTICES),
@ -721,7 +746,8 @@ impl State {
text_instance_buffers: VertexBufferPool::new(), text_instance_buffers: VertexBufferPool::new(),
text_bind_group_layout, text_bind_group_layout,
fonts: HashMap::new(), fonts,
font_textures,
write_textures: HashMap::new(), write_textures: HashMap::new(),
screen_uniform, screen_uniform,
@ -852,6 +878,7 @@ impl State {
enum DrawMode { enum DrawMode {
Sprites, Sprites,
Circles, Circles,
Text,
} }
trait DrawModeInstance: Sized { 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<Self> {
state.text_instance_buffers.get_mut(&vb)
}
}
#[derive(Debug)] #[derive(Debug)]
struct DrawCall { struct DrawCall {
mode: DrawMode, mode: DrawMode,
@ -947,6 +985,17 @@ impl DrawCall {
.buffer .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 => { DrawMode::Circles => {
&state &state
.circle_instance_buffers .circle_instance_buffers
@ -1060,7 +1109,7 @@ impl<'a> FrameBuilder<'a> {
GraphicsCommand::StrokeColor(c) => { GraphicsCommand::StrokeColor(c) => {
self.stroke_color = 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::Sprite(si) => self.push_sprite(si),
GraphicsCommand::Circle(cc) => self.push_circle(cc), GraphicsCommand::Circle(cc) => self.push_circle(cc),
GraphicsCommand::UseTexture(id) => self.use_texture(id), GraphicsCommand::UseTexture(id) => self.use_texture(id),
@ -1140,6 +1189,11 @@ impl<'a> FrameBuilder<'a> {
.state .state
.circle_instance_buffers .circle_instance_buffers
.get_a_buffer(&self.state.device), .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) { fn flush(&mut self) {
if self.draw_calls.len() > 0 { if self.draw_calls.len() > 0 {
let mut pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@ -1220,6 +1305,7 @@ impl<'a> FrameBuilder<'a> {
match call.mode { match call.mode {
DrawMode::Sprites => pass.set_pipeline(&self.state.sprite_pipeline), DrawMode::Sprites => pass.set_pipeline(&self.state.sprite_pipeline),
DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline), DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline),
DrawMode::Text => pass.set_pipeline(&self.state.text_pipeline),
} }
last_mode = Some(call.mode); last_mode = Some(call.mode);
} }

View file

@ -45,12 +45,9 @@ enum CellCacheKey {
pub struct FontCache { pub struct FontCache {
font: Font, font: Font,
size: f32, size: f32,
atlas_width: u16,
atlas_height: u16,
char_width: u16,
char_height: u16,
texture: wgpu::Texture, texture: wgpu::Texture,
view: wgpu::TextureView, pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
cells: Vec<GlyphCell>, cells: Vec<GlyphCell>,
cell_cache: LruCache<CellCacheKey, usize>, cell_cache: LruCache<CellCacheKey, usize>,
@ -58,7 +55,6 @@ pub struct FontCache {
enum SlotState { enum SlotState {
Empty, Empty,
Allocated(u16, fontdue::Metrics),
Rendered(u16, fontdue::Metrics), Rendered(u16, fontdue::Metrics),
} }
@ -70,8 +66,15 @@ struct GlyphCell {
state: SlotState, state: SlotState,
} }
pub struct Glyph {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl FontCache { 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()) let font = fontdue::Font::from_bytes(bytes, fontdue::FontSettings::default())
.expect("Could not parse font"); .expect("Could not parse font");
@ -90,10 +93,21 @@ impl FontCache {
sample_count: 4, sample_count: 4,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm, 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: &[], view_formats: &[],
}); });
let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); 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. // Measure the font to figure out the size of a cell in the cache.
// NOTE: This metric nonsense is bad, probably. // NOTE: This metric nonsense is bad, probably.
@ -133,28 +147,44 @@ impl FontCache {
); );
} }
// Set up the binding group and pipeline and whatnot.
FontCache { FontCache {
font, font,
size, size,
atlas_width,
atlas_height,
char_width: char_width as u16,
char_height: char_height as u16,
texture, texture,
view, view,
sampler,
cells, cells,
cell_cache, cell_cache,
} }
} }
fn rasterize_character(&mut self, queue: &wgpu::Queue, index: u16, slot: &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);
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
}
};
// 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); let (metrics, bitmap) = self.font.rasterize_indexed(index, self.size);
let mut texture = self.texture.as_image_copy(); let mut texture = self.texture.as_image_copy();
texture.origin.x = slot.x.into(); texture.origin.x = cell.x.into();
texture.origin.y = slot.y.into(); texture.origin.y = cell.y.into();
queue.write_texture( queue.write_texture(
texture, texture,
@ -170,22 +200,18 @@ impl FontCache {
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
); );
}
fn get_glyph_slot(&mut self, index: u16) -> &mut GlyphCell { cell.state = SlotState::Rendered(index, metrics.clone());
let key = CellCacheKey::GlyphIndex(index); metrics
if let Some(cell_index) = self.cell_cache.get(&key) {
return &mut self.cells[*cell_index];
} }
};
if let Some((_, cell_index)) = self.cell_cache.pop_lru() { Glyph {
self.cell_cache.put(key, cell_index); x: cell.x as f32 + metrics.bounds.xmin,
let cell = &mut self.cells[cell_index]; y: cell.y as f32 + metrics.bounds.ymin,
cell.state = SlotState::Empty; w: metrics.bounds.width,
return cell; h: metrics.bounds.height,
} }
panic!("Did not put enough things in the LRU cache, why is it empty here?");
} }
} }