[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,
fonts: HashMap<u32, text::FontCache>,
font_textures: HashMap<u32, wgpu::BindGroup>,
write_textures: HashMap<u32, texture::Texture>,
@ -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<Self> {
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);
}

View file

@ -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<GlyphCell>,
cell_cache: LruCache<CellCacheKey, usize>,
@ -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,
}
}
}