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.

This commit is contained in:
jkds
2026-01-04 12:53:53 +01:00
parent 6b9ef3aff2
commit 6c0dafbceb
2 changed files with 90 additions and 33 deletions

View File

@@ -34,7 +34,8 @@ pub fn make_texture_atlas(render_api: &RenderApi, glyphs: &Vec<Glyph>) -> 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<Glyph>) -> 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<swash::GlyphId>,
glyph_keys: Vec<GlyphKey>,
sprites: Vec<swash::scale::image::Image>,
// LHS x pos of glyph
x_pos: Vec<usize>,
@@ -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<swash::GlyphId>,
glyph_keys: Vec<GlyphKey>,
infos: Vec<GlyphInfo>,
/// 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])
}
}

View File

@@ -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<DrawInstruction> {
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()