From 6c0dafbcebdff765820adeb1675b069f780483e5 Mon Sep 17 00:00:00 2001 From: jkds Date: Sun, 4 Jan 2026 12:53:53 +0100 Subject: [PATCH] app/text2: modify text rendering so we build a single atlas for an entire parley Layout. this is handy if a layout does not change (text/styles) just the wrapping. then we can reuse the atlas. --- bin/app/src/text2/atlas.rs | 48 +++++++++++++----------- bin/app/src/text2/render.rs | 75 +++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/bin/app/src/text2/atlas.rs b/bin/app/src/text2/atlas.rs index 62696a354..54d584c36 100644 --- a/bin/app/src/text2/atlas.rs +++ b/bin/app/src/text2/atlas.rs @@ -34,7 +34,8 @@ pub fn make_texture_atlas(render_api: &RenderApi, glyphs: &Vec) -> Render } */ -//pub struct Sprite(swash::scale::image::Image); +pub(super) type RunIdx = usize; +type GlyphKey = (swash::GlyphId, RunIdx); /// Responsible for aggregating glyphs, and then producing a single software /// blitted texture usable in a single draw call. @@ -42,14 +43,13 @@ pub fn make_texture_atlas(render_api: &RenderApi, glyphs: &Vec) -> Render /// /// ```rust /// let mut atlas = Atlas::new(&render_api); -/// atlas.push(&glyphs); // repeat as needed for shaped lines +/// atlas.push_glyph(glyph, run_idx, &mut scaler); /// let atlas = atlas.make().unwrap(); -/// let uv = atlas.fetch_uv(glyph_id).unwrap(); +/// let uv = atlas.fetch_uv(glyph_id, run_idx).unwrap(); /// let atlas_texture_id = atlas.texture_id; /// ``` pub struct Atlas<'a> { - scaler: swash::scale::Scaler<'a>, - glyph_ids: Vec, + glyph_keys: Vec, sprites: Vec, // LHS x pos of glyph x_pos: Vec, @@ -62,10 +62,9 @@ pub struct Atlas<'a> { } impl<'a> Atlas<'a> { - pub fn new(scaler: swash::scale::Scaler<'a>, render_api: &'a RenderApi, tag: DebugTag) -> Self { + pub fn new(render_api: &'a RenderApi, tag: DebugTag) -> Self { Self { - scaler, - glyph_ids: vec![], + glyph_keys: vec![], sprites: vec![], x_pos: vec![], @@ -80,12 +79,18 @@ impl<'a> Atlas<'a> { } } - pub fn push_glyph(&mut self, glyph_id: swash::GlyphId) { - if self.glyph_ids.contains(&glyph_id) { + pub fn push_glyph( + &mut self, + glyph_id: swash::GlyphId, + run_idx: RunIdx, + scaler: &mut swash::scale::Scaler, + ) { + let glyph_key = (glyph_id, run_idx); + if self.glyph_keys.contains(&glyph_key) { return } - self.glyph_ids.push(glyph_id); + self.glyph_keys.push(glyph_key); let rendered_glyph = swash::scale::Render::new( // Select our source order @@ -97,7 +102,7 @@ impl<'a> Atlas<'a> { ) // Select the simple alpha (non-subpixel) format .format(zeno::Format::Alpha) - .render(&mut self.scaler, glyph_id) + .render(scaler, glyph_id) .unwrap(); let glyph_width = rendered_glyph.placement.width as usize; @@ -171,12 +176,12 @@ impl<'a> Atlas<'a> { /// `rendered_atlas.fetch_uv(my_glyph_id)`. /// The texture ID is a struct member: `rendered_atlas.texture_id`. pub fn make(self) -> RenderedAtlas { - //if self.glyph_ids.is_empty() { + //if self.glyph_keys.is_empty() { // return Err(Error::AtlasIsEmpty) //} - assert_eq!(self.glyph_ids.len(), self.sprites.len()); - assert_eq!(self.glyph_ids.len(), self.x_pos.len()); + assert_eq!(self.glyph_keys.len(), self.sprites.len()); + assert_eq!(self.glyph_keys.len(), self.x_pos.len()); let atlas = self.render(); let texture = self.render_api.new_texture( @@ -188,7 +193,7 @@ impl<'a> Atlas<'a> { ); let uv_rects = self.compute_uvs(); - let glyph_ids = self.glyph_ids; + let glyph_keys = self.glyph_keys; let mut infos = Vec::with_capacity(self.sprites.len()); for (uv_rect, sprite) in uv_rects.into_iter().zip(self.sprites.into_iter()) { @@ -200,7 +205,7 @@ impl<'a> Atlas<'a> { infos.push(GlyphInfo { uv_rect, place: sprite.placement, is_color }); } - RenderedAtlas { glyph_ids, infos, texture } + RenderedAtlas { glyph_keys, infos, texture } } } @@ -269,7 +274,7 @@ pub struct GlyphInfo { /// Final result computed from `Atlas::make()`. #[derive(Clone)] pub struct RenderedAtlas { - glyph_ids: Vec, + glyph_keys: Vec, infos: Vec, /// Allocated atlas texture. pub texture: ManagedTexturePtr, @@ -277,12 +282,13 @@ pub struct RenderedAtlas { impl RenderedAtlas { /// Get UV coords for a glyph within the rendered atlas. - pub fn fetch_uv(&self, glyph_id: swash::GlyphId) -> Option<&GlyphInfo> { - let glyphs_len = self.glyph_ids.len(); + pub fn fetch_uv(&self, glyph_id: swash::GlyphId, run_idx: RunIdx) -> Option<&GlyphInfo> { + let glyphs_len = self.glyph_keys.len(); assert_eq!(glyphs_len, self.infos.len()); + let glyph_key = (glyph_id, run_idx); for i in 0..glyphs_len { - if self.glyph_ids[i] == glyph_id { + if self.glyph_keys[i] == glyph_key { return Some(&self.infos[i]) } } diff --git a/bin/app/src/text2/render.rs b/bin/app/src/text2/render.rs index 62b28ae27..0db5d9775 100644 --- a/bin/app/src/text2/render.rs +++ b/bin/app/src/text2/render.rs @@ -21,7 +21,7 @@ use crate::{ mesh::{Color, MeshBuilder, COLOR_WHITE}, }; -use super::atlas::{Atlas, RenderedAtlas}; +use super::atlas::{Atlas, RenderedAtlas, RunIdx}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DebugRenderOptions(u32); @@ -63,15 +63,40 @@ pub fn render_layout_with_opts( render_api: &RenderApi, tag: DebugTag, ) -> Vec { - let mut scale_cx = swash::scale::ScaleContext::new(); + // First pass to create atlas + let mut scale_ctx = swash::scale::ScaleContext::new(); + let mut atlas = Atlas::new(render_api, tag); + let mut run_idx = 0; + for line in layout.lines() { + for item in line.items() { + match item { + parley::PositionedLayoutItem::GlyphRun(glyph_run) => { + push_glyphs(&mut atlas, &glyph_run, run_idx, &mut scale_ctx, render_api, tag); + run_idx += 1; + } + parley::PositionedLayoutItem::InlineBox(_) => {} + } + } + } + // Render the atlas + let atlas = atlas.make(); + + // Second pass to draw glyphs let mut run_idx = 0; let mut instrs = vec![]; for line in layout.lines() { for item in line.items() { match item { parley::PositionedLayoutItem::GlyphRun(glyph_run) => { - let mesh = - render_glyph_run(&mut scale_cx, &glyph_run, run_idx, opts, render_api, tag); + let mesh = render_glyph_run( + &mut scale_ctx, + &glyph_run, + run_idx, + opts, + &atlas, + render_api, + tag, + ); instrs.push(DrawInstruction::Draw(mesh)); run_idx += 1; } @@ -82,11 +107,38 @@ pub fn render_layout_with_opts( instrs } +fn push_glyphs( + atlas: &mut Atlas, + glyph_run: &parley::GlyphRun<'_, Color>, + run_idx: RunIdx, + scale_ctx: &mut swash::scale::ScaleContext, + render_api: &RenderApi, + tag: DebugTag, +) { + let run = glyph_run.run(); + let font = run.font(); + let font_size = run.font_size(); + let normalized_coords = run.normalized_coords(); + let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap(); + + let mut scaler = scale_ctx + .builder(font_ref) + .size(font_size) + .hint(true) + .normalized_coords(normalized_coords) + .build(); + + for glyph in glyph_run.glyphs() { + atlas.push_glyph(glyph.id as u16, run_idx, &mut scaler); + } +} + fn render_glyph_run( scale_ctx: &mut swash::scale::ScaleContext, glyph_run: &parley::GlyphRun<'_, Color>, - _run_idx: usize, + run_idx: usize, opts: DebugRenderOptions, + atlas: &RenderedAtlas, render_api: &RenderApi, tag: DebugTag, ) -> DrawMesh { @@ -96,8 +148,6 @@ fn render_glyph_run( let color = style.brush; //trace!(target: "text::render", "render_glyph_run run_idx={run_idx} baseline={run_y}"); - let atlas = create_atlas(scale_ctx, glyph_run, render_api, tag); - let mut mesh = MeshBuilder::new(tag); if let Some(underline) = &style.underline { @@ -105,7 +155,7 @@ fn render_glyph_run( } for glyph in glyph_run.glyphs() { - let glyph_inf = atlas.fetch_uv(glyph.id as u16).expect("missing glyph UV rect"); + let glyph_inf = atlas.fetch_uv(glyph.id as u16, run_idx).expect("missing glyph UV rect"); let glyph_x = run_x + glyph.x; let glyph_y = run_y - glyph.y; @@ -133,7 +183,7 @@ fn render_glyph_run( ); } - mesh.alloc(render_api).draw_with_textures(vec![atlas.texture]) + mesh.alloc(render_api).draw_with_textures(vec![atlas.texture.clone()]) } fn render_underline( @@ -170,6 +220,7 @@ fn render_underline( fn create_atlas( scale_ctx: &mut swash::scale::ScaleContext, glyph_run: &parley::GlyphRun<'_, Color>, + run_idx: usize, render_api: &RenderApi, tag: DebugTag, ) -> RenderedAtlas { @@ -179,16 +230,16 @@ fn create_atlas( let normalized_coords = run.normalized_coords(); let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap(); - let scaler = scale_ctx + let mut scaler = scale_ctx .builder(font_ref) .size(font_size) .hint(true) .normalized_coords(normalized_coords) .build(); - let mut atlas = Atlas::new(scaler, render_api, tag); + let mut atlas = Atlas::new(render_api, tag); for glyph in glyph_run.glyphs() { - atlas.push_glyph(glyph.id as u16); + atlas.push_glyph(glyph.id as u16, run_idx, &mut scaler); } //atlas.dump(&format!("/tmp/atlas_{run_idx}.png")); atlas.make()