diff --git a/src/atlas.rs b/src/atlas.rs index 84497db..a070ae9 100644 --- a/src/atlas.rs +++ b/src/atlas.rs @@ -7,12 +7,18 @@ struct ImageData { data: Vec, } +pub enum AtlasContent { + Mask, + Color, +} + pub struct Atlas { packer: Packer, new_data: Vec, pub atlas_texture: wgpu::Texture, area_used: i32, did_clear: bool, + content: AtlasContent, } impl Atlas { @@ -50,8 +56,29 @@ impl Atlas { } } - pub fn new(device: &wgpu::Device) -> Self { - let atlas_texture = device.create_texture(&Self::get_texture_desc()); + pub fn new(device: &wgpu::Device, content: AtlasContent) -> Self { + let texture_size = wgpu::Extent3d { + width: Atlas::ATLAS_SIZE, + height: Atlas::ATLAS_SIZE, + depth_or_array_layers: 1, + }; + let format = match content { + AtlasContent::Mask => wgpu::TextureFormat::R8Unorm, + AtlasContent::Color => wgpu::TextureFormat::Rgba8Unorm, + }; + let desc = wgpu::TextureDescriptor { + size: texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING, + label: Some("atlas_texture"), + view_formats: &[format], + }; + let atlas_texture = device.create_texture(&desc); Self { packer: Packer::new(Atlas::get_packer_config()), @@ -59,6 +86,7 @@ impl Atlas { atlas_texture, area_used: 0, did_clear: false, + content, } } @@ -78,10 +106,15 @@ impl Atlas { } pub fn update(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) { + let pixels = match self.content { + AtlasContent::Mask => 1, + AtlasContent::Color => 4, + }; + if self.did_clear { // encoder.clear_texture(&self.atlas_texture, &wgpu::ImageSubresourceRange::default()); - let sz = Atlas::ATLAS_SIZE as usize; + let sz = Atlas::ATLAS_SIZE as usize * pixels; let data = vec![0_u8; sz * sz]; @@ -121,14 +154,15 @@ impl Atlas { for data in &self.new_data { // Pad data to wgpu::COPY_BYTES_PER_ROW_ALIGNMENT let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as i32; - let padding = (align - data.rect.width % align) % align; - let padded_width = data.rect.width + padding; + let width = data.rect.width * pixels as i32; + let padding = (align - width % align) % align; + let padded_width = width + padding; let mut padded_data = vec![]; padded_data.reserve((padded_width * data.rect.height) as usize); let mut i = 0; for _ in 0..data.rect.height { - for _ in 0..data.rect.width { + for _ in 0..width { padded_data.push(data.data[i]); i += 1; } diff --git a/src/glyphs.rs b/src/glyphs.rs index 2ad05f9..454da69 100644 --- a/src/glyphs.rs +++ b/src/glyphs.rs @@ -1,6 +1,10 @@ -use crate::atlas::Atlas; +use crate::atlas::{Atlas, AtlasContent}; use rect_packer::Rect; -use std::collections::HashMap; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, + sync::Arc, +}; #[derive(Copy, Clone, Debug)] pub struct GlyphInfo { @@ -8,13 +12,49 @@ pub struct GlyphInfo { pub metrics: fontdue::Metrics, } -pub struct GlyphCache { - pub atlas: Atlas, +#[derive(Clone, Eq)] +pub struct ImageId { + id: Arc, +} + +impl ImageId { + pub fn new() -> Self { + Self { + id: Arc::new(false), + } + } +} + +impl PartialEq for ImageId { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.id, &other.id) + } +} + +impl Hash for ImageId { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + (Arc::as_ptr(&self.id) as usize).hash(state) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct AtlasImage { + pub rect: Option, +} + +pub struct AtlasCache { + pub mask_atlas: Atlas, + pub color_atlas: Atlas, pub font: fontdue::Font, - info: HashMap<(char, u32), GlyphInfo>, + glyphs: HashMap<(char, u32), GlyphInfo>, + mask_images: HashMap, + color_images: HashMap, } -impl GlyphCache { +impl AtlasCache { pub fn new(device: &wgpu::Device) -> Self { let mut settings = fontdue::FontSettings::default(); settings.collection_index = 0; @@ -22,12 +62,41 @@ impl GlyphCache { let font = include_bytes!("fonts/Anodina-Regular.ttf") as &[u8]; Self { - atlas: Atlas::new(device), + mask_atlas: Atlas::new(device, AtlasContent::Mask), + color_atlas: Atlas::new(device, AtlasContent::Color), font: fontdue::Font::from_bytes(font, settings).unwrap(), - info: HashMap::new(), + glyphs: HashMap::new(), + mask_images: HashMap::new(), + color_images: HashMap::new(), } } + pub fn get_mask_image( + &mut self, + id: ImageId, + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + ) -> AtlasImage { + let mask_atlas = &mut self.mask_atlas; + *self.mask_images.entry(id).or_insert_with(|| AtlasImage { + rect: mask_atlas.add_region(&image(), width, height), + }) + } + + pub fn get_color_image( + &mut self, + id: ImageId, + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + ) -> AtlasImage { + let color_atlas = &mut self.color_atlas; + *self.color_images.entry(id).or_insert_with(|| AtlasImage { + rect: color_atlas.add_region(&image(), width, height), + }) + } + pub fn get_glyph(&mut self, c: char, size: f32) -> GlyphInfo { let factor = 65536.0; @@ -35,7 +104,7 @@ impl GlyphCache { let size_fixed_point = (size * factor) as u32; // Do we already have a glyph? - match self.info.get(&(c, size_fixed_point)) { + match self.glyphs.get(&(c, size_fixed_point)) { Some(info) => *info, None => { let (metrics, data) = self.font.rasterize(c, size_fixed_point as f32 / factor); @@ -52,31 +121,31 @@ impl GlyphCache { */ let rect = - self.atlas + self.mask_atlas .add_region(&data, metrics.width as u32, metrics.height as u32); let info = GlyphInfo { rect, metrics }; - self.info.insert((c, size_fixed_point), info); + self.glyphs.insert((c, size_fixed_point), info); info } } } pub fn update(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) { - self.atlas.update(device, encoder); + self.mask_atlas.update(device, encoder); + self.color_atlas.update(device, encoder); } - pub fn create_view(&self) -> wgpu::TextureView { - self.atlas.create_view() - } - - pub fn usage(&self) -> f32 { - self.atlas.usage() - } - - pub fn clear(&mut self) { - self.info.clear(); - self.atlas.clear(); + pub fn check_usage(&mut self) { + if self.mask_atlas.usage() > 0.7 { + self.glyphs.clear(); + self.mask_atlas.clear(); + self.mask_images.clear(); + } + if self.color_atlas.usage() > 0.7 { + self.color_atlas.clear(); + self.color_images.clear(); + } } } diff --git a/src/lib.rs b/src/lib.rs index b84900f..c045c73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,9 @@ pub use color::Color; pub mod atlas; mod glyphs; -use glyphs::GlyphCache; +pub use glyphs::ImageId; + +use glyphs::AtlasCache; use wgpu::util::DeviceExt; @@ -92,7 +94,7 @@ pub struct Vger { scissor_count: usize, path_scanner: PathScanner, pen: LocalPoint, - pub glyph_cache: GlyphCache, + pub glyph_cache: AtlasCache, layout: Layout, images: Vec>, image_bind_groups: Vec>, @@ -146,6 +148,16 @@ impl Vger { wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, @@ -168,9 +180,9 @@ impl Vger { label: Some("image_bind_group_layout"), }); - let glyph_cache = GlyphCache::new(&device); - - let texture_view = glyph_cache.create_view(); + let glyph_cache = AtlasCache::new(&device); + let mask_texture_view = glyph_cache.mask_atlas.create_view(); + let color_texture_view = glyph_cache.color_atlas.create_view(); let uniforms = GPUVec::new_uniforms(&device, "uniforms"); @@ -187,10 +199,14 @@ impl Vger { uniforms.bind_group_entry(0), wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&texture_view), + resource: wgpu::BindingResource::TextureView(&mask_texture_view), }, wgpu::BindGroupEntry { binding: 2, + resource: wgpu::BindingResource::TextureView(&color_texture_view), + }, + wgpu::BindGroupEntry { + binding: 3, resource: wgpu::BindingResource::Sampler(&glyph_sampler), }, ], @@ -201,7 +217,7 @@ impl Vger { layout: &image_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&texture_view), + resource: wgpu::BindingResource::TextureView(&mask_texture_view), }], label: Some("vger default image bind group"), }); @@ -387,12 +403,7 @@ impl Vger { queue.submit(Some(encoder.finish())); // If we're getting close to full, reset the glyph cache. - let usage = self.glyph_cache.usage(); - // println!("glyph cache usage {}", usage); - if usage > 0.7 { - // println!("clearing glyph cache"); - self.glyph_cache.clear(); - } + self.glyph_cache.check_usage(); } fn render(&mut self, prim: Prim) { @@ -688,6 +699,64 @@ impl Vger { ); } + pub fn render_cached_mask( + &mut self, + x: f32, + y: f32, + id: ImageId, + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + paint_index: PaintIndex, + ) { + let info = self.glyph_cache.get_color_image(id, width, height, image); + if let Some(rect) = info.rect { + let mut prim = Prim::default(); + prim.prim_type = PrimType::AtlasMask as u32; + let x = x as f32; + let y = y as f32; + prim.quad_bounds = [x, y, x + rect.width as f32, y + rect.height as f32]; + prim.tex_bounds = [ + rect.x as f32, + rect.y as f32, + (rect.x + rect.width) as f32, + (rect.y + rect.height) as f32, + ]; + prim.paint = paint_index.index as u32; + prim.xform = self.add_xform() as u32; + prim.scissor = self.add_scissor() as u32; + self.render(prim); + } + } + + pub fn render_cached_image( + &mut self, + x: f32, + y: f32, + id: ImageId, + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + ) { + let info = self.glyph_cache.get_color_image(id, width, height, image); + if let Some(rect) = info.rect { + let mut prim = Prim::default(); + prim.prim_type = PrimType::AtlasImage as u32; + let x = x as f32; + let y = y as f32; + prim.quad_bounds = [x, y, x + rect.width as f32, y + rect.height as f32]; + prim.tex_bounds = [ + rect.x as f32, + rect.y as f32, + (rect.x + rect.width) as f32, + (rect.y + rect.height) as f32, + ]; + prim.xform = self.add_xform() as u32; + prim.scissor = self.add_scissor() as u32; + self.render(prim); + } + } + /// Renders text. pub fn text(&mut self, text: &str, size: u32, color: Color, max_width: Option) { self.setup_layout(text, size, max_width); @@ -707,7 +776,7 @@ impl Vger { if let Some(rect) = info.rect { let mut prim = Prim::default(); - prim.prim_type = PrimType::Glyph as u32; + prim.prim_type = PrimType::AtlasMask as u32; prim.xform = xform; prim.scissor = scissor; assert!(glyph.width == rect.width as usize); diff --git a/src/prim.rs b/src/prim.rs index d3ac0e9..135a1a7 100644 --- a/src/prim.rs +++ b/src/prim.rs @@ -25,8 +25,11 @@ pub enum PrimType { /// Connection wire. See https://www.shadertoy.com/view/NdsXRl Wire, - /// Text rendering. - Glyph, + /// A mask from an atlas. Used for text rendering. + AtlasMask, + + /// An image from an atlas. E.g. emoji or icons. + AtlasImage, /// Path fills. PathFill, diff --git a/src/shader.wgsl b/src/shader.wgsl index c05a7c5..5144f97 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -24,14 +24,17 @@ const vgerCurve = 6; /// Connection wire. See https://www.shadertoy.com/view/NdsXRl const vgerWire = 7; -/// Text rendering. -const vgerGlyph = 8; +/// A mask from an atlas. +const vgerAtlasMask = 8; + +/// An color image from an atlas. +const vgerAtlasImage = 9; /// Path fills. -const vgerPathFill = 9; +const vgerPathFill = 10; /// Rounded blurred rectangle. -const vgerBlurredRect = 10; +const vgerBlurredRect = 11; struct Prim { @@ -335,18 +338,22 @@ fn sdPrimBounds(prim: Prim) -> BBox { b.min = min(prim.cv0, prim.cv1); b.max = max(prim.cv0, prim.cv1); } - case 8u: { // vgerGlyph + case 8u: { // vgerAtlasMask + b.min = prim.cv0; + b.max = prim.cv1; + } + case 9u: { // vgerAtlasImage b.min = prim.cv0; b.max = prim.cv1; } - case 9u: { // vgerPathFill + case 10u: { // vgerPathFill b.min = vec2(1e10, 1e10); b.max = -b.min; for(var i: i32 = 0; i < i32(prim.count * 3u); i = i+1) { b = expand(b, cvs.cvs[i32(prim.start)+i]); } } - case 10u: { // vgerBlurredRect + case 11u: { // vgerBlurredRect b.min = prim.cv0; b.max = prim.cv1; } @@ -427,12 +434,17 @@ fn sdPrim(prim: Prim, p: vec2, filterWidth: f32) -> f32 { case 7u: { // vgerSegment d = sdSegment2(p, prim.cv0, prim.cv1, prim.width); } - case 8u: { // vgerGlyph + case 8u: { // vgerAtlasMask + let center = 0.5*(prim.cv1 + prim.cv0); + let size = prim.cv1 - prim.cv0; + d = sdBox(p - center, 0.5*size, prim.radius); + } + case 9u: { // vgerAtlasImage let center = 0.5*(prim.cv1 + prim.cv0); let size = prim.cv1 - prim.cv0; d = sdBox(p - center, 0.5*size, prim.radius); } - case 9u: { // vgerPathFill + case 10u: { // vgerPathFill for(var i=0; i, filterWidth: f32) -> f32 { d = d * s; break; } - case 10u: { // vgerBlurredRect + case 11u: { // vgerBlurredRect let blur_radius = prim.cv2.x; let center = 0.5*(prim.cv1 + prim.cv0); let half_size = 0.5*(prim.cv1 - prim.cv0); @@ -634,6 +646,10 @@ var glyph_atlas: texture_2d; @group(1) @binding(2) +var color_atlas: texture_2d; + +@group(1) +@binding(3) var samp : sampler; @group(2) @@ -682,7 +698,8 @@ fn fs_main( // Look up glyph alpha (if not a glyph, still have to because of wgsl). // let a = textureSample(glyph_atlas, samp, (in.t+0.5)/1024.0).r; - let a = textureLoad(glyph_atlas, vec2(in.t), 0).r; + let mask_sample = textureLoad(glyph_atlas, vec2(in.t), 0).r; + let image_sample = textureLoad(color_atlas, vec2(in.t), 0); // Look up image color (if no active image, still have to because of wgsl). // Note that we could use a separate shader if that's a perf hit. @@ -691,13 +708,12 @@ fn fs_main( let s = scissor_mask(scissor, in.p); - if(prim.prim_type == 8u) { // vgerGlyph - + if(prim.prim_type == 8u) { // vgerAtlasMask let c = paint.inner_color; // XXX: using toLinear is a bit of a guess. Gets us closer // to matching the glyph atlas in the output. - var color = vec4(c.rgb, toLinear(a)); + var color = vec4(c.rgb, mask_sample * c.a); //if(glow) { // color.a *= paint.glow; @@ -705,6 +721,10 @@ fn fs_main( return s * color; } + + if(prim.prim_type == 9u) { // vgerAtlasImage + return s * image_sample; + } let d = sdPrim(prim, in.t, fw); if paint.image == -1 { diff --git a/tests/tests.rs b/tests/tests.rs index edaf8f8..15dce02 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -354,7 +354,7 @@ fn text_small() { let atlas_png_name = "text_small_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue, @@ -383,7 +383,7 @@ fn text_scale() { let atlas_png_name = "text_scale_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue, @@ -421,7 +421,7 @@ fn text_box() { let atlas_png_name = "text_box_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue,