Fix tessellation of Shape::Vec of heterogenous TextureId:s (#1445)
Closes https://github.com/emilk/egui/issues/1443
This commit is contained in:
parent
c2039920de
commit
10f30a0c52
7 changed files with 114 additions and 64 deletions
|
@ -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)).
|
||||
|
|
|
@ -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)).
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)).
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue