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)).
* 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)).
* `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 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`.
* `Frame` is no longer `Clone` or `Sync`.
* 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)).

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 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 text_shape = TextShape::new(egui::Pos2::ZERO, galley);
let font_image_size = fonts.font_image_size();
c.bench_function("tessellate_text", |b| {
b.iter(|| {
tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh);
tessellator.tessellate_text(&text_shape, &mut mesh);
mesh.clear();
});
});

View file

@ -5,6 +5,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
* Improved logging on rendering failures.
* 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)).

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 `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)).
* 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

View file

@ -64,6 +64,7 @@ impl Shadow {
use crate::tessellator::*;
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
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(
pixels_per_point,
TessellationOptions {
@ -71,6 +72,7 @@ impl Shadow {
feathering_size_in_pixels: extrusion * pixels_per_point,
..Default::default()
},
font_tex_size,
);
let mut mesh = Mesh::default();
tessellator.tessellate_rect(&rect, &mut mesh);

View file

@ -651,6 +651,7 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
pub struct Tessellator {
pixels_per_point: f32,
options: TessellationOptions,
font_tex_size: [usize; 2],
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
feathering: f32,
/// Only used for culling
@ -661,7 +662,13 @@ pub struct Tessellator {
impl 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 pixel_size = 1.0 / pixels_per_point;
options.feathering_size_in_pixels * pixel_size
@ -671,6 +678,7 @@ impl Tessellator {
Self {
pixels_per_point,
options,
font_tex_size,
feathering,
clip_rect: Rect::EVERYTHING,
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`].
///
/// * `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.
/// * `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 {
Shape::Noop => {}
Shape::Vec(vec) => {
for shape in vec {
self.tessellate_shape(tex_size, shape, out);
self.tessellate_shape(shape, out);
}
}
Shape::Circle(circle) => {
@ -736,7 +801,7 @@ impl Tessellator {
out,
);
}
self.tessellate_text(tex_size, &text_shape, out);
self.tessellate_text(&text_shape, out);
}
Shape::QuadraticBezier(quadratic_shape) => {
self.tessellate_quadratic_bezier(quadratic_shape, out);
@ -902,16 +967,9 @@ impl Tessellator {
}
/// 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.
/// * `out`: triangles are appended to this.
pub fn tessellate_text(
&mut self,
tex_size: [usize; 2],
text_shape: &TextShape,
out: &mut Mesh,
) {
pub fn tessellate_text(&mut self, text_shape: &TextShape, out: &mut Mesh) {
let TextShape {
pos: galley_pos,
galley,
@ -934,7 +992,10 @@ impl Tessellator {
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);
@ -1099,7 +1160,7 @@ impl Tessellator {
/// * `pixels_per_point`: number of physical pixels to each logical point
/// * `options`: tessellation quality
/// * `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`].
///
@ -1109,56 +1170,18 @@ pub fn tessellate_shapes(
pixels_per_point: f32,
options: TessellationOptions,
shapes: Vec<ClippedShape>,
tex_size: [usize; 2],
font_tex_size: [usize; 2],
) -> 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();
for ClippedShape(new_clip_rect, new_shape) in shapes {
if !new_clip_rect.is_positive() {
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!();
}
}
for clipped_shape in shapes {
tessellator.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
}
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 {
@ -1178,7 +1201,6 @@ pub fn tessellate_shapes(
fn add_clip_rects(
tessellator: &mut Tessellator,
tex_size: [usize; 2],
clipped_primitives: Vec<ClippedPrimitive>,
) -> Vec<ClippedPrimitive> {
tessellator.clip_rect = Rect::EVERYTHING;
@ -1189,7 +1211,6 @@ fn add_clip_rects(
.flat_map(|clipped_primitive| {
let mut clip_rect_mesh = Mesh::default();
tessellator.tessellate_shape(
tex_size,
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
&mut clip_rect_mesh,
);
@ -1204,3 +1225,27 @@ fn add_clip_rects(
})
.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);
}