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

(cherry picked from commit 4ed267394a)
This commit is contained in:
Jeff Young 2023-05-26 23:03:14 +01:00
parent 1566b357cb
commit 45c7490180
9 changed files with 97 additions and 32 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
{
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 )
{
@ -266,11 +269,25 @@ void KIGFX::DS_PAINTER::draw( const DS_DRAW_ITEM_TEXT* aItem, int aLayer ) const
m_gal->SetStrokeColor( color );
m_gal->SetFillColor( color );
TEXT_ATTRIBUTES attrs = aItem->GetAttributes();
attrs.m_StrokeWidth = std::max( aItem->GetEffectiveTextPenWidth(),
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

@ -393,6 +393,9 @@ void EDA_TEXT::SetTextY( int aY )
void EDA_TEXT::Offset( const VECTOR2I& aOffset )
{
if( aOffset.x == 0 && aOffset.y == 0 )
return;
m_pos += aOffset;
for( std::unique_ptr<KIFONT::GLYPH>& glyph : m_render_cache )
@ -461,7 +464,7 @@ std::vector<std::unique_ptr<KIFONT::GLYPH>>*
EDA_TEXT::GetRenderCache( const KIFONT::FONT* aFont, const wxString& forResolvedText,
const VECTOR2I& aOffset ) const
{
if( getDrawFont()->IsOutline() )
if( aFont->IsOutline() )
{
EDA_ANGLE resolvedAngle = GetDrawRotation();
@ -869,12 +872,23 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
KIFONT::FONT* font = getDrawFont();
int penWidth = GetEffectiveTextPenWidth();
wxString shownText( GetShownText( true ) );
VECTOR2I drawPos = GetDrawPos();
TEXT_ATTRIBUTES attrs = GetAttributes();
std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = nullptr;
if( aUseTextRotation )
{
attrs.m_Angle = GetDrawRotation();
if( font->IsOutline() )
cache = GetRenderCache( font, shownText, drawPos );
}
else
{
attrs.m_Angle = ANGLE_0;
}
if( aTriangulate )
{
@ -896,7 +910,10 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
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
{
@ -913,7 +930,10 @@ std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangula
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;

View File

@ -362,6 +362,9 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
VECTOR2I cursor( 0, 0 );
if( aGlyphs )
aGlyphs->reserve( glyphCount );
for( unsigned int i = 0; i < glyphCount; i++ )
{
// 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;
SHAPE_LINE_CHAIN shape;
shape.ReservePoints( points.size() );
for( const VECTOR2D& v : points )
{
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 ) );
}

View File

@ -47,6 +47,7 @@
#include <macros.h>
#include <geometry/geometry_utils.h>
#include <thread_pool.h>
#include <profile.h>
#include <trace_helpers.h>
@ -2781,15 +2782,31 @@ void OPENGL_GAL::DrawGlyphs( const std::vector<std::unique_ptr<KIFONT::GLYPH>>&
// Optimized path for stroke fonts that pre-reserves glyph triangles.
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 )
{
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++ )
{
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 )
{
if( GetHorizJustify() == GR_TEXT_H_ALIGN_CENTER && GetVertJustify() == GR_TEXT_V_ALIGN_CENTER )
return;
VECTOR2I delta( 0, 0 );
BOX2I bbox = GetTextBox();

View File

@ -1148,7 +1148,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 )
{

View File

@ -485,6 +485,14 @@ public:
*/
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.
*

View File

@ -1,7 +1,7 @@
/*
* 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
* 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 )
{
for( unsigned ii = 0; ii < aControlPoints.size(); ++ii )
m_ctrlPts.emplace_back( VECTOR2I( aControlPoints[ii] ) );
m_ctrlPts.reserve( aControlPoints.size() );
for( const VECTOR2I& pt : aControlPoints )
m_ctrlPts.emplace_back( pt );
m_minSegLen = 0.0;
}
@ -59,8 +61,10 @@ void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen, int a
std::vector<VECTOR2D> buffer;
GetPoly( buffer, double( aMinSegLen ), aMaxSegCount );
for( unsigned ii = 0; ii < buffer.size(); ++ii )
aOutput.emplace_back( VECTOR2I( int( buffer[ii].x ), int( buffer[ii].y ) ) );
aOutput.reserve( buffer.size() );
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
// with a max error value.
// 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.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:
// The offset from last point:
VECTOR2D delta = vertex - aOutput.back();
double dist = delta.EuclideanNorm();
VECTOR2D delta = vertex - aOutput.back();
VECTOR2D::extended_type dist_sq = delta.SquaredEuclideanNorm();
if( dist > aMinSegLen )
if( dist_sq > minSegLen_sq )
aOutput.push_back( vertex );
}
}

View File

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