Add clearance cache to improve clearance outline rendering performance

When displaying pad and track clearance outlines, GetOwnClearance()
was called repeatedly during rendering, re-evaluating DRC rules on
every paint refresh. With complex DRC rules, this caused significant
slowdown.

Add a lazy-evaluated cache in DRC_ENGINE keyed by (UUID, layer) that
stores clearance values. The cache is invalidated when DRC rules
change (in InitEngine) or properties change that could affect clearance
(net, layer, pad type)
This commit is contained in:
Seth Hillbrand
2026-01-07 15:32:57 -08:00
parent 6bf80a242f
commit b0d0dd57ef
9 changed files with 201 additions and 35 deletions
+8
View File
@@ -30,6 +30,7 @@
#include <wx/log.h>
#include <drc/drc_engine.h>
#include <drc/drc_rtree.h>
#include <board_design_settings.h>
#include <board_commit.h>
@@ -1090,6 +1091,13 @@ void BOARD::SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings )
}
void BOARD::InvalidateClearanceCache( const KIID& aUuid )
{
if( m_designSettings && m_designSettings->m_DRCEngine )
m_designSettings->m_DRCEngine->InvalidateClearanceCache( aUuid );
}
int BOARD::GetMaxClearanceValue() const
{
if( !m_maxClearanceValue.has_value() )
+9
View File
@@ -778,6 +778,15 @@ public:
BOARD_DESIGN_SETTINGS& GetDesignSettings() const;
void SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings );
/**
* Invalidate the clearance cache for a specific item.
*
* Called by items when properties that could affect clearance change.
*
* @param aUuid the UUID of the item to invalidate.
*/
void InvalidateClearanceCache( const KIID& aUuid );
BOARD_STACKUP GetStackupOrDefault() const;
const PAGE_INFO& GetPageSettings() const { return m_paper; }
+17 -13
View File
@@ -26,6 +26,7 @@
#include <board.h>
#include <board_connected_item.h>
#include <board_design_settings.h>
#include <drc/drc_engine.h>
#include <connectivity/connectivity_data.h>
#include <lset.h>
#include <properties/property_validators.h>
@@ -44,6 +45,16 @@ BOARD_CONNECTED_ITEM::BOARD_CONNECTED_ITEM( BOARD_ITEM* aParent, KICAD_T idtype
}
void BOARD_CONNECTED_ITEM::SetLayer( PCB_LAYER_ID aLayer )
{
BOARD_ITEM::SetLayer( aLayer );
// Invalidate clearance cache since layer can affect clearance rules
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}
void BOARD_CONNECTED_ITEM::UnpackNet( const kiapi::board::types::Net& aProto )
{
if( BOARD* board = GetBoard() )
@@ -88,27 +99,20 @@ bool BOARD_CONNECTED_ITEM::SetNetCode( int aNetCode, bool aNoAssert )
if( !aNoAssert )
wxASSERT( m_netinfo );
// Invalidate clearance cache since net can affect clearance rules
if( board )
board->InvalidateClearanceCache( m_Uuid );
return ( m_netinfo != nullptr );
}
int BOARD_CONNECTED_ITEM::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const
{
DRC_CONSTRAINT constraint;
if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine )
{
BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
constraint = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
}
if( constraint.Value().HasMin() )
{
if( aSource )
*aSource = constraint.GetName();
return constraint.Value().Min();
return bds.m_DRCEngine->GetCachedOwnClearance( this, aLayer, aSource );
}
return 0;
@@ -215,7 +219,7 @@ static struct BOARD_CONNECTED_ITEM_DESC
// Replace layer property as the properties panel will set a restriction for copper layers
// only for BOARD_CONNECTED_ITEM that we don't want to apply to BOARD_ITEM
auto layer = new PROPERTY_ENUM<BOARD_CONNECTED_ITEM, PCB_LAYER_ID, BOARD_ITEM>(
auto layer = new PROPERTY_ENUM<BOARD_CONNECTED_ITEM, PCB_LAYER_ID>(
_HKI( "Layer" ),
&BOARD_CONNECTED_ITEM::SetLayer, &BOARD_CONNECTED_ITEM::GetLayer );
layer->SetChoices( layerEnum.Choices() );
+3
View File
@@ -47,6 +47,9 @@ class BOARD_CONNECTED_ITEM : public BOARD_ITEM
public:
BOARD_CONNECTED_ITEM( BOARD_ITEM* aParent, KICAD_T idtype );
void SetLayer( PCB_LAYER_ID aLayer ) override;
PCB_LAYER_ID GetLayer() const override { return BOARD_ITEM::GetLayer(); }
// Do not create a copy constructor & operator=.
// The ones generated by the compiler are adequate.
+66
View File
@@ -752,6 +752,7 @@ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath )
}
m_constraintMap.clear();
m_ownClearanceCache.clear();
m_board->IncrementTimeStamp(); // Clear board-level caches
@@ -2221,3 +2222,68 @@ std::vector<BOARD_ITEM*> DRC_ENGINE::GetItemsMatchingCondition( const wxString&
return matches;
}
int DRC_ENGINE::GetCachedOwnClearance( const BOARD_ITEM* aItem, PCB_LAYER_ID aLayer,
wxString* aSource )
{
DRC_OWN_CLEARANCE_CACHE_KEY key{ aItem->m_Uuid, aLayer };
auto it = m_ownClearanceCache.find( key );
if( it != m_ownClearanceCache.end() )
{
// Cache hit. We don't cache the source string since it's rarely requested
// and caching it would add complexity.
return it->second;
}
// Cache miss - evaluate the constraint
DRC_CONSTRAINT_T constraintType = CLEARANCE_CONSTRAINT;
if( aItem->Type() == PCB_PAD_T )
{
const PAD* pad = static_cast<const PAD*>( aItem );
if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
constraintType = HOLE_CLEARANCE_CONSTRAINT;
}
DRC_CONSTRAINT constraint = EvalRules( constraintType, aItem, nullptr, aLayer );
int clearance = 0;
if( constraint.Value().HasMin() )
{
clearance = constraint.Value().Min();
if( aSource )
*aSource = constraint.GetName();
}
// Store in cache
m_ownClearanceCache[key] = clearance;
return clearance;
}
void DRC_ENGINE::InvalidateClearanceCache( const KIID& aUuid )
{
// Remove all entries for this item (across all layers)
auto it = m_ownClearanceCache.begin();
while( it != m_ownClearanceCache.end() )
{
if( it->first.m_uuid == aUuid )
it = m_ownClearanceCache.erase( it );
else
++it;
}
}
void DRC_ENGINE::ClearClearanceCache()
{
m_ownClearanceCache.clear();
}
+68
View File
@@ -27,12 +27,46 @@
#include <vector>
#include <unordered_map>
#include <kiid.h>
#include <layer_ids.h>
#include <units_provider.h>
#include <pcb_shape.h>
#include <lset.h>
#include <drc/drc_rule.h>
/**
* Cache key for own clearance lookups, combining item UUID and layer.
*/
struct DRC_OWN_CLEARANCE_CACHE_KEY
{
KIID m_uuid;
PCB_LAYER_ID m_layer;
bool operator==( const DRC_OWN_CLEARANCE_CACHE_KEY& aOther ) const
{
return m_uuid == aOther.m_uuid && m_layer == aOther.m_layer;
}
};
namespace std
{
template <>
struct hash<DRC_OWN_CLEARANCE_CACHE_KEY>
{
std::size_t operator()( const DRC_OWN_CLEARANCE_CACHE_KEY& aKey ) const
{
std::size_t seed = 0xa82de1c0;
seed ^= std::hash<KIID>{}( aKey.m_uuid ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 );
seed ^= std::hash<int>{}( static_cast<int>( aKey.m_layer ) ) + 0x9e3779b9
+ ( seed << 6 ) + ( seed >> 2 );
return seed;
}
};
}
class BOARD_COMMIT;
class BOARD_DESIGN_SETTINGS;
class DRC_TEST_PROVIDER;
@@ -162,6 +196,36 @@ public:
DRC_CONSTRAINT EvalZoneConnection( const BOARD_ITEM* a, const BOARD_ITEM* b,
PCB_LAYER_ID aLayer, REPORTER* aReporter = nullptr );
/**
* Get the cached own clearance for an item on a specific layer.
*
* This is used by BOARD_CONNECTED_ITEM::GetOwnClearance() to avoid re-evaluating
* DRC rules on every paint refresh.
*
* @param aItem the item to get clearance for.
* @param aLayer the layer in question.
* @param aSource optionally reports the source as a user-readable string.
* @return the clearance in internal units.
*/
int GetCachedOwnClearance( const BOARD_ITEM* aItem, PCB_LAYER_ID aLayer,
wxString* aSource = nullptr );
/**
* Invalidate the clearance cache for a specific item.
*
* Called when item properties that could affect clearance (net, type, layer) change.
*
* @param aUuid the UUID of the item to invalidate.
*/
void InvalidateClearanceCache( const KIID& aUuid );
/**
* Clear the entire clearance cache.
*
* Called when DRC rules change or board design settings change.
*/
void ClearClearanceCache();
void ProcessAssertions( const BOARD_ITEM* a,
std::function<void( const DRC_CONSTRAINT* )> aFailureHandler,
REPORTER* aReporter = nullptr );
@@ -270,4 +334,8 @@ protected:
PROGRESS_REPORTER* m_progressReporter;
std::shared_ptr<KIGFX::VIEW_OVERLAY> m_debugOverlay;
// Cache for GetOwnClearance lookups to improve rendering performance.
// Key is (UUID, layer), value is clearance in internal units.
std::unordered_map<DRC_OWN_CLEARANCE_CACHE_KEY, int> m_ownClearanceCache;
};
+17 -21
View File
@@ -1355,6 +1355,10 @@ void PAD::SetAttribute( PAD_ATTRIB aAttribute )
SetNetCode( NETINFO_LIST::UNCONNECTED );
break;
}
// Invalidate clearance cache since pad type affects constraint evaluation
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}
SetDirty();
@@ -1581,29 +1585,21 @@ std::optional<int> PAD::GetClearanceOverrides( wxString* aSource ) const
}
void PAD::SetLayerSet( const LSET& aLayers )
{
m_padStack.SetLayerSet( aLayers );
SetDirty();
// Invalidate clearance cache since layer set can affect clearance rules
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}
int PAD::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const
{
DRC_CONSTRAINT c;
if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine )
{
BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
if( GetAttribute() == PAD_ATTRIB::NPTH )
c = bds.m_DRCEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
else
c = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
}
if( c.Value().HasMin() )
{
if( aSource )
*aSource = c.GetName();
return c.Value().Min();
}
return 0;
// The NPTH vs regular pad logic is handled in DRC_ENGINE::GetCachedOwnClearance
return BOARD_CONNECTED_ITEM::GetOwnClearance( aLayer, aSource );
}
+1 -1
View File
@@ -556,7 +556,7 @@ public:
m_polyDirty[ERROR_OUTSIDE] = true;
}
void SetLayerSet( const LSET& aLayers ) override { m_padStack.SetLayerSet( aLayers ); SetDirty(); }
void SetLayerSet( const LSET& aLayers ) override;
LSET GetLayerSet() const override { return m_padStack.LayerSet(); }
void SetAttribute( PAD_ATTRIB aAttribute );
+12
View File
@@ -1640,6 +1640,10 @@ void PCB_VIA::SetLayerPair( PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer )
Padstack().Drill().start = aTopLayer;
Padstack().Drill().end = aBottomLayer;
SanitizeLayers();
// Invalidate clearance cache since layer can affect clearance rules
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}
@@ -1651,6 +1655,10 @@ void PCB_VIA::SetTopLayer( PCB_LAYER_ID aLayer )
Padstack().Drill().start = aLayer;
SanitizeLayers();
// Invalidate clearance cache since layer can affect clearance rules
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}
@@ -1662,6 +1670,10 @@ void PCB_VIA::SetBottomLayer( PCB_LAYER_ID aLayer )
Padstack().Drill().end = aLayer;
SanitizeLayers();
// Invalidate clearance cache since layer can affect clearance rules
if( BOARD* board = GetBoard() )
board->InvalidateClearanceCache( m_Uuid );
}