Removed shaderless OpenGL backend.
This commit is contained in:
parent
bd182aad9f
commit
f9d74ccb70
|
@ -55,7 +55,7 @@ EDA_DRAW_PANEL_GAL::EDA_DRAW_PANEL_GAL( wxWindow* aParentWindow, wxWindowID aWin
|
||||||
m_view = NULL;
|
m_view = NULL;
|
||||||
m_painter = NULL;
|
m_painter = NULL;
|
||||||
|
|
||||||
SwitchBackend( aGalType, true );
|
SwitchBackend( aGalType );
|
||||||
SetBackgroundStyle( wxBG_STYLE_CUSTOM );
|
SetBackgroundStyle( wxBG_STYLE_CUSTOM );
|
||||||
|
|
||||||
// Initial display settings
|
// Initial display settings
|
||||||
|
@ -149,13 +149,10 @@ void EDA_DRAW_PANEL_GAL::Refresh( bool eraseBackground, const wxRect* rect )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType, bool aUseShaders )
|
void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType )
|
||||||
{
|
{
|
||||||
wxLogDebug( wxT( "EDA_DRAW_PANEL_GAL::SwitchBackend: using shaders: %s" ),
|
|
||||||
aUseShaders ? "true" : "false" );
|
|
||||||
|
|
||||||
// Do not do anything if the currently used GAL is correct
|
// Do not do anything if the currently used GAL is correct
|
||||||
if( aGalType == m_currentGal && aUseShaders == m_useShaders && m_gal != NULL )
|
if( aGalType == m_currentGal && m_gal != NULL )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
delete m_gal;
|
delete m_gal;
|
||||||
|
@ -163,7 +160,7 @@ void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType, bool aUseShaders )
|
||||||
switch( aGalType )
|
switch( aGalType )
|
||||||
{
|
{
|
||||||
case GAL_TYPE_OPENGL:
|
case GAL_TYPE_OPENGL:
|
||||||
m_gal = new KiGfx::OPENGL_GAL( this, this, this, aUseShaders );
|
m_gal = new KiGfx::OPENGL_GAL( this, this, this );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GAL_TYPE_CAIRO:
|
case GAL_TYPE_CAIRO:
|
||||||
|
@ -191,5 +188,4 @@ void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType, bool aUseShaders )
|
||||||
m_gal->ResizeScreen( size.GetX(), size.GetY() );
|
m_gal->ResizeScreen( size.GetX(), size.GetY() );
|
||||||
|
|
||||||
m_currentGal = aGalType;
|
m_currentGal = aGalType;
|
||||||
m_useShaders = aUseShaders;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ void InitTesselatorCallbacks( GLUtesselator* aTesselator );
|
||||||
const int glAttributes[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0 };
|
const int glAttributes[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0 };
|
||||||
|
|
||||||
OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
|
OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
|
||||||
wxEvtHandler* aPaintListener, bool isUseShaders, const wxString& aName ) :
|
wxEvtHandler* aPaintListener, const wxString& aName ) :
|
||||||
wxGLCanvas( aParent, wxID_ANY, (int*) glAttributes, wxDefaultPosition, wxDefaultSize,
|
wxGLCanvas( aParent, wxID_ANY, (int*) glAttributes, wxDefaultPosition, wxDefaultSize,
|
||||||
wxEXPAND, aName ),
|
wxEXPAND, aName ),
|
||||||
cachedManager( true ),
|
cachedManager( true ),
|
||||||
|
@ -70,7 +70,6 @@ OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
|
||||||
// Initialize the flags
|
// Initialize the flags
|
||||||
isGlewInitialized = false;
|
isGlewInitialized = false;
|
||||||
isFramebufferInitialized = false;
|
isFramebufferInitialized = false;
|
||||||
isUseShader = isUseShaders;
|
|
||||||
isShaderInitialized = false;
|
isShaderInitialized = false;
|
||||||
isGrouping = false;
|
isGrouping = false;
|
||||||
wxSize parentSize = aParent->GetSize();
|
wxSize parentSize = aParent->GetSize();
|
||||||
|
@ -255,7 +254,7 @@ void OPENGL_GAL::BeginDrawing()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the shaders
|
// Compile the shaders
|
||||||
if( !isShaderInitialized && isUseShader )
|
if( !isShaderInitialized )
|
||||||
{
|
{
|
||||||
if( !shader.LoadBuiltinShader( 0, SHADER_TYPE_VERTEX ) )
|
if( !shader.LoadBuiltinShader( 0, SHADER_TYPE_VERTEX ) )
|
||||||
wxLogFatalError( wxT( "Cannot compile vertex shader!" ) );
|
wxLogFatalError( wxT( "Cannot compile vertex shader!" ) );
|
||||||
|
@ -351,49 +350,29 @@ inline void OPENGL_GAL::drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2
|
||||||
return;
|
return;
|
||||||
|
|
||||||
VECTOR2D perpendicularVector( -startEndVector.y * scale, startEndVector.x * scale );
|
VECTOR2D perpendicularVector( -startEndVector.y * scale, startEndVector.x * scale );
|
||||||
|
glm::vec4 vector( perpendicularVector.x, perpendicularVector.y, 0.0, 0.0 );
|
||||||
|
|
||||||
if( isUseShader )
|
// The perpendicular vector also needs transformations
|
||||||
{
|
vector = currentManager->GetTransformation() * vector;
|
||||||
glm::vec4 vector( perpendicularVector.x, perpendicularVector.y, 0.0, 0.0 );
|
|
||||||
|
|
||||||
// The perpendicular vector also needs transformations
|
// Line width is maintained by the vertex shader
|
||||||
vector = currentManager->GetTransformation() * vector;
|
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
||||||
|
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
|
||||||
|
|
||||||
// Line width is maintained by the vertex shader
|
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
||||||
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v1
|
||||||
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
|
|
||||||
|
|
||||||
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
||||||
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v1
|
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
|
||||||
|
|
||||||
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
||||||
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
|
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
|
||||||
|
|
||||||
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
||||||
currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
|
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
|
||||||
|
|
||||||
currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
|
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
||||||
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
|
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v2
|
||||||
|
|
||||||
currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
|
|
||||||
currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v2
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Compute the edge points of the line
|
|
||||||
VECTOR2D v0 = aStartPoint + perpendicularVector;
|
|
||||||
VECTOR2D v1 = aStartPoint - perpendicularVector;
|
|
||||||
VECTOR2D v2 = aEndPoint + perpendicularVector;
|
|
||||||
VECTOR2D v3 = aEndPoint - perpendicularVector;
|
|
||||||
|
|
||||||
currentManager->Vertex( v0.x, v0.y, layerDepth );
|
|
||||||
currentManager->Vertex( v1.x, v1.y, layerDepth );
|
|
||||||
currentManager->Vertex( v3.x, v3.y, layerDepth );
|
|
||||||
|
|
||||||
currentManager->Vertex( v0.x, v0.y, layerDepth );
|
|
||||||
currentManager->Vertex( v3.x, v3.y, layerDepth );
|
|
||||||
currentManager->Vertex( v2.x, v2.y, layerDepth );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -533,143 +512,58 @@ void OPENGL_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEn
|
||||||
|
|
||||||
void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
|
void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
|
||||||
{
|
{
|
||||||
if( isUseShader )
|
if( isFillEnabled )
|
||||||
{
|
{
|
||||||
if( isFillEnabled )
|
currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
|
||||||
{
|
|
||||||
currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
|
|
||||||
|
|
||||||
/* Draw a triangle that contains the circle, then shade it leaving only the circle.
|
/* Draw a triangle that contains the circle, then shade it leaving only the circle.
|
||||||
Parameters given to setShader are indices of the triangle's vertices
|
Parameters given to setShader are indices of the triangle's vertices
|
||||||
(if you want to understand more, check the vertex shader source [shader.vert]).
|
(if you want to understand more, check the vertex shader source [shader.vert]).
|
||||||
Shader uses this coordinates to determine if fragments are inside the circle or not.
|
Shader uses this coordinates to determine if fragments are inside the circle or not.
|
||||||
v2
|
v2
|
||||||
/\
|
/\
|
||||||
//\\
|
//\\
|
||||||
v0 /_\/_\ v1
|
v0 /_\/_\ v1
|
||||||
*/
|
*/
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0 );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0 );
|
||||||
currentManager->Vertex( aCenterPoint.x - aRadius * sqrt( 3.0f ), // v0
|
currentManager->Vertex( aCenterPoint.x - aRadius * sqrt( 3.0f ), // v0
|
||||||
aCenterPoint.y - aRadius, layerDepth );
|
aCenterPoint.y - aRadius, layerDepth );
|
||||||
|
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0 );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0 );
|
||||||
currentManager->Vertex( aCenterPoint.x + aRadius* sqrt( 3.0f ), // v1
|
currentManager->Vertex( aCenterPoint.x + aRadius* sqrt( 3.0f ), // v1
|
||||||
aCenterPoint.y - aRadius, layerDepth );
|
aCenterPoint.y - aRadius, layerDepth );
|
||||||
|
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0 );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0 );
|
||||||
currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + aRadius * 2.0f, // v2
|
currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + aRadius * 2.0f, // v2
|
||||||
layerDepth );
|
layerDepth );
|
||||||
}
|
|
||||||
|
|
||||||
if( isStrokeEnabled )
|
|
||||||
{
|
|
||||||
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
|
|
||||||
|
|
||||||
/* Draw a triangle that contains the circle, then shade it leaving only the circle.
|
|
||||||
Parameters given to setShader are indices of the triangle's vertices
|
|
||||||
(if you want to understand more, check the vertex shader source [shader.vert]).
|
|
||||||
and the line width. Shader uses this coordinates to determine if fragments are
|
|
||||||
inside the circle or not.
|
|
||||||
v2
|
|
||||||
/\
|
|
||||||
//\\
|
|
||||||
v0 /_\/_\ v1
|
|
||||||
*/
|
|
||||||
double outerRadius = aRadius + ( lineWidth / 2 );
|
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 1.0, aRadius, lineWidth );
|
|
||||||
currentManager->Vertex( aCenterPoint.x - outerRadius * sqrt( 3.0f ), // v0
|
|
||||||
aCenterPoint.y - outerRadius, layerDepth );
|
|
||||||
|
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 2.0, aRadius, lineWidth );
|
|
||||||
currentManager->Vertex( aCenterPoint.x + outerRadius * sqrt( 3.0f ), // v1
|
|
||||||
aCenterPoint.y - outerRadius, layerDepth );
|
|
||||||
|
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 3.0, aRadius, lineWidth );
|
|
||||||
currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + outerRadius * 2.0f, // v2
|
|
||||||
layerDepth );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if( isStrokeEnabled )
|
||||||
{
|
{
|
||||||
if( isStrokeEnabled )
|
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
|
||||||
{
|
|
||||||
// Compute the factors for the unit circle
|
|
||||||
double outerScale = lineWidth / aRadius / 2;
|
|
||||||
double innerScale = -outerScale;
|
|
||||||
outerScale += 1.0;
|
|
||||||
innerScale += 1.0;
|
|
||||||
|
|
||||||
if( innerScale < outerScale )
|
/* Draw a triangle that contains the circle, then shade it leaving only the circle.
|
||||||
{
|
Parameters given to setShader are indices of the triangle's vertices
|
||||||
// Draw the outline
|
(if you want to understand more, check the vertex shader source [shader.vert]).
|
||||||
VERTEX* circle = circleContainer.GetAllVertices();
|
and the line width. Shader uses this coordinates to determine if fragments are
|
||||||
int next;
|
inside the circle or not.
|
||||||
|
v2
|
||||||
|
/\
|
||||||
|
//\\
|
||||||
|
v0 /_\/_\ v1
|
||||||
|
*/
|
||||||
|
double outerRadius = aRadius + ( lineWidth / 2 );
|
||||||
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 1.0, aRadius, lineWidth );
|
||||||
|
currentManager->Vertex( aCenterPoint.x - outerRadius * sqrt( 3.0f ), // v0
|
||||||
|
aCenterPoint.y - outerRadius, layerDepth );
|
||||||
|
|
||||||
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b,
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 2.0, aRadius, lineWidth );
|
||||||
strokeColor.a );
|
currentManager->Vertex( aCenterPoint.x + outerRadius * sqrt( 3.0f ), // v1
|
||||||
|
aCenterPoint.y - outerRadius, layerDepth );
|
||||||
|
|
||||||
Save();
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 3.0, aRadius, lineWidth );
|
||||||
|
currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + outerRadius * 2.0f, // v2
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
|
layerDepth );
|
||||||
currentManager->Scale( aRadius, aRadius, 0.0f );
|
|
||||||
|
|
||||||
for( int i = 0; i < 3 * CIRCLE_POINTS; ++i )
|
|
||||||
{
|
|
||||||
// verticesCircle contains precomputed circle points interleaved with vertex
|
|
||||||
// (0,0,0), so filled circles can be drawn as consecutive triangles, ie:
|
|
||||||
// { 0,a,b, 0,c,d, 0,e,f, 0,g,h, ... }
|
|
||||||
// where letters stand for consecutive circle points and 0 for (0,0,0) vertex.
|
|
||||||
|
|
||||||
// We have to skip all (0,0,0) vertices (every third vertex)
|
|
||||||
if( i % 3 == 0 )
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
// Depending on the vertex, next circle point
|
|
||||||
// may be stored in the next vertex..
|
|
||||||
next = i + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ..or 2 vertices away (in case it is preceded by (0,0,0) vertex)
|
|
||||||
next = i + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentManager->Vertex( circle[i].x * innerScale,
|
|
||||||
circle[i].y * innerScale,
|
|
||||||
layerDepth );
|
|
||||||
currentManager->Vertex( circle[i].x * outerScale,
|
|
||||||
circle[i].y * outerScale,
|
|
||||||
layerDepth );
|
|
||||||
currentManager->Vertex( circle[next].x * innerScale,
|
|
||||||
circle[next].y * innerScale,
|
|
||||||
layerDepth );
|
|
||||||
|
|
||||||
currentManager->Vertex( circle[i].x * outerScale,
|
|
||||||
circle[i].y * outerScale,
|
|
||||||
layerDepth );
|
|
||||||
currentManager->Vertex( circle[next].x * outerScale,
|
|
||||||
circle[next].y * outerScale,
|
|
||||||
layerDepth );
|
|
||||||
currentManager->Vertex( circle[next].x * innerScale,
|
|
||||||
circle[next].y * innerScale,
|
|
||||||
layerDepth );
|
|
||||||
}
|
|
||||||
|
|
||||||
Restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filled circles are easy to draw by using the stored vertices list
|
|
||||||
if( isFillEnabled )
|
|
||||||
{
|
|
||||||
currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
|
|
||||||
|
|
||||||
Save();
|
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, layerDepth );
|
|
||||||
currentManager->Scale( aRadius, aRadius, 0.0f );
|
|
||||||
currentManager->Vertices( circleContainer.GetAllVertices(), CIRCLE_POINTS * 3 );
|
|
||||||
Restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,124 +587,61 @@ void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, d
|
||||||
void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
|
void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
|
||||||
double aAngle )
|
double aAngle )
|
||||||
{
|
{
|
||||||
if( isUseShader )
|
Save();
|
||||||
{
|
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
|
||||||
Save();
|
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
|
|
||||||
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
|
||||||
|
|
||||||
/* Draw a triangle that contains the semicircle, then shade it to leave only
|
/* Draw a triangle that contains the semicircle, then shade it to leave only
|
||||||
* the semicircle. Parameters given to setShader are indices of the triangle's vertices
|
* the semicircle. Parameters given to setShader are indices of the triangle's vertices
|
||||||
(if you want to understand more, check the vertex shader source [shader.vert]).
|
(if you want to understand more, check the vertex shader source [shader.vert]).
|
||||||
Shader uses this coordinates to determine if fragments are inside the semicircle or not.
|
Shader uses this coordinates to determine if fragments are inside the semicircle or not.
|
||||||
v2
|
v2
|
||||||
/\
|
/\
|
||||||
/__\
|
/__\
|
||||||
v0 //__\\ v1
|
v0 //__\\ v1
|
||||||
*/
|
*/
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f );
|
||||||
currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
|
currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
|
||||||
|
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f );
|
||||||
currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
|
currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
|
||||||
|
|
||||||
currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f );
|
currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f );
|
||||||
currentManager->Vertex( 0.0f, aRadius * 2.0f, layerDepth ); // v2
|
currentManager->Vertex( 0.0f, aRadius * 2.0f, layerDepth ); // v2
|
||||||
|
|
||||||
Restore();
|
Restore();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Save();
|
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, layerDepth );
|
|
||||||
currentManager->Scale( aRadius, aRadius, 0.0f );
|
|
||||||
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
|
||||||
|
|
||||||
// It is enough just to draw a half of the circle vertices to make a semicircle
|
|
||||||
currentManager->Vertices( circleContainer.GetAllVertices(), ( CIRCLE_POINTS * 3 ) / 2 );
|
|
||||||
|
|
||||||
Restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
|
void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
|
||||||
double aAngle )
|
double aAngle )
|
||||||
{
|
{
|
||||||
if( isUseShader )
|
double outerRadius = aRadius + ( lineWidth / 2 );
|
||||||
{
|
|
||||||
double outerRadius = aRadius + ( lineWidth / 2 );
|
|
||||||
|
|
||||||
Save();
|
Save();
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
|
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
|
||||||
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
||||||
|
|
||||||
/* Draw a triangle that contains the semicircle, then shade it to leave only
|
/* Draw a triangle that contains the semicircle, then shade it to leave only
|
||||||
* the semicircle. Parameters given to setShader are indices of the triangle's vertices
|
* the semicircle. Parameters given to setShader are indices of the triangle's vertices
|
||||||
(if you want to understand more, check the vertex shader source [shader.vert]), the
|
(if you want to understand more, check the vertex shader source [shader.vert]), the
|
||||||
radius and the line width. Shader uses this coordinates to determine if fragments are
|
radius and the line width. Shader uses this coordinates to determine if fragments are
|
||||||
inside the semicircle or not.
|
inside the semicircle or not.
|
||||||
v2
|
v2
|
||||||
/\
|
/\
|
||||||
/__\
|
/__\
|
||||||
v0 //__\\ v1
|
v0 //__\\ v1
|
||||||
*/
|
*/
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, lineWidth );
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, lineWidth );
|
||||||
currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
|
currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
|
||||||
|
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, lineWidth );
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, lineWidth );
|
||||||
currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
|
currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
|
||||||
|
|
||||||
currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, lineWidth );
|
currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, lineWidth );
|
||||||
currentManager->Vertex( 0.0f, outerRadius * 2.0f, layerDepth ); // v2
|
currentManager->Vertex( 0.0f, outerRadius * 2.0f, layerDepth ); // v2
|
||||||
|
|
||||||
Restore();
|
Restore();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Compute the factors for the unit circle
|
|
||||||
double innerScale = 1.0 - lineWidth / aRadius;
|
|
||||||
|
|
||||||
Save();
|
|
||||||
currentManager->Translate( aCenterPoint.x, aCenterPoint.y, layerDepth );
|
|
||||||
currentManager->Scale( aRadius, aRadius, 0.0f );
|
|
||||||
currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
|
|
||||||
|
|
||||||
// Draw the outline
|
|
||||||
VERTEX* circle = circleContainer.GetAllVertices();
|
|
||||||
int next;
|
|
||||||
|
|
||||||
for( int i = 0; i < ( 3 * CIRCLE_POINTS ) / 2; ++i )
|
|
||||||
{
|
|
||||||
// verticesCircle contains precomputed circle points interleaved with vertex
|
|
||||||
// (0,0,0), so filled circles can be drawn as consecutive triangles, ie:
|
|
||||||
// { 0,a,b, 0,c,d, 0,e,f, 0,g,h, ... }
|
|
||||||
// where letters stand for consecutive circle points and 0 for (0,0,0) vertex.
|
|
||||||
|
|
||||||
// We have to skip all (0,0,0) vertices (every third vertex)
|
|
||||||
if( i % 3 == 0 )
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
// Depending on the vertex, next circle point may be stored in the next vertex..
|
|
||||||
next = i + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ..or 2 vertices away (in case it is preceded by (0,0,0) vertex)
|
|
||||||
next = i + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentManager->Vertex( circle[i].x * innerScale, circle[i].y * innerScale, 0.0 );
|
|
||||||
currentManager->Vertex( circle[i].x, circle[i].y, 0.0 );
|
|
||||||
currentManager->Vertex( circle[next].x * innerScale, circle[next].y * innerScale, 0.0 );
|
|
||||||
|
|
||||||
currentManager->Vertex( circle[i].x, circle[i].y, 0.0 );
|
|
||||||
currentManager->Vertex( circle[next].x, circle[next].y, 0.0 );
|
|
||||||
currentManager->Vertex( circle[next].x * innerScale, circle[next].y * innerScale, 0.0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
Restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -834,66 +665,24 @@ void OPENGL_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double a
|
||||||
|
|
||||||
if( isStrokeEnabled )
|
if( isStrokeEnabled )
|
||||||
{
|
{
|
||||||
if( isUseShader )
|
double alphaIncrement = 2.0 * M_PI / CIRCLE_POINTS;
|
||||||
|
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
|
||||||
|
|
||||||
|
VECTOR2D p( cos( aStartAngle ) * aRadius, sin( aStartAngle ) * aRadius );
|
||||||
|
double alpha;
|
||||||
|
for( alpha = aStartAngle + alphaIncrement; alpha < aEndAngle; alpha += alphaIncrement )
|
||||||
{
|
{
|
||||||
double alphaIncrement = 2.0 * M_PI / CIRCLE_POINTS;
|
VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
|
||||||
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
|
DrawLine( p, p_next );
|
||||||
|
|
||||||
VECTOR2D p( cos( aStartAngle ) * aRadius, sin( aStartAngle ) * aRadius );
|
p = p_next;
|
||||||
double alpha;
|
|
||||||
for( alpha = aStartAngle + alphaIncrement; alpha < aEndAngle; alpha += alphaIncrement )
|
|
||||||
{
|
|
||||||
VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
|
|
||||||
DrawLine( p, p_next );
|
|
||||||
|
|
||||||
p = p_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the last missing part
|
|
||||||
if( alpha != aEndAngle )
|
|
||||||
{
|
|
||||||
VECTOR2D p_last( cos( aEndAngle ) * aRadius, sin( aEndAngle ) * aRadius );
|
|
||||||
DrawLine( p, p_last );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Draw the last missing part
|
||||||
|
if( alpha != aEndAngle )
|
||||||
{
|
{
|
||||||
Scale( VECTOR2D( aRadius, aRadius ) );
|
VECTOR2D p_last( cos( aEndAngle ) * aRadius, sin( aEndAngle ) * aRadius );
|
||||||
|
DrawLine( p, p_last );
|
||||||
double outerScale = lineWidth / aRadius / 2;
|
|
||||||
double innerScale = -outerScale;
|
|
||||||
|
|
||||||
outerScale += 1.0;
|
|
||||||
innerScale += 1.0;
|
|
||||||
|
|
||||||
double alphaIncrement = 2.0 * M_PI / CIRCLE_POINTS;
|
|
||||||
currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
|
|
||||||
|
|
||||||
for( double alpha = aStartAngle; alpha < aEndAngle; )
|
|
||||||
{
|
|
||||||
double v0[] = { cos( alpha ) * innerScale, sin( alpha ) * innerScale };
|
|
||||||
double v1[] = { cos( alpha ) * outerScale, sin( alpha ) * outerScale };
|
|
||||||
|
|
||||||
alpha += alphaIncrement;
|
|
||||||
|
|
||||||
if( alpha > aEndAngle )
|
|
||||||
alpha = aEndAngle;
|
|
||||||
|
|
||||||
double v2[] = { cos( alpha ) * innerScale, sin( alpha ) * innerScale };
|
|
||||||
double v3[] = { cos( alpha ) * outerScale, sin( alpha ) * outerScale };
|
|
||||||
|
|
||||||
currentManager->Vertex( v0[0], v0[1], 0.0 );
|
|
||||||
currentManager->Vertex( v1[0], v1[1], 0.0 );
|
|
||||||
currentManager->Vertex( v2[0], v2[1], 0.0 );
|
|
||||||
|
|
||||||
currentManager->Vertex( v1[0], v1[1], 0.0 );
|
|
||||||
currentManager->Vertex( v3[0], v3[1], 0.0 );
|
|
||||||
currentManager->Vertex( v2[0], v2[1], 0.0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw line caps
|
|
||||||
drawFilledSemiCircle( startPoint, lineWidth / aRadius / 2.0, aStartAngle + M_PI );
|
|
||||||
drawFilledSemiCircle( endPoint, lineWidth / aRadius / 2.0, aEndAngle );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1362,6 +1151,7 @@ void OPENGL_GAL::DrawCursor( VECTOR2D aCursorPosition )
|
||||||
|
|
||||||
void OPENGL_GAL::DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
void OPENGL_GAL::DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
||||||
{
|
{
|
||||||
|
// TODO change to simple drawline
|
||||||
// We check, if we got a horizontal or a vertical grid line and compute the offset
|
// We check, if we got a horizontal or a vertical grid line and compute the offset
|
||||||
VECTOR2D perpendicularVector;
|
VECTOR2D perpendicularVector;
|
||||||
|
|
||||||
|
@ -1382,9 +1172,7 @@ void OPENGL_GAL::DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEnd
|
||||||
VECTOR2D point3 = aEndPoint + perpendicularVector;
|
VECTOR2D point3 = aEndPoint + perpendicularVector;
|
||||||
VECTOR2D point4 = aEndPoint - perpendicularVector;
|
VECTOR2D point4 = aEndPoint - perpendicularVector;
|
||||||
|
|
||||||
// Set color
|
|
||||||
currentManager->Color( gridColor.r, gridColor.g, gridColor.b, gridColor.a );
|
currentManager->Color( gridColor.r, gridColor.g, gridColor.b, gridColor.a );
|
||||||
|
|
||||||
currentManager->Shader( SHADER_NONE );
|
currentManager->Shader( SHADER_NONE );
|
||||||
|
|
||||||
// Draw the quad for the grid line
|
// Draw the quad for the grid line
|
||||||
|
|
|
@ -64,7 +64,7 @@ public:
|
||||||
* Switches method of rendering graphics.
|
* Switches method of rendering graphics.
|
||||||
* @param aGalType is a type of rendering engine that you want to use.
|
* @param aGalType is a type of rendering engine that you want to use.
|
||||||
*/
|
*/
|
||||||
void SwitchBackend( GalType aGalType, bool aUseShaders = false );
|
void SwitchBackend( GalType aGalType );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function GetGAL
|
* Function GetGAL
|
||||||
|
@ -89,8 +89,7 @@ protected:
|
||||||
///< using GAL
|
///< using GAL
|
||||||
KiGfx::WX_VIEW_CONTROLS* m_viewControls; ///< Control for VIEW (moving, zooming, etc.)
|
KiGfx::WX_VIEW_CONTROLS* m_viewControls; ///< Control for VIEW (moving, zooming, etc.)
|
||||||
GalType m_currentGal; ///< Currently used GAL
|
GalType m_currentGal; ///< Currently used GAL
|
||||||
bool m_useShaders; ///< Are shaders used? (only for OpenGL GAL)
|
wxLongLong m_timeStamp; ///< Timestamp for framelimiter
|
||||||
wxLongLong m_timeStamp;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -82,14 +82,9 @@ public:
|
||||||
* a wxCommandEvent holding EVT_GAL_REDRAW, as sent by PostPaint().
|
* a wxCommandEvent holding EVT_GAL_REDRAW, as sent by PostPaint().
|
||||||
*
|
*
|
||||||
* @param aName is the name of this window for use by wxWindow::FindWindowByName()
|
* @param aName is the name of this window for use by wxWindow::FindWindowByName()
|
||||||
*
|
|
||||||
* @param isUseShaders is a flag, that indicates, if shaders should be used
|
|
||||||
* for higher quality rendering.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener = NULL,
|
OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener = NULL,
|
||||||
wxEvtHandler* aPaintListener = NULL, bool isUseShaders = false,
|
wxEvtHandler* aPaintListener = NULL, const wxString& aName = wxT( "GLCanvas" ) );
|
||||||
const wxString& aName = wxT( "GLCanvas" ) );
|
|
||||||
|
|
||||||
virtual ~OPENGL_GAL();
|
virtual ~OPENGL_GAL();
|
||||||
|
|
||||||
|
@ -366,7 +361,6 @@ private:
|
||||||
bool isGlewInitialized; ///< Is GLEW initialized?
|
bool isGlewInitialized; ///< Is GLEW initialized?
|
||||||
bool isFramebufferInitialized; ///< Are the framebuffers initialized?
|
bool isFramebufferInitialized; ///< Are the framebuffers initialized?
|
||||||
bool isShaderInitialized; ///< Was the shader initialized?
|
bool isShaderInitialized; ///< Was the shader initialized?
|
||||||
bool isUseShader; ///< Should the shaders be used?
|
|
||||||
bool isGrouping; ///< Was a group started?
|
bool isGrouping; ///< Was a group started?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -85,9 +85,7 @@ static EDA_HOTKEY HkSwitchHighContrastMode( wxT("Switch Highcontrast mode"),
|
||||||
static EDA_HOTKEY HkCanvasDefault( wxT( "Switch to default canvas" ),
|
static EDA_HOTKEY HkCanvasDefault( wxT( "Switch to default canvas" ),
|
||||||
HK_CANVAS_DEFAULT, GR_KB_ALT + WXK_F9 );
|
HK_CANVAS_DEFAULT, GR_KB_ALT + WXK_F9 );
|
||||||
static EDA_HOTKEY HkCanvasOpenGL( wxT( "Switch to OpenGL canvas" ),
|
static EDA_HOTKEY HkCanvasOpenGL( wxT( "Switch to OpenGL canvas" ),
|
||||||
HK_CANVAS_OPENGL, GR_KB_ALT + WXK_F10 );
|
HK_CANVAS_OPENGL, GR_KB_ALT + WXK_F11 );
|
||||||
static EDA_HOTKEY HkCanvasOpenGLShaders( wxT( "Switch to OpenGL canvas with shaders" ),
|
|
||||||
HK_CANVAS_OPENGL_SHADERS, GR_KB_ALT + WXK_F11 );
|
|
||||||
static EDA_HOTKEY HkCanvasCairo( wxT( "Switch to Cairo canvas" ),
|
static EDA_HOTKEY HkCanvasCairo( wxT( "Switch to Cairo canvas" ),
|
||||||
HK_CANVAS_CAIRO, GR_KB_ALT + WXK_F12 );
|
HK_CANVAS_CAIRO, GR_KB_ALT + WXK_F12 );
|
||||||
|
|
||||||
|
@ -237,7 +235,7 @@ EDA_HOTKEY* board_edit_Hotkey_List[] =
|
||||||
&HkRecordMacros6, &HkCallMacros6, &HkRecordMacros7, &HkCallMacros7,
|
&HkRecordMacros6, &HkCallMacros6, &HkRecordMacros7, &HkCallMacros7,
|
||||||
&HkRecordMacros8, &HkCallMacros8, &HkRecordMacros9, &HkCallMacros9,
|
&HkRecordMacros8, &HkCallMacros8, &HkRecordMacros9, &HkCallMacros9,
|
||||||
&HkSwitchHighContrastMode,
|
&HkSwitchHighContrastMode,
|
||||||
&HkCanvasDefault, &HkCanvasCairo, &HkCanvasOpenGL, &HkCanvasOpenGLShaders,
|
&HkCanvasDefault, &HkCanvasCairo, &HkCanvasOpenGL,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,6 @@ enum hotkey_id_commnand {
|
||||||
HK_SWITCH_HIGHCONTRAST_MODE,
|
HK_SWITCH_HIGHCONTRAST_MODE,
|
||||||
HK_CANVAS_DEFAULT,
|
HK_CANVAS_DEFAULT,
|
||||||
HK_CANVAS_OPENGL,
|
HK_CANVAS_OPENGL,
|
||||||
HK_CANVAS_OPENGL_SHADERS,
|
|
||||||
HK_CANVAS_CAIRO,
|
HK_CANVAS_CAIRO,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -364,13 +364,6 @@ void PCB_EDIT_FRAME::ReCreateMenuBar()
|
||||||
AddMenuItem( viewMenu, ID_MENU_CANVAS_OPENGL,
|
AddMenuItem( viewMenu, ID_MENU_CANVAS_OPENGL,
|
||||||
text, _( "Switch the canvas implementation to OpenGL" ),
|
text, _( "Switch the canvas implementation to OpenGL" ),
|
||||||
KiBitmap( tools_xpm ) );
|
KiBitmap( tools_xpm ) );
|
||||||
|
|
||||||
text = AddHotkeyName( _( "&Switch canvas to OpenGL (shaders)" ), g_Pcbnew_Editor_Hokeys_Descr,
|
|
||||||
HK_CANVAS_OPENGL_SHADERS, IS_ACCELERATOR );
|
|
||||||
|
|
||||||
AddMenuItem( viewMenu, ID_MENU_CANVAS_OPENGL_SHADERS,
|
|
||||||
text, _( "Switch the canvas implementation to OpenGL that uses shaders" ),
|
|
||||||
KiBitmap( tools_xpm ) );
|
|
||||||
|
|
||||||
text = AddHotkeyName( _( "&Switch canvas to Cairo" ), g_Pcbnew_Editor_Hokeys_Descr,
|
text = AddHotkeyName( _( "&Switch canvas to Cairo" ), g_Pcbnew_Editor_Hokeys_Descr,
|
||||||
HK_CANVAS_CAIRO, IS_ACCELERATOR );
|
HK_CANVAS_CAIRO, IS_ACCELERATOR );
|
||||||
|
|
|
@ -163,7 +163,6 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
|
||||||
EVT_MENU( ID_MENU_CANVAS_DEFAULT, PCB_EDIT_FRAME::SwitchCanvas )
|
EVT_MENU( ID_MENU_CANVAS_DEFAULT, PCB_EDIT_FRAME::SwitchCanvas )
|
||||||
EVT_MENU( ID_MENU_CANVAS_CAIRO, PCB_EDIT_FRAME::SwitchCanvas )
|
EVT_MENU( ID_MENU_CANVAS_CAIRO, PCB_EDIT_FRAME::SwitchCanvas )
|
||||||
EVT_MENU( ID_MENU_CANVAS_OPENGL, PCB_EDIT_FRAME::SwitchCanvas )
|
EVT_MENU( ID_MENU_CANVAS_OPENGL, PCB_EDIT_FRAME::SwitchCanvas )
|
||||||
EVT_MENU( ID_MENU_CANVAS_OPENGL_SHADERS, PCB_EDIT_FRAME::SwitchCanvas )
|
|
||||||
|
|
||||||
// Menu Get Design Rules Editor
|
// Menu Get Design Rules Editor
|
||||||
EVT_MENU( ID_MENU_PCB_SHOW_DESIGN_RULES_DIALOG, PCB_EDIT_FRAME::ShowDesignRulesEditor )
|
EVT_MENU( ID_MENU_PCB_SHOW_DESIGN_RULES_DIALOG, PCB_EDIT_FRAME::ShowDesignRulesEditor )
|
||||||
|
@ -611,12 +610,7 @@ void PCB_EDIT_FRAME::SwitchCanvas( wxCommandEvent& aEvent )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ID_MENU_CANVAS_OPENGL:
|
case ID_MENU_CANVAS_OPENGL:
|
||||||
m_galCanvas->SwitchBackend( EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL, false );
|
m_galCanvas->SwitchBackend( EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL );
|
||||||
UseGalCanvas( true );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ID_MENU_CANVAS_OPENGL_SHADERS:
|
|
||||||
m_galCanvas->SwitchBackend( EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL, true );
|
|
||||||
UseGalCanvas( true );
|
UseGalCanvas( true );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,6 @@ enum pcbnew_ids
|
||||||
ID_MENU_PCB_SHOW_3D_FRAME,
|
ID_MENU_PCB_SHOW_3D_FRAME,
|
||||||
ID_MENU_CANVAS_DEFAULT,
|
ID_MENU_CANVAS_DEFAULT,
|
||||||
ID_MENU_CANVAS_OPENGL,
|
ID_MENU_CANVAS_OPENGL,
|
||||||
ID_MENU_CANVAS_OPENGL_SHADERS,
|
|
||||||
ID_MENU_CANVAS_CAIRO,
|
ID_MENU_CANVAS_CAIRO,
|
||||||
ID_PCB_USER_GRID_SETUP,
|
ID_PCB_USER_GRID_SETUP,
|
||||||
ID_PCB_GEN_BOM_FILE_FROM_BOARD,
|
ID_PCB_GEN_BOM_FILE_FROM_BOARD,
|
||||||
|
|
Loading…
Reference in New Issue