@ -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 ) ;
Tessellator ::new ( pixels_per_point , options , font_tex_size , prepared_discs )
. tessellate_shapes ( shapes )
}
let mut clipped_primitives : Vec < ClippedPrimitive > = Vec ::default ( ) ;
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 ! ( ) ;
#[ cfg(feature = " rayon " ) ]
if self . options . parallel_tessellation {
self . parallel_tessellation_of_large_shapes ( & mut shapes ) ;
}
for clipped_shape in shapes {
tessellator . tessellate_clipped_shape ( clipped_shape , & mut clipped_primitives ) ;
}
let mut clipped_primitives : Vec < ClippedPrimitive > = Vec ::default ( ) ;
if options . debug_paint_clip_rects {
clipped_primitives = add_clip_rects ( & mut tessellator , clipped_primitives ) ;
}
{
crate ::profile_scope ! ( "tessellate" ) ;
for clipped_shape in shapes {
self . tessellate_clipped_shape ( clipped_shape , & mut clipped_primitives ) ;
}
}
if options . debug_ignore_clip_rects {
for clipped_primitive in & mut clipped_primitives {
clipped_primitive . clip_rect = Rect ::EVERYTHING ;
if self . options . debug_paint_clip_rects {
clipped_primitives = self . add_clip_rects ( clipped_primitives ) ;
}
}
clipped_primitives . retain ( | p | {
p . clip_rect . is_positive ( )
& & match & p . primitive {
Primitive ::Mesh ( mesh ) = > ! mesh . is_empty ( ) ,
Primitive ::Callback ( _ ) = > true ,
if self . options . debug_ignore_clip_rects {
for clipped_primitive in & mut clipped_primitives {
clipped_primitive . clip_rect = Rect ::EVERYTHING ;
}
} ) ;
}
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 . 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
}
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 ! ( ) ;
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 ) ) ;
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 ,
) ;
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 ,
[
clipped_primitive ,
ClippedPrimitive {
clip_rect : Rect ::EVERYTHING , // whatever
primitive : Primitive ::Mesh ( clip_rect_mesh ) ,
} ,
]
} )
. collect ( )
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 ) ;
}