From 10f30a0c5222726c3a31550773503ee063c65472 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 3 Apr 2022 18:14:27 +0200 Subject: [PATCH] Fix tessellation of Shape::Vec of heterogenous TextureId:s (#1445) Closes https://github.com/emilk/egui/issues/1443 --- CHANGELOG.md | 1 - eframe/CHANGELOG.md | 1 + egui_demo_lib/benches/benchmark.rs | 7 +- egui_glow/CHANGELOG.md | 1 + epaint/CHANGELOG.md | 1 + epaint/src/shadow.rs | 2 + epaint/src/tessellator.rs | 165 ++++++++++++++++++----------- 7 files changed, 114 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f23311..f70dbbfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)). diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 6fcd90a7..8c053f76 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -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)). diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index b3e86bbc..0dceba6a 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -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(); }); }); diff --git a/egui_glow/CHANGELOG.md b/egui_glow/CHANGELOG.md index f2ce04d4..436f2e51 100644 --- a/egui_glow/CHANGELOG.md +++ b/egui_glow/CHANGELOG.md @@ -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)). diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index 09ab4c28..231bccd5 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -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 diff --git a/epaint/src/shadow.rs b/epaint/src/shadow.rs index 35ce41cd..c6590aca 100644 --- a/epaint/src/shadow.rs +++ b/epaint/src/shadow.rs @@ -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); diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 1f9fe1a0..55e62ce9 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -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, + ) { + 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, - tex_size: [usize; 2], + font_tex_size: [usize; 2], ) -> Vec { - 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 = 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, ) -> Vec { 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); +}