From 45c7490180bdf292f16155ba7edbb153fd10208f Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Fri, 26 May 2023 23:03:14 +0100 Subject: [PATCH] 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 4ed267394a44cd665838c80d7bc3c300ce879cfb) --- common/drawing_sheet/ds_painter.cpp | 23 +++++++++++++--- common/eda_text.cpp | 26 +++++++++++++++--- common/font/outline_font.cpp | 12 ++++----- common/gal/opengl/opengl_gal.cpp | 27 +++++++++++++++---- eeschema/lib_text.cpp | 3 +++ eeschema/tools/ee_selection_tool.cpp | 2 +- .../include/geometry/shape_line_chain.h | 8 ++++++ libs/kimath/src/bezier_curves.cpp | 23 +++++++++------- libs/kimath/src/geometry/shape_poly_set.cpp | 5 +--- 9 files changed, 97 insertions(+), 32 deletions(-) diff --git a/common/drawing_sheet/ds_painter.cpp b/common/drawing_sheet/ds_painter.cpp index baea6e5a62..88cc97dfda 100644 --- a/common/drawing_sheet/ds_painter.cpp +++ b/common/drawing_sheet/ds_painter.cpp @@ -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>* 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 ); + } } diff --git a/common/eda_text.cpp b/common/eda_text.cpp index d3acf4f2c4..61d00a7127 100644 --- a/common/eda_text.cpp +++ b/common/eda_text.cpp @@ -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& glyph : m_render_cache ) @@ -461,7 +464,7 @@ std::vector>* 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 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>* 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 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 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; diff --git a/common/font/outline_font.cpp b/common/font/outline_font.cpp index cbc33ee493..6f27bc30d9 100644 --- a/common/font/outline_font.cpp +++ b/common/font/outline_font.cpp @@ -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 ) ); } diff --git a/common/gal/opengl/opengl_gal.cpp b/common/gal/opengl/opengl_gal.cpp index 3333918ca8..8d957a98de 100644 --- a/common/gal/opengl/opengl_gal.cpp +++ b/common/gal/opengl/opengl_gal.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include @@ -2781,15 +2782,31 @@ void OPENGL_GAL::DrawGlyphs( const std::vector>& // 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( 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& glyph : aGlyphs ) { const auto& outlineGlyph = static_cast( *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( outlineGlyph ).CacheTriangulation( false ); - for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ ) { const SHAPE_POLY_SET::TRIANGULATED_POLYGON* polygon = diff --git a/eeschema/lib_text.cpp b/eeschema/lib_text.cpp index cb4dde71ce..9c032dc400 100644 --- a/eeschema/lib_text.cpp +++ b/eeschema/lib_text.cpp @@ -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(); diff --git a/eeschema/tools/ee_selection_tool.cpp b/eeschema/tools/ee_selection_tool.cpp index 51d4bfc771..a35bfc6130 100644 --- a/eeschema/tools/ee_selection_tool.cpp +++ b/eeschema/tools/ee_selection_tool.cpp @@ -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 ) { diff --git a/libs/kimath/include/geometry/shape_line_chain.h b/libs/kimath/include/geometry/shape_line_chain.h index 776d1dbf47..f477b61e1c 100644 --- a/libs/kimath/include/geometry/shape_line_chain.h +++ b/libs/kimath/include/geometry/shape_line_chain.h @@ -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. * diff --git a/libs/kimath/src/bezier_curves.cpp b/libs/kimath/src/bezier_curves.cpp index d64725f7be..8e368d32ea 100644 --- a/libs/kimath/src/bezier_curves.cpp +++ b/libs/kimath/src/bezier_curves.cpp @@ -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& 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& aOutput, int aMinSegLen, int a std::vector 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& 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& 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 ); } } diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index b27402be14..49a080d905 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -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 ); }