Parallell tessellation (#3934)
* Part of https://github.com/emilk/egui/issues/1485 This adds a `rayon` feature to `epaint` and `egui` to parallelize tessellation of large shapes, such as high-resolution plot lines.
This commit is contained in:
parent
8860930ec8
commit
1db291721f
|
|
@ -1011,14 +1011,30 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
|
@ -1460,6 +1476,8 @@ dependencies = [
|
|||
"log",
|
||||
"nohash-hasher",
|
||||
"parking_lot",
|
||||
"puffin",
|
||||
"rayon",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -3000,6 +3018,26 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rctree"
|
||||
version = "0.5.0"
|
||||
|
|
|
|||
|
|
@ -66,7 +66,12 @@ persistence = ["serde", "epaint/serde", "ron"]
|
|||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
##
|
||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
||||
puffin = ["dep:puffin"]
|
||||
puffin = ["dep:puffin", "epaint/puffin"]
|
||||
|
||||
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
||||
##
|
||||
## This can help performance for graphics-intense applications.
|
||||
rayon = ["epaint/rayon"]
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
||||
|
|
|
|||
|
|
@ -1977,13 +1977,13 @@ impl Context {
|
|||
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||
let clipped_primitives = {
|
||||
crate::profile_scope!("tessellator::tessellate_shapes");
|
||||
tessellator::tessellate_shapes(
|
||||
tessellator::Tessellator::new(
|
||||
pixels_per_point,
|
||||
tessellation_options,
|
||||
font_tex_size,
|
||||
prepared_discs,
|
||||
shapes,
|
||||
)
|
||||
.tessellate_shapes(shapes)
|
||||
};
|
||||
ctx.paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
||||
clipped_primitives
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ impl Widget for &mut epaint::TessellationOptions {
|
|||
debug_ignore_clip_rects,
|
||||
bezier_tolerance,
|
||||
epsilon: _,
|
||||
parallel_tessellation,
|
||||
validate_meshes,
|
||||
} = self;
|
||||
|
||||
ui.checkbox(feathering, "Feathering (antialias)")
|
||||
|
|
@ -176,6 +178,12 @@ impl Widget for &mut epaint::TessellationOptions {
|
|||
ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles");
|
||||
ui.checkbox(debug_paint_text_rects, "Paint text bounds");
|
||||
});
|
||||
|
||||
ui.add_enabled(epaint::HAS_RAYON, crate::Checkbox::new(parallel_tessellation, "Parallelize tessellation")
|
||||
).on_hover_text("Only available if epaint was compiled with the rayon feature")
|
||||
.on_disabled_hover_text("epaint was not compiled with the rayon feature");
|
||||
|
||||
ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc.");
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,16 @@ log = ["dep:log"]
|
|||
## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||
mint = ["emath/mint"]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
##
|
||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
||||
puffin = ["dep:puffin"]
|
||||
|
||||
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
||||
##
|
||||
## This can help performance for graphics-intense applications.
|
||||
rayon = ["dep:rayon"]
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
|
||||
|
||||
|
|
@ -88,6 +98,8 @@ bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
|||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
log = { version = "0.4", optional = true, features = ["std"] }
|
||||
puffin = { workspace = true, optional = true }
|
||||
rayon = { version = "1.7", optional = true }
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde) .
|
||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||
|
|
|
|||
|
|
@ -60,14 +60,14 @@ fn tessellate_circles(c: &mut Criterion) {
|
|||
let prepared_discs = atlas.prepared_discs();
|
||||
|
||||
b.iter(|| {
|
||||
let clipped_primitive = tessellate_shapes(
|
||||
let mut tessellator = Tessellator::new(
|
||||
pixels_per_point,
|
||||
options,
|
||||
font_tex_size,
|
||||
prepared_discs.clone(),
|
||||
clipped_shapes.clone(),
|
||||
);
|
||||
black_box(clipped_primitive);
|
||||
let clipped_primitives = tessellator.tessellate_shapes(clipped_shapes.clone());
|
||||
black_box(clipped_primitives);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,13 +52,16 @@ pub use {
|
|||
},
|
||||
stats::PaintStats,
|
||||
stroke::Stroke,
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
tessellator::{TessellationOptions, Tessellator},
|
||||
text::{FontFamily, FontId, Fonts, Galley},
|
||||
texture_atlas::TextureAtlas,
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use tessellator::tessellate_shapes;
|
||||
|
||||
pub use ecolor::{Color32, Hsva, HsvaGamma, Rgba};
|
||||
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
||||
|
||||
|
|
@ -172,3 +175,38 @@ pub(crate) fn f64_hash<H: std::hash::Hasher>(state: &mut H, f: f64) {
|
|||
f.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Was epaint compiled with the `rayon` feature?
|
||||
pub const HAS_RAYON: bool = cfg!(feature = "rayon");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
mod profiling_scopes {
|
||||
#![allow(unused_macros)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_function {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
||||
puffin::profile_function!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_function;
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_scope {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
||||
puffin::profile_scope!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_scope;
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use profiling_scopes::*;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ impl Mesh {
|
|||
|
||||
/// Are all indices within the bounds of the contained vertices?
|
||||
pub fn is_valid(&self) -> bool {
|
||||
crate::profile_function!();
|
||||
|
||||
if let Ok(n) = u32::try_from(self.vertices.len()) {
|
||||
self.indices.iter().all(|&i| i < n)
|
||||
} else {
|
||||
|
|
@ -106,6 +108,7 @@ impl Mesh {
|
|||
|
||||
/// Append all the indices and vertices of `other` to `self`.
|
||||
pub fn append(&mut self, other: Self) {
|
||||
crate::profile_function!();
|
||||
crate::epaint_assert!(other.is_valid());
|
||||
|
||||
if self.is_empty() {
|
||||
|
|
|
|||
|
|
@ -654,6 +654,15 @@ pub struct TessellationOptions {
|
|||
|
||||
/// The default value will be 1.0e-5, it will be used during float compare.
|
||||
pub epsilon: f32,
|
||||
|
||||
/// If `rayon` feature is activated, should we parallelize tessellation?
|
||||
pub parallel_tessellation: bool,
|
||||
|
||||
/// If `true`, invalid meshes will be silently ignored.
|
||||
/// If `false`, invalid meshes will cause a panic.
|
||||
///
|
||||
/// The default is `false` to save performance.
|
||||
pub validate_meshes: bool,
|
||||
}
|
||||
|
||||
impl Default for TessellationOptions {
|
||||
|
|
@ -669,6 +678,8 @@ impl Default for TessellationOptions {
|
|||
debug_ignore_clip_rects: false,
|
||||
bezier_tolerance: 0.1,
|
||||
epsilon: 1.0e-5,
|
||||
parallel_tessellation: true,
|
||||
validate_meshes: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1065,6 +1076,7 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
|
|||
/// For performance reasons it is smart to reuse the same [`Tessellator`].
|
||||
///
|
||||
/// See also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
||||
#[derive(Clone)]
|
||||
pub struct Tessellator {
|
||||
pixels_per_point: f32,
|
||||
options: TessellationOptions,
|
||||
|
|
@ -1086,6 +1098,9 @@ pub struct Tessellator {
|
|||
impl Tessellator {
|
||||
/// Create a new [`Tessellator`].
|
||||
///
|
||||
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||
/// * `options`: tessellation quality
|
||||
/// * `shapes`: what to tessellate
|
||||
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
|
||||
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
||||
pub fn new(
|
||||
|
|
@ -1132,31 +1147,22 @@ impl Tessellator {
|
|||
clipped_shape: ClippedShape,
|
||||
out_primitives: &mut Vec<ClippedPrimitive>,
|
||||
) {
|
||||
let ClippedShape {
|
||||
clip_rect: new_clip_rect,
|
||||
shape: new_shape,
|
||||
} = clipped_shape;
|
||||
let ClippedShape { clip_rect, shape } = clipped_shape;
|
||||
|
||||
if !new_clip_rect.is_positive() {
|
||||
if !clip_rect.is_positive() {
|
||||
return; // skip empty clip rectangles
|
||||
}
|
||||
|
||||
if let Shape::Vec(shapes) = new_shape {
|
||||
if let Shape::Vec(shapes) = shape {
|
||||
for shape in shapes {
|
||||
self.tessellate_clipped_shape(
|
||||
ClippedShape {
|
||||
clip_rect: new_clip_rect,
|
||||
shape,
|
||||
},
|
||||
out_primitives,
|
||||
);
|
||||
self.tessellate_clipped_shape(ClippedShape { clip_rect, shape }, out_primitives);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Shape::Callback(callback) = new_shape {
|
||||
if let Shape::Callback(callback) = shape {
|
||||
out_primitives.push(ClippedPrimitive {
|
||||
clip_rect: new_clip_rect,
|
||||
clip_rect,
|
||||
primitive: Primitive::Callback(callback),
|
||||
});
|
||||
return;
|
||||
|
|
@ -1165,10 +1171,10 @@ impl Tessellator {
|
|||
let start_new_mesh = match out_primitives.last() {
|
||||
None => true,
|
||||
Some(output_clipped_primitive) => {
|
||||
output_clipped_primitive.clip_rect != new_clip_rect
|
||||
output_clipped_primitive.clip_rect != clip_rect
|
||||
|| match &output_clipped_primitive.primitive {
|
||||
Primitive::Mesh(output_mesh) => {
|
||||
output_mesh.texture_id != new_shape.texture_id()
|
||||
output_mesh.texture_id != shape.texture_id()
|
||||
}
|
||||
Primitive::Callback(_) => true,
|
||||
}
|
||||
|
|
@ -1177,7 +1183,7 @@ impl Tessellator {
|
|||
|
||||
if start_new_mesh {
|
||||
out_primitives.push(ClippedPrimitive {
|
||||
clip_rect: new_clip_rect,
|
||||
clip_rect,
|
||||
primitive: Primitive::Mesh(Mesh::default()),
|
||||
});
|
||||
}
|
||||
|
|
@ -1185,8 +1191,8 @@ impl Tessellator {
|
|||
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);
|
||||
self.clip_rect = clip_rect;
|
||||
self.tessellate_shape(shape, out_mesh);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
|
@ -1199,6 +1205,8 @@ impl Tessellator {
|
|||
/// * `shape`: the shape to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_shape(&mut self, shape: Shape, out: &mut Mesh) {
|
||||
crate::profile_function!();
|
||||
|
||||
match shape {
|
||||
Shape::Noop => {}
|
||||
Shape::Vec(vec) => {
|
||||
|
|
@ -1210,16 +1218,20 @@ impl Tessellator {
|
|||
self.tessellate_circle(circle, out);
|
||||
}
|
||||
Shape::Mesh(mesh) => {
|
||||
if !mesh.is_valid() {
|
||||
crate::profile_scope!("mesh");
|
||||
|
||||
if self.options.validate_meshes && !mesh.is_valid() {
|
||||
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
||||
return;
|
||||
}
|
||||
// note: `append` still checks if the mesh is valid if extra asserts are enabled.
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
out.append(mesh);
|
||||
}
|
||||
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
||||
|
|
@ -1362,6 +1374,8 @@ impl Tessellator {
|
|||
return;
|
||||
}
|
||||
|
||||
crate::profile_function!();
|
||||
|
||||
let PathShape {
|
||||
points,
|
||||
closed,
|
||||
|
|
@ -1674,21 +1688,7 @@ impl Tessellator {
|
|||
}
|
||||
}
|
||||
|
||||
/// Turns [`Shape`]:s into sets of triangles.
|
||||
///
|
||||
/// The given shapes will tessellated in the same order as they are given.
|
||||
/// They will be batched together by clip rectangle.
|
||||
///
|
||||
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||
/// * `options`: tessellation quality
|
||||
/// * `shapes`: what to tessellate
|
||||
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
|
||||
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
||||
///
|
||||
/// The implementation uses a [`Tessellator`].
|
||||
///
|
||||
/// ## Returns
|
||||
/// A list of clip rectangles with matching [`Mesh`].
|
||||
#[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"]
|
||||
pub fn tessellate_shapes(
|
||||
pixels_per_point: f32,
|
||||
options: TessellationOptions,
|
||||
|
|
@ -1696,67 +1696,146 @@ pub fn tessellate_shapes(
|
|||
prepared_discs: Vec<PreparedDisc>,
|
||||
shapes: Vec<ClippedShape>,
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
let mut tessellator =
|
||||
Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs);
|
||||
|
||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||
|
||||
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, clipped_primitives);
|
||||
}
|
||||
|
||||
if options.debug_ignore_clip_rects {
|
||||
for clipped_primitive in &mut clipped_primitives {
|
||||
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
||||
}
|
||||
}
|
||||
|
||||
clipped_primitives.retain(|p| {
|
||||
p.clip_rect.is_positive()
|
||||
&& match &p.primitive {
|
||||
Primitive::Mesh(mesh) => !mesh.is_empty(),
|
||||
Primitive::Callback(_) => true,
|
||||
}
|
||||
});
|
||||
|
||||
for clipped_primitive in &clipped_primitives {
|
||||
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
|
||||
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
|
||||
}
|
||||
}
|
||||
|
||||
clipped_primitives
|
||||
Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs)
|
||||
.tessellate_shapes(shapes)
|
||||
}
|
||||
|
||||
fn add_clip_rects(
|
||||
tessellator: &mut Tessellator,
|
||||
clipped_primitives: Vec<ClippedPrimitive>,
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
tessellator.clip_rect = Rect::EVERYTHING;
|
||||
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
||||
impl Tessellator {
|
||||
/// Turns [`Shape`]:s into sets of triangles.
|
||||
///
|
||||
/// The given shapes will tessellated in the same order as they are given.
|
||||
/// They will be batched together by clip rectangle.
|
||||
///
|
||||
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||
/// * `options`: tessellation quality
|
||||
/// * `shapes`: what to tessellate
|
||||
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
|
||||
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
||||
///
|
||||
/// The implementation uses a [`Tessellator`].
|
||||
///
|
||||
/// ## Returns
|
||||
/// A list of clip rectangles with matching [`Mesh`].
|
||||
#[allow(unused_mut)]
|
||||
pub fn tessellate_shapes(&mut self, mut shapes: Vec<ClippedShape>) -> Vec<ClippedPrimitive> {
|
||||
crate::profile_function!();
|
||||
|
||||
clipped_primitives
|
||||
.into_iter()
|
||||
.flat_map(|clipped_primitive| {
|
||||
let mut clip_rect_mesh = Mesh::default();
|
||||
tessellator.tessellate_shape(
|
||||
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
|
||||
&mut clip_rect_mesh,
|
||||
);
|
||||
#[cfg(feature = "rayon")]
|
||||
if self.options.parallel_tessellation {
|
||||
self.parallel_tessellation_of_large_shapes(&mut shapes);
|
||||
}
|
||||
|
||||
[
|
||||
clipped_primitive,
|
||||
ClippedPrimitive {
|
||||
clip_rect: Rect::EVERYTHING, // whatever
|
||||
primitive: Primitive::Mesh(clip_rect_mesh),
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||
|
||||
{
|
||||
crate::profile_scope!("tessellate");
|
||||
for clipped_shape in shapes {
|
||||
self.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
|
||||
}
|
||||
}
|
||||
|
||||
if self.options.debug_paint_clip_rects {
|
||||
clipped_primitives = self.add_clip_rects(clipped_primitives);
|
||||
}
|
||||
|
||||
if self.options.debug_ignore_clip_rects {
|
||||
for clipped_primitive in &mut clipped_primitives {
|
||||
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
||||
}
|
||||
}
|
||||
|
||||
clipped_primitives.retain(|p| {
|
||||
p.clip_rect.is_positive()
|
||||
&& match &p.primitive {
|
||||
Primitive::Mesh(mesh) => !mesh.is_empty(),
|
||||
Primitive::Callback(_) => true,
|
||||
}
|
||||
});
|
||||
|
||||
for clipped_primitive in &clipped_primitives {
|
||||
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
|
||||
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
|
||||
}
|
||||
}
|
||||
|
||||
clipped_primitives
|
||||
}
|
||||
|
||||
/// Find large shapes and throw them on the rayon thread pool,
|
||||
/// then replace the original shape with their tessellated meshes.
|
||||
#[cfg(feature = "rayon")]
|
||||
fn parallel_tessellation_of_large_shapes(&self, shapes: &mut [ClippedShape]) {
|
||||
crate::profile_function!();
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
// We only parallelize large/slow stuff, because each tessellation job
|
||||
// will allocate a new Mesh, and so it creates a lot of extra memory framentation
|
||||
// and callocations that is only worth it for large shapes.
|
||||
fn should_parallelize(shape: &Shape) -> bool {
|
||||
match shape {
|
||||
Shape::Vec(shapes) => 4 < shapes.len() || shapes.iter().any(should_parallelize),
|
||||
|
||||
Shape::Path(path_shape) => 32 < path_shape.points.len(),
|
||||
|
||||
Shape::QuadraticBezier(_) | Shape::CubicBezier(_) => true,
|
||||
|
||||
Shape::Noop
|
||||
| Shape::Text(_)
|
||||
| Shape::Circle(_)
|
||||
| Shape::Mesh(_)
|
||||
| Shape::LineSegment { .. }
|
||||
| Shape::Rect(_)
|
||||
| Shape::Callback(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
let tessellated: Vec<(usize, Mesh)> = shapes
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, clipped_shape)| should_parallelize(&clipped_shape.shape))
|
||||
.map(|(index, clipped_shape)| {
|
||||
crate::profile_scope!("tessellate_big_shape");
|
||||
// TODO: reuse tessellator in a thread local
|
||||
let mut tessellator = (*self).clone();
|
||||
let mut mesh = Mesh::default();
|
||||
tessellator.tessellate_shape(clipped_shape.shape.clone(), &mut mesh);
|
||||
(index, mesh)
|
||||
})
|
||||
.collect();
|
||||
|
||||
crate::profile_scope!("distribute results", tessellated.len().to_string());
|
||||
for (index, mesh) in tessellated {
|
||||
shapes[index].shape = Shape::Mesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_clip_rects(
|
||||
&mut self,
|
||||
clipped_primitives: Vec<ClippedPrimitive>,
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
self.clip_rect = Rect::EVERYTHING;
|
||||
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
||||
|
||||
clipped_primitives
|
||||
.into_iter()
|
||||
.flat_map(|clipped_primitive| {
|
||||
let mut clip_rect_mesh = Mesh::default();
|
||||
self.tessellate_shape(
|
||||
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
|
||||
&mut clip_rect_mesh,
|
||||
);
|
||||
|
||||
[
|
||||
clipped_primitive,
|
||||
ClippedPrimitive {
|
||||
clip_rect: Rect::EVERYTHING, // whatever
|
||||
primitive: Primitive::Mesh(clip_rect_mesh),
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1785,12 +1864,8 @@ fn test_tessellator() {
|
|||
let font_tex_size = [1024, 1024]; // unused
|
||||
let prepared_discs = vec![]; // unused
|
||||
|
||||
let primitives = tessellate_shapes(
|
||||
1.0,
|
||||
Default::default(),
|
||||
font_tex_size,
|
||||
prepared_discs,
|
||||
clipped_shapes,
|
||||
);
|
||||
let primitives = Tessellator::new(1.0, Default::default(), font_tex_size, prepared_discs)
|
||||
.tessellate_shapes(clipped_shapes);
|
||||
|
||||
assert_eq!(primitives.len(), 2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue