Tighten glyph atlas updates

This commit is contained in:
iris committed 2026-09-13 02:42:28 -04:00
1 parent 71e46c125a
commit 5fe7c3b6df
4 files changed
+91 -73

No files matched your search

+76 -52
View File
@@ -1,5 +1,5 @@
use crate::{ use crate::{
PatchRect, TextureHandle, Textures, GlyphPrimitive, PatchRect, TextureHandle, Textures,
util::{HashMap, Vec2}, util::{HashMap, Vec2},
}; };
use image::RgbaImage; use image::RgbaImage;
@@ -27,18 +27,28 @@ pub struct GlyphKey {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct GlyphEntry { pub struct GlyphEntry {
pub uv_min: [f32; 2], pub uv_min: Vec2,
pub uv_max: [f32; 2], pub uv_max: Vec2,
/// Offset from the glyph's pen position to the top-left of its pixels. /// Offset from the glyph's pen position to the top-left of its pixels.
pub left: i32, pub left: i32,
pub top: i32, pub top: i32,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
pub is_color: bool, pub is_colored: bool,
pub view_idx: u32, pub view_idx: u32,
pub sampler_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 { struct Page {
handle: TextureHandle, handle: TextureHandle,
x: u32, x: u32,
@@ -95,13 +105,13 @@ impl GlyphAtlas {
let page = &self.pages[page_idx]; let page = &self.pages[page_idx];
let scale = 1.0 / PAGE as f32; let scale = 1.0 / PAGE as f32;
let entry = GlyphEntry { let entry = GlyphEntry {
uv_min: [x as f32 * scale, y as f32 * scale], uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
uv_max: [(x + w) as f32 * scale, (y + h) as f32 * scale], uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale),
left: image.placement.left, left: image.placement.left,
top: image.placement.top, top: image.placement.top,
width: w, width: w,
height: h, height: h,
is_color: matches!(image.content, Content::Color), is_colored: matches!(image.content, Content::Color),
view_idx: page.handle.primitive().view_idx, view_idx: page.handle.primitive().view_idx,
sampler_idx: page.handle.primitive().sampler_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) { fn allocate(&mut self, w: u32, h: u32, textures: &mut Textures) -> (usize, u32, u32) {
let need_w = w + PAD; if let Some((i, (x, y))) = self
let need_h = h + PAD; .pages
if let Some(i) = self.pages.iter().position(|p| fits(p, need_w, need_h)) { .iter_mut()
let page = &mut self.pages[i]; .enumerate()
if page.x + need_w > PAGE { .find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
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);
return (i, x, y); return (i, x, y);
} }
@@ -148,45 +152,65 @@ impl GlyphAtlas {
} }
} }
fn fits(page: &Page, need_w: u32, need_h: u32) -> bool { impl Page {
(page.x + need_w <= PAGE && page.y + need_h <= PAGE) fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
|| (need_w + PAD <= PAGE && page.y + page.shelf_height + need_h <= PAGE) 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. /// 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) { fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) {
let w = image.placement.width; let width = image.placement.width as usize;
let h = image.placement.height; let height = image.placement.height as usize;
match image.content { let page_stride = page.width() as usize * 4;
Content::Mask => { let x = x as usize * 4;
for row in 0..h { let y = y as usize;
for col in 0..w { let page = page.as_mut();
let a = image.data[(row * w + col) as usize];
page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a])); 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::SubpixelMask => {
Content::Color => { let start = row * width * 4;
for row in 0..h { for (target, source) in target
for col in 0..w { .as_chunks_mut::<4>()
let i = ((row * w + col) * 4) as usize; .0
let px = [ .iter_mut()
image.data[i], .zip(image.data[start..start + width * 4].as_chunks::<4>().0)
image.data[i + 1], {
image.data[i + 2], target.copy_from_slice(&[255, 255, 255, source[1]]);
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]));
} }
} }
} }
+4 -3
View File
@@ -6,6 +6,7 @@ use crate::{
ArrBuf, ArrBuf,
data::{MaskIdx, PrimitiveInstance}, data::{MaskIdx, PrimitiveInstance},
}, },
util::Vec2,
}; };
use bytemuck::Pod; use bytemuck::Pod;
use wgpu::*; use wgpu::*;
@@ -234,8 +235,8 @@ pub struct TexturePrimitive {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct GlyphPrimitive { pub struct GlyphPrimitive {
pub uv_min: [f32; 2], pub uv_min: Vec2,
pub uv_max: [f32; 2], pub uv_max: Vec2,
pub view_idx: u32, pub view_idx: u32,
pub sampler_idx: u32, pub sampler_idx: u32,
pub color: Color<u8>, pub color: Color<u8>,
@@ -243,7 +244,7 @@ pub struct GlyphPrimitive {
} }
impl GlyphPrimitive { impl GlyphPrimitive {
pub const IS_COLOR: u32 = 1; pub const IS_COLORED: u32 = 1;
} }
pub struct PrimitiveVec<T> { pub struct PrimitiveVec<T> {
+10 -10
View File
@@ -17,36 +17,36 @@ pub struct GpuTextures {
impl GpuTextures { impl GpuTextures {
pub fn update(&mut self, textures: &mut Textures) -> bool { pub fn update(&mut self, textures: &mut Textures) -> bool {
let mut changed = false; let mut bindings_changed = false;
for update in textures.updates() { for update in textures.updates() {
match update { bindings_changed |= match update {
TextureUpdate::Push(image) => { TextureUpdate::Push(image) => {
self.push(image); self.push(image);
changed = true; true
} }
TextureUpdate::Set(i, image) => { TextureUpdate::Set(i, image) => {
self.set(i, image); self.set(i, image);
changed = true; true
} }
TextureUpdate::Patch(i, rect, image) => { TextureUpdate::Patch(i, rect, image) => {
// Patching contents leaves the binding array unchanged.
self.patch(i, rect, image); self.patch(i, rect, image);
false
} }
TextureUpdate::SetFree => { TextureUpdate::SetFree => {
self.view_count += 1; self.view_count += 1;
changed = true; true
} }
TextureUpdate::Free(i) => { TextureUpdate::Free(i) => {
self.free(i); self.free(i);
changed = true; true
} }
TextureUpdate::PushFree => { TextureUpdate::PushFree => {
self.push_free(); self.push_free();
changed = true; true
} }
} };
} }
changed bindings_changed
} }
fn set(&mut self, i: u32, image: &DynamicImage) { fn set(&mut self, i: u32, image: &DynamicImage) {
self.view_count += 1; self.view_count += 1;
+1 -8
View File
@@ -101,13 +101,6 @@ impl<'a> Painter<'a> {
} }
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) { 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() { for glyph in text.glyphs.iter() {
let mut region = origin; let mut region = origin;
region.x.end = region.x.start; region.x.end = region.x.start;
@@ -122,7 +115,7 @@ impl<'a> Painter<'a> {
view_idx: glyph.entry.view_idx, view_idx: glyph.entry.view_idx,
sampler_idx: glyph.entry.sampler_idx, sampler_idx: glyph.entry.sampler_idx,
color: text.color, color: text.color,
flags: flags_for(glyph.entry.is_color), flags: glyph.entry.flags(),
}, },
region, region,
); );