Outline font performance improvements.

1) Don't fracture font glyphs when generating them; we're going
   to fracture during triangulation anyway.
2) Don't check for self-intersection when deciding to fracture.
   It costs nearly as much as the fracture does.
3) Cache drawing sheet text.
4) Use the current font when checking for cache validity.
5) Parallelize glyph triangulation.
6) Don't invalidate bounding box caches when offset by {0,0}
7) Use the glyph cache when generating text effective shape.
8) Short-circuit NormalizeJustification() if its center/center.
9) Don't triangulate for GuessSelectionCandidates()
10) Avoid sqrt whenever possible.
11) Pre-allocate bezier and SHAPE_LINE_CHAIN buffers.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/14303
This commit is contained in:
Jeff Young 2023-05-26 23:03:14 +01:00
parent ff37ebe61a
commit 4ed267394a
9 changed files with 100 additions and 33 deletions

View File

@ -253,7 +253,10 @@ void KIGFX::DS_PAINTER::draw( const DS_DRAW_ITEM_POLYPOLYGONS* aItem, int aLayer
void KIGFX::DS_PAINTER::draw( const DS_DRAW_ITEM_TEXT* aItem, int aLayer ) const void KIGFX::DS_PAINTER::draw( const DS_DRAW_ITEM_TEXT* aItem, int aLayer ) const
{ {
KIFONT::FONT* font = aItem->GetFont(); KIFONT::FONT* font = aItem->GetFont();
wxString shownText( aItem->GetShownText( true ) );
VECTOR2I text_offset = aItem->GetTextPos();
TEXT_ATTRIBUTES attrs = aItem->GetAttributes();
if( !font ) if( !font )
{ {
@ -266,11 +269,25 @@ void KIGFX::DS_PAINTER::draw( const DS_DRAW_ITEM_TEXT* aItem, int aLayer ) const
m_gal->SetStrokeColor( color ); m_gal->SetStrokeColor( color );
m_gal->SetFillColor( color ); m_gal->SetFillColor( color );
TEXT_ATTRIBUTES attrs = aItem->GetAttributes();
attrs.m_StrokeWidth = std::max( aItem->GetEffectiveTextPenWidth(), attrs.m_StrokeWidth = std::max( aItem->GetEffectiveTextPenWidth(),
m_renderSettings.GetDefaultPenWidth() ); m_renderSettings.GetDefaultPenWidth() );
font->Draw( m_gal, aItem->GetShownText( true ), aItem->GetTextPos(), attrs ); std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = nullptr;
if( font->IsOutline() )
cache = aItem->GetRenderCache( font, shownText, text_offset );
if( cache )
{
m_gal->SetLineWidth( attrs.m_StrokeWidth );
m_gal->DrawGlyphs( *cache );
}
else
{
m_gal->SetIsFill( font->IsOutline() );
m_gal->SetIsStroke( font->IsStroke() );
font->Draw( m_gal, shownText, text_offset, attrs );
}
} }

View File

@ -395,6 +395,9 @@ void EDA_TEXT::SetTextY( int aY )
void EDA_TEXT::Offset( const VECTOR2I& aOffset ) void EDA_TEXT::Offset( const VECTOR2I& aOffset )
{ {
if( aOffset.x == 0 && aOffset.y == 0 )
return;
m_pos += aOffset; m_pos += aOffset;
for( std::unique_ptr<KIFONT::GLYPH>& glyph : m_render_cache ) for( std::unique_ptr<KIFONT::GLYPH>& glyph : m_render_cache )
@ -463,7 +466,7 @@ std::vector<std::unique_ptr<KIFONT::GLYPH>>*
EDA_TEXT::GetRenderCache( const KIFONT::FONT* aFont, const wxString& forResolvedText, EDA_TEXT::GetRenderCache( const KIFONT::FONT* aFont, const wxString& forResolvedText,
const VECTOR2I& aOffset ) const const VECTOR2I& aOffset ) const
{ {
if( getDrawFont()->IsOutline() ) if( aFont->IsOutline() )
{ {
EDA_ANGLE resolvedAngle = GetDrawRotation(); EDA_ANGLE resolvedAngle = GetDrawRotation();
@ -533,6 +536,8 @@ BOX2I EDA_TEXT::GetTextBox( int aLine, bool aInvertY ) const
BOX2I strokeBBox; BOX2I strokeBBox;
KIGFX::GAL_DISPLAY_OPTIONS empty_opts; KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
KIFONT::FONT* font = getDrawFont(); KIFONT::FONT* font = getDrawFont();
wxString shownText( GetShownText( true ) );
TEXT_ATTRIBUTES attrs = GetAttributes();
CALLBACK_GAL callback_gal( CALLBACK_GAL callback_gal(
empty_opts, empty_opts,
@ -548,7 +553,7 @@ BOX2I EDA_TEXT::GetTextBox( int aLine, bool aInvertY ) const
bbox.Merge( aPoly.BBox() ); bbox.Merge( aPoly.BBox() );
} ); } );
font->Draw( &callback_gal, GetShownText( true ), drawPos, GetAttributes() ); font->Draw( &callback_gal, shownText, drawPos, attrs );
if( strokeBBox.GetSizeMax() > 0 ) if( strokeBBox.GetSizeMax() > 0 )
{ {
@ -806,12 +811,23 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
KIGFX::GAL_DISPLAY_OPTIONS empty_opts; KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
KIFONT::FONT* font = getDrawFont(); KIFONT::FONT* font = getDrawFont();
int penWidth = GetEffectiveTextPenWidth(); int penWidth = GetEffectiveTextPenWidth();
wxString shownText( GetShownText( true ) );
VECTOR2I drawPos = GetDrawPos();
TEXT_ATTRIBUTES attrs = GetAttributes(); TEXT_ATTRIBUTES attrs = GetAttributes();
std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = nullptr;
if( aUseTextRotation ) if( aUseTextRotation )
{
attrs.m_Angle = GetDrawRotation(); attrs.m_Angle = GetDrawRotation();
if( font->IsOutline() )
cache = GetRenderCache( font, shownText, drawPos );
}
else else
{
attrs.m_Angle = ANGLE_0; attrs.m_Angle = ANGLE_0;
}
if( aTriangulate ) if( aTriangulate )
{ {
@ -833,7 +849,10 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
shape->AddShape( triShape ); shape->AddShape( triShape );
} ); } );
font->Draw( &callback_gal, GetShownText( true ), GetDrawPos(), attrs ); if( cache )
callback_gal.DrawGlyphs( *cache );
else
font->Draw( &callback_gal, shownText, drawPos, attrs );
} }
else else
{ {
@ -850,7 +869,10 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
shape->AddShape( aPoly.Clone() ); shape->AddShape( aPoly.Clone() );
} ); } );
font->Draw( &callback_gal, GetShownText( true ), GetDrawPos(), attrs ); if( cache )
callback_gal.DrawGlyphs( *cache );
else
font->Draw( &callback_gal, shownText, drawPos, attrs );
} }
return shape; return shape;

View File

@ -362,6 +362,9 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
VECTOR2I cursor( 0, 0 ); VECTOR2I cursor( 0, 0 );
if( aGlyphs )
aGlyphs->reserve( glyphCount );
for( unsigned int i = 0; i < glyphCount; i++ ) for( unsigned int i = 0; i < glyphCount; i++ )
{ {
// Don't process glyphs that were already included in a previous cluster // Don't process glyphs that were already included in a previous cluster
@ -403,6 +406,8 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
GLYPH_POINTS points = c.m_Points; GLYPH_POINTS points = c.m_Points;
SHAPE_LINE_CHAIN shape; SHAPE_LINE_CHAIN shape;
shape.ReservePoints( points.size() );
for( const VECTOR2D& v : points ) for( const VECTOR2D& v : points )
{ {
VECTOR2D pt( v + cursor ); VECTOR2D pt( v + cursor );
@ -447,13 +452,6 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
} }
} }
// FONT TODO we might not want to do Fracture() here;
// knockout text (eg. silkscreen labels with a background) will
// need to do that after the contours have been turned into holes
// and vice versa
if( glyph->HasHoles() )
glyph->Fracture( SHAPE_POLY_SET::PM_FAST ); // FONT TODO verify aFastMode
aGlyphs->push_back( std::move( glyph ) ); aGlyphs->push_back( std::move( glyph ) );
} }

View File

@ -48,6 +48,7 @@
#include <macros.h> #include <macros.h>
#include <geometry/geometry_utils.h> #include <geometry/geometry_utils.h>
#include <thread_pool.h>
#include <profile.h> #include <profile.h>
#include <trace_helpers.h> #include <trace_helpers.h>
@ -2804,15 +2805,31 @@ void OPENGL_GAL::DrawGlyphs( const std::vector<std::unique_ptr<KIFONT::GLYPH>>&
// Optimized path for stroke fonts that pre-reserves glyph triangles. // Optimized path for stroke fonts that pre-reserves glyph triangles.
int triangleCount = 0; int triangleCount = 0;
if( aGlyphs.size() > 2 )
{
thread_pool& tp = GetKiCadThreadPool();
tp.push_loop( aGlyphs.size(),
[&]( const int a, const int b)
{
for( int ii = a; ii < b; ++ii )
{
auto glyph = static_cast<KIFONT::OUTLINE_GLYPH*>( aGlyphs.at( ii ).get() );
// Only call CacheTriangulation() if it has never been done before.
// Otherwise we'll hash the triangulation to see if it has been edited,
// and all our glpyh editing ops update the triangulation anyway.
if( glyph->TriangulatedPolyCount() == 0 )
glyph->CacheTriangulation( false );
}
} );
tp.wait_for_tasks();
}
for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs ) for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
{ {
const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph ); const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );
// Only call CacheTriangulation if it has never been done before. Otherwise we'll hash
// the triangulation to see if it has been edited, and glyphs after creation are read-only.
if( outlineGlyph.TriangulatedPolyCount() == 0 )
const_cast<KIFONT::OUTLINE_GLYPH&>( outlineGlyph ).CacheTriangulation( false );
for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ ) for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ )
{ {
const SHAPE_POLY_SET::TRIANGULATED_POLYGON* polygon = const SHAPE_POLY_SET::TRIANGULATED_POLYGON* polygon =

View File

@ -133,6 +133,9 @@ void LIB_TEXT::MoveTo( const VECTOR2I& newPosition )
void LIB_TEXT::NormalizeJustification( bool inverse ) void LIB_TEXT::NormalizeJustification( bool inverse )
{ {
if( GetHorizJustify() == GR_TEXT_H_ALIGN_CENTER && GetVertJustify() == GR_TEXT_V_ALIGN_CENTER )
return;
VECTOR2I delta( 0, 0 ); VECTOR2I delta( 0, 0 );
BOX2I bbox = GetTextBox(); BOX2I bbox = GetTextBox();

View File

@ -1147,7 +1147,7 @@ void EE_SELECTION_TOOL::GuessSelectionCandidates( EE_COLLECTOR& collector, const
} }
} }
text->GetEffectiveTextShape()->Collide( poss, closestDist, &dist ); text->GetEffectiveTextShape( false )->Collide( poss, closestDist, &dist );
} }
else if( symbol ) else if( symbol )
{ {

View File

@ -485,6 +485,14 @@ public:
*/ */
long long int Length() const; long long int Length() const;
/**
* Allocate a number of points all at once (for performance).
*/
void ReservePoints( size_t aSize )
{
m_points.reserve( aSize );
}
/** /**
* Append a new point at the end of the line chain. * Append a new point at the end of the line chain.
* *

View File

@ -1,7 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -46,8 +46,10 @@ BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1,
BEZIER_POLY::BEZIER_POLY( const std::vector<VECTOR2I>& aControlPoints ) BEZIER_POLY::BEZIER_POLY( const std::vector<VECTOR2I>& aControlPoints )
{ {
for( unsigned ii = 0; ii < aControlPoints.size(); ++ii ) m_ctrlPts.reserve( aControlPoints.size() );
m_ctrlPts.emplace_back( VECTOR2I( aControlPoints[ii] ) );
for( const VECTOR2I& pt : aControlPoints )
m_ctrlPts.emplace_back( pt );
m_minSegLen = 0.0; m_minSegLen = 0.0;
} }
@ -59,8 +61,10 @@ void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen, int a
std::vector<VECTOR2D> buffer; std::vector<VECTOR2D> buffer;
GetPoly( buffer, double( aMinSegLen ), aMaxSegCount ); GetPoly( buffer, double( aMinSegLen ), aMaxSegCount );
for( unsigned ii = 0; ii < buffer.size(); ++ii ) aOutput.reserve( buffer.size() );
aOutput.emplace_back( VECTOR2I( int( buffer[ii].x ), int( buffer[ii].y ) ) );
for( const VECTOR2D& pt : buffer )
aOutput.emplace_back( VECTOR2I( (int) pt.x, (int) pt.y ) );
} }
@ -70,7 +74,8 @@ void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen, in
// FIXME Brute force method, use a better (recursive?) algorithm // FIXME Brute force method, use a better (recursive?) algorithm
// with a max error value. // with a max error value.
// to optimize the number of segments // to optimize the number of segments
double dt = 1.0 / aMaxSegCount; double dt = 1.0 / aMaxSegCount;
VECTOR2D::extended_type minSegLen_sq = aMinSegLen * aMinSegLen;
aOutput.clear(); aOutput.clear();
aOutput.push_back( m_ctrlPts[0] ); aOutput.push_back( m_ctrlPts[0] );
@ -96,10 +101,10 @@ void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen, in
// a minimal filter on the length of the segment being created: // a minimal filter on the length of the segment being created:
// The offset from last point: // The offset from last point:
VECTOR2D delta = vertex - aOutput.back(); VECTOR2D delta = vertex - aOutput.back();
double dist = delta.EuclideanNorm(); VECTOR2D::extended_type dist_sq = delta.SquaredEuclideanNorm();
if( dist > aMinSegLen ) if( dist_sq > minSegLen_sq )
aOutput.push_back( vertex ); aOutput.push_back( vertex );
} }
} }

View File

@ -2824,10 +2824,7 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
tmpSet.ClearArcs(); tmpSet.ClearArcs();
if( tmpSet.HasHoles() || tmpSet.IsSelfIntersecting() ) tmpSet.Fracture( PM_FAST );
tmpSet.Fracture( PM_FAST );
else if( aSimplify )
tmpSet.Simplify( PM_FAST );
m_triangulationValid = triangulate( tmpSet, -1, m_triangulatedPolys ); m_triangulationValid = triangulate( tmpSet, -1, m_triangulatedPolys );
} }