From 71aa3c39f7f7a70c1047b9e50dc12cc6ea448c6c Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 31 Aug 2023 08:22:59 -0700 Subject: [PATCH] [oden] Let's get started on text This has already been a journey and it will keep being a journey I think. --- Cargo.lock | 60 +++++++++- Cargo.toml | 2 + src/Inconsolata-Regular.ttf | Bin 0 -> 97864 bytes src/lib.rs | 146 ++++++++++++++++++++++++ src/text.rs | 214 ++++++++++++++++++++++++++++++++++++ src/text_shader.wgsl | 85 ++++++++++++++ 6 files changed, 503 insertions(+), 4 deletions(-) create mode 100644 src/Inconsolata-Regular.ttf create mode 100644 src/text.rs create mode 100644 src/text_shader.wgsl diff --git a/Cargo.lock b/Cargo.lock index 0f1a715c..b0f0a3ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" version = "0.4.1" @@ -481,7 +487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.12.3", "lock_api", "once_cell", "parking_lot_core", @@ -595,6 +601,16 @@ dependencies = [ "miniz_oxide 0.7.1", ] +[[package]] +name = "fontdue" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" +dependencies = [ + "hashbrown 0.13.2", + "ttf-parser 0.15.2", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -765,7 +781,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags 1.3.2", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -786,6 +802,25 @@ dependencies = [ "ahash 0.7.6", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + [[package]] name = "hassle-rs" version = "0.10.0" @@ -865,7 +900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1143,6 +1178,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" +dependencies = [ + "hashbrown 0.14.0", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1489,8 +1533,10 @@ dependencies = [ "anyhow", "bytemuck", "env_logger", + "fontdue", "image", "log", + "lru", "notify", "oden-js", "pollster", @@ -1551,7 +1597,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" dependencies = [ - "ttf-parser", + "ttf-parser 0.19.0", ] [[package]] @@ -2646,6 +2692,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "ttf-parser" version = "0.19.0" diff --git a/Cargo.toml b/Cargo.toml index 25912249..9c3d0ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,10 @@ tracing = ["tracy-client/enable"] anyhow = "1.0" bytemuck = { version = "1.13", features = ["derive"] } env_logger = "0.10" +fontdue = "0.7.3" image = { version = "0.24", default-features = false, features = ["png"] } log = "0.4" +lru = "0.11.0" notify = "6" oden-js = { path = "oden-js" } pollster = "0.3" diff --git a/src/Inconsolata-Regular.ttf b/src/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..457d262cf5ec2f0555fcb8d38fefaa67af315a11 GIT binary patch literal 97864 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 literal 0 HcmV?d00001 diff --git a/src/lib.rs b/src/lib.rs index 410dea38..2e86f274 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use winit::{ mod script; use script::graphics::GraphicsCommand; +mod text; mod texture; #[repr(C)] @@ -144,6 +145,57 @@ impl CircleInstance { } } +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct GlyphInstance { + // TODO: If it becomes important I can load the cell information onto the + // GPU in a texture or something and then just specify the top-left + // and index. + src_top_left: [f32; 2], + src_dims: [f32; 2], + + dest_top_left: [f32; 2], + dest_dims: [f32; 2], + + color: [f32; 4], +} + +impl GlyphInstance { + fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 5, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + shader_location: 7, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress, + shader_location: 8, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 9, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct ScreenUniforms { @@ -354,6 +406,12 @@ struct State { circle_pipeline: wgpu::RenderPipeline, circle_instance_buffers: VertexBufferPool, + text_pipeline: wgpu::RenderPipeline, + text_instance_buffers: VertexBufferPool, + + text_bind_group_layout: wgpu::BindGroupLayout, + fonts: HashMap, + write_textures: HashMap, screen_uniform: ScreenUniforms, @@ -453,6 +511,9 @@ impl State { label: Some("camera_bind_group"), }); + // ==================================================================== + // Sprites + // ==================================================================== let sprite_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Sprite Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("sprite_shader.wgsl").into()), @@ -478,6 +539,7 @@ impl State { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: config.format, + // TODO: This should be premultiplied alpha blending probably. blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], @@ -503,6 +565,9 @@ impl State { multiview: None, }); + // ==================================================================== + // Circles + // ==================================================================== let circle_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Circle Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("circle_shader.wgsl").into()), @@ -553,6 +618,81 @@ impl State { multiview: None, }); + // ==================================================================== + // Text + // ==================================================================== + let text_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: true, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + label: Some("text_bind_group_layout"), + }); + + let text_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Text Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("text_shader.wgsl").into()), + }); + + let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Text Pipeline Layout"), + bind_group_layouts: &[&screen_uniform_bind_group_layout, &text_bind_group_layout], + push_constant_ranges: &[], + }); + + let text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Text Pipeline"), + layout: Some(&text_pipeline_layout), + vertex: wgpu::VertexState { + module: &text_shader, + entry_point: "vs_main", + buffers: &[Vertex::desc(), GlyphInstance::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &text_shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: true, + }, + multiview: None, + }); + let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Sprite Vertex Buffer"), contents: bytemuck::cast_slice(SPRITE_VERTICES), @@ -577,6 +717,12 @@ impl State { circle_pipeline, circle_instance_buffers: VertexBufferPool::new(), + text_pipeline, + text_instance_buffers: VertexBufferPool::new(), + + text_bind_group_layout, + fonts: HashMap::new(), + write_textures: HashMap::new(), screen_uniform, screen_uniform_buffer, diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 00000000..271c754c --- /dev/null +++ b/src/text.rs @@ -0,0 +1,214 @@ +use fontdue::Font; +use lru::LruCache; +use wgpu; + +/* +A note on how Casey Muratori's refterm works in terms of font rendering, +because it's not bad and handles things that we're not going to handle +because I just want to get some dang text on the dang screen right now. + +- Step 1: You break the text into runs of characters that need to be + rendered together. + +- Step 2: You figure out how many uniform cells this run occupies. + +- Step 3: For each cell, you figure out if you already have the necessary + part of the run in the texture atlas. (Right? Each part of the run has a + distinct ID based on the actual run and the cell within the run.) If you + don't have this [run,cell] in the atlas, then: + + - Step 3a: Render the run as a bitmap, if you haven't already. + - Step 3b: Get coordinates from the cache for this [run,cell] pair, + evicting something if necessary. + - Step 3c: Copy the part of the bitmap for this [run,cell] into the + texture atlas at the coordinates. + + Put the coordinates (either newly generated or pulled from the cache) into + the cell. + +- Step 4: Load all the cells onto the GPU and do a shader that renders the + cells. + +Specifically what I'm doing right now is going character-by-caracter, and not +doing runs and not handling things that are wider than a cell. I'm also not +doing the efficient big grid render because I want to render characters at +pixel offsets and kern between characters and whatnot, which is different +from what they're doing. Mine is almost certainly less efficient. + */ + +#[derive(Eq, PartialEq, Hash)] +enum CellCacheKey { + Garbage(u32), // Exists so that I can prime the LRU, not used generally. + GlyphIndex(u16), // Actual factual cache entries. +} + +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, + + cells: Vec, + cell_cache: LruCache, +} + +enum SlotState { + Empty, + Allocated(u16, fontdue::Metrics), + Rendered(u16, fontdue::Metrics), +} + +struct GlyphCell { + // The coordinates in the atlas + x: u16, + y: u16, + + state: SlotState, +} + +impl FontCache { + 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"); + + // Set up the texture that we'll use to cache the glyphs we're rendering. + let atlas_width = 2048; + let atlas_height = 2048; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Font Glyph Atlas"), + size: wgpu::Extent3d { + width: atlas_width.into(), + height: atlas_height.into(), + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 4, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + // Measure the font to figure out the size of a cell in the cache. + // NOTE: This metric nonsense is bad, probably. + let mut char_height = 0; + if let Some(line_metrics) = font.horizontal_line_metrics(size) { + char_height = (line_metrics.new_line_size + 0.5) as usize; + } + + let metrics = font.metrics('M', size); + let mut char_width = metrics.width; + char_height = metrics.height.max(char_height); + + let metrics = font.metrics('g', size); + char_width = metrics.width.max(char_width); + char_height = metrics.height.max(char_height); + + // Allocate the individual cells in the texture atlas; this records + // the state of what's in the cell and whatnot. + let mut cells = vec![]; + for y in (0..atlas_height).step_by(char_height) { + for x in (0..atlas_width).step_by(char_width) { + cells.push(GlyphCell { + x, + y, + state: SlotState::Empty, + }); + } + } + + // Allocate the LRU cache for the cells. Fill it with garbage so that + // we can always "allocate" by pulling from the LRU. + let mut cell_cache = LruCache::new(std::num::NonZeroUsize::new(cells.len()).unwrap()); + for i in 0..cells.len() { + cell_cache.put( + CellCacheKey::Garbage(i.try_into().expect("Too many cells!")), + i, + ); + } + + // 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, + 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 { + let key = CellCacheKey::GlyphIndex(index); + 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() { + self.cell_cache.put(key, cell_index); + let cell = &mut self.cells[cell_index]; + cell.state = SlotState::Empty; + return cell; + } + + panic!("Did not put enough things in the LRU cache, why is it empty here?"); + } +} + +// pub fn inconsolata() { +// let font = include_bytes!("./Inconsolata-Regular.ttf") as &[u8]; +// let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default()).unwrap(); + +// let text = "Hello World!"; + +// // Break to characters. +// let size = 16.0; +// let mut prev = None; +// let mut left = 0.0; +// for c in text.chars() { +// // TODO: Cache this. +// let (metrics, bitmap) = font.rasterize(c, size); +// left += match prev { +// Some(pc) => match font.horizontal_kern(pc, c, size) { +// Some(k) => k, +// None => 0.0, +// }, +// None => 0.0, +// }; +// left += metrics.advance_width; +// } +// } diff --git a/src/text_shader.wgsl b/src/text_shader.wgsl new file mode 100644 index 00000000..2bd11680 --- /dev/null +++ b/src/text_shader.wgsl @@ -0,0 +1,85 @@ +// ---------------------------------------------------------------------------- +// Vertex shader +// ---------------------------------------------------------------------------- + +struct VertexInput { + @location(0) position : vec3, + @location(1) tex_coords : vec2, +}; + +struct InstanceInput { + @location(5) src_top_left: vec2, + @location(6) src_dims: vec2, + @location(7) dest_top_left: vec2, + @location(8) dest_dims: vec2, +}; + +struct VertexOutput { + @builtin(position) clip_position : vec4, + @location(0) tex_coords : vec2, +}; + +@vertex fn vs_main(vertex : VertexInput, instance : InstanceInput)->VertexOutput { + var out : VertexOutput; + out.tex_coords = instance.src_top_left + (vertex.tex_coords * instance.src_dims); + + let in_pos = instance.dest_top_left + (vec2f(vertex.position.x, vertex.position.y) * instance.dest_dims); + + let position = adjust_for_resolution(in_pos); + out.clip_position = vec4f(position.x, position.y, vertex.position.z, 1.0); + return out; +} + +// ---------------------------------------------------------------------------- +// Fragment shader +// ---------------------------------------------------------------------------- + +@group(1) @binding(0) var t_diffuse : texture_multisampled_2d; +@group(1) @binding(1) var s_diffuse : sampler; + +@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { + // TODO: Should we be sampling here for the shader? + let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); + return textureLoad(t_diffuse, tc, 0); +} + + +// ---------------------------------------------------------------------------- +// Resolution Handling +// ---------------------------------------------------------------------------- + +struct ScreenUniform { + resolution : vec2f, +}; +@group(0) @binding(0) // 1. + var screen : ScreenUniform; + +const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. + +fn adjust_for_resolution(in_pos: vec2) -> vec2 { + // Adjust in_pos for the "resolution" of the screen. + let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. + + // the actual resolution of the screen. + let screen_ar = screen.resolution.x / screen.resolution.y; + + // Compute the difference in resolution ... correctly? + // + // nudge is the amount to add to the logical resolution so that the pixels + // stay the same size but we respect the aspect ratio of the screen. (So + // there's more of them in either the x or y direction.) + var nudge = vec2f(0.0); + if (screen_ar > RES_AR) { + nudge.x = (RES.y * screen_ar) - RES.x; + } else { + nudge.y = (RES.x / screen_ar) - RES.y; + } + var new_logical_resolution = RES + nudge; + + // Now we can convert the incoming position to clip space, in the new screen. + let centered = in_pos + (nudge / 2.0); + var position = (2.0 * centered / new_logical_resolution) - 1.0; + position.y = -position.y; + + return position; +}