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]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.16"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -1460,6 +1476,8 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"puffin",
|
||||||
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3000,6 +3018,26 @@ version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
|
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]]
|
[[package]]
|
||||||
name = "rctree"
|
name = "rctree"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,12 @@ persistence = ["serde", "epaint/serde", "ron"]
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||||
##
|
##
|
||||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
## 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).
|
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
||||||
|
|
|
||||||
|
|
@ -1977,13 +1977,13 @@ impl Context {
|
||||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||||
let clipped_primitives = {
|
let clipped_primitives = {
|
||||||
crate::profile_scope!("tessellator::tessellate_shapes");
|
crate::profile_scope!("tessellator::tessellate_shapes");
|
||||||
tessellator::tessellate_shapes(
|
tessellator::Tessellator::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
font_tex_size,
|
font_tex_size,
|
||||||
prepared_discs,
|
prepared_discs,
|
||||||
shapes,
|
|
||||||
)
|
)
|
||||||
|
.tessellate_shapes(shapes)
|
||||||
};
|
};
|
||||||
ctx.paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
ctx.paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
||||||
clipped_primitives
|
clipped_primitives
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,8 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||||
debug_ignore_clip_rects,
|
debug_ignore_clip_rects,
|
||||||
bezier_tolerance,
|
bezier_tolerance,
|
||||||
epsilon: _,
|
epsilon: _,
|
||||||
|
parallel_tessellation,
|
||||||
|
validate_meshes,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.checkbox(feathering, "Feathering (antialias)")
|
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_clip_rects, "Paint clip rectangles");
|
||||||
ui.checkbox(debug_paint_text_rects, "Paint text bounds");
|
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
|
.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`](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"]
|
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).
|
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/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 }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
log = { version = "0.4", optional = true, features = ["std"] }
|
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) .
|
## Allow serialization using [`serde`](https://docs.rs/serde) .
|
||||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,14 @@ fn tessellate_circles(c: &mut Criterion) {
|
||||||
let prepared_discs = atlas.prepared_discs();
|
let prepared_discs = atlas.prepared_discs();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let clipped_primitive = tessellate_shapes(
|
let mut tessellator = Tessellator::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
options,
|
options,
|
||||||
font_tex_size,
|
font_tex_size,
|
||||||
prepared_discs.clone(),
|
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,
|
stats::PaintStats,
|
||||||
stroke::Stroke,
|
stroke::Stroke,
|
||||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
tessellator::{TessellationOptions, Tessellator},
|
||||||
text::{FontFamily, FontId, Fonts, Galley},
|
text::{FontFamily, FontId, Fonts, Galley},
|
||||||
texture_atlas::TextureAtlas,
|
texture_atlas::TextureAtlas,
|
||||||
texture_handle::TextureHandle,
|
texture_handle::TextureHandle,
|
||||||
textures::TextureManager,
|
textures::TextureManager,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
pub use tessellator::tessellate_shapes;
|
||||||
|
|
||||||
pub use ecolor::{Color32, Hsva, HsvaGamma, Rgba};
|
pub use ecolor::{Color32, Hsva, HsvaGamma, Rgba};
|
||||||
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
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);
|
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?
|
/// Are all indices within the bounds of the contained vertices?
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
|
crate::profile_function!();
|
||||||
|
|
||||||
if let Ok(n) = u32::try_from(self.vertices.len()) {
|
if let Ok(n) = u32::try_from(self.vertices.len()) {
|
||||||
self.indices.iter().all(|&i| i < n)
|
self.indices.iter().all(|&i| i < n)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -106,6 +108,7 @@ impl Mesh {
|
||||||
|
|
||||||
/// Append all the indices and vertices of `other` to `self`.
|
/// Append all the indices and vertices of `other` to `self`.
|
||||||
pub fn append(&mut self, other: Self) {
|
pub fn append(&mut self, other: Self) {
|
||||||
|
crate::profile_function!();
|
||||||
crate::epaint_assert!(other.is_valid());
|
crate::epaint_assert!(other.is_valid());
|
||||||
|
|
||||||
if self.is_empty() {
|
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.
|
/// The default value will be 1.0e-5, it will be used during float compare.
|
||||||
pub epsilon: f32,
|
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 {
|
impl Default for TessellationOptions {
|
||||||
|
|
@ -669,6 +678,8 @@ impl Default for TessellationOptions {
|
||||||
debug_ignore_clip_rects: false,
|
debug_ignore_clip_rects: false,
|
||||||
bezier_tolerance: 0.1,
|
bezier_tolerance: 0.1,
|
||||||
epsilon: 1.0e-5,
|
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`].
|
/// For performance reasons it is smart to reuse the same [`Tessellator`].
|
||||||
///
|
///
|
||||||
/// See also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
/// See also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Tessellator {
|
pub struct Tessellator {
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
|
|
@ -1086,6 +1098,9 @@ pub struct Tessellator {
|
||||||
impl Tessellator {
|
impl Tessellator {
|
||||||
/// Create a new [`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.
|
/// * `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.
|
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
|
@ -1132,31 +1147,22 @@ impl Tessellator {
|
||||||
clipped_shape: ClippedShape,
|
clipped_shape: ClippedShape,
|
||||||
out_primitives: &mut Vec<ClippedPrimitive>,
|
out_primitives: &mut Vec<ClippedPrimitive>,
|
||||||
) {
|
) {
|
||||||
let ClippedShape {
|
let ClippedShape { clip_rect, shape } = clipped_shape;
|
||||||
clip_rect: new_clip_rect,
|
|
||||||
shape: new_shape,
|
|
||||||
} = clipped_shape;
|
|
||||||
|
|
||||||
if !new_clip_rect.is_positive() {
|
if !clip_rect.is_positive() {
|
||||||
return; // skip empty clip rectangles
|
return; // skip empty clip rectangles
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Shape::Vec(shapes) = new_shape {
|
if let Shape::Vec(shapes) = shape {
|
||||||
for shape in shapes {
|
for shape in shapes {
|
||||||
self.tessellate_clipped_shape(
|
self.tessellate_clipped_shape(ClippedShape { clip_rect, shape }, out_primitives);
|
||||||
ClippedShape {
|
|
||||||
clip_rect: new_clip_rect,
|
|
||||||
shape,
|
|
||||||
},
|
|
||||||
out_primitives,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Shape::Callback(callback) = new_shape {
|
if let Shape::Callback(callback) = shape {
|
||||||
out_primitives.push(ClippedPrimitive {
|
out_primitives.push(ClippedPrimitive {
|
||||||
clip_rect: new_clip_rect,
|
clip_rect,
|
||||||
primitive: Primitive::Callback(callback),
|
primitive: Primitive::Callback(callback),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -1165,10 +1171,10 @@ impl Tessellator {
|
||||||
let start_new_mesh = match out_primitives.last() {
|
let start_new_mesh = match out_primitives.last() {
|
||||||
None => true,
|
None => true,
|
||||||
Some(output_clipped_primitive) => {
|
Some(output_clipped_primitive) => {
|
||||||
output_clipped_primitive.clip_rect != new_clip_rect
|
output_clipped_primitive.clip_rect != clip_rect
|
||||||
|| match &output_clipped_primitive.primitive {
|
|| match &output_clipped_primitive.primitive {
|
||||||
Primitive::Mesh(output_mesh) => {
|
Primitive::Mesh(output_mesh) => {
|
||||||
output_mesh.texture_id != new_shape.texture_id()
|
output_mesh.texture_id != shape.texture_id()
|
||||||
}
|
}
|
||||||
Primitive::Callback(_) => true,
|
Primitive::Callback(_) => true,
|
||||||
}
|
}
|
||||||
|
|
@ -1177,7 +1183,7 @@ impl Tessellator {
|
||||||
|
|
||||||
if start_new_mesh {
|
if start_new_mesh {
|
||||||
out_primitives.push(ClippedPrimitive {
|
out_primitives.push(ClippedPrimitive {
|
||||||
clip_rect: new_clip_rect,
|
clip_rect,
|
||||||
primitive: Primitive::Mesh(Mesh::default()),
|
primitive: Primitive::Mesh(Mesh::default()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1185,8 +1191,8 @@ impl Tessellator {
|
||||||
let out = out_primitives.last_mut().unwrap();
|
let out = out_primitives.last_mut().unwrap();
|
||||||
|
|
||||||
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
|
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
|
||||||
self.clip_rect = new_clip_rect;
|
self.clip_rect = clip_rect;
|
||||||
self.tessellate_shape(new_shape, out_mesh);
|
self.tessellate_shape(shape, out_mesh);
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
@ -1199,6 +1205,8 @@ impl Tessellator {
|
||||||
/// * `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, shape: Shape, out: &mut Mesh) {
|
pub fn tessellate_shape(&mut self, shape: Shape, out: &mut Mesh) {
|
||||||
|
crate::profile_function!();
|
||||||
|
|
||||||
match shape {
|
match shape {
|
||||||
Shape::Noop => {}
|
Shape::Noop => {}
|
||||||
Shape::Vec(vec) => {
|
Shape::Vec(vec) => {
|
||||||
|
|
@ -1210,16 +1218,20 @@ impl Tessellator {
|
||||||
self.tessellate_circle(circle, out);
|
self.tessellate_circle(circle, out);
|
||||||
}
|
}
|
||||||
Shape::Mesh(mesh) => {
|
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");
|
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// note: `append` still checks if the mesh is valid if extra asserts are enabled.
|
||||||
|
|
||||||
if self.options.coarse_tessellation_culling
|
if self.options.coarse_tessellation_culling
|
||||||
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.append(mesh);
|
out.append(mesh);
|
||||||
}
|
}
|
||||||
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
||||||
|
|
@ -1362,6 +1374,8 @@ impl Tessellator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::profile_function!();
|
||||||
|
|
||||||
let PathShape {
|
let PathShape {
|
||||||
points,
|
points,
|
||||||
closed,
|
closed,
|
||||||
|
|
@ -1674,6 +1688,19 @@ impl Tessellator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"]
|
||||||
|
pub fn tessellate_shapes(
|
||||||
|
pixels_per_point: f32,
|
||||||
|
options: TessellationOptions,
|
||||||
|
font_tex_size: [usize; 2],
|
||||||
|
prepared_discs: Vec<PreparedDisc>,
|
||||||
|
shapes: Vec<ClippedShape>,
|
||||||
|
) -> Vec<ClippedPrimitive> {
|
||||||
|
Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs)
|
||||||
|
.tessellate_shapes(shapes)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tessellator {
|
||||||
/// Turns [`Shape`]:s into sets of triangles.
|
/// Turns [`Shape`]:s into sets of triangles.
|
||||||
///
|
///
|
||||||
/// The given shapes will tessellated in the same order as they are given.
|
/// The given shapes will tessellated in the same order as they are given.
|
||||||
|
|
@ -1689,27 +1716,29 @@ impl Tessellator {
|
||||||
///
|
///
|
||||||
/// ## Returns
|
/// ## Returns
|
||||||
/// A list of clip rectangles with matching [`Mesh`].
|
/// A list of clip rectangles with matching [`Mesh`].
|
||||||
pub fn tessellate_shapes(
|
#[allow(unused_mut)]
|
||||||
pixels_per_point: f32,
|
pub fn tessellate_shapes(&mut self, mut shapes: Vec<ClippedShape>) -> Vec<ClippedPrimitive> {
|
||||||
options: TessellationOptions,
|
crate::profile_function!();
|
||||||
font_tex_size: [usize; 2],
|
|
||||||
prepared_discs: Vec<PreparedDisc>,
|
#[cfg(feature = "rayon")]
|
||||||
shapes: Vec<ClippedShape>,
|
if self.options.parallel_tessellation {
|
||||||
) -> Vec<ClippedPrimitive> {
|
self.parallel_tessellation_of_large_shapes(&mut shapes);
|
||||||
let mut tessellator =
|
}
|
||||||
Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs);
|
|
||||||
|
|
||||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||||
|
|
||||||
|
{
|
||||||
|
crate::profile_scope!("tessellate");
|
||||||
for clipped_shape in shapes {
|
for clipped_shape in shapes {
|
||||||
tessellator.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
|
self.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.debug_paint_clip_rects {
|
if self.options.debug_paint_clip_rects {
|
||||||
clipped_primitives = add_clip_rects(&mut tessellator, clipped_primitives);
|
clipped_primitives = self.add_clip_rects(clipped_primitives);
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.debug_ignore_clip_rects {
|
if self.options.debug_ignore_clip_rects {
|
||||||
for clipped_primitive in &mut clipped_primitives {
|
for clipped_primitive in &mut clipped_primitives {
|
||||||
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
||||||
}
|
}
|
||||||
|
|
@ -1732,18 +1761,67 @@ pub fn tessellate_shapes(
|
||||||
clipped_primitives
|
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(
|
fn add_clip_rects(
|
||||||
tessellator: &mut Tessellator,
|
&mut self,
|
||||||
clipped_primitives: Vec<ClippedPrimitive>,
|
clipped_primitives: Vec<ClippedPrimitive>,
|
||||||
) -> Vec<ClippedPrimitive> {
|
) -> Vec<ClippedPrimitive> {
|
||||||
tessellator.clip_rect = Rect::EVERYTHING;
|
self.clip_rect = Rect::EVERYTHING;
|
||||||
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
||||||
|
|
||||||
clipped_primitives
|
clipped_primitives
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.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(
|
self.tessellate_shape(
|
||||||
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,
|
||||||
);
|
);
|
||||||
|
|
@ -1758,6 +1836,7 @@ fn add_clip_rects(
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tessellator() {
|
fn test_tessellator() {
|
||||||
|
|
@ -1785,12 +1864,8 @@ fn test_tessellator() {
|
||||||
let font_tex_size = [1024, 1024]; // unused
|
let font_tex_size = [1024, 1024]; // unused
|
||||||
let prepared_discs = vec![]; // unused
|
let prepared_discs = vec![]; // unused
|
||||||
|
|
||||||
let primitives = tessellate_shapes(
|
let primitives = Tessellator::new(1.0, Default::default(), font_tex_size, prepared_discs)
|
||||||
1.0,
|
.tessellate_shapes(clipped_shapes);
|
||||||
Default::default(),
|
|
||||||
font_tex_size,
|
|
||||||
prepared_discs,
|
|
||||||
clipped_shapes,
|
|
||||||
);
|
|
||||||
assert_eq!(primitives.len(), 2);
|
assert_eq!(primitives.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue