diff --git a/core/src/render/atlas.rs b/core/src/render/atlas.rs index 3996d00..3315c56 100644 --- a/core/src/render/atlas.rs +++ b/core/src/render/atlas.rs @@ -1,5 +1,5 @@ use crate::{ - PatchRect, TextureHandle, Textures, + GlyphPrimitive, PatchRect, TextureHandle, Textures, util::{HashMap, Vec2}, }; use image::RgbaImage; @@ -27,18 +27,28 @@ pub struct GlyphKey { #[derive(Clone, Copy)] pub struct GlyphEntry { - pub uv_min: [f32; 2], - pub uv_max: [f32; 2], + pub uv_min: Vec2, + pub uv_max: Vec2, /// Offset from the glyph's pen position to the top-left of its pixels. pub left: i32, pub top: i32, pub width: u32, pub height: u32, - pub is_color: bool, + pub is_colored: bool, pub view_idx: u32, pub sampler_idx: u32, } +impl GlyphEntry { + pub(crate) fn flags(&self) -> u32 { + if self.is_colored { + GlyphPrimitive::IS_COLORED + } else { + 0 + } + } +} + struct Page { handle: TextureHandle, x: u32, @@ -95,13 +105,13 @@ impl GlyphAtlas { let page = &self.pages[page_idx]; let scale = 1.0 / PAGE as f32; let entry = GlyphEntry { - uv_min: [x as f32 * scale, y as f32 * scale], - uv_max: [(x + w) as f32 * scale, (y + h) as f32 * scale], + uv_min: Vec2::new(x as f32 * scale, y as f32 * scale), + uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale), left: image.placement.left, top: image.placement.top, width: w, height: h, - is_color: matches!(image.content, Content::Color), + is_colored: matches!(image.content, Content::Color), view_idx: page.handle.primitive().view_idx, sampler_idx: page.handle.primitive().sampler_idx, }; @@ -110,18 +120,12 @@ impl GlyphAtlas { } fn allocate(&mut self, w: u32, h: u32, textures: &mut Textures) -> (usize, u32, u32) { - let need_w = w + PAD; - let need_h = h + PAD; - if let Some(i) = self.pages.iter().position(|p| fits(p, need_w, need_h)) { - let page = &mut self.pages[i]; - if page.x + need_w > PAGE { - page.y += page.shelf_height; - page.x = PAD; - page.shelf_height = 0; - } - let (x, y) = (page.x, page.y); - page.x += need_w; - page.shelf_height = page.shelf_height.max(need_h); + if let Some((i, (x, y))) = self + .pages + .iter_mut() + .enumerate() + .find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position))) + { return (i, x, y); } @@ -148,45 +152,65 @@ impl GlyphAtlas { } } -fn fits(page: &Page, need_w: u32, need_h: u32) -> bool { - (page.x + need_w <= PAGE && page.y + need_h <= PAGE) - || (need_w + PAD <= PAGE && page.y + page.shelf_height + need_h <= PAGE) +impl Page { + fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> { + let need_w = w + PAD; + let need_h = h + PAD; + if self.x + need_w > PAGE { + if need_w + PAD > PAGE || self.y + self.shelf_height + need_h > PAGE { + return None; + } + self.y += self.shelf_height; + self.x = PAD; + self.shelf_height = 0; + } else if self.y + need_h > PAGE { + return None; + } + + let position = (self.x, self.y); + self.x += need_w; + self.shelf_height = self.shelf_height.max(need_h); + Some(position) + } } /// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time. fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) { - let w = image.placement.width; - let h = image.placement.height; - match image.content { - Content::Mask => { - for row in 0..h { - for col in 0..w { - let a = image.data[(row * w + col) as usize]; - page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a])); + let width = image.placement.width as usize; + let height = image.placement.height as usize; + let page_stride = page.width() as usize * 4; + let x = x as usize * 4; + let y = y as usize; + let page = page.as_mut(); + + for row in 0..height { + let start = (y + row) * page_stride + x; + let target = &mut page[start..start + width * 4]; + match image.content { + Content::Color => { + let start = row * width * 4; + target.copy_from_slice(&image.data[start..start + width * 4]); + } + Content::Mask => { + let start = row * width; + for (target, &alpha) in target + .as_chunks_mut::<4>() + .0 + .iter_mut() + .zip(&image.data[start..start + width]) + { + target.copy_from_slice(&[255, 255, 255, alpha]); } } - } - Content::Color => { - for row in 0..h { - for col in 0..w { - let i = ((row * w + col) * 4) as usize; - let px = [ - image.data[i], - image.data[i + 1], - image.data[i + 2], - image.data[i + 3], - ]; - page.put_pixel(x + col, y + row, image::Rgba(px)); - } - } - } - Content::SubpixelMask => { - // Preserve readable output if the rasterizer returns a subpixel mask. - for row in 0..h { - for col in 0..w { - let i = ((row * w + col) * 4) as usize; - let a = image.data[i + 1]; - page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a])); + Content::SubpixelMask => { + let start = row * width * 4; + for (target, source) in target + .as_chunks_mut::<4>() + .0 + .iter_mut() + .zip(image.data[start..start + width * 4].as_chunks::<4>().0) + { + target.copy_from_slice(&[255, 255, 255, source[1]]); } } } diff --git a/core/src/render/primitive.rs b/core/src/render/primitive.rs index 28f6b3d..4d77a79 100644 --- a/core/src/render/primitive.rs +++ b/core/src/render/primitive.rs @@ -6,6 +6,7 @@ use crate::{ ArrBuf, data::{MaskIdx, PrimitiveInstance}, }, + util::Vec2, }; use bytemuck::Pod; use wgpu::*; @@ -234,8 +235,8 @@ pub struct TexturePrimitive { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct GlyphPrimitive { - pub uv_min: [f32; 2], - pub uv_max: [f32; 2], + pub uv_min: Vec2, + pub uv_max: Vec2, pub view_idx: u32, pub sampler_idx: u32, pub color: Color, @@ -243,7 +244,7 @@ pub struct GlyphPrimitive { } impl GlyphPrimitive { - pub const IS_COLOR: u32 = 1; + pub const IS_COLORED: u32 = 1; } pub struct PrimitiveVec { diff --git a/core/src/render/texture.rs b/core/src/render/texture.rs index f8710b6..b7af152 100644 --- a/core/src/render/texture.rs +++ b/core/src/render/texture.rs @@ -17,36 +17,36 @@ pub struct GpuTextures { impl GpuTextures { pub fn update(&mut self, textures: &mut Textures) -> bool { - let mut changed = false; + let mut bindings_changed = false; for update in textures.updates() { - match update { + bindings_changed |= match update { TextureUpdate::Push(image) => { self.push(image); - changed = true; + true } TextureUpdate::Set(i, image) => { self.set(i, image); - changed = true; + true } TextureUpdate::Patch(i, rect, image) => { - // Patching contents leaves the binding array unchanged. self.patch(i, rect, image); + false } TextureUpdate::SetFree => { self.view_count += 1; - changed = true; + true } TextureUpdate::Free(i) => { self.free(i); - changed = true; + true } TextureUpdate::PushFree => { self.push_free(); - changed = true; + true } - } + }; } - changed + bindings_changed } fn set(&mut self, i: u32, image: &DynamicImage) { self.view_count += 1; diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 3e17ead..052b049 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -101,13 +101,6 @@ impl<'a> Painter<'a> { } pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) { - let flags_for = |is_color| { - if is_color { - GlyphPrimitive::IS_COLOR - } else { - 0 - } - }; for glyph in text.glyphs.iter() { let mut region = origin; region.x.end = region.x.start; @@ -122,7 +115,7 @@ impl<'a> Painter<'a> { view_idx: glyph.entry.view_idx, sampler_idx: glyph.entry.sampler_idx, color: text.color, - flags: flags_for(glyph.entry.is_color), + flags: glyph.entry.flags(), }, region, );