Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,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)).
Expand Down
1 change: 1 addition & 0 deletions eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).


Expand Down
7 changes: 4 additions & 3 deletions egui_demo_lib/benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Expand Down
1 change: 1 addition & 0 deletions egui_glow/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).


Expand Down
1 change: 1 addition & 0 deletions epaint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions epaint/src/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ 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 {
feathering: true,
feathering_size_in_pixels: extrusion * pixels_per_point,
..Default::default()
},
font_tex_size,
);
let mut mesh = Mesh::default();
tessellator.tessellate_rect(&rect, &mut mesh);
Expand Down
165 changes: 105 additions & 60 deletions epaint/src/tessellator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -671,6 +678,7 @@ impl Tessellator {
Self {
pixels_per_point,
options,
font_tex_size,
feathering,
clip_rect: Rect::EVERYTHING,
scratchpad_points: Default::default(),
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);

Expand Down Expand Up @@ -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`].
///
Expand All @@ -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 {
Expand All @@ -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;
Expand All @@ -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,
);
Expand All @@ -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);
}