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)).
|
* `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)).
|
||||||
|
|
|
@ -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)).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue