egui/epaint/src/tessellator.rs

843 lines
28 KiB
Rust
Raw Normal View History

//! Converts graphics primitives into textured triangles.
//!
2021-01-10 10:43:01 +00:00
//! This module converts lines, circles, text and more represented by [`Shape`]
2021-01-25 20:23:24 +00:00
//! into textured triangles represented by [`Mesh`].
2020-07-30 10:30:20 +00:00
#![allow(clippy::identity_op)]
use crate::*;
use emath::*;
use std::f32::consts::TAU;
2019-01-04 13:14:32 +00:00
2020-04-20 21:33:16 +00:00
// ----------------------------------------------------------------------------
2020-04-19 21:34:34 +00:00
#[derive(Clone, Debug, Default)]
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
struct PathPoint {
pos: Pos2,
2020-09-01 21:54:21 +00:00
/// For filled paths the normal is used for anti-aliasing (both strokes and filled areas).
///
2020-09-01 21:54:21 +00:00
/// For strokes the normal is also used for giving thickness to the path
/// (i.e. in what direction to expand).
///
/// The normal could be estimated by differences between successive points,
/// but that would be less accurate (and in some cases slower).
///
/// Normals are normally unit-length.
normal: Vec2,
}
/// A connected line (without thickness or gaps) which can be tessellated
2020-09-01 21:54:21 +00:00
/// to either to a stroke (with thickness) or a filled convex area.
2020-12-28 23:51:27 +00:00
/// Used as a scratch-pad during tessellation.
2020-04-19 21:34:34 +00:00
#[derive(Clone, Debug, Default)]
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
pub struct Path(Vec<PathPoint>);
impl Path {
#[inline(always)]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline(always)]
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional)
}
2020-05-11 11:11:01 +00:00
#[inline(always)]
pub fn add_point(&mut self, pos: Pos2, normal: Vec2) {
self.0.push(PathPoint { pos, normal });
}
pub fn add_circle(&mut self, center: Pos2, radius: f32) {
2020-05-17 15:44:29 +00:00
let n = (radius * 4.0).round() as i32; // TODO: tweak a bit more
let n = n.clamp(4, 64);
self.reserve(n as usize);
for i in 0..n {
let angle = remap(i as f32, 0.0..=n as f32, 0.0..=TAU);
let normal = vec2(angle.cos(), angle.sin());
self.add_point(center + radius * normal, normal);
}
}
2020-05-11 11:11:01 +00:00
pub fn add_line_segment(&mut self, points: [Pos2; 2]) {
self.reserve(2);
2020-05-11 11:11:01 +00:00
let normal = (points[1] - points[0]).normalized().rot90();
self.add_point(points[0], normal);
self.add_point(points[1], normal);
}
2020-05-23 12:14:36 +00:00
pub fn add_open_points(&mut self, points: &[Pos2]) {
let n = points.len();
assert!(n >= 2);
2020-05-11 11:11:01 +00:00
if n == 2 {
// Common case optimization:
self.add_line_segment([points[0], points[1]]);
} else {
self.reserve(n);
2020-05-11 11:11:01 +00:00
self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
2021-03-30 20:36:45 +00:00
let mut n0 = (points[1] - points[0]).normalized().rot90();
2020-05-11 11:11:01 +00:00
for i in 1..n - 1 {
let mut n1 = (points[i + 1] - points[i]).normalized().rot90();
// Handle duplicated points (but not triplicated...):
if n0 == Vec2::ZERO {
n0 = n1;
} else if n1 == Vec2::ZERO {
n1 = n0;
}
let normal = (n0 + n1) / 2.0;
let length_sq = normal.length_sq();
let right_angle_length_sq = 0.5;
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
if sharper_than_a_right_angle {
// cut off the sharp corner
let center_normal = normal.normalized();
let n0c = (n0 + center_normal) / 2.0;
let n1c = (n1 + center_normal) / 2.0;
self.add_point(points[i], n0c / n0c.length_sq());
self.add_point(points[i], n1c / n1c.length_sq());
} else {
// miter join
self.add_point(points[i], normal / length_sq);
}
2021-03-30 20:36:45 +00:00
n0 = n1;
2020-05-11 11:11:01 +00:00
}
self.add_point(
points[n - 1],
(points[n - 1] - points[n - 2]).normalized().rot90(),
);
}
}
pub fn add_line_loop(&mut self, points: &[Pos2]) {
let n = points.len();
assert!(n >= 2);
self.reserve(n);
2021-03-30 20:36:45 +00:00
let mut n0 = (points[0] - points[n - 1]).normalized().rot90();
for i in 0..n {
2021-03-30 20:36:45 +00:00
let next_i = if i + 1 == n { 0 } else { i + 1 };
let mut n1 = (points[next_i] - points[i]).normalized().rot90();
// Handle duplicated points (but not triplicated...):
if n0 == Vec2::ZERO {
n0 = n1;
} else if n1 == Vec2::ZERO {
n1 = n0;
}
let normal = (n0 + n1) / 2.0;
let length_sq = normal.length_sq();
let right_angle_length_sq = 0.5;
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
if sharper_than_a_right_angle {
// cut off the sharp corner
let center_normal = normal.normalized();
let n0c = (n0 + center_normal) / 2.0;
let n1c = (n1 + center_normal) / 2.0;
self.add_point(points[i], n0c / n0c.length_sq());
self.add_point(points[i], n1c / n1c.length_sq());
} else {
// miter join
self.add_point(points[i], normal / length_sq);
}
2021-03-30 20:36:45 +00:00
n0 = n1;
}
}
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
/// Open-ended.
pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
stroke_path(&self.0, PathType::Open, stroke, options, out)
}
/// A closed path (returning to the first point).
pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
stroke_path(&self.0, PathType::Closed, stroke, options, out)
}
pub fn stroke(
&self,
path_type: PathType,
stroke: Stroke,
options: &TessellationOptions,
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
out: &mut Mesh,
) {
stroke_path(&self.0, path_type, stroke, options, out)
}
/// The path is taken to be closed (i.e. returning to the start again).
pub fn fill(&self, color: Color32, options: &TessellationOptions, out: &mut Mesh) {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
fill_closed_path(&self.0, color, options, out)
}
}
pub mod path {
//! Helpers for constructing paths
use super::*;
/// overwrites existing points
pub fn rounded_rectangle(path: &mut Vec<Pos2>, rect: Rect, corner_radius: f32) {
path.clear();
let min = rect.min;
let max = rect.max;
let cr = corner_radius
.min(rect.width() * 0.5)
.min(rect.height() * 0.5);
if cr <= 0.0 {
let min = rect.min;
let max = rect.max;
path.reserve(4);
path.push(pos2(min.x, min.y));
path.push(pos2(max.x, min.y));
path.push(pos2(max.x, max.y));
path.push(pos2(min.x, max.y));
} else {
add_circle_quadrant(path, pos2(max.x - cr, max.y - cr), cr, 0.0);
add_circle_quadrant(path, pos2(min.x + cr, max.y - cr), cr, 1.0);
add_circle_quadrant(path, pos2(min.x + cr, min.y + cr), cr, 2.0);
add_circle_quadrant(path, pos2(max.x - cr, min.y + cr), cr, 3.0);
}
}
/// Add one quadrant of a circle
///
/// * quadrant 0: right bottom
/// * quadrant 1: left bottom
/// * quadrant 2: left top
/// * quadrant 3: right top
//
// Derivation:
//
// * angle 0 * TAU / 4 = right
// - quadrant 0: right bottom
// * angle 1 * TAU / 4 = bottom
// - quadrant 1: left bottom
// * angle 2 * TAU / 4 = left
// - quadrant 2: left top
// * angle 3 * TAU / 4 = top
// - quadrant 3: right top
// * angle 4 * TAU / 4 = right
pub fn add_circle_quadrant(path: &mut Vec<Pos2>, center: Pos2, radius: f32, quadrant: f32) {
// TODO: optimize with precalculated vertices for some radii ranges
let n = (radius * 0.75).round() as i32; // TODO: tweak a bit more
let n = n.clamp(2, 32);
const RIGHT_ANGLE: f32 = TAU / 4.0;
path.reserve(n as usize + 1);
for i in 0..=n {
let angle = remap(
i as f32,
0.0..=n as f32,
quadrant * RIGHT_ANGLE..=(quadrant + 1.0) * RIGHT_ANGLE,
);
path.push(center + radius * Vec2::angled(angle));
}
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, PartialEq)]
pub enum PathType {
Open,
Closed,
}
2020-12-28 23:51:27 +00:00
/// Tessellation quality options
#[derive(Clone, Copy, Debug, PartialEq)]
2021-01-10 10:37:47 +00:00
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
2020-12-28 23:51:27 +00:00
pub struct TessellationOptions {
/// Size of a point in pixels, e.g. 2.0. Used to snap text to pixel boundaries.
pub pixels_per_point: f32,
/// Size of a pixel in points, e.g. 0.5, or larger if you want more blurry edges.
pub aa_size: f32,
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
/// This setting does not affect text.
/// Default: `true`.
pub anti_alias: bool,
/// If `true` (default) cull certain primitives before tessellating them
pub coarse_tessellation_culling: bool,
2020-08-09 15:24:32 +00:00
/// Output the clip rectangles to be painted?
2020-04-21 08:33:33 +00:00
pub debug_paint_clip_rects: bool,
/// Output the text-containing rectangles
pub debug_paint_text_rects: bool,
/// If true, no clipping will be done
pub debug_ignore_clip_rects: bool,
2020-04-21 08:33:33 +00:00
}
2020-12-28 23:51:27 +00:00
impl Default for TessellationOptions {
2020-04-21 08:33:33 +00:00
fn default() -> Self {
Self {
pixels_per_point: 1.0,
2020-04-21 08:33:33 +00:00
aa_size: 1.0,
2020-12-19 14:11:43 +00:00
anti_alias: true,
coarse_tessellation_culling: true,
debug_paint_text_rects: false,
2020-04-21 08:33:33 +00:00
debug_paint_clip_rects: false,
debug_ignore_clip_rects: false,
2020-04-21 08:33:33 +00:00
}
}
}
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
impl TessellationOptions {
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
Self {
pixels_per_point,
aa_size: 1.0 / pixels_per_point,
..Default::default()
}
}
}
impl TessellationOptions {
#[inline(always)]
pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.pixels_per_point).round() / self.pixels_per_point
}
}
2020-12-28 23:51:27 +00:00
/// Tessellate the given convex area into a polygon.
fn fill_closed_path(
path: &[PathPoint],
2021-01-02 16:02:18 +00:00
color: Color32,
options: &TessellationOptions,
2021-01-25 20:23:24 +00:00
out: &mut Mesh,
) {
if color == Color32::TRANSPARENT {
return;
}
let n = path.len() as u32;
if options.anti_alias {
out.reserve_triangles(3 * n as usize);
out.reserve_vertices(2 * n as usize);
let color_outer = Color32::TRANSPARENT;
let idx_inner = out.vertices.len() as u32;
let idx_outer = idx_inner + 1;
for i in 2..n {
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
}
let mut i0 = n - 1;
for i1 in 0..n {
let p1 = &path[i1 as usize];
2021-03-30 20:36:45 +00:00
let dm = 0.5 * options.aa_size * p1.normal;
out.colored_vertex(p1.pos - dm, color);
out.colored_vertex(p1.pos + dm, color_outer);
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
i0 = i1;
}
} else {
out.reserve_triangles(n as usize);
let idx = out.vertices.len() as u32;
out.vertices.extend(path.iter().map(|p| Vertex {
pos: p.pos,
uv: WHITE_UV,
color,
}));
for i in 2..n {
out.add_triangle(idx, idx + i - 1, idx + i);
}
}
}
2019-01-05 14:28:07 +00:00
2020-12-28 23:51:27 +00:00
/// Tessellate the given path as a stroke with thickness.
2020-09-01 21:54:21 +00:00
fn stroke_path(
path: &[PathPoint],
path_type: PathType,
2020-09-01 21:54:21 +00:00
stroke: Stroke,
options: &TessellationOptions,
2021-01-25 20:23:24 +00:00
out: &mut Mesh,
) {
if stroke.width <= 0.0 || stroke.color == Color32::TRANSPARENT {
return;
}
let n = path.len() as u32;
let idx = out.vertices.len() as u32;
if options.anti_alias {
2020-09-01 21:54:21 +00:00
let color_inner = stroke.color;
let color_outer = Color32::TRANSPARENT;
2020-09-01 21:54:21 +00:00
let thin_line = stroke.width <= options.aa_size;
if thin_line {
/*
We paint the line using three edges: outer, inner, outer.
. o i o outer, inner, outer
. |---| aa_size (pixel width)
*/
// Fade out as it gets thinner:
2020-09-01 21:54:21 +00:00
let color_inner = mul_color(color_inner, stroke.width / options.aa_size);
if color_inner == Color32::TRANSPARENT {
return;
}
out.reserve_triangles(4 * n as usize);
out.reserve_vertices(3 * n as usize);
2020-05-20 19:22:53 +00:00
let mut i0 = n - 1;
for i1 in 0..n {
let connect_with_previous = path_type == PathType::Closed || i1 > 0;
let p1 = &path[i1 as usize];
let p = p1.pos;
let n = p1.normal;
out.colored_vertex(p + n * options.aa_size, color_outer);
out.colored_vertex(p, color_inner);
out.colored_vertex(p - n * options.aa_size, color_outer);
if connect_with_previous {
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
out.add_triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
}
i0 = i1;
}
} else {
2020-08-09 15:24:32 +00:00
// thick line
// TODO: line caps for really thick lines?
/*
We paint the line using four edges: outer, inner, inner, outer
. o i p i o outer, inner, point, inner, outer
. |---| aa_size (pixel width)
. |--------------| width
. |---------| outer_rad
. |-----| inner_rad
*/
out.reserve_triangles(6 * n as usize);
out.reserve_vertices(4 * n as usize);
2020-05-20 19:22:53 +00:00
let mut i0 = n - 1;
for i1 in 0..n {
let connect_with_previous = path_type == PathType::Closed || i1 > 0;
2020-09-01 21:54:21 +00:00
let inner_rad = 0.5 * (stroke.width - options.aa_size);
let outer_rad = 0.5 * (stroke.width + options.aa_size);
let p1 = &path[i1 as usize];
let p = p1.pos;
let n = p1.normal;
out.colored_vertex(p + n * outer_rad, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner);
out.colored_vertex(p - n * inner_rad, color_inner);
out.colored_vertex(p - n * outer_rad, color_outer);
if connect_with_previous {
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
}
i0 = i1;
2019-01-05 19:14:16 +00:00
}
}
} else {
out.reserve_triangles(2 * n as usize);
out.reserve_vertices(2 * n as usize);
2020-05-20 19:22:53 +00:00
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
let last_index = if path_type == PathType::Closed {
n
} else {
n - 1
};
for i in 0..last_index {
out.add_triangle(
idx + (2 * i + 0) % (2 * n),
idx + (2 * i + 1) % (2 * n),
idx + (2 * i + 2) % (2 * n),
);
out.add_triangle(
idx + (2 * i + 2) % (2 * n),
idx + (2 * i + 1) % (2 * n),
idx + (2 * i + 3) % (2 * n),
);
}
2020-09-01 21:54:21 +00:00
let thin_line = stroke.width <= options.aa_size;
if thin_line {
// Fade out thin lines rather than making them thinner
let radius = options.aa_size / 2.0;
2020-09-01 21:54:21 +00:00
let color = mul_color(stroke.color, stroke.width / options.aa_size);
if color == Color32::TRANSPARENT {
return;
}
for p in path {
out.colored_vertex(p.pos + radius * p.normal, color);
out.colored_vertex(p.pos - radius * p.normal, color);
}
} else {
2020-09-01 21:54:21 +00:00
let radius = stroke.width / 2.0;
for p in path {
out.colored_vertex(p.pos + radius * p.normal, stroke.color);
out.colored_vertex(p.pos - radius * p.normal, stroke.color);
}
2019-01-05 14:28:07 +00:00
}
}
}
2019-01-05 14:28:07 +00:00
2021-01-02 16:02:18 +00:00
fn mul_color(color: Color32, factor: f32) -> Color32 {
crate::epaint_assert!(0.0 <= factor && factor <= 1.0);
2021-01-19 23:30:07 +00:00
// As an unfortunate side-effect of using premultiplied alpha
// we need a somewhat expensive conversion to linear space and back.
color.linear_multiply(factor)
}
// ----------------------------------------------------------------------------
2019-01-05 19:14:16 +00:00
2021-01-25 20:23:24 +00:00
/// Converts [`Shape`]s into [`Mesh`].
2020-12-29 00:13:14 +00:00
pub struct Tessellator {
2021-01-10 13:39:03 +00:00
options: TessellationOptions,
2020-12-29 00:13:14 +00:00
/// Only used for culling
2021-01-10 13:39:03 +00:00
clip_rect: Rect,
2020-12-29 00:13:14 +00:00
scratchpad_points: Vec<Pos2>,
scratchpad_path: Path,
}
2020-12-29 00:13:14 +00:00
impl Tessellator {
pub fn from_options(options: TessellationOptions) -> Self {
Self {
options,
clip_rect: Rect::EVERYTHING,
2020-12-29 00:13:14 +00:00
scratchpad_points: Default::default(),
scratchpad_path: Default::default(),
}
}
2021-01-25 20:23:24 +00:00
/// Tessellate a single [`Shape`] into a [`Mesh`].
2020-12-29 00:13:14 +00:00
///
2021-01-10 10:43:01 +00:00
/// * `shape`: the shape to tessellate
2020-12-29 00:13:14 +00:00
/// * `options`: tessellation quality
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
2020-12-29 00:13:14 +00:00
/// * `out`: where the triangles are put
2021-01-10 10:43:01 +00:00
/// * `scratchpad_path`: if you plan to run `tessellate_shape`
2020-12-29 00:13:14 +00:00
/// many times, pass it a reference to the same `Path` to avoid excessive allocations.
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) {
2020-12-29 00:13:14 +00:00
let clip_rect = self.clip_rect;
let options = &self.options;
2020-12-29 00:13:14 +00:00
2021-01-10 10:43:01 +00:00
match shape {
Shape::Noop => {}
Shape::Vec(vec) => {
for shape in vec {
self.tessellate_shape(tex_size, shape, out)
2020-12-29 00:13:14 +00:00
}
}
Shape::Circle(CircleShape {
2020-12-29 00:13:14 +00:00
center,
radius,
fill,
stroke,
}) => {
2020-12-29 00:13:14 +00:00
if radius <= 0.0 {
return;
}
2020-12-29 00:13:14 +00:00
if options.coarse_tessellation_culling
&& !clip_rect.expand(radius + stroke.width).contains(center)
{
return;
}
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
self.scratchpad_path.clear();
self.scratchpad_path.add_circle(center, radius);
self.scratchpad_path.fill(fill, options, out);
self.scratchpad_path.stroke_closed(stroke, options, out);
}
2021-01-25 20:23:24 +00:00
Shape::Mesh(mesh) => {
if !mesh.is_valid() {
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
return;
}
if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds())
{
return;
}
out.append(mesh);
2020-12-29 00:13:14 +00:00
}
2021-01-10 10:43:01 +00:00
Shape::LineSegment { points, stroke } => {
if stroke.is_empty() {
return;
}
if options.coarse_tessellation_culling
&& !clip_rect
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
{
return;
}
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
self.scratchpad_path.clear();
self.scratchpad_path.add_line_segment(points);
self.scratchpad_path.stroke_open(stroke, options, out);
2020-12-29 00:13:14 +00:00
}
Shape::Path(path_shape) => {
self.tessellate_path(path_shape, out);
2020-12-29 00:13:14 +00:00
}
Shape::Rect(rect_shape) => {
self.tessellate_rect(&rect_shape, out);
2020-12-29 00:13:14 +00:00
}
Shape::Text(text_shape) => {
if options.debug_paint_text_rects {
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
self.tessellate_rect(
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
out,
);
}
self.tessellate_text(tex_size, text_shape, out);
2020-04-20 21:33:16 +00:00
}
}
2020-12-29 00:13:14 +00:00
}
pub(crate) fn tessellate_path(&mut self, path_shape: PathShape, out: &mut Mesh) {
if path_shape.points.len() < 2 {
return;
}
if self.options.coarse_tessellation_culling
&& !path_shape.bounding_rect().intersects(self.clip_rect)
{
return;
}
let PathShape {
points,
closed,
fill,
stroke,
} = path_shape;
self.scratchpad_path.clear();
if closed {
self.scratchpad_path.add_line_loop(&points);
} else {
self.scratchpad_path.add_open_points(&points);
}
if fill != Color32::TRANSPARENT {
crate::epaint_assert!(
closed,
"You asked to fill a path that is not closed. That makes no sense."
);
self.scratchpad_path.fill(fill, &self.options, out);
}
let typ = if closed {
PathType::Closed
} else {
PathType::Open
};
self.scratchpad_path.stroke(typ, stroke, &self.options, out);
}
pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
let RectShape {
mut rect,
2020-04-20 21:33:16 +00:00
corner_radius,
fill,
2020-09-01 21:54:21 +00:00
stroke,
2020-12-29 00:13:14 +00:00
} = *rect;
2020-12-29 00:13:14 +00:00
if self.options.coarse_tessellation_culling
&& !rect.expand(stroke.width).intersects(self.clip_rect)
{
return;
}
if rect.is_negative() {
2020-12-29 00:13:14 +00:00
return;
}
2020-12-29 00:13:14 +00:00
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
// Make sure we can handle that:
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
rect.max = rect.max.at_most(pos2(1e7, 1e7));
2020-12-29 00:13:14 +00:00
let path = &mut self.scratchpad_path;
path.clear();
path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius);
path.add_line_loop(&self.scratchpad_points);
path.fill(fill, &self.options, out);
path.stroke_closed(stroke, &self.options, out);
2020-12-29 00:13:14 +00:00
}
pub fn tessellate_text(&mut self, tex_size: [usize; 2], text_shape: TextShape, out: &mut Mesh) {
let TextShape {
pos: galley_pos,
galley,
underline,
override_text_color,
angle,
} = text_shape;
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
if galley.is_empty() {
2020-12-29 00:13:14 +00:00
return;
2020-12-16 20:59:33 +00:00
}
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
out.vertices.reserve(galley.num_vertices);
out.indices.reserve(galley.num_indices);
2020-12-16 20:59:33 +00:00
// The contents of the galley is already snapped to pixel coordinates,
// but we need to make sure the galley ends up on the start of a physical pixel:
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
let galley_pos = pos2(
self.options.round_to_pixel(galley_pos.x),
self.options.round_to_pixel(galley_pos.y),
);
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
2020-12-16 20:59:33 +00:00
let rotator = Rot2::from_angle(angle);
for row in &galley.rows {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
if row.visuals.mesh.is_empty() {
continue;
}
let mut row_rect = row.visuals.mesh_bounds;
if angle != 0.0 {
row_rect = row_rect.rotate_bb(rotator);
}
row_rect = row_rect.translate(galley_pos.to_vec2());
2020-12-16 20:59:33 +00:00
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
// culling individual lines of text is important, since a single `Shape::Text`
// can span hundreds of lines.
continue;
}
2020-12-16 20:59:33 +00:00
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
let index_offset = out.vertices.len() as u32;
2021-01-30 13:48:36 +00:00
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
out.indices.extend(
row.visuals
.mesh
.indices
.iter()
.map(|index| index + index_offset),
);
2021-01-30 13:48:36 +00:00
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
out.vertices.extend(
row.visuals
.mesh
.vertices
.iter()
.enumerate()
.map(|(i, vertex)| {
let Vertex { pos, uv, mut color } = *vertex;
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
if let Some(override_text_color) = override_text_color {
if row.visuals.glyph_vertex_range.contains(&i) {
color = override_text_color;
}
}
let offset = if angle == 0.0 {
pos.to_vec2()
} else {
rotator * pos.to_vec2()
};
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
Vertex {
pos: galley_pos + offset,
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
2021-01-30 13:48:36 +00:00
color,
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
}
}),
);
if underline != Stroke::none() {
self.scratchpad_path.clear();
self.scratchpad_path
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
self.scratchpad_path
.stroke_open(underline, &self.options, out);
2020-12-29 00:13:14 +00:00
}
2020-12-16 20:59:33 +00:00
}
2019-01-04 13:14:32 +00:00
}
}
2020-04-20 21:33:16 +00:00
2021-01-10 10:43:01 +00:00
/// Turns [`Shape`]:s into sets of triangles.
///
2021-01-10 10:43:01 +00:00
/// The given shapes will be painted back-to-front (painters algorithm).
/// They will be batched together by clip rectangle.
///
2021-01-10 10:43:01 +00:00
/// * `shapes`: the shape to tessellate
2020-12-28 23:51:27 +00:00
/// * `options`: tessellation quality
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
///
/// ## Returns
2021-01-25 20:23:24 +00:00
/// A list of clip rectangles with matching [`Mesh`].
2021-01-10 10:43:01 +00:00
pub fn tessellate_shapes(
shapes: Vec<ClippedShape>,
2020-12-28 23:51:27 +00:00
options: TessellationOptions,
tex_size: [usize; 2],
) -> Vec<ClippedMesh> {
2020-12-29 00:13:14 +00:00
let mut tessellator = Tessellator::from_options(options);
2020-05-11 11:11:01 +00:00
let mut clipped_meshes: Vec<ClippedMesh> = Vec::default();
for ClippedShape(clip_rect, shape) in shapes {
if !clip_rect.is_positive() {
continue; // skip empty clip rectangles
}
let start_new_mesh = match clipped_meshes.last() {
None => true,
Some(cm) => cm.0 != clip_rect || cm.1.texture_id != shape.texture_id(),
};
if start_new_mesh {
clipped_meshes.push(ClippedMesh(clip_rect, Mesh::default()));
2020-04-20 21:33:16 +00:00
}
let out = &mut clipped_meshes.last_mut().unwrap().1;
2020-12-29 00:13:14 +00:00
tessellator.clip_rect = clip_rect;
tessellator.tessellate_shape(tex_size, shape, out);
2020-04-20 21:33:16 +00:00
}
2020-05-11 16:14:02 +00:00
if options.debug_paint_clip_rects {
for ClippedMesh(clip_rect, mesh) in &mut clipped_meshes {
tessellator.clip_rect = Rect::EVERYTHING;
2021-01-10 10:43:01 +00:00
tessellator.tessellate_shape(
tex_size,
Shape::rect_stroke(
*clip_rect,
0.0,
Stroke::new(2.0, Color32::from_rgb(150, 255, 150)),
),
2021-01-25 20:23:24 +00:00
mesh,
2020-05-11 16:14:02 +00:00
)
}
}
if options.debug_ignore_clip_rects {
for ClippedMesh(clip_rect, _) in &mut clipped_meshes {
*clip_rect = Rect::EVERYTHING;
}
}
for ClippedMesh(_, mesh) in &clipped_meshes {
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
}
clipped_meshes
2020-04-20 21:33:16 +00:00
}