Specialize the pad wall drawing

Avoids slowdown when zoom/pan with many THTs as everything remains
cached.  Still slow in Cairo but this is expected

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20506
This commit is contained in:
Seth Hillbrand 2025-08-24 11:24:23 -07:00
parent 66c42f396c
commit 3ef862f7b6
11 changed files with 188 additions and 34 deletions

View File

@ -322,6 +322,12 @@ void CAIRO_GAL_BASE::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& a
}
void CAIRO_GAL_BASE::DrawHoleWall( const VECTOR2D& aCenterPoint, double aRadius, double aWallWidth )
{
DrawCircle( aCenterPoint, aRadius + aWallWidth );
}
void CAIRO_GAL_BASE::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
{
syncLineWidth();

View File

@ -67,6 +67,7 @@ GAL::GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions ) :
SetLayerDepth( 0.0 );
SetFlip( false, false );
SetLineWidth( 1.0f );
SetMinLineWidth( 1.0f );
computeWorldScale();
SetAxesEnabled( false );

View File

@ -407,10 +407,13 @@ OPENGL_GAL::OPENGL_GAL( const KIGFX::VC_SETTINGS& aVcSettings, GAL_DISPLAY_OPTIO
SetTarget( TARGET_NONCACHED );
// Avoid uninitialized variables:
ufm_worldPixelSize = 1;
ufm_screenPixelSize = 1;
ufm_pixelSizeMultiplier = 1;
ufm_antialiasingOffset = 1;
ufm_worldPixelSize = -1;
ufm_screenPixelSize = -1;
ufm_pixelSizeMultiplier = -1;
ufm_antialiasingOffset = -1;
ufm_minLinePixelWidth = -1;
ufm_fontTexture = -1;
ufm_fontTextureWidth = -1;
m_swapInterval = 0;
}
@ -666,14 +669,6 @@ void OPENGL_GAL::BeginDrawing()
glActiveTexture( GL_TEXTURE0 );
}
// Set shader parameter
GLint ufm_fontTexture = m_shader->AddParameter( "u_fontTexture" );
GLint ufm_fontTextureWidth = m_shader->AddParameter( "u_fontTextureWidth" );
ufm_worldPixelSize = m_shader->AddParameter( "u_worldPixelSize" );
ufm_screenPixelSize = m_shader->AddParameter( "u_screenPixelSize" );
ufm_pixelSizeMultiplier = m_shader->AddParameter( "u_pixelSizeMultiplier" );
ufm_antialiasingOffset = m_shader->AddParameter( "u_antialiasingOffset" );
m_shader->Use();
m_shader->SetParameter( ufm_fontTexture, (int) FONT_TEXTURE_UNIT );
m_shader->SetParameter( ufm_fontTextureWidth, (int) font_image.width );
@ -694,6 +689,7 @@ void OPENGL_GAL::BeginDrawing()
renderingOffset.x *= screenPixelSize.x;
renderingOffset.y *= screenPixelSize.y;
m_shader->SetParameter( ufm_antialiasingOffset, renderingOffset );
m_shader->SetParameter( ufm_minLinePixelWidth, GetMinLineWidth() );
m_shader->Deactivate();
// Something between BeginDrawing and EndDrawing seems to depend on
@ -710,6 +706,18 @@ void OPENGL_GAL::BeginDrawing()
#endif /* KICAD_GAL_PROFILE */
}
void OPENGL_GAL::SetMinLineWidth( float aLineWidth )
{
GAL::SetMinLineWidth( aLineWidth );
if( m_shader && ufm_minLinePixelWidth != -1 )
{
m_shader->Use();
m_shader->SetParameter( ufm_minLinePixelWidth, aLineWidth );
m_shader->Deactivate();
}
}
void OPENGL_GAL::EndDrawing()
{
@ -901,6 +909,25 @@ void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
}
void OPENGL_GAL::DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
double aWallWidth )
{
if( m_isFillEnabled )
{
m_currentManager->Color( m_fillColor.r, m_fillColor.g, m_fillColor.b, m_fillColor.a );
m_currentManager->Shader( SHADER_HOLE_WALL, 1.0, aHoleRadius, aWallWidth );
m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
m_currentManager->Shader( SHADER_HOLE_WALL, 2.0, aHoleRadius, aWallWidth );
m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
m_currentManager->Shader( SHADER_HOLE_WALL, 3.0, aHoleRadius, aWallWidth );
m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
}
}
void OPENGL_GAL::drawCircle( const VECTOR2D& aCenterPoint, double aRadius, bool aReserve )
{
if( m_isFillEnabled )
@ -2802,6 +2829,9 @@ void OPENGL_GAL::init()
if( !m_shader->IsLinked() && !m_shader->Link() )
throw std::runtime_error( "Cannot link the shaders!" );
// Set up shader parameters after linking
setupShaderParameters();
// Check if video card supports textures big enough to fit the font atlas
int maxTextureSize;
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTextureSize );
@ -2830,6 +2860,19 @@ void OPENGL_GAL::init()
}
void OPENGL_GAL::setupShaderParameters()
{
// Initialize shader uniform parameter locations
ufm_fontTexture = m_shader->AddParameter( "u_fontTexture" );
ufm_fontTextureWidth = m_shader->AddParameter( "u_fontTextureWidth" );
ufm_worldPixelSize = m_shader->AddParameter( "u_worldPixelSize" );
ufm_screenPixelSize = m_shader->AddParameter( "u_screenPixelSize" );
ufm_pixelSizeMultiplier = m_shader->AddParameter( "u_pixelSizeMultiplier" );
ufm_antialiasingOffset = m_shader->AddParameter( "u_antialiasingOffset" );
ufm_minLinePixelWidth = m_shader->AddParameter( "u_minLinePixelWidth" );
}
// Callback functions for the tesselator. Compare Redbook Chapter 11.
void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
{

View File

@ -35,6 +35,7 @@ const float SHADER_FILLED_CIRCLE = 2.0;
const float SHADER_STROKED_CIRCLE = 3.0;
const float SHADER_FONT = 4.0;
const float SHADER_LINE_A = 5.0;
const float SHADER_HOLE_WALL = 11.0;
varying vec4 v_shaderParams;
varying vec2 v_circleCoords;
@ -124,6 +125,10 @@ void main()
{
strokedCircle( v_circleCoords, v_shaderParams[2], v_shaderParams[3] );
}
else if( mode == SHADER_HOLE_WALL )
{
strokedCircle( v_circleCoords, v_shaderParams[2], v_shaderParams[3] );
}
else if( mode == SHADER_FONT )
{
vec2 tex = v_shaderParams.yz;

View File

@ -36,6 +36,7 @@ const float SHADER_LINE_C = 7.0;
const float SHADER_LINE_D = 8.0;
const float SHADER_LINE_E = 9.0;
const float SHADER_LINE_F = 10.0;
const float SHADER_HOLE_WALL = 11.0;
// Minimum line width
const float MIN_WIDTH = 1.0;
@ -72,8 +73,8 @@ void computeLineCoords( bool posture, vec2 vs, vec2 vp, vec2 texcoord, vec2 dir,
vec2 s = sign( vec2( gl_ModelViewProjectionMatrix[0][0], gl_ModelViewProjectionMatrix[1][1] ) );
if( pixelWidth < 1.0 )
pixelWidth = 1.0;
if( pixelWidth < u_minLinePixelWidth )
pixelWidth = u_minLinePixelWidth;
if ( pixelWidth > 1.0 || u_pixelSizeMultiplier > 1.0 )
{
@ -126,8 +127,59 @@ void computeCircleCoords( float mode, float vertexIndex, float radius, float lin
vec4 adjust = vec4(-1, -1, 0, 0);
if( pixelWidth < 1.0 )
pixelWidth = 1.0;
if( pixelWidth < u_minLinePixelWidth )
pixelWidth = u_minLinePixelWidth;
if( vertexIndex == 1.0 )
{
v_circleCoords = vec2( -sqrt( 3.0 ), -1.0 );
delta = vec4( -pixelR * sqrt(3.0), -pixelR, 0, 0 );
}
else if( vertexIndex == 2.0 )
{
v_circleCoords = vec2( sqrt( 3.0 ), -1.0 );
delta = vec4( pixelR * sqrt( 3.0 ), -pixelR, 0, 0 );
}
else if( vertexIndex == 3.0 )
{
v_circleCoords = vec2( 0.0, 2.0 );
delta = vec4( 0, 2 * pixelR, 0, 0 );
}
else if( vertexIndex == 4.0 )
{
v_circleCoords = vec2( -sqrt( 3.0 ), 0.0 );
delta = vec4( 0, 0, 0, 0 );
}
else if( vertexIndex == 5.0 )
{
v_circleCoords = vec2( sqrt( 3.0 ), 0.0 );
delta = vec4( 0, 0, 0, 0 );
}
else if( vertexIndex == 6.0 )
{
v_circleCoords = vec2( 0.0, 2.0 );
delta = vec4( 0, 0, 0, 0 );
}
v_shaderParams[2] = pixelR;
v_shaderParams[3] = pixelWidth;
delta.x *= u_screenPixelSize.x;
delta.y *= u_screenPixelSize.y;
gl_Position = center + delta + adjust;
gl_FrontColor = gl_Color;
}
void computeHoleWallCoords( float vertexIndex, float radius, float lineWidth )
{
vec4 delta;
vec4 center = roundv( gl_ModelViewProjectionMatrix * gl_Vertex + vec4(1, 1, 0, 0), u_screenPixelSize );
float pixelWidth = roundr( lineWidth / u_worldPixelSize, 1.0 );
if( pixelWidth < u_minLinePixelWidth )
pixelWidth = u_minLinePixelWidth;
float pixelR = roundr( radius / u_worldPixelSize, 1.0 ) + pixelWidth;
vec4 adjust = vec4(-1, -1, 0, 0);
if( vertexIndex == 1.0 )
{
@ -195,6 +247,8 @@ void main()
computeLineCoords( posture, vs, vp, vec2( -1, 1 ), vec2( -1, 0 ), lineWidth, true );
else if( mode == SHADER_LINE_F )
computeLineCoords( posture, -vs, vp, vec2( 1, 1 ), vec2( -1, 0 ), lineWidth, false );
else if( mode == SHADER_HOLE_WALL )
computeHoleWallCoords( v_shaderParams.y, v_shaderParams.z, v_shaderParams.w );
else if( mode == SHADER_FILLED_CIRCLE || mode == SHADER_STROKED_CIRCLE)
computeCircleCoords( mode, v_shaderParams.y, v_shaderParams.z, v_shaderParams.w );
else

View File

@ -78,6 +78,10 @@ public:
/// @copydoc GAL::DrawCircle()
void DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) override;
/// @copydoc GAL::DrawHoleWall()
void DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
double aWallWidth ) override;
/// @copydoc GAL::DrawArc()
void DrawArc( const VECTOR2D& aCenterPoint, double aRadius, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aAngle ) override;

View File

@ -142,6 +142,16 @@ public:
*/
virtual void DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) {};
/**
* Draw a hole wall ring.
*
* @param aCenterPoint is the center point of the hole.
* @param aHoleRadius is the radius of the hole.
* @param aWallWidth is the wall thickness.
*/
virtual void DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
double aWallWidth ) {};
/**
* Draw an arc.
*
@ -369,6 +379,16 @@ public:
m_lineWidth = aLineWidth;
}
/**
* Set the minimum line width in pixels.
*
* @param aLineWidth is the minimum line width.
*/
virtual void SetMinLineWidth( float aLineWidth )
{
m_minLineWidth = aLineWidth;
}
/**
* Get the line width.
*
@ -379,6 +399,16 @@ public:
return m_lineWidth;
}
/**
* Get the minimum line width in pixels.
*
* @return the minimum line width.
*/
inline float GetMinLineWidth() const
{
return m_minLineWidth;
}
/**
* Set the depth of the layer (position on the z-axis)
*
@ -1084,6 +1114,7 @@ protected:
bool m_globalFlipY; ///< Flag for Y axis flipping
float m_lineWidth; ///< The line width
float m_minLineWidth; ///< Minimum line width in pixels
bool m_isFillEnabled; ///< Is filling of graphic objects enabled ?
bool m_isStrokeEnabled; ///< Are the outlines stroked ?

View File

@ -113,6 +113,8 @@ public:
return IsShownOnScreen() && !GetClientRect().IsEmpty();
}
void SetMinLineWidth( float aLineWidth ) override;
// ---------------
// Drawing methods
// ---------------
@ -131,6 +133,10 @@ public:
/// @copydoc GAL::DrawCircle()
void DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) override;
/// @copydoc GAL::DrawHoleWall()
void DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
double aWallWidth ) override;
/// @copydoc GAL::DrawArc()
void DrawArc( const VECTOR2D& aCenterPoint, double aRadius, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aAngle ) override;
@ -377,6 +383,9 @@ private:
GLint ufm_screenPixelSize;
GLint ufm_pixelSizeMultiplier;
GLint ufm_antialiasingOffset;
GLint ufm_minLinePixelWidth;
GLint ufm_fontTexture;
GLint ufm_fontTextureWidth;
/// wx cursor showing the current native cursor.
WX_CURSOR_TYPE m_currentwxCursor;
@ -586,6 +595,13 @@ private:
VECTOR2D getScreenPixelSize() const;
/**
* Set up the shader parameters for OpenGL rendering.
* This method initializes all the uniform parameter locations
* after the shader has been linked.
*/
void setupShaderParameters();
/**
* Basic OpenGL initialization and feature checks.
*

View File

@ -53,7 +53,8 @@ enum SHADER_MODE
SHADER_LINE_C = 7,
SHADER_LINE_D = 8,
SHADER_LINE_E = 9,
SHADER_LINE_F = 10
SHADER_LINE_F = 10,
SHADER_HOLE_WALL = 11
};
///< Data structure for vertices {X,Y,Z,R,G,B,A,shader&param}

View File

@ -1863,17 +1863,6 @@ double PAD::ViewGetLOD( int aLayer, const KIGFX::VIEW* aView ) const
return lodScaleForThreshold( aView, minSize, pcbIUScale.mmToIU( 0.5 ) );
}
// Hole walls always need a repaint when zoom level changes after the last
// LAYER_PAD_HOLEWALLS shape rebuild
if( aLayer == LAYER_PAD_HOLEWALLS )
{
if( aView->GetGAL()->GetZoomFactor() != m_lastGalZoomLevel )
{
aView->Update( this, KIGFX::REPAINT );
m_lastGalZoomLevel = aView->GetGAL()->GetZoomFactor();
}
}
VECTOR2L padSize = GetBoundingBox().GetSize();
int64_t minSide = std::min( padSize.x, padSize.y );

View File

@ -1448,22 +1448,26 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
m_gal->SetIsStroke( false );
double widthFactor = ADVANCED_CFG::GetCfg().m_HoleWallPaintingMultiplier;
double lineWidth = widthFactor * m_holePlatingThickness;
// Prevent the hole wall from being drawn too thin (at least two pixels)
// or too thick (cap at the size of the pad )
lineWidth = std::max( lineWidth, 2.0 / m_gal->GetWorldScale() );
lineWidth = std::min( lineWidth, aPad->GetSizeX() / 2.0 );
lineWidth = std::min( lineWidth, aPad->GetSizeY() / 2.0 );
m_gal->SetFillColor( color );
m_gal->SetMinLineWidth( lineWidth );
std::shared_ptr<SHAPE_SEGMENT> slot = aPad->GetEffectiveHoleShape();
int holeSize = slot->GetWidth() + ( 2 * lineWidth );
if( slot->GetSeg().A == slot->GetSeg().B ) // Circular hole
m_gal->DrawCircle( slot->GetSeg().A, KiROUND( holeSize / 2.0 ) );
{
double holeRadius = slot->GetWidth() / 2.0;
m_gal->DrawHoleWall( slot->GetSeg().A, holeRadius, lineWidth );
}
else
{
int holeSize = slot->GetWidth() + ( 2 * lineWidth );
m_gal->DrawSegment( slot->GetSeg().A, slot->GetSeg().B, holeSize );
}
m_gal->SetMinLineWidth( 1.0 );
return;
}