Optimize: get glyph uv rects during layouts instead of in tesselation
This allows them to be cached, saving around 20% total CPU. It also makes the code more nicely structured
This commit is contained in:
parent
22cd1a8e10
commit
0802a9d9c0
4 changed files with 71 additions and 46 deletions
|
@ -634,36 +634,38 @@ impl Tessellator {
|
||||||
out.reserve_triangles(num_chars * 2);
|
out.reserve_triangles(num_chars * 2);
|
||||||
out.reserve_vertices(num_chars * 4);
|
out.reserve_vertices(num_chars * 4);
|
||||||
|
|
||||||
let tex_w = fonts.texture().width as f32;
|
let inv_tex_w = 1.0 / fonts.texture().width as f32;
|
||||||
let tex_h = fonts.texture().height as f32;
|
let inv_tex_h = 1.0 / fonts.texture().height as f32;
|
||||||
|
|
||||||
let clip_rect = self.clip_rect.expand(2.0); // Some fudge to handle letters that are slightly larger than expected.
|
let clip_rect = self.clip_rect.expand(2.0); // Some fudge to handle letters that are slightly larger than expected.
|
||||||
|
|
||||||
let font = &fonts[galley.text_style];
|
for row in &galley.rows {
|
||||||
let mut chars = galley.text.chars();
|
let row_min_y = pos.y + row.y_min;
|
||||||
for line in &galley.rows {
|
let row_max_y = pos.y + row.y_max;
|
||||||
let line_min_y = pos.y + line.y_min;
|
let is_line_visible = row_max_y >= clip_rect.min.y && row_min_y <= clip_rect.max.y;
|
||||||
let line_max_y = line_min_y + font.row_height();
|
|
||||||
let is_line_visible = line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y;
|
|
||||||
|
|
||||||
for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) {
|
if self.options.coarse_tessellation_culling && !is_line_visible {
|
||||||
let c = chars.next().unwrap();
|
// culling individual lines of text is important, since a single `Shape::Text`
|
||||||
|
// can span hundreds of lines.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if self.options.coarse_tessellation_culling && !is_line_visible {
|
for (x_offset, uv_rect) in row.x_offsets.iter().zip(&row.uv_rects) {
|
||||||
// culling individual lines of text is important, since a single `Shape::Text`
|
if let Some(glyph) = uv_rect {
|
||||||
// can span hundreds of lines.
|
let mut left_top = pos + glyph.offset + vec2(*x_offset, row.y_min);
|
||||||
continue;
|
left_top.x = fonts.round_to_pixel(left_top.x); // Pixel-perfection.
|
||||||
}
|
left_top.y = fonts.round_to_pixel(left_top.y); // Pixel-perfection.
|
||||||
|
|
||||||
if let Some(glyph) = font.uv_rect(c) {
|
|
||||||
let mut left_top = pos + glyph.offset + vec2(*x_offset, line.y_min);
|
|
||||||
left_top.x = font.round_to_pixel(left_top.x); // Pixel-perfection.
|
|
||||||
left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection.
|
|
||||||
|
|
||||||
let rect = Rect::from_min_max(left_top, left_top + glyph.size);
|
let rect = Rect::from_min_max(left_top, left_top + glyph.size);
|
||||||
let uv = Rect::from_min_max(
|
let uv = Rect::from_min_max(
|
||||||
pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h),
|
pos2(
|
||||||
pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h),
|
glyph.min.0 as f32 * inv_tex_w,
|
||||||
|
glyph.min.1 as f32 * inv_tex_h,
|
||||||
|
),
|
||||||
|
pos2(
|
||||||
|
glyph.max.0 as f32 * inv_tex_w,
|
||||||
|
glyph.max.1 as f32 * inv_tex_h,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if fake_italics {
|
if fake_italics {
|
||||||
|
@ -698,12 +700,7 @@ impl Tessellator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if line.ends_with_newline {
|
|
||||||
let newline = chars.next().unwrap();
|
|
||||||
debug_assert_eq!(newline, '\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assert_eq!(chars.next(), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use emath::{vec2, Vec2};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct UvRect {
|
pub struct UvRect {
|
||||||
/// X/Y offset for nice rendering (unit: points).
|
/// X/Y offset for nice rendering (unit: points).
|
||||||
pub offset: Vec2,
|
pub offset: Vec2,
|
||||||
|
@ -220,7 +220,7 @@ impl Font {
|
||||||
self.text_style
|
self.text_style
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||||
}
|
}
|
||||||
|
@ -310,6 +310,7 @@ impl Font {
|
||||||
let x_offsets = self.layout_single_row_fragment(&text);
|
let x_offsets = self.layout_single_row_fragment(&text);
|
||||||
let row = Row {
|
let row = Row {
|
||||||
x_offsets,
|
x_offsets,
|
||||||
|
uv_rects: vec![], // will be filled in later
|
||||||
y_min: 0.0,
|
y_min: 0.0,
|
||||||
y_max: self.row_height(),
|
y_max: self.row_height(),
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
|
@ -322,8 +323,7 @@ impl Font {
|
||||||
rows: vec![row],
|
rows: vec![row],
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
galley.sanity_check();
|
self.finalize_galley(galley)
|
||||||
galley
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Always returns at least one row.
|
/// Always returns at least one row.
|
||||||
|
@ -389,6 +389,7 @@ impl Font {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
rows.push(Row {
|
rows.push(Row {
|
||||||
x_offsets: vec![first_row_indentation],
|
x_offsets: vec![first_row_indentation],
|
||||||
|
uv_rects: vec![],
|
||||||
y_min: cursor_y,
|
y_min: cursor_y,
|
||||||
y_max: cursor_y + row_height,
|
y_max: cursor_y + row_height,
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
|
@ -396,6 +397,7 @@ impl Font {
|
||||||
} else if text.ends_with('\n') {
|
} else if text.ends_with('\n') {
|
||||||
rows.push(Row {
|
rows.push(Row {
|
||||||
x_offsets: vec![0.0],
|
x_offsets: vec![0.0],
|
||||||
|
uv_rects: vec![],
|
||||||
y_min: cursor_y,
|
y_min: cursor_y,
|
||||||
y_max: cursor_y + row_height,
|
y_max: cursor_y + row_height,
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
|
@ -415,8 +417,7 @@ impl Font {
|
||||||
rows,
|
rows,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
galley.sanity_check();
|
self.finalize_galley(galley)
|
||||||
galley
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A paragraph is text with no line break character in it.
|
/// A paragraph is text with no line break character in it.
|
||||||
|
@ -431,6 +432,7 @@ impl Font {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return vec![Row {
|
return vec![Row {
|
||||||
x_offsets: vec![first_row_indentation],
|
x_offsets: vec![first_row_indentation],
|
||||||
|
uv_rects: vec![],
|
||||||
y_min: 0.0,
|
y_min: 0.0,
|
||||||
y_max: self.row_height(),
|
y_max: self.row_height(),
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
|
@ -461,28 +463,26 @@ impl Font {
|
||||||
{
|
{
|
||||||
// Allow the first row to be completely empty, because we know there will be more space on the next row:
|
// Allow the first row to be completely empty, because we know there will be more space on the next row:
|
||||||
assert_eq!(row_start_idx, 0);
|
assert_eq!(row_start_idx, 0);
|
||||||
let row = Row {
|
out_rows.push(Row {
|
||||||
x_offsets: vec![first_row_indentation],
|
x_offsets: vec![first_row_indentation],
|
||||||
|
uv_rects: vec![],
|
||||||
y_min: cursor_y,
|
y_min: cursor_y,
|
||||||
y_max: cursor_y + self.row_height(),
|
y_max: cursor_y + self.row_height(),
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
};
|
});
|
||||||
row.sanity_check();
|
|
||||||
out_rows.push(row);
|
|
||||||
cursor_y = self.round_to_pixel(cursor_y + self.row_height());
|
cursor_y = self.round_to_pixel(cursor_y + self.row_height());
|
||||||
first_row_indentation = 0.0; // Continue all other rows as if there is no indentation
|
first_row_indentation = 0.0; // Continue all other rows as if there is no indentation
|
||||||
} else if let Some(last_kept_index) = row_break_candidates.get() {
|
} else if let Some(last_kept_index) = row_break_candidates.get() {
|
||||||
let row = Row {
|
out_rows.push(Row {
|
||||||
x_offsets: full_x_offsets[row_start_idx..=last_kept_index + 1]
|
x_offsets: full_x_offsets[row_start_idx..=last_kept_index + 1]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| first_row_indentation + x - row_start_x)
|
.map(|x| first_row_indentation + x - row_start_x)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
uv_rects: vec![], // Will be filled in later!
|
||||||
y_min: cursor_y,
|
y_min: cursor_y,
|
||||||
y_max: cursor_y + self.row_height(),
|
y_max: cursor_y + self.row_height(),
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
};
|
});
|
||||||
row.sanity_check();
|
|
||||||
out_rows.push(row);
|
|
||||||
|
|
||||||
row_start_idx = last_kept_index + 1;
|
row_start_idx = last_kept_index + 1;
|
||||||
row_start_x = first_row_indentation + full_x_offsets[row_start_idx];
|
row_start_x = first_row_indentation + full_x_offsets[row_start_idx];
|
||||||
|
@ -495,21 +495,39 @@ impl Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
if row_start_idx + 1 < full_x_offsets.len() {
|
if row_start_idx + 1 < full_x_offsets.len() {
|
||||||
let row = Row {
|
out_rows.push(Row {
|
||||||
x_offsets: full_x_offsets[row_start_idx..]
|
x_offsets: full_x_offsets[row_start_idx..]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| first_row_indentation + x - row_start_x)
|
.map(|x| first_row_indentation + x - row_start_x)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
uv_rects: vec![], // Will be filled in later!
|
||||||
y_min: cursor_y,
|
y_min: cursor_y,
|
||||||
y_max: cursor_y + self.row_height(),
|
y_max: cursor_y + self.row_height(),
|
||||||
ends_with_newline: false,
|
ends_with_newline: false,
|
||||||
};
|
});
|
||||||
row.sanity_check();
|
|
||||||
out_rows.push(row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out_rows
|
out_rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize_galley(&self, mut galley: Galley) -> Galley {
|
||||||
|
let mut chars = galley.text.chars();
|
||||||
|
for row in &mut galley.rows {
|
||||||
|
row.uv_rects.clear();
|
||||||
|
row.uv_rects.reserve(row.char_count_excluding_newline());
|
||||||
|
for _ in 0..row.char_count_excluding_newline() {
|
||||||
|
let c = chars.next().unwrap();
|
||||||
|
row.uv_rects.push(self.uv_rect(c));
|
||||||
|
}
|
||||||
|
if row.ends_with_newline {
|
||||||
|
let newline = chars.next().unwrap();
|
||||||
|
assert_eq!(newline, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(chars.next(), None);
|
||||||
|
galley.sanity_check();
|
||||||
|
galley
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps track of good places to break a long row of text.
|
/// Keeps track of good places to break a long row of text.
|
||||||
|
|
|
@ -247,6 +247,7 @@ impl Fonts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn pixels_per_point(&self) -> f32 {
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
self.pixels_per_point
|
self.pixels_per_point
|
||||||
}
|
}
|
||||||
|
@ -255,6 +256,11 @@ impl Fonts {
|
||||||
&self.definitions
|
&self.definitions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||||
|
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
/// Call each frame to get the latest available font texture data.
|
/// Call each frame to get the latest available font texture data.
|
||||||
pub fn texture(&self) -> Arc<Texture> {
|
pub fn texture(&self) -> Arc<Texture> {
|
||||||
let atlas = self.atlas.lock();
|
let atlas = self.atlas.lock();
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
//! and the start of the second row.
|
//! and the start of the second row.
|
||||||
//! [`CCursor::prefer_next_row`] etc selects which.
|
//! [`CCursor::prefer_next_row`] etc selects which.
|
||||||
|
|
||||||
use super::cursor::*;
|
use super::{cursor::*, font::UvRect};
|
||||||
use emath::{pos2, NumExt, Rect, Vec2};
|
use emath::{pos2, NumExt, Rect, Vec2};
|
||||||
|
|
||||||
/// A collection of text locked into place.
|
/// A collection of text locked into place.
|
||||||
|
@ -52,6 +52,9 @@ pub struct Row {
|
||||||
/// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1`
|
/// `x_offsets.len() + (ends_with_newline as usize) == text.chars().count() + 1`
|
||||||
pub x_offsets: Vec<f32>,
|
pub x_offsets: Vec<f32>,
|
||||||
|
|
||||||
|
/// Per-character. Used when rendering.
|
||||||
|
pub uv_rects: Vec<Option<UvRect>>,
|
||||||
|
|
||||||
/// Top of the row, offset within the Galley.
|
/// Top of the row, offset within the Galley.
|
||||||
/// Unit: points.
|
/// Unit: points.
|
||||||
pub y_min: f32,
|
pub y_min: f32,
|
||||||
|
@ -71,6 +74,7 @@ pub struct Row {
|
||||||
impl Row {
|
impl Row {
|
||||||
pub fn sanity_check(&self) {
|
pub fn sanity_check(&self) {
|
||||||
assert!(!self.x_offsets.is_empty());
|
assert!(!self.x_offsets.is_empty());
|
||||||
|
assert!(self.x_offsets.len() == self.uv_rects.len() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Excludes the implicit `\n` after the `Row`, if any.
|
/// Excludes the implicit `\n` after the `Row`, if any.
|
||||||
|
|
Loading…
Reference in a new issue