Performance enhancements for connectivity.

1) Generate SHAPE_POLY_SET triangulation by outline so they can be
shared between connectivity system and other clients.
2) Don't add items to connectivity when reading board; we're going
to do a total rebuild anyway.
3) Use multithreading when caching triangulation.
This commit is contained in:
Jeff Young 2022-02-15 12:17:31 +00:00
parent 98b9c6e2a1
commit 5c9e718407
10 changed files with 122 additions and 79 deletions

View File

@ -125,7 +125,7 @@ public:
TRIANGULATED_POLYGON* parent; TRIANGULATED_POLYGON* parent;
}; };
TRIANGULATED_POLYGON(); TRIANGULATED_POLYGON( int aSourceOutline );
TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther ); TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther );
~TRIANGULATED_POLYGON(); ~TRIANGULATED_POLYGON();
@ -154,6 +154,8 @@ public:
size_t GetTriangleCount() const { return m_triangles.size(); } size_t GetTriangleCount() const { return m_triangles.size(); }
int GetSourceOutlineIndex() const { return m_sourceOutline; }
std::deque<TRI>& Triangles() { return m_triangles; } std::deque<TRI>& Triangles() { return m_triangles; }
const std::deque<TRI>& Triangles() const { return m_triangles; } const std::deque<TRI>& Triangles() const { return m_triangles; }
@ -169,6 +171,7 @@ public:
} }
private: private:
int m_sourceOutline;
std::deque<TRI> m_triangles; std::deque<TRI> m_triangles;
std::deque<VECTOR2I> m_vertices; std::deque<VECTOR2I> m_vertices;
}; };

View File

@ -2246,8 +2246,7 @@ bool SHAPE_POLY_SET::IsTriangulationUpToDate() const
} }
static void partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aPoly, int aSize, static SHAPE_POLY_SET partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aPoly, int aSize )
SHAPE_POLY_SET& aOut )
{ {
BOX2I bb = aPoly.BBox(); BOX2I bb = aPoly.BBox();
@ -2255,7 +2254,7 @@ static void partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aPoly, int a
double h = bb.GetHeight(); double h = bb.GetHeight();
if( w == 0.0 || h == 0.0 ) if( w == 0.0 || h == 0.0 )
return; return aPoly;
int n_cells_x, n_cells_y; int n_cells_x, n_cells_y;
@ -2306,13 +2305,13 @@ static void partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aPoly, int a
ps1.Fracture( SHAPE_POLY_SET::PM_FAST ); ps1.Fracture( SHAPE_POLY_SET::PM_FAST );
ps2.Fracture( SHAPE_POLY_SET::PM_FAST ); ps2.Fracture( SHAPE_POLY_SET::PM_FAST );
aOut = ps1;
for( int i = 0; i < ps2.OutlineCount(); i++ ) for( int i = 0; i < ps2.OutlineCount(); i++ )
aOut.AddOutline( ps2.COutline( i ) ); ps1.AddOutline( ps2.COutline( i ) );
if( !aOut.OutlineCount() ) if( ps1.OutlineCount() )
aOut = aPoly; return ps1;
else
return aPoly;
} }
@ -2338,46 +2337,60 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition )
if( !recalculate ) if( !recalculate )
return; return;
SHAPE_POLY_SET tmpSet; auto triangulate =
[]( SHAPE_POLY_SET& polySet, int forOutline,
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest )
{
bool triangulationValid = false;
while( polySet.OutlineCount() > 0 )
{
if( !dest.empty() && dest.back()->GetTriangleCount() == 0 )
dest.erase( dest.end() - 1 );
dest.push_back( std::make_unique<TRIANGULATED_POLYGON>( forOutline ) );
PolygonTriangulation tess( *dest.back() );
// If the tessellation fails, we re-fracture the polygon, which will
// first simplify the system before fracturing and removing the holes
// This may result in multiple, disjoint polygons.
if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front() ) )
{
polySet.Fracture( PM_FAST );
triangulationValid = false;
continue;
}
polySet.DeletePolygon( 0 );
triangulationValid = true;
}
return triangulationValid;
};
m_triangulatedPolys.clear();
m_triangulationValid = true;
if( aPartition ) if( aPartition )
{ {
// This partitions into regularly-sized grids (1cm in Pcbnew) for( int ii = 0; ii < OutlineCount(); ++ii )
SHAPE_POLY_SET flattened( *this ); {
flattened.ClearArcs(); // This partitions into regularly-sized grids (1cm in Pcbnew)
partitionPolyIntoRegularCellGrid( flattened, 1e7, tmpSet ); SHAPE_POLY_SET flattened( Outline( ii ) );
flattened.ClearArcs();
SHAPE_POLY_SET partitions = partitionPolyIntoRegularCellGrid( flattened, 1e7 );
m_triangulationValid &= triangulate( partitions, ii, m_triangulatedPolys );
}
} }
else else
{ {
tmpSet = *this; SHAPE_POLY_SET tmpSet( *this );
if( tmpSet.HasHoles() ) if( tmpSet.HasHoles() )
tmpSet.Fracture( PM_FAST ); tmpSet.Fracture( PM_FAST );
}
m_triangulatedPolys.clear(); m_triangulationValid = triangulate( tmpSet, -1, m_triangulatedPolys );
m_triangulationValid = false;
while( tmpSet.OutlineCount() > 0 )
{
if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
m_triangulatedPolys.erase( m_triangulatedPolys.end() - 1 );
m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>() );
PolygonTriangulation tess( *m_triangulatedPolys.back() );
// If the tessellation fails, we re-fracture the polygon, which will
// first simplify the system before fracturing and removing the holes
// This may result in multiple, disjoint polygons.
if( !tess.TesselatePolygon( tmpSet.Polygon( 0 ).front() ) )
{
tmpSet.Fracture( PM_FAST );
m_triangulationValid = false;
continue;
}
tmpSet.DeletePolygon( 0 );
m_triangulationValid = true;
} }
if( m_triangulationValid ) if( m_triangulationValid )
@ -2496,6 +2509,7 @@ void SHAPE_POLY_SET::TRIANGULATED_POLYGON::AddTriangle( int a, int b, int c )
SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther ) SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther )
{ {
m_sourceOutline = aOther.m_sourceOutline;
m_vertices = aOther.m_vertices; m_vertices = aOther.m_vertices;
m_triangles = aOther.m_triangles; m_triangles = aOther.m_triangles;
@ -2506,6 +2520,7 @@ SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON( const TRIANGULATED_P
SHAPE_POLY_SET::TRIANGULATED_POLYGON& SHAPE_POLY_SET::TRIANGULATED_POLYGON::operator=( const TRIANGULATED_POLYGON& aOther ) SHAPE_POLY_SET::TRIANGULATED_POLYGON& SHAPE_POLY_SET::TRIANGULATED_POLYGON::operator=( const TRIANGULATED_POLYGON& aOther )
{ {
m_sourceOutline = aOther.m_sourceOutline;
m_vertices = aOther.m_vertices; m_vertices = aOther.m_vertices;
m_triangles = aOther.m_triangles; m_triangles = aOther.m_triangles;
@ -2516,7 +2531,8 @@ SHAPE_POLY_SET::TRIANGULATED_POLYGON& SHAPE_POLY_SET::TRIANGULATED_POLYGON::oper
} }
SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON() SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON( int aSourceOutline ) :
m_sourceOutline( aSourceOutline )
{ {
} }

View File

@ -26,6 +26,7 @@
*/ */
#include <iterator> #include <iterator>
#include <thread>
#include <drc/drc_rtree.h> #include <drc/drc_rtree.h>
#include <pcb_base_frame.h> #include <pcb_base_frame.h>
#include <board_design_settings.h> #include <board_design_settings.h>
@ -55,6 +56,7 @@
#include <tool/selection_conditions.h> #include <tool/selection_conditions.h>
#include <convert_shape_list_to_polygon.h> #include <convert_shape_list_to_polygon.h>
#include <wx/log.h> #include <wx/log.h>
#include <progress_reporter.h>
// This is an odd place for this, but CvPcb won't link if it's in board_item.cpp like I first // This is an odd place for this, but CvPcb won't link if it's in board_item.cpp like I first
// tried it. // tried it.
@ -619,6 +621,44 @@ void BOARD::SetZoneSettings( const ZONE_SETTINGS& aSettings )
} }
void BOARD::CacheTriangulation( PROGRESS_REPORTER* aReporter )
{
if( aReporter )
aReporter->Report( _( "Tessellating copper zones..." ) );
std::atomic<size_t> next( 0 );
std::atomic<size_t> count_done( 0 );
size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
{
std::thread t = std::thread(
[ this, &count_done, &next ]( )
{
for( size_t i = next.fetch_add( 1 ); i < m_zones.size(); i = next.fetch_add( 1 ) )
m_zones[i]->CacheTriangulation();
count_done++;
} );
t.detach();
}
// Finalize the triangulation threads
while( count_done < parallelThreadCount )
{
if( aReporter && m_zones.size() )
{
aReporter->SetCurrentProgress( (double) count_done / (double) m_zones.size() );
aReporter->KeepRefreshing();
}
std::this_thread::sleep_for( std::chrono::milliseconds( 30 ) );
}
}
void BOARD::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode ) void BOARD::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode )
{ {
if( aBoardItem == nullptr ) if( aBoardItem == nullptr )
@ -704,10 +744,12 @@ void BOARD::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode )
aBoardItem->SetParent( this ); aBoardItem->SetParent( this );
aBoardItem->ClearEditFlags(); aBoardItem->ClearEditFlags();
m_connectivity->Add( aBoardItem );
if( aMode != ADD_MODE::BULK_INSERT && aMode != ADD_MODE::BULK_APPEND ) if( aMode != ADD_MODE::BULK_INSERT && aMode != ADD_MODE::BULK_APPEND )
{
m_connectivity->Add( aBoardItem );
InvokeListeners( &BOARD_LISTENER::OnBoardItemAdded, *this, aBoardItem ); InvokeListeners( &BOARD_LISTENER::OnBoardItemAdded, *this, aBoardItem );
}
} }

View File

@ -308,6 +308,8 @@ public:
*/ */
void FinalizeBulkRemove( std::vector<BOARD_ITEM*>& aRemovedItems ); void FinalizeBulkRemove( std::vector<BOARD_ITEM*>& aRemovedItems );
void CacheTriangulation( PROGRESS_REPORTER* aReporter = nullptr );
/** /**
* Get the first footprint on the board or nullptr. * Get the first footprint on the board or nullptr.
* *

View File

@ -438,9 +438,6 @@ void CN_CONNECTIVITY_ALGO::Build( BOARD* aBoard, PROGRESS_REPORTER* aReporter )
delta = std::max( delta, size / 10 ); delta = std::max( delta, size / 10 );
if( aReporter )
aReporter->KeepRefreshing( false );
for( ZONE* zone : aBoard->Zones() ) for( ZONE* zone : aBoard->Zones() )
{ {
Add( zone ); Add( zone );

View File

@ -86,11 +86,16 @@ bool CONNECTIVITY_DATA::Update( BOARD_ITEM* aItem )
void CONNECTIVITY_DATA::Build( BOARD* aBoard, PROGRESS_REPORTER* aReporter ) void CONNECTIVITY_DATA::Build( BOARD* aBoard, PROGRESS_REPORTER* aReporter )
{ {
aBoard->CacheTriangulation( aReporter );
std::unique_lock<KISPINLOCK> lock( m_lock, std::try_to_lock ); std::unique_lock<KISPINLOCK> lock( m_lock, std::try_to_lock );
if( !lock ) if( !lock )
return; return;
if( aReporter )
aReporter->Report( _( "Updating nets..." ) );
m_connAlgo.reset( new CN_CONNECTIVITY_ALGO ); m_connAlgo.reset( new CN_CONNECTIVITY_ALGO );
m_connAlgo->Build( aBoard, aReporter ); m_connAlgo->Build( aBoard, aReporter );

View File

@ -292,12 +292,19 @@ public:
{ {
const SHAPE_POLY_SET& fill = aParent->GetFilledPolysList( aLayer ); const SHAPE_POLY_SET& fill = aParent->GetFilledPolysList( aLayer );
m_triangulatedSubpoly = SHAPE_POLY_SET( fill.COutline( aSubpolyIndex ) ); if( !fill.IsTriangulationUpToDate() )
m_triangulatedSubpoly.CacheTriangulation(); const_cast<SHAPE_POLY_SET&>( fill ).CacheTriangulation();
for( size_t ii = 0; ii < m_triangulatedSubpoly.TriangulatedPolyCount(); ++ii ) m_triangulatedPoly = fill;
for( unsigned int ii = 0; ii < m_triangulatedPoly.TriangulatedPolyCount(); ++ii )
{ {
for( auto& tri : m_triangulatedSubpoly.TriangulatedPolygon( ii )->Triangles() ) const auto* triangleSet = m_triangulatedPoly.TriangulatedPolygon( ii );
if( triangleSet->GetSourceOutlineIndex() != aSubpolyIndex )
continue;
for( const SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRI& tri : triangleSet->Triangles() )
{ {
BOX2I bbox = tri.BBox(); BOX2I bbox = tri.BBox();
const int mmin[2] = { bbox.GetX(), bbox.GetY() }; const int mmin[2] = { bbox.GetX(), bbox.GetY() };
@ -369,7 +376,7 @@ private:
std::vector<VECTOR2I> m_testOutlinePoints; std::vector<VECTOR2I> m_testOutlinePoints;
int m_subpolyIndex; int m_subpolyIndex;
PCB_LAYER_ID m_layer; PCB_LAYER_ID m_layer;
SHAPE_POLY_SET m_triangulatedSubpoly; SHAPE_POLY_SET m_triangulatedPoly;
RTree<const SHAPE*, int, 2, double> m_rTree; RTree<const SHAPE*, int, 2, double> m_rTree;
}; };

View File

@ -929,7 +929,6 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
std::vector<ZONE*> toFill; std::vector<ZONE*> toFill;
// Rebuild list of nets (full ratsnest rebuild) // Rebuild list of nets (full ratsnest rebuild)
progressReporter.Report( _( "Updating nets" ) );
GetBoard()->BuildConnectivity( &progressReporter ); GetBoard()->BuildConnectivity( &progressReporter );
// Load project settings after setting up board; some of them depend on the nets list // Load project settings after setting up board; some of them depend on the nets list
@ -970,7 +969,6 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
if( filler.Fill( toFill ) ) if( filler.Fill( toFill ) )
{ {
commit.Push( _( "Convert Zone(s)" ) ); commit.Push( _( "Convert Zone(s)" ) );
progressReporter.Report( _( "Updating nets" ) );
GetBoard()->BuildConnectivity( &progressReporter ); GetBoard()->BuildConnectivity( &progressReporter );
} }
} }

View File

@ -182,23 +182,7 @@ void PCB_DRAW_PANEL_GAL::DisplayBoard( BOARD* aBoard, PROGRESS_REPORTER* aReport
{ {
m_view->Clear(); m_view->Clear();
auto zones = aBoard->Zones(); aBoard->CacheTriangulation();
std::atomic<size_t> next( 0 );
std::atomic<size_t> count_done( 0 );
size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
{
std::thread t = std::thread( [ &count_done, &next, &zones ]( )
{
for( size_t i = next.fetch_add( 1 ); i < zones.size(); i = next.fetch_add( 1 ) )
zones[i]->CacheTriangulation();
count_done++;
} );
t.detach();
}
if( m_drawingSheet ) if( m_drawingSheet )
m_drawingSheet->SetFileName( TO_UTF8( aBoard->GetFileName() ) ); m_drawingSheet->SetFileName( TO_UTF8( aBoard->GetFileName() ) );
@ -219,15 +203,6 @@ void PCB_DRAW_PANEL_GAL::DisplayBoard( BOARD* aBoard, PROGRESS_REPORTER* aReport
for( PCB_MARKER* marker : aBoard->Markers() ) for( PCB_MARKER* marker : aBoard->Markers() )
m_view->Add( marker ); m_view->Add( marker );
// Finalize the triangulation threads
while( count_done < parallelThreadCount )
{
if( aReporter )
aReporter->KeepRefreshing();
std::this_thread::sleep_for( std::chrono::milliseconds( 30 ) );
}
// Load zones // Load zones
for( ZONE* zone : aBoard->Zones() ) for( ZONE* zone : aBoard->Zones() )
m_view->Add( zone ); m_view->Add( zone );

View File

@ -157,7 +157,6 @@ void ZONE_FILLER_TOOL::FillAllZones( wxWindow* aCaller, PROGRESS_REPORTER* aRepo
if( filler.Fill( toFill ) ) if( filler.Fill( toFill ) )
{ {
reporter->AdvancePhase(); reporter->AdvancePhase();
reporter->Report( _( "Updating nets..." ) );
board()->GetConnectivity()->Build( board(), reporter.get() ); board()->GetConnectivity()->Build( board(), reporter.get() );
commit.Push( _( "Fill Zone(s)" ), true, true, false ); commit.Push( _( "Fill Zone(s)" ), true, true, false );
@ -215,7 +214,6 @@ int ZONE_FILLER_TOOL::ZoneFill( const TOOL_EVENT& aEvent )
if( filler.Fill( toFill ) ) if( filler.Fill( toFill ) )
{ {
reporter->AdvancePhase(); reporter->AdvancePhase();
reporter->Report( _( "Updating nets..." ) );
board()->GetConnectivity()->Build( board(), reporter.get() ); board()->GetConnectivity()->Build( board(), reporter.get() );
commit.Push( _( "Fill Zone(s)" ), true, true, false ); commit.Push( _( "Fill Zone(s)" ), true, true, false );