Fix tessellation of Shape::Vec of heterogenous TextureId:s (#1445)

Closes https://github.com/emilk/egui/issues/1443
This commit is contained in:
Emil Ernerfeldt 2022-04-03 18:14:27 +02:00 committed by GitHub
parent c2039920de
commit 10f30a0c52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 64 deletions

View file

@ -18,7 +18,6 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
* Renamed `Frame::margin` to `Frame::inner_margin`. * Renamed `Frame::margin` to `Frame::inner_margin`.
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)). * Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
* `dark-light` (dark mode detection) is now an opt-in feature for `eframe` and `egui_glow` ([#1437](https://github.com/emilk/egui/pull/1437)).
### Fixed 🐛 ### Fixed 🐛
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). * Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).

View file

@ -19,6 +19,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
* Changed `App::update` to take `&mut Frame` instead of `&Frame`. * Changed `App::update` to take `&mut Frame` instead of `&Frame`.
* `Frame` is no longer `Clone` or `Sync`. * `Frame` is no longer `Clone` or `Sync`.
* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)). * Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)). * Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).

View file

@ -123,13 +123,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}); });
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width); let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
let mut tessellator = egui::epaint::Tessellator::new(1.0, Default::default()); let font_image_size = fonts.font_image_size();
let mut tessellator =
egui::epaint::Tessellator::new(1.0, Default::default(), font_image_size);
let mut mesh = egui::epaint::Mesh::default(); let mut mesh = egui::epaint::Mesh::default();
let text_shape = TextShape::new(egui::Pos2::ZERO, galley); let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
let font_image_size = fonts.font_image_size();
c.bench_function("tessellate_text", |b| { c.bench_function("tessellate_text", |b| {
b.iter(|| { b.iter(|| {
tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh); tessellator.tessellate_text(&text_shape, &mut mesh);
mesh.clear(); mesh.clear();
}); });
}); });

View file

@ -5,6 +5,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased ## Unreleased
* Improved logging on rendering failures. * Improved logging on rendering failures.
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. * Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)). * Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).

View file

@ -9,6 +9,7 @@ All notable changes to the epaint crate will be documented in this file.
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)). * Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)). * Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
* Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)). * Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)).
* Fix panic when tessellating a [`Shape::Vec`] containing meshes with differing `TextureId`:s ([#1445](https://github.com/emilk/egui/pull/1445)).
## 0.17.0 - 2022-02-22 ## 0.17.0 - 2022-02-22

View file

@ -64,6 +64,7 @@ impl Shadow {
use crate::tessellator::*; use crate::tessellator::*;
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
let pixels_per_point = 1.0; // doesn't matter here let pixels_per_point = 1.0; // doesn't matter here
let font_tex_size = [1; 2]; // unused size we are not tessellating text.
let mut tessellator = Tessellator::new( let mut tessellator = Tessellator::new(
pixels_per_point, pixels_per_point,
TessellationOptions { TessellationOptions {
@ -71,6 +72,7 @@ impl Shadow {
feathering_size_in_pixels: extrusion * pixels_per_point, feathering_size_in_pixels: extrusion * pixels_per_point,
..Default::default() ..Default::default()
}, },
font_tex_size,
); );
let mut mesh = Mesh::default(); let mut mesh = Mesh::default();
tessellator.tessellate_rect(&rect, &mut mesh); tessellator.tessellate_rect(&rect, &mut mesh);

View file

@ -651,6 +651,7 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
pub struct Tessellator { pub struct Tessellator {
pixels_per_point: f32, pixels_per_point: f32,
options: TessellationOptions, options: TessellationOptions,
font_tex_size: [usize; 2],
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled /// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
feathering: f32, feathering: f32,
/// Only used for culling /// Only used for culling
@ -661,7 +662,13 @@ pub struct Tessellator {
impl Tessellator { impl Tessellator {
/// Create a new [`Tessellator`]. /// Create a new [`Tessellator`].
pub fn new(pixels_per_point: f32, options: TessellationOptions) -> Self { ///
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
pub fn new(
pixels_per_point: f32,
options: TessellationOptions,
font_tex_size: [usize; 2],
) -> Self {
let feathering = if options.feathering { let feathering = if options.feathering {
let pixel_size = 1.0 / pixels_per_point; let pixel_size = 1.0 / pixels_per_point;
options.feathering_size_in_pixels * pixel_size options.feathering_size_in_pixels * pixel_size
@ -671,6 +678,7 @@ impl Tessellator {
Self { Self {
pixels_per_point, pixels_per_point,
options, options,
font_tex_size,
feathering, feathering,
clip_rect: Rect::EVERYTHING, clip_rect: Rect::EVERYTHING,
scratchpad_points: Default::default(), scratchpad_points: Default::default(),
@ -692,17 +700,74 @@ impl Tessellator {
} }
} }
/// Tessellate a clipped shape into a list of primitives.
pub fn tessellate_clipped_shape(
&mut self,
clipped_shape: ClippedShape,
out_primitives: &mut Vec<ClippedPrimitive>,
) {
let ClippedShape(new_clip_rect, new_shape) = clipped_shape;
if !new_clip_rect.is_positive() {
return; // skip empty clip rectangles
}
if let Shape::Vec(shapes) = new_shape {
for shape in shapes {
self.tessellate_clipped_shape(ClippedShape(new_clip_rect, shape), out_primitives);
}
return;
}
if let Shape::Callback(callback) = new_shape {
out_primitives.push(ClippedPrimitive {
clip_rect: new_clip_rect,
primitive: Primitive::Callback(callback),
});
return;
}
let start_new_mesh = match out_primitives.last() {
None => true,
Some(output_clipped_primitive) => {
output_clipped_primitive.clip_rect != new_clip_rect
|| if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive {
output_mesh.texture_id != new_shape.texture_id()
} else {
true
}
}
};
if start_new_mesh {
out_primitives.push(ClippedPrimitive {
clip_rect: new_clip_rect,
primitive: Primitive::Mesh(Mesh::default()),
});
}
let out = out_primitives.last_mut().unwrap();
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
self.clip_rect = new_clip_rect;
self.tessellate_shape(new_shape, out_mesh);
} else {
unreachable!();
}
}
/// Tessellate a single [`Shape`] into a [`Mesh`]. /// Tessellate a single [`Shape`] into a [`Mesh`].
/// ///
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). /// This call can panic the given shape is of [`Shape::Vec`] or [`Shape::Callback`].
/// For that, use [`Self::tessellate_clipped_shape`] instead.
/// * `shape`: the shape to tessellate. /// * `shape`: the shape to tessellate.
/// * `out`: triangles are appended to this. /// * `out`: triangles are appended to this.
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) { pub fn tessellate_shape(&mut self, shape: Shape, out: &mut Mesh) {
match shape { match shape {
Shape::Noop => {} Shape::Noop => {}
Shape::Vec(vec) => { Shape::Vec(vec) => {
for shape in vec { for shape in vec {
self.tessellate_shape(tex_size, shape, out); self.tessellate_shape(shape, out);
} }
} }
Shape::Circle(circle) => { Shape::Circle(circle) => {
@ -736,7 +801,7 @@ impl Tessellator {
out, out,
); );
} }
self.tessellate_text(tex_size, &text_shape, out); self.tessellate_text(&text_shape, out);
} }
Shape::QuadraticBezier(quadratic_shape) => { Shape::QuadraticBezier(quadratic_shape) => {
self.tessellate_quadratic_bezier(quadratic_shape, out); self.tessellate_quadratic_bezier(quadratic_shape, out);
@ -902,16 +967,9 @@ impl Tessellator {
} }
/// Tessellate a single [`TextShape`] into a [`Mesh`]. /// Tessellate a single [`TextShape`] into a [`Mesh`].
///
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
/// * `text_shape`: the text to tessellate. /// * `text_shape`: the text to tessellate.
/// * `out`: triangles are appended to this. /// * `out`: triangles are appended to this.
pub fn tessellate_text( pub fn tessellate_text(&mut self, text_shape: &TextShape, out: &mut Mesh) {
&mut self,
tex_size: [usize; 2],
text_shape: &TextShape,
out: &mut Mesh,
) {
let TextShape { let TextShape {
pos: galley_pos, pos: galley_pos,
galley, galley,
@ -934,7 +992,10 @@ impl Tessellator {
self.round_to_pixel(galley_pos.y), self.round_to_pixel(galley_pos.y),
); );
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); let uv_normalizer = vec2(
1.0 / self.font_tex_size[0] as f32,
1.0 / self.font_tex_size[1] as f32,
);
let rotator = Rot2::from_angle(*angle); let rotator = Rot2::from_angle(*angle);
@ -1099,7 +1160,7 @@ impl Tessellator {
/// * `pixels_per_point`: number of physical pixels to each logical point /// * `pixels_per_point`: number of physical pixels to each logical point
/// * `options`: tessellation quality /// * `options`: tessellation quality
/// * `shapes`: what to tessellate /// * `shapes`: what to tessellate
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles) /// * `font_tex_size`: size of the font texture (required to normalize glyph uv rectangles)
/// ///
/// The implementation uses a [`Tessellator`]. /// The implementation uses a [`Tessellator`].
/// ///
@ -1109,56 +1170,18 @@ pub fn tessellate_shapes(
pixels_per_point: f32, pixels_per_point: f32,
options: TessellationOptions, options: TessellationOptions,
shapes: Vec<ClippedShape>, shapes: Vec<ClippedShape>,
tex_size: [usize; 2], font_tex_size: [usize; 2],
) -> Vec<ClippedPrimitive> { ) -> Vec<ClippedPrimitive> {
let mut tessellator = Tessellator::new(pixels_per_point, options); let mut tessellator = Tessellator::new(pixels_per_point, options, font_tex_size);
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default(); let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
for ClippedShape(new_clip_rect, new_shape) in shapes { for clipped_shape in shapes {
if !new_clip_rect.is_positive() { tessellator.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
continue; // skip empty clip rectangles
}
if let Shape::Callback(callback) = new_shape {
clipped_primitives.push(ClippedPrimitive {
clip_rect: new_clip_rect,
primitive: Primitive::Callback(callback),
});
} else {
let start_new_mesh = match clipped_primitives.last() {
None => true,
Some(output_clipped_primitive) => {
output_clipped_primitive.clip_rect != new_clip_rect
|| if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive
{
output_mesh.texture_id != new_shape.texture_id()
} else {
true
}
}
};
if start_new_mesh {
clipped_primitives.push(ClippedPrimitive {
clip_rect: new_clip_rect,
primitive: Primitive::Mesh(Mesh::default()),
});
}
let out = clipped_primitives.last_mut().unwrap();
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
tessellator.clip_rect = new_clip_rect;
tessellator.tessellate_shape(tex_size, new_shape, out_mesh);
} else {
unreachable!();
}
}
} }
if options.debug_paint_clip_rects { if options.debug_paint_clip_rects {
clipped_primitives = add_clip_rects(&mut tessellator, tex_size, clipped_primitives); clipped_primitives = add_clip_rects(&mut tessellator, clipped_primitives);
} }
if options.debug_ignore_clip_rects { if options.debug_ignore_clip_rects {
@ -1178,7 +1201,6 @@ pub fn tessellate_shapes(
fn add_clip_rects( fn add_clip_rects(
tessellator: &mut Tessellator, tessellator: &mut Tessellator,
tex_size: [usize; 2],
clipped_primitives: Vec<ClippedPrimitive>, clipped_primitives: Vec<ClippedPrimitive>,
) -> Vec<ClippedPrimitive> { ) -> Vec<ClippedPrimitive> {
tessellator.clip_rect = Rect::EVERYTHING; tessellator.clip_rect = Rect::EVERYTHING;
@ -1189,7 +1211,6 @@ fn add_clip_rects(
.flat_map(|clipped_primitive| { .flat_map(|clipped_primitive| {
let mut clip_rect_mesh = Mesh::default(); let mut clip_rect_mesh = Mesh::default();
tessellator.tessellate_shape( tessellator.tessellate_shape(
tex_size,
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke), Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
&mut clip_rect_mesh, &mut clip_rect_mesh,
); );
@ -1204,3 +1225,27 @@ fn add_clip_rects(
}) })
.collect() .collect()
} }
#[test]
fn test_tessellator() {
use crate::*;
let mut shapes = Vec::with_capacity(2);
let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
let mut mesh = Mesh::with_texture(TextureId::Managed(1));
mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
shapes.push(Shape::mesh(mesh));
let mut mesh = Mesh::with_texture(TextureId::Managed(2));
mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
shapes.push(Shape::mesh(mesh));
let shape = Shape::Vec(shapes);
let clipped_shapes = vec![ClippedShape(rect, shape)];
let primitives = tessellate_shapes(1.0, Default::default(), clipped_shapes, [100, 100]);
assert_eq!(primitives.len(), 2);
}