summaryrefslogtreecommitdiff
path: root/common/gal
diff options
context:
space:
mode:
authorsaurabhb172020-02-26 16:11:59 +0530
committerGitHub2020-02-26 16:11:59 +0530
commite255d0622297488c1c52755be670733418c994cf (patch)
tree1392c90227aeea231c1d86371131e04c40382918 /common/gal
parent0db48f6533517ecebfd9f0693f89deca28408b76 (diff)
parentc38609295ad4b617aef472b9c575aee18710a50f (diff)
downloadKiCad-eSim-e255d0622297488c1c52755be670733418c994cf.tar.gz
KiCad-eSim-e255d0622297488c1c52755be670733418c994cf.tar.bz2
KiCad-eSim-e255d0622297488c1c52755be670733418c994cf.zip
Merge pull request #1 from saurabhb17/develop
Secondary files
Diffstat (limited to 'common/gal')
-rw-r--r--common/gal/cairo/cairo_compositor.cpp159
-rw-r--r--common/gal/cairo/cairo_gal.cpp1074
-rw-r--r--common/gal/color4d.cpp179
-rw-r--r--common/gal/graphics_abstraction_layer.cpp240
-rw-r--r--common/gal/opengl/cached_container.cpp547
-rw-r--r--common/gal/opengl/gpu_manager.cpp302
-rw-r--r--common/gal/opengl/noncached_container.cpp89
-rw-r--r--common/gal/opengl/opengl_compositor.cpp291
-rw-r--r--common/gal/opengl/opengl_gal.cpp1204
-rw-r--r--common/gal/opengl/shader.cpp272
-rw-r--r--common/gal/opengl/shader.frag75
-rw-r--r--common/gal/opengl/shader.vert95
-rw-r--r--common/gal/opengl/vertex_container.cpp60
-rw-r--r--common/gal/opengl/vertex_item.cpp53
-rw-r--r--common/gal/opengl/vertex_manager.cpp234
-rw-r--r--common/gal/stroke_font.cpp403
16 files changed, 5277 insertions, 0 deletions
diff --git a/common/gal/cairo/cairo_compositor.cpp b/common/gal/cairo/cairo_compositor.cpp
new file mode 100644
index 0000000..f81cb5a
--- /dev/null
+++ b/common/gal/cairo/cairo_compositor.cpp
@@ -0,0 +1,159 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http:O//www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file cairo_compositor.cpp
+ * @brief Class that handles multitarget rendering (ie. to different textures/surfaces) and
+ * later compositing into a single image (Cairo flavour).
+ */
+
+#include <gal/cairo/cairo_compositor.h>
+#include <wx/log.h>
+
+using namespace KIGFX;
+
+CAIRO_COMPOSITOR::CAIRO_COMPOSITOR( cairo_t** aMainContext ) :
+ m_current( 0 ), m_currentContext( aMainContext ), m_mainContext( *aMainContext )
+{
+ // Do not have uninitialized members:
+ cairo_matrix_init_identity( &m_matrix );
+ m_stride = 0;
+ m_bufferSize = 0;
+}
+
+
+CAIRO_COMPOSITOR::~CAIRO_COMPOSITOR()
+{
+ clean();
+}
+
+
+void CAIRO_COMPOSITOR::Initialize()
+{
+ // Nothing has to be done
+}
+
+
+void CAIRO_COMPOSITOR::Resize( unsigned int aWidth, unsigned int aHeight )
+{
+ clean();
+
+ assert( aWidth > 0 );
+ assert( aHeight > 0 );
+
+ m_width = aWidth;
+ m_height = aHeight;
+
+ m_stride = cairo_format_stride_for_width( CAIRO_FORMAT_ARGB32, m_width );
+ m_bufferSize = m_stride * m_height;
+}
+
+
+unsigned int CAIRO_COMPOSITOR::CreateBuffer()
+{
+ // Pixel storage
+ BitmapPtr bitmap( new unsigned int[m_bufferSize] );
+
+ memset( bitmap.get(), 0x00, m_bufferSize * sizeof(int) );
+
+ // Create the Cairo surface
+ cairo_surface_t* surface = cairo_image_surface_create_for_data(
+ (unsigned char*) bitmap.get(),
+ CAIRO_FORMAT_ARGB32, m_width,
+ m_height, m_stride );
+ cairo_t* context = cairo_create( surface );
+#ifdef __WXDEBUG__
+ cairo_status_t status = cairo_status( context );
+ wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
+#endif /* __WXDEBUG__ */
+
+ // Set default settings for the buffer
+ cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );
+ cairo_set_line_join( context, CAIRO_LINE_JOIN_ROUND );
+ cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );
+
+ // Use the same transformation matrix as the main context
+ cairo_get_matrix( m_mainContext, &m_matrix );
+ cairo_set_matrix( context, &m_matrix );
+
+ // Store the new buffer
+ CAIRO_BUFFER buffer = { context, surface, bitmap };
+ m_buffers.push_back( buffer );
+
+ return usedBuffers();
+}
+
+
+void CAIRO_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
+{
+ wxASSERT_MSG( aBufferHandle <= usedBuffers(), wxT( "Tried to use a not existing buffer" ) );
+
+ // Get currently used transformation matrix, so it can be applied to the new buffer
+ cairo_get_matrix( *m_currentContext, &m_matrix );
+
+ m_current = aBufferHandle - 1;
+ *m_currentContext = m_buffers[m_current].context;
+
+ // Apply the current transformation matrix
+ cairo_set_matrix( *m_currentContext, &m_matrix );
+}
+
+
+void CAIRO_COMPOSITOR::ClearBuffer()
+{
+ // Clear the pixel storage
+ memset( m_buffers[m_current].bitmap.get(), 0x00, m_bufferSize * sizeof(int) );
+}
+
+
+void CAIRO_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle )
+{
+ wxASSERT_MSG( aBufferHandle <= usedBuffers(), wxT( "Tried to use a not existing buffer" ) );
+
+ // Reset the transformation matrix, so it is possible to composite images using
+ // screen coordinates instead of world coordinates
+ cairo_get_matrix( m_mainContext, &m_matrix );
+ cairo_identity_matrix( m_mainContext );
+
+ // Draw the selected buffer contents
+ cairo_set_source_surface( m_mainContext, m_buffers[aBufferHandle - 1].surface, 0.0, 0.0 );
+ cairo_paint( m_mainContext );
+
+ // Restore the transformation matrix
+ cairo_set_matrix( m_mainContext, &m_matrix );
+}
+
+
+void CAIRO_COMPOSITOR::clean()
+{
+ CAIRO_BUFFERS::const_iterator it;
+
+ for( it = m_buffers.begin(); it != m_buffers.end(); ++it )
+ {
+ cairo_destroy( it->context );
+ cairo_surface_destroy( it->surface );
+ }
+
+ m_buffers.clear();
+}
diff --git a/common/gal/cairo/cairo_gal.cpp b/common/gal/cairo/cairo_gal.cpp
new file mode 100644
index 0000000..b7ead4c
--- /dev/null
+++ b/common/gal/cairo/cairo_gal.cpp
@@ -0,0 +1,1074 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ *
+ * CAIRO_GAL - Graphics Abstraction Layer for Cairo
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <wx/image.h>
+#include <wx/log.h>
+
+#include <gal/cairo/cairo_gal.h>
+#include <gal/cairo/cairo_compositor.h>
+#include <gal/definitions.h>
+
+#include <limits>
+
+using namespace KIGFX;
+
+
+const float CAIRO_GAL::LAYER_ALPHA = 0.8;
+
+
+CAIRO_GAL::CAIRO_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
+ wxEvtHandler* aPaintListener, const wxString& aName ) :
+ wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName )
+{
+ parentWindow = aParent;
+ mouseListener = aMouseListener;
+ paintListener = aPaintListener;
+
+ // Initialize the flags
+ isGrouping = false;
+ isInitialized = false;
+ isDeleteSavedPixels = false;
+ validCompositor = false;
+ groupCounter = 0;
+
+ // Connecting the event handlers
+ Connect( wxEVT_PAINT, wxPaintEventHandler( CAIRO_GAL::onPaint ) );
+
+ // Mouse events are skipped to the parent
+ Connect( wxEVT_MOTION, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+#if defined _WIN32 || defined _WIN64
+ Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
+#endif
+
+ SetSize( aParent->GetSize() );
+ screenSize = VECTOR2I( aParent->GetSize() );
+
+ cursorPixels = NULL;
+ cursorPixelsSaved = NULL;
+ initCursor();
+
+ // Grid color settings are different in Cairo and OpenGL
+ SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) );
+
+ // Allocate memory for pixel storage
+ allocateBitmaps();
+
+ initSurface();
+}
+
+
+CAIRO_GAL::~CAIRO_GAL()
+{
+ deinitSurface();
+ deleteBitmaps();
+
+ delete cursorPixels;
+ delete cursorPixelsSaved;
+
+ ClearCache();
+}
+
+
+void CAIRO_GAL::BeginDrawing()
+{
+ initSurface();
+
+ if( !validCompositor )
+ setCompositor();
+
+ compositor->SetMainContext( context );
+ compositor->SetBuffer( mainBuffer );
+
+ // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
+ cairo_push_group( currentContext );
+}
+
+
+void CAIRO_GAL::EndDrawing()
+{
+ // Force remaining objects to be drawn
+ Flush();
+
+ // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
+ cairo_pop_group_to_source( currentContext );
+ cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
+
+ // Merge buffers on the screen
+ compositor->DrawBuffer( mainBuffer );
+ compositor->DrawBuffer( overlayBuffer );
+
+ // This code was taken from the wxCairo example - it's not the most efficient one
+ // Here is a good place for optimizations
+
+ // Now translate the raw context data from the format stored
+ // by cairo into a format understood by wxImage.
+ unsigned char* wxOutputPtr = wxOutput;
+
+ for( size_t count = 0; count < bufferSize; count++ )
+ {
+ unsigned int value = bitmapBuffer[count];
+ *wxOutputPtr++ = ( value >> 16 ) & 0xff; // Red pixel
+ *wxOutputPtr++ = ( value >> 8 ) & 0xff; // Green pixel
+ *wxOutputPtr++ = value & 0xff; // Blue pixel
+ }
+
+ wxImage img( screenSize.x, screenSize.y, (unsigned char*) wxOutput, true );
+ wxBitmap bmp( img );
+ wxClientDC client_dc( this );
+ wxBufferedDC dc;
+ dc.Init( &client_dc, bmp );
+
+ // Now it is the time to blit the mouse cursor
+ blitCursor( dc );
+
+ deinitSurface();
+}
+
+
+void CAIRO_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
+ cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
+ double aWidth )
+{
+ if( isFillEnabled )
+ {
+ // Filled tracks mode
+ SetLineWidth( aWidth );
+
+ cairo_move_to( currentContext, (double) aStartPoint.x, (double) aStartPoint.y );
+ cairo_line_to( currentContext, (double) aEndPoint.x, (double) aEndPoint.y );
+ }
+ else
+ {
+ // Outline mode for tracks
+ VECTOR2D startEndVector = aEndPoint - aStartPoint;
+ double lineAngle = atan2( startEndVector.y, startEndVector.x );
+ double lineLength = startEndVector.EuclideanNorm();
+
+ cairo_save( currentContext );
+
+ cairo_translate( currentContext, aStartPoint.x, aStartPoint.y );
+ cairo_rotate( currentContext, lineAngle );
+
+ cairo_arc( currentContext, 0.0, 0.0, aWidth / 2.0, M_PI / 2.0, 3.0 * M_PI / 2.0 );
+ cairo_arc( currentContext, lineLength, 0.0, aWidth / 2.0, -M_PI / 2.0, M_PI / 2.0 );
+
+ cairo_move_to( currentContext, 0.0, aWidth / 2.0 );
+ cairo_line_to( currentContext, lineLength, aWidth / 2.0 );
+
+ cairo_move_to( currentContext, 0.0, -aWidth / 2.0 );
+ cairo_line_to( currentContext, lineLength, -aWidth / 2.0 );
+
+ cairo_restore( currentContext );
+ }
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
+{
+ // A circle is drawn using an arc
+ cairo_new_sub_path( currentContext );
+ cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, 0.0, 2 * M_PI );
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle,
+ double aEndAngle )
+{
+ SWAP( aStartAngle, >, aEndAngle );
+
+ cairo_new_sub_path( currentContext );
+ cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, aStartAngle, aEndAngle );
+
+ if( isFillEnabled )
+ {
+ VECTOR2D startPoint( cos( aStartAngle ) * aRadius + aCenterPoint.x,
+ sin( aStartAngle ) * aRadius + aCenterPoint.y );
+ VECTOR2D endPoint( cos( aEndAngle ) * aRadius + aCenterPoint.x,
+ sin( aEndAngle ) * aRadius + aCenterPoint.y );
+
+ cairo_move_to( currentContext, aCenterPoint.x, aCenterPoint.y );
+ cairo_line_to( currentContext, startPoint.x, startPoint.y );
+ cairo_line_to( currentContext, endPoint.x, endPoint.y );
+ cairo_close_path( currentContext );
+ }
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ // Calculate the diagonal points
+ VECTOR2D diagonalPointA( aEndPoint.x, aStartPoint.y );
+ VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );
+
+ // The path is composed from 4 segments
+ cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
+ cairo_line_to( currentContext, diagonalPointA.x, diagonalPointA.y );
+ cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
+ cairo_line_to( currentContext, diagonalPointB.x, diagonalPointB.y );
+ cairo_close_path( currentContext );
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
+ const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint )
+{
+ cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
+ cairo_curve_to( currentContext, aControlPointA.x, aControlPointA.y, aControlPointB.x,
+ aControlPointB.y, aEndPoint.x, aEndPoint.y );
+ cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
+{
+ screenSize = VECTOR2I( aWidth, aHeight );
+
+ // Recreate the bitmaps
+ deleteBitmaps();
+ allocateBitmaps();
+
+ if( validCompositor )
+ compositor->Resize( aWidth, aHeight );
+
+ validCompositor = false;
+
+ SetSize( wxSize( aWidth, aHeight ) );
+}
+
+
+bool CAIRO_GAL::Show( bool aShow )
+{
+ bool s = wxWindow::Show( aShow );
+
+ if( aShow )
+ wxWindow::Raise();
+
+ return s;
+}
+
+
+void CAIRO_GAL::Flush()
+{
+ storePath();
+}
+
+
+void CAIRO_GAL::ClearScreen( const COLOR4D& aColor )
+{
+ backgroundColor = aColor;
+ cairo_set_source_rgb( currentContext, aColor.r, aColor.g, aColor.b );
+ cairo_rectangle( currentContext, 0.0, 0.0, screenSize.x, screenSize.y );
+ cairo_fill( currentContext );
+}
+
+
+void CAIRO_GAL::SetIsFill( bool aIsFillEnabled )
+{
+ storePath();
+ isFillEnabled = aIsFillEnabled;
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SET_FILL;
+ groupElement.boolArgument = aIsFillEnabled;
+ currentGroup->push_back( groupElement );
+ }
+}
+
+
+void CAIRO_GAL::SetIsStroke( bool aIsStrokeEnabled )
+{
+ storePath();
+ isStrokeEnabled = aIsStrokeEnabled;
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SET_STROKE;
+ groupElement.boolArgument = aIsStrokeEnabled;
+ currentGroup->push_back( groupElement );
+ }
+}
+
+
+void CAIRO_GAL::SetStrokeColor( const COLOR4D& aColor )
+{
+ storePath();
+ strokeColor = aColor;
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SET_STROKECOLOR;
+ groupElement.arguments[0] = strokeColor.r;
+ groupElement.arguments[1] = strokeColor.g;
+ groupElement.arguments[2] = strokeColor.b;
+ groupElement.arguments[3] = strokeColor.a;
+ currentGroup->push_back( groupElement );
+ }
+}
+
+
+void CAIRO_GAL::SetFillColor( const COLOR4D& aColor )
+{
+ storePath();
+ fillColor = aColor;
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SET_FILLCOLOR;
+ groupElement.arguments[0] = fillColor.r;
+ groupElement.arguments[1] = fillColor.g;
+ groupElement.arguments[2] = fillColor.b;
+ groupElement.arguments[3] = fillColor.a;
+ currentGroup->push_back( groupElement );
+ }
+}
+
+
+void CAIRO_GAL::SetLineWidth( double aLineWidth )
+{
+ storePath();
+
+ lineWidth = aLineWidth;
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SET_LINE_WIDTH;
+ groupElement.arguments[0] = aLineWidth;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ // Make lines appear at least 1 pixel wide, no matter of zoom
+ double x = 1.0, y = 1.0;
+ cairo_device_to_user_distance( currentContext, &x, &y );
+ double minWidth = std::min( fabs( x ), fabs( y ) );
+ cairo_set_line_width( currentContext, std::max( aLineWidth, minWidth ) );
+ }
+}
+
+
+void CAIRO_GAL::SetLayerDepth( double aLayerDepth )
+{
+ super::SetLayerDepth( aLayerDepth );
+
+ if( isInitialized )
+ {
+ storePath();
+
+ cairo_pop_group_to_source( currentContext );
+ cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
+
+ cairo_push_group( currentContext );
+ }
+}
+
+
+void CAIRO_GAL::Transform( const MATRIX3x3D& aTransformation )
+{
+ cairo_matrix_t cairoTransformation;
+
+ cairo_matrix_init( &cairoTransformation,
+ aTransformation.m_data[0][0],
+ aTransformation.m_data[1][0],
+ aTransformation.m_data[0][1],
+ aTransformation.m_data[1][1],
+ aTransformation.m_data[0][2],
+ aTransformation.m_data[1][2] );
+
+ cairo_transform( currentContext, &cairoTransformation );
+}
+
+
+void CAIRO_GAL::Rotate( double aAngle )
+{
+ storePath();
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_ROTATE;
+ groupElement.arguments[0] = aAngle;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ cairo_rotate( currentContext, aAngle );
+ }
+}
+
+
+void CAIRO_GAL::Translate( const VECTOR2D& aTranslation )
+{
+ storePath();
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_TRANSLATE;
+ groupElement.arguments[0] = aTranslation.x;
+ groupElement.arguments[1] = aTranslation.y;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ cairo_translate( currentContext, aTranslation.x, aTranslation.y );
+ }
+}
+
+
+void CAIRO_GAL::Scale( const VECTOR2D& aScale )
+{
+ storePath();
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SCALE;
+ groupElement.arguments[0] = aScale.x;
+ groupElement.arguments[1] = aScale.y;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ cairo_scale( currentContext, aScale.x, aScale.y );
+ }
+}
+
+
+void CAIRO_GAL::Save()
+{
+ storePath();
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_SAVE;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ cairo_save( currentContext );
+ }
+}
+
+
+void CAIRO_GAL::Restore()
+{
+ storePath();
+
+ if( isGrouping )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.command = CMD_RESTORE;
+ currentGroup->push_back( groupElement );
+ }
+ else
+ {
+ cairo_restore( currentContext );
+ }
+}
+
+
+int CAIRO_GAL::BeginGroup()
+{
+ initSurface();
+
+ // If the grouping is started: the actual path is stored in the group, when
+ // a attribute was changed or when grouping stops with the end group method.
+ storePath();
+
+ GROUP group;
+ int groupNumber = getNewGroupNumber();
+ groups.insert( std::make_pair( groupNumber, group ) );
+ currentGroup = &groups[groupNumber];
+ isGrouping = true;
+
+ return groupNumber;
+}
+
+
+void CAIRO_GAL::EndGroup()
+{
+ storePath();
+ isGrouping = false;
+
+ deinitSurface();
+}
+
+
+void CAIRO_GAL::DrawGroup( int aGroupNumber )
+{
+ // This method implements a small Virtual Machine - all stored commands
+ // are executed; nested calling is also possible
+
+ storePath();
+
+ for( GROUP::iterator it = groups[aGroupNumber].begin();
+ it != groups[aGroupNumber].end(); ++it )
+ {
+ switch( it->command )
+ {
+ case CMD_SET_FILL:
+ isFillEnabled = it->boolArgument;
+ break;
+
+ case CMD_SET_STROKE:
+ isStrokeEnabled = it->boolArgument;
+ break;
+
+ case CMD_SET_FILLCOLOR:
+ fillColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
+ it->arguments[3] );
+ break;
+
+ case CMD_SET_STROKECOLOR:
+ strokeColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
+ it->arguments[3] );
+ break;
+
+ case CMD_SET_LINE_WIDTH:
+ {
+ // Make lines appear at least 1 pixel wide, no matter of zoom
+ double x = 1.0, y = 1.0;
+ cairo_device_to_user_distance( currentContext, &x, &y );
+ double minWidth = std::min( fabs( x ), fabs( y ) );
+ cairo_set_line_width( currentContext, std::max( it->arguments[0], minWidth ) );
+ }
+ break;
+
+
+ case CMD_STROKE_PATH:
+ cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g, strokeColor.b );
+ cairo_append_path( currentContext, it->cairoPath );
+ cairo_stroke( currentContext );
+ break;
+
+ case CMD_FILL_PATH:
+ cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
+ cairo_append_path( currentContext, it->cairoPath );
+ cairo_fill( currentContext );
+ break;
+
+ case CMD_TRANSFORM:
+ cairo_matrix_t matrix;
+ cairo_matrix_init( &matrix, it->arguments[0], it->arguments[1], it->arguments[2],
+ it->arguments[3], it->arguments[4], it->arguments[5] );
+ cairo_transform( currentContext, &matrix );
+ break;
+
+ case CMD_ROTATE:
+ cairo_rotate( currentContext, it->arguments[0] );
+ break;
+
+ case CMD_TRANSLATE:
+ cairo_translate( currentContext, it->arguments[0], it->arguments[1] );
+ break;
+
+ case CMD_SCALE:
+ cairo_scale( currentContext, it->arguments[0], it->arguments[1] );
+ break;
+
+ case CMD_SAVE:
+ cairo_save( currentContext );
+ break;
+
+ case CMD_RESTORE:
+ cairo_restore( currentContext );
+ break;
+
+ case CMD_CALL_GROUP:
+ DrawGroup( it->intArgument );
+ break;
+ }
+ }
+}
+
+
+void CAIRO_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
+{
+ storePath();
+
+ for( GROUP::iterator it = groups[aGroupNumber].begin();
+ it != groups[aGroupNumber].end(); ++it )
+ {
+ if( it->command == CMD_SET_FILLCOLOR || it->command == CMD_SET_STROKECOLOR )
+ {
+ it->arguments[0] = aNewColor.r;
+ it->arguments[1] = aNewColor.g;
+ it->arguments[2] = aNewColor.b;
+ it->arguments[3] = aNewColor.a;
+ }
+ }
+}
+
+
+void CAIRO_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
+{
+ // Cairo does not have any possibilities to change the depth coordinate of stored items,
+ // it depends only on the order of drawing
+}
+
+
+void CAIRO_GAL::DeleteGroup( int aGroupNumber )
+{
+ storePath();
+
+ // Delete the Cairo paths
+ std::deque<GROUP_ELEMENT>::iterator it, end;
+
+ for( it = groups[aGroupNumber].begin(), end = groups[aGroupNumber].end(); it != end; ++it )
+ {
+ if( it->command == CMD_FILL_PATH || it->command == CMD_STROKE_PATH )
+ {
+ cairo_path_destroy( it->cairoPath );
+ }
+ }
+
+ // Delete the group
+ groups.erase( aGroupNumber );
+}
+
+
+void CAIRO_GAL::ClearCache()
+{
+ for( int i = groups.size() - 1; i >= 0; --i )
+ {
+ DeleteGroup( i );
+ }
+}
+
+
+void CAIRO_GAL::SaveScreen()
+{
+ // Copy the current bitmap to the backup buffer
+ int offset = 0;
+
+ for( int j = 0; j < screenSize.y; j++ )
+ {
+ for( int i = 0; i < stride; i++ )
+ {
+ bitmapBufferBackup[offset + i] = bitmapBuffer[offset + i];
+ offset += stride;
+ }
+ }
+}
+
+
+void CAIRO_GAL::RestoreScreen()
+{
+ int offset = 0;
+
+ for( int j = 0; j < screenSize.y; j++ )
+ {
+ for( int i = 0; i < stride; i++ )
+ {
+ bitmapBuffer[offset + i] = bitmapBufferBackup[offset + i];
+ offset += stride;
+ }
+ }
+}
+
+
+void CAIRO_GAL::SetTarget( RENDER_TARGET aTarget )
+{
+ // If the compositor is not set, that means that there is a recaching process going on
+ // and we do not need the compositor now
+ if( !validCompositor )
+ return;
+
+ // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
+ if( isInitialized )
+ {
+ storePath();
+
+ cairo_pop_group_to_source( currentContext );
+ cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
+ }
+
+ switch( aTarget )
+ {
+ default:
+ case TARGET_CACHED:
+ case TARGET_NONCACHED:
+ compositor->SetBuffer( mainBuffer );
+ break;
+
+ case TARGET_OVERLAY:
+ compositor->SetBuffer( overlayBuffer );
+ break;
+ }
+
+ if( isInitialized )
+ cairo_push_group( currentContext );
+
+ currentTarget = aTarget;
+}
+
+
+RENDER_TARGET CAIRO_GAL::GetTarget() const
+{
+ return currentTarget;
+}
+
+
+void CAIRO_GAL::ClearTarget( RENDER_TARGET aTarget )
+{
+ // Save the current state
+ unsigned int currentBuffer = compositor->GetBuffer();
+
+ switch( aTarget )
+ {
+ // Cached and noncached items are rendered to the same buffer
+ default:
+ case TARGET_CACHED:
+ case TARGET_NONCACHED:
+ compositor->SetBuffer( mainBuffer );
+ break;
+
+ case TARGET_OVERLAY:
+ compositor->SetBuffer( overlayBuffer );
+ break;
+ }
+
+ compositor->ClearBuffer();
+
+ // Restore the previous state
+ compositor->SetBuffer( currentBuffer );
+}
+
+
+void CAIRO_GAL::SetCursorSize( unsigned int aCursorSize )
+{
+ GAL::SetCursorSize( aCursorSize );
+ initCursor();
+}
+
+
+void CAIRO_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
+{
+ // Now we should only store the position of the mouse cursor
+ // The real drawing routines are in blitCursor()
+ cursorPosition = VECTOR2D( aCursorPosition.x - cursorSize / 2,
+ aCursorPosition.y - cursorSize / 2 );
+}
+
+
+void CAIRO_GAL::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
+ cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
+ cairo_set_source_rgb( currentContext, gridColor.r, gridColor.g, gridColor.b );
+ cairo_stroke( currentContext );
+}
+
+
+void CAIRO_GAL::storePath()
+{
+ if( isElementAdded )
+ {
+ isElementAdded = false;
+
+ if( !isGrouping )
+ {
+ if( isFillEnabled )
+ {
+ cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
+ cairo_fill_preserve( currentContext );
+ }
+
+ if( isStrokeEnabled )
+ {
+ cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g,
+ strokeColor.b );
+ cairo_stroke_preserve( currentContext );
+ }
+ }
+ else
+ {
+ // Copy the actual path, append it to the global path list
+ // then check, if the path needs to be stroked/filled and
+ // add this command to the group list;
+ if( isStrokeEnabled )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.cairoPath = cairo_copy_path( currentContext );
+ groupElement.command = CMD_STROKE_PATH;
+ currentGroup->push_back( groupElement );
+ }
+
+ if( isFillEnabled )
+ {
+ GROUP_ELEMENT groupElement;
+ groupElement.cairoPath = cairo_copy_path( currentContext );
+ groupElement.command = CMD_FILL_PATH;
+ currentGroup->push_back( groupElement );
+ }
+ }
+
+ cairo_new_path( currentContext );
+ }
+}
+
+
+void CAIRO_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
+{
+ PostPaint();
+}
+
+
+void CAIRO_GAL::skipMouseEvent( wxMouseEvent& aEvent )
+{
+ // Post the mouse event to the event listener registered in constructor, if any
+ if( mouseListener )
+ wxPostEvent( mouseListener, aEvent );
+}
+
+
+void CAIRO_GAL::initCursor()
+{
+ if( cursorPixels )
+ delete cursorPixels;
+
+ if( cursorPixelsSaved )
+ delete cursorPixelsSaved;
+
+ cursorPixels = new wxBitmap( cursorSize, cursorSize );
+ cursorPixelsSaved = new wxBitmap( cursorSize, cursorSize );
+
+ wxMemoryDC cursorShape( *cursorPixels );
+
+ cursorShape.SetBackground( *wxTRANSPARENT_BRUSH );
+ wxColour color( cursorColor.r * cursorColor.a * 255, cursorColor.g * cursorColor.a * 255,
+ cursorColor.b * cursorColor.a * 255, 255 );
+ wxPen pen = wxPen( color );
+ cursorShape.SetPen( pen );
+ cursorShape.Clear();
+
+ cursorShape.DrawLine( 0, cursorSize / 2, cursorSize, cursorSize / 2 );
+ cursorShape.DrawLine( cursorSize / 2, 0, cursorSize / 2, cursorSize );
+}
+
+
+void CAIRO_GAL::blitCursor( wxBufferedDC& clientDC )
+{
+ if( !isCursorEnabled )
+ return;
+
+ wxMemoryDC cursorSave( *cursorPixelsSaved );
+ wxMemoryDC cursorShape( *cursorPixels );
+
+ if( !isDeleteSavedPixels )
+ {
+ // Restore pixels that were overpainted by the previous cursor
+ clientDC.Blit( savedCursorPosition.x, savedCursorPosition.y,
+ cursorSize, cursorSize, &cursorSave, 0, 0 );
+ }
+ else
+ {
+ isDeleteSavedPixels = false;
+ }
+
+ // Store pixels that are going to be overpainted
+ VECTOR2D cursorScreen = ToScreen( cursorPosition ) - cursorSize / 2.0f;
+ cursorSave.Blit( 0, 0, cursorSize, cursorSize, &clientDC, cursorScreen.x, cursorScreen.y );
+
+ // Draw the cursor
+ clientDC.Blit( cursorScreen.x, cursorScreen.y, cursorSize, cursorSize,
+ &cursorShape, 0, 0, wxOR );
+
+ savedCursorPosition.x = (wxCoord) cursorScreen.x;
+ savedCursorPosition.y = (wxCoord) cursorScreen.y;
+}
+
+
+void CAIRO_GAL::allocateBitmaps()
+{
+ // Create buffer, use the system independent Cairo context backend
+ stride = cairo_format_stride_for_width( GAL_FORMAT, screenSize.x );
+ bufferSize = stride * screenSize.y;
+
+ bitmapBuffer = new unsigned int[bufferSize];
+ bitmapBufferBackup = new unsigned int[bufferSize];
+ wxOutput = new unsigned char[bufferSize * 3];
+}
+
+
+void CAIRO_GAL::deleteBitmaps()
+{
+ delete[] bitmapBuffer;
+ delete[] bitmapBufferBackup;
+ delete[] wxOutput;
+}
+
+
+void CAIRO_GAL::initSurface()
+{
+ if( isInitialized )
+ return;
+
+ // Create the Cairo surface
+ surface = cairo_image_surface_create_for_data( (unsigned char*) bitmapBuffer, GAL_FORMAT,
+ screenSize.x, screenSize.y, stride );
+ context = cairo_create( surface );
+#ifdef __WXDEBUG__
+ cairo_status_t status = cairo_status( context );
+ wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
+#endif /* __WXDEBUG__ */
+ currentContext = context;
+
+ cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );
+
+ // Clear the screen
+ ClearScreen( backgroundColor );
+
+ // Compute the world <-> screen transformations
+ ComputeWorldScreenMatrix();
+
+ cairo_matrix_init( &cairoWorldScreenMatrix, worldScreenMatrix.m_data[0][0],
+ worldScreenMatrix.m_data[1][0], worldScreenMatrix.m_data[0][1],
+ worldScreenMatrix.m_data[1][1], worldScreenMatrix.m_data[0][2],
+ worldScreenMatrix.m_data[1][2] );
+
+ cairo_set_matrix( context, &cairoWorldScreenMatrix );
+
+ // Start drawing with a new path
+ cairo_new_path( context );
+ isElementAdded = true;
+
+ cairo_set_line_join( context, CAIRO_LINE_JOIN_ROUND );
+ cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );
+
+ lineWidth = 0;
+
+ isDeleteSavedPixels = true;
+ isInitialized = true;
+}
+
+
+void CAIRO_GAL::deinitSurface()
+{
+ if( !isInitialized )
+ return;
+
+ // Destroy Cairo objects
+ cairo_destroy( context );
+ cairo_surface_destroy( surface );
+
+ isInitialized = false;
+}
+
+
+void CAIRO_GAL::setCompositor()
+{
+ // Recreate the compositor with the new Cairo context
+ compositor.reset( new CAIRO_COMPOSITOR( &currentContext ) );
+ compositor->Resize( screenSize.x, screenSize.y );
+
+ // Prepare buffers
+ mainBuffer = compositor->CreateBuffer();
+ overlayBuffer = compositor->CreateBuffer();
+
+ validCompositor = true;
+}
+
+
+void CAIRO_GAL::drawPoly( const std::deque<VECTOR2D>& aPointList )
+{
+ // Iterate over the point list and draw the segments
+ std::deque<VECTOR2D>::const_iterator it = aPointList.begin();
+
+ cairo_move_to( currentContext, it->x, it->y );
+
+ for( ++it; it != aPointList.end(); ++it )
+ {
+ cairo_line_to( currentContext, it->x, it->y );
+ }
+
+ isElementAdded = true;
+}
+
+
+void CAIRO_GAL::drawPoly( const VECTOR2D aPointList[], int aListSize )
+{
+ // Iterate over the point list and draw the segments
+ const VECTOR2D* ptr = aPointList;
+
+ cairo_move_to( currentContext, ptr->x, ptr->y );
+
+ for( int i = 0; i < aListSize; ++i )
+ {
+ ++ptr;
+ cairo_line_to( currentContext, ptr->x, ptr->y );
+ }
+
+ isElementAdded = true;
+}
+
+
+unsigned int CAIRO_GAL::getNewGroupNumber()
+{
+ wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(),
+ wxT( "There are no free slots to store a group" ) );
+
+ while( groups.find( groupCounter ) != groups.end() )
+ {
+ groupCounter++;
+ }
+
+ return groupCounter++;
+}
diff --git a/common/gal/color4d.cpp b/common/gal/color4d.cpp
new file mode 100644
index 0000000..3b509d3
--- /dev/null
+++ b/common/gal/color4d.cpp
@@ -0,0 +1,179 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ *
+ * Color class
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <gal/color4d.h>
+
+using namespace KIGFX;
+
+COLOR4D::COLOR4D( EDA_COLOR_T aColor )
+{
+ r = g_ColorRefs[aColor].m_Red / 255.0;
+ g = g_ColorRefs[aColor].m_Green / 255.0;
+ b = g_ColorRefs[aColor].m_Blue / 255.0;
+ a = 1.0;
+}
+
+
+#ifdef WX_COMPATIBILITY
+ COLOR4D::COLOR4D( const wxColour& aColor )
+ {
+ r = aColor.Red();
+ g = aColor.Green();
+ b = aColor.Blue();
+ a = aColor.Alpha();
+ }
+#endif
+
+
+const bool COLOR4D::operator==( const COLOR4D& aColor )
+{
+ return a == aColor.a && r == aColor.r && g == aColor.g && b == aColor.b;
+}
+
+
+const bool COLOR4D::operator!=( const COLOR4D& aColor )
+{
+ return a != aColor.a || r != aColor.r || g != aColor.g || b != aColor.b;
+}
+
+
+void COLOR4D::ToHSV( double& aOutH, double& aOutS, double& aOutV ) const
+{
+ double min, max, delta;
+
+ min = r < g ? r : g;
+ min = min < b ? min : b;
+
+ max = r > g ? r : g;
+ max = max > b ? max : b;
+
+ aOutV = max; // v
+ delta = max - min;
+
+ if( max > 0.0 )
+ {
+ aOutS = ( delta / max ); // s
+ }
+ else
+ {
+ // r = g = b = 0 // s = 0, v is undefined
+ aOutS = 0.0;
+ aOutH = NAN; // its now undefined
+ return;
+ }
+
+ if( r >= max ) // > is bogus, just keeps compiler happy
+ aOutH = ( g - b ) / delta; // between yellow & magenta
+ else if( g >= max )
+ aOutH = 2.0 + ( b - r ) / delta; // between cyan & yellow
+ else
+ aOutH = 4.0 + ( r - g ) / delta; // between magenta & cyan
+
+ aOutH *= 60.0; // degrees
+
+ if( aOutH < 0.0 )
+ aOutH += 360.0;
+}
+
+
+void COLOR4D::FromHSV( double aInH, double aInS, double aInV )
+{
+ double hh, p, q, t, ff;
+ long i;
+
+ if( aInS <= 0.0 ) // < is bogus, just shuts up warnings
+ {
+ r = aInV;
+ g = aInV;
+ b = aInV;
+ return;
+ }
+
+ hh = aInH;
+
+ if( hh >= 360.0 )
+ hh = 0.0;
+
+ hh /= 60.0;
+
+ i = (long) hh;
+ ff = hh - i;
+
+ p = aInV * ( 1.0 - aInS );
+ q = aInV * ( 1.0 - ( aInS * ff ) );
+ t = aInV * ( 1.0 - ( aInS * ( 1.0 - ff ) ) );
+
+ switch( i )
+ {
+ case 0:
+ r = aInV;
+ g = t;
+ b = p;
+ break;
+
+ case 1:
+ r = q;
+ g = aInV;
+ b = p;
+ break;
+
+ case 2:
+ r = p;
+ g = aInV;
+ b = t;
+ break;
+
+ case 3:
+ r = p;
+ g = q;
+ b = aInV;
+ break;
+
+ case 4:
+ r = t;
+ g = p;
+ b = aInV;
+ break;
+
+ case 5:
+ default:
+ r = aInV;
+ g = p;
+ b = q;
+ break;
+ }
+}
+
+
+COLOR4D& COLOR4D::Saturate( double aFactor )
+{
+ double h, s, v;
+
+ ToHSV( h, s, v );
+ FromHSV( h, aFactor, 1.0 );
+
+ return *this;
+}
diff --git a/common/gal/graphics_abstraction_layer.cpp b/common/gal/graphics_abstraction_layer.cpp
new file mode 100644
index 0000000..d9e9eac
--- /dev/null
+++ b/common/gal/graphics_abstraction_layer.cpp
@@ -0,0 +1,240 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ *
+ * Graphics Abstraction Layer (GAL) - base class
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <wx/log.h>
+
+#include <gal/graphics_abstraction_layer.h>
+#include <gal/definitions.h>
+
+using namespace KIGFX;
+
+
+const double GAL::METRIC_UNIT_LENGTH = 1e9;
+
+
+GAL::GAL() :
+ strokeFont( this )
+{
+ // Set the default values for the internal variables
+ SetIsFill( false );
+ SetIsStroke( true );
+ SetFillColor( COLOR4D( 0.0, 0.0, 0.0, 0.0 ) );
+ SetStrokeColor( COLOR4D( 1.0, 1.0, 1.0, 1.0 ) );
+ SetLookAtPoint( VECTOR2D( 0, 0 ) );
+ SetZoomFactor( 1.0 );
+ SetWorldUnitLength( 1.0 / METRIC_UNIT_LENGTH * 2.54 ); // 1 inch in nanometers
+ SetScreenDPI( 106 ); // Display resolution setting
+ SetDepthRange( VECTOR2D( GAL::MIN_DEPTH, GAL::MAX_DEPTH ) );
+ SetLayerDepth( 0.0 );
+ SetFlip( false, false );
+ SetLineWidth( 1.0 );
+ ComputeWorldScale();
+
+ // Set grid defaults
+ SetGridVisibility( true );
+ SetGridStyle( GRID_STYLE_LINES );
+ SetGridDrawThreshold( 10 );
+ SetCoarseGrid( 10 );
+ SetGridLineWidth( 0.5 );
+
+ // Initialize the cursor shape
+ SetCursorColor( COLOR4D( 1.0, 1.0, 1.0, 1.0 ) );
+ SetCursorSize( 80 );
+ SetCursorEnabled( false );
+
+ strokeFont.LoadNewStrokeFont( newstroke_font, newstroke_font_bufsize );
+}
+
+
+GAL::~GAL()
+{
+}
+
+
+void GAL::SetTextAttributes( const EDA_TEXT* aText )
+{
+ strokeFont.SetGlyphSize( VECTOR2D( aText->GetSize() ) );
+ strokeFont.SetHorizontalJustify( aText->GetHorizJustify() );
+ strokeFont.SetVerticalJustify( aText->GetVertJustify() );
+ strokeFont.SetBold( aText->IsBold() );
+ strokeFont.SetItalic( aText->IsItalic() );
+ strokeFont.SetMirrored( aText->IsMirrored() );
+}
+
+
+void GAL::ComputeWorldScreenMatrix()
+{
+ ComputeWorldScale();
+
+ worldScreenMatrix.SetIdentity();
+
+ MATRIX3x3D translation;
+ translation.SetIdentity();
+ translation.SetTranslation( 0.5 * VECTOR2D( screenSize ) );
+
+ MATRIX3x3D scale;
+ scale.SetIdentity();
+ scale.SetScale( VECTOR2D( worldScale, worldScale ) );
+
+ MATRIX3x3D flip;
+ flip.SetIdentity();
+ flip.SetScale( VECTOR2D( flipX, flipY ) );
+
+ MATRIX3x3D lookat;
+ lookat.SetIdentity();
+ lookat.SetTranslation( -lookAtPoint );
+
+ worldScreenMatrix = translation * flip * scale * lookat * worldScreenMatrix;
+ screenWorldMatrix = worldScreenMatrix.Inverse();
+}
+
+
+void GAL::DrawGrid()
+{
+ if( !gridVisibility )
+ return;
+
+ SetTarget( TARGET_NONCACHED );
+
+ // Draw the grid
+ // For the drawing the start points, end points and increments have
+ // to be calculated in world coordinates
+ VECTOR2D worldStartPoint = screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
+ VECTOR2D worldEndPoint = screenWorldMatrix * VECTOR2D( screenSize );
+
+ int gridScreenSizeDense = KiROUND( gridSize.x * worldScale );
+ int gridScreenSizeCoarse = KiROUND( gridSize.x * static_cast<double>( gridTick ) * worldScale );
+
+ // Compute the line marker or point radius of the grid
+ double marker = 2.0 * gridLineWidth / worldScale;
+ double doubleMarker = 2.0 * marker;
+
+ // Check if the grid would not be too dense
+ if( std::max( gridScreenSizeDense, gridScreenSizeCoarse ) > gridDrawThreshold )
+ {
+ // Compute grid variables
+ int gridStartX = KiROUND( worldStartPoint.x / gridSize.x );
+ int gridEndX = KiROUND( worldEndPoint.x / gridSize.x );
+ int gridStartY = KiROUND( worldStartPoint.y / gridSize.y );
+ int gridEndY = KiROUND( worldEndPoint.y / gridSize.y );
+
+ assert( gridEndX >= gridStartX );
+ assert( gridEndY >= gridStartY );
+
+ // Correct the index, else some lines are not correctly painted
+ gridStartX -= std::abs( gridOrigin.x / gridSize.x ) + 1;
+ gridStartY -= std::abs( gridOrigin.y / gridSize.y ) + 1;
+ gridEndX += std::abs( gridOrigin.x / gridSize.x ) + 1;
+ gridEndY += std::abs( gridOrigin.y / gridSize.y ) + 1;
+
+ // Draw the grid behind all other layers
+ SetLayerDepth( depthRange.y * 0.75 );
+
+ if( gridStyle == GRID_STYLE_LINES )
+ {
+ SetIsFill( false );
+ SetIsStroke( true );
+ SetStrokeColor( gridColor );
+
+ // Now draw the grid, every coarse grid line gets the double width
+
+ // Vertical lines
+ for( int j = gridStartY; j < gridEndY; j += 1 )
+ {
+ if( j % gridTick == 0 && gridScreenSizeDense > gridDrawThreshold )
+ SetLineWidth( doubleMarker );
+ else
+ SetLineWidth( marker );
+
+ if( ( j % gridTick == 0 && gridScreenSizeCoarse > gridDrawThreshold )
+ || gridScreenSizeDense > gridDrawThreshold )
+ {
+ drawGridLine( VECTOR2D( gridStartX * gridSize.x, j * gridSize.y + gridOrigin.y ),
+ VECTOR2D( gridEndX * gridSize.x, j * gridSize.y + gridOrigin.y ) );
+ }
+ }
+
+ // Horizontal lines
+ for( int i = gridStartX; i < gridEndX; i += 1 )
+ {
+ if( i % gridTick == 0 && gridScreenSizeDense > gridDrawThreshold )
+ SetLineWidth( doubleMarker );
+ else
+ SetLineWidth( marker );
+
+ if( ( i % gridTick == 0 && gridScreenSizeCoarse > gridDrawThreshold )
+ || gridScreenSizeDense > gridDrawThreshold )
+ {
+ drawGridLine( VECTOR2D( i * gridSize.x + gridOrigin.x, gridStartY * gridSize.y ),
+ VECTOR2D( i * gridSize.x + gridOrigin.x, gridEndY * gridSize.y ) );
+ }
+ }
+ }
+ else // Dotted grid
+ {
+ bool tickX, tickY;
+ SetIsFill( true );
+ SetIsStroke( false );
+ SetFillColor( gridColor );
+
+ for( int j = gridStartY; j < gridEndY; j += 1 )
+ {
+ if( j % gridTick == 0 && gridScreenSizeDense > gridDrawThreshold )
+ tickY = true;
+ else
+ tickY = false;
+
+ for( int i = gridStartX; i < gridEndX; i += 1 )
+ {
+ if( i % gridTick == 0 && gridScreenSizeDense > gridDrawThreshold )
+ tickX = true;
+ else
+ tickX = false;
+
+ if( tickX || tickY || gridScreenSizeDense > gridDrawThreshold )
+ {
+ double radius = ( tickX && tickY ) ? doubleMarker : marker;
+ DrawRectangle( VECTOR2D( i * gridSize.x - radius + gridOrigin.x,
+ j * gridSize.y - radius + gridOrigin.y ),
+ VECTOR2D( i * gridSize.x + radius + gridOrigin.x,
+ j * gridSize.y + radius + gridOrigin.y ) );
+ }
+ }
+ }
+ }
+ }
+}
+
+
+VECTOR2D GAL::GetGridPoint( const VECTOR2D& aPoint ) const
+{
+ return VECTOR2D( KiROUND( ( aPoint.x - gridOffset.x ) / gridSize.x ) * gridSize.x + gridOffset.x,
+ KiROUND( ( aPoint.y - gridOffset.y ) / gridSize.y ) * gridSize.y + gridOffset.y );
+}
+
+const int GAL::MIN_DEPTH = -1024;
+const int GAL::MAX_DEPTH = 1023;
+const int GAL::GRID_DEPTH = MAX_DEPTH - 1;
diff --git a/common/gal/opengl/cached_container.cpp b/common/gal/opengl/cached_container.cpp
new file mode 100644
index 0000000..8cf3513
--- /dev/null
+++ b/common/gal/opengl/cached_container.cpp
@@ -0,0 +1,547 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file cached_container.cpp
+ * @brief Class to store instances of VERTEX with caching. It allows storing VERTEX objects and
+ * associates them with VERTEX_ITEMs. This leads to a possibility of caching vertices data in the
+ * GPU memory and a fast reuse of that data.
+ */
+
+#include <gal/opengl/cached_container.h>
+#include <gal/opengl/vertex_manager.h>
+#include <gal/opengl/vertex_item.h>
+#include <gal/opengl/shader.h>
+#include <confirm.h>
+#include <wx/log.h>
+#include <list>
+#ifdef __WXDEBUG__
+#include <profile.h>
+#endif /* __WXDEBUG__ */
+
+using namespace KIGFX;
+
+CACHED_CONTAINER::CACHED_CONTAINER( unsigned int aSize ) :
+ VERTEX_CONTAINER( aSize ), m_item( NULL )
+{
+ // In the beginning there is only free space
+ m_freeChunks.insert( CHUNK( aSize, 0 ) );
+
+ // Do not have uninitialized members:
+ m_chunkSize = 0;
+ m_chunkOffset = 0;
+ m_itemSize = 0;
+}
+
+
+void CACHED_CONTAINER::SetItem( VERTEX_ITEM* aItem )
+{
+ wxASSERT( aItem != NULL );
+
+ m_item = aItem;
+ m_itemSize = m_item->GetSize();
+ m_chunkSize = m_itemSize;
+
+ if( m_itemSize == 0 )
+ m_items.insert( m_item ); // The item was not stored before
+ else
+ m_chunkOffset = m_item->GetOffset();
+
+#if CACHED_CONTAINER_TEST > 1
+ wxLogDebug( wxT( "Adding/editing item 0x%08lx (size %d)" ), (long) m_item, m_itemSize );
+#endif
+}
+
+
+void CACHED_CONTAINER::FinishItem()
+{
+ wxASSERT( m_item != NULL );
+ wxASSERT( m_item->GetSize() == m_itemSize );
+
+ // Finishing the previously edited item
+ if( m_itemSize < m_chunkSize )
+ {
+ // There is some not used but reserved memory left, so we should return it to the pool
+ int itemOffset = m_item->GetOffset();
+
+ // Add the not used memory back to the pool
+ m_freeChunks.insert( CHUNK( m_chunkSize - m_itemSize, itemOffset + m_itemSize ) );
+ m_freeSpace += ( m_chunkSize - m_itemSize );
+ // mergeFreeChunks(); // veery slow and buggy
+ }
+
+#if CACHED_CONTAINER_TEST > 1
+ wxLogDebug( wxT( "Finishing item 0x%08lx (size %d)" ), (long) m_item, m_itemSize );
+ test();
+ m_item = NULL; // electric fence
+#endif
+}
+
+
+VERTEX* CACHED_CONTAINER::Allocate( unsigned int aSize )
+{
+ wxASSERT( m_item != NULL );
+
+ if( m_failed )
+ return NULL;
+
+ if( m_itemSize + aSize > m_chunkSize )
+ {
+ // There is not enough space in the currently reserved chunk, so we have to resize it
+
+ // Reserve a bigger memory chunk for the current item and
+ // make it multiple of 3 to store triangles
+ m_chunkSize = ( 2 * m_itemSize ) + aSize + ( 3 - aSize % 3 );
+ // Save the current size before reallocating
+ m_chunkOffset = reallocate( m_chunkSize );
+
+ if( m_chunkOffset > m_currentSize )
+ {
+ m_failed = true;
+ return NULL;
+ }
+ }
+
+ VERTEX* reserved = &m_vertices[m_chunkOffset + m_itemSize];
+ m_itemSize += aSize;
+ // Now the item officially possesses the memory chunk
+ m_item->setSize( m_itemSize );
+
+ // The content has to be updated
+ m_dirty = true;
+
+#if CACHED_CONTAINER_TEST > 1
+ test();
+#endif
+#if CACHED_CONTAINER_TEST > 2
+ showFreeChunks();
+ showReservedChunks();
+#endif
+
+ return reserved;
+}
+
+
+void CACHED_CONTAINER::Delete( VERTEX_ITEM* aItem )
+{
+ wxASSERT( aItem != NULL );
+ wxASSERT( m_items.find( aItem ) != m_items.end() );
+
+ int size = aItem->GetSize();
+ int offset = aItem->GetOffset();
+
+#if CACHED_CONTAINER_TEST > 1
+ wxLogDebug( wxT( "Removing 0x%08lx (size %d offset %d)" ), (long) aItem, size, offset );
+#endif
+
+ // Insert a free memory chunk entry in the place where item was stored
+ if( size > 0 )
+ {
+ m_freeChunks.insert( CHUNK( size, offset ) );
+ m_freeSpace += size;
+ // Indicate that the item is not stored in the container anymore
+ aItem->setSize( 0 );
+ }
+
+ m_items.erase( aItem );
+
+#if CACHED_CONTAINER_TEST > 1
+ test();
+#endif
+
+ // Dynamic memory freeing, there is no point in holding
+ // a large amount of memory when there is no use for it
+ if( m_freeSpace > ( 0.75 * m_currentSize ) && m_currentSize > m_initialSize )
+ {
+ resizeContainer( 0.5 * m_currentSize );
+ }
+}
+
+
+void CACHED_CONTAINER::Clear()
+{
+ // Change size to the default one
+ m_vertices = static_cast<VERTEX*>( realloc( m_vertices,
+ m_initialSize * sizeof( VERTEX ) ) );
+
+ // Reset state variables
+ m_freeSpace = m_initialSize;
+ m_currentSize = m_initialSize;
+ m_failed = false;
+
+ // Set the size of all the stored VERTEX_ITEMs to 0, so it is clear that they are not held
+ // in the container anymore
+ ITEMS::iterator it;
+
+ for( it = m_items.begin(); it != m_items.end(); ++it )
+ {
+ ( *it )->setSize( 0 );
+ }
+
+ m_items.clear();
+
+
+ // Now there is only free space left
+ m_freeChunks.clear();
+ m_freeChunks.insert( CHUNK( m_freeSpace, 0 ) );
+}
+
+
+unsigned int CACHED_CONTAINER::reallocate( unsigned int aSize )
+{
+ wxASSERT( aSize > 0 );
+
+#if CACHED_CONTAINER_TEST > 2
+ wxLogDebug( wxT( "Resize 0x%08lx from %d to %d" ), (long) m_item, m_itemSize, aSize );
+#endif
+
+ // Is there enough space to store vertices?
+ if( m_freeSpace < aSize )
+ {
+ bool result;
+
+ // Would it be enough to double the current space?
+ if( aSize < m_freeSpace + m_currentSize )
+ {
+ // Yes: exponential growing
+ result = resizeContainer( m_currentSize * 2 );
+ }
+ else
+ {
+ // No: grow to the nearest greater power of 2
+ result = resizeContainer( pow( 2, ceil( log2( m_currentSize * 2 + aSize ) ) ) );
+ }
+
+ if( !result )
+ return UINT_MAX;
+ }
+
+ // Look for the free space chunk of at least given size
+ FREE_CHUNK_MAP::iterator newChunk = m_freeChunks.lower_bound( aSize );
+
+ if( newChunk == m_freeChunks.end() )
+ {
+ // In the case when there is enough space to store the vertices,
+ // but the free space is not continous we should defragment the container
+ if( !defragment() )
+ return UINT_MAX;
+
+ // Update the current offset
+ m_chunkOffset = m_item->GetOffset();
+
+ // We can take the first free chunk, as there is only one after defragmentation
+ // and we can be sure that it provides enough space to store the object
+ newChunk = m_freeChunks.begin();
+ }
+
+ // Parameters of the allocated cuhnk
+ unsigned int chunkSize = newChunk->first;
+ unsigned int chunkOffset = newChunk->second;
+
+ wxASSERT( chunkSize >= aSize );
+ wxASSERT( chunkOffset < m_currentSize );
+
+ // Check if the item was previously stored in the container
+ if( m_itemSize > 0 )
+ {
+#if CACHED_CONTAINER_TEST > 3
+ wxLogDebug( wxT( "Moving 0x%08x from 0x%08x to 0x%08x" ),
+ (int) m_item, oldChunkOffset, chunkOffset );
+#endif
+ // The item was reallocated, so we have to copy all the old data to the new place
+ memcpy( &m_vertices[chunkOffset], &m_vertices[m_chunkOffset], m_itemSize * VertexSize );
+
+ // Free the space previously used by the chunk
+ wxASSERT( m_itemSize > 0 );
+ m_freeChunks.insert( CHUNK( m_itemSize, m_chunkOffset ) );
+ m_freeSpace += m_itemSize;
+ }
+
+ // Remove the allocated chunk from the free space pool
+ m_freeChunks.erase( newChunk );
+
+ // If there is some space left, return it to the pool - add an entry for it
+ if( chunkSize > aSize )
+ {
+ m_freeChunks.insert( CHUNK( chunkSize - aSize, chunkOffset + aSize ) );
+ }
+
+ m_freeSpace -= aSize;
+ // mergeFreeChunks(); // veery slow and buggy
+
+ m_item->setOffset( chunkOffset );
+
+ return chunkOffset;
+}
+
+
+bool CACHED_CONTAINER::defragment( VERTEX* aTarget )
+{
+#if CACHED_CONTAINER_TEST > 0
+ wxLogDebug( wxT( "Defragmenting" ) );
+
+ prof_counter totalTime;
+ prof_start( &totalTime );
+#endif
+
+ if( aTarget == NULL )
+ {
+ // No target was specified, so we have to reallocate our own space
+ int size = m_currentSize * sizeof( VERTEX );
+ aTarget = static_cast<VERTEX*>( malloc( size ) );
+
+ if( aTarget == NULL )
+ {
+ DisplayError( NULL, wxString::Format(
+ wxT( "CACHED_CONTAINER::defragment: Run out of memory (malloc %d bytes)" ),
+ size ) );
+ return false;
+ }
+ }
+
+ int newOffset = 0;
+ ITEMS::iterator it, it_end;
+
+ for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
+ {
+ VERTEX_ITEM* item = *it;
+ int itemOffset = item->GetOffset();
+ int itemSize = item->GetSize();
+
+ // Move an item to the new container
+ memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VertexSize );
+
+ // Update new offset
+ item->setOffset( newOffset );
+
+ // Move to the next free space
+ newOffset += itemSize;
+ }
+
+ free( m_vertices );
+ m_vertices = aTarget;
+
+ // Now there is only one big chunk of free memory
+ m_freeChunks.clear();
+ wxASSERT( m_freeSpace > 0 );
+ m_freeChunks.insert( CHUNK( m_freeSpace, m_currentSize - m_freeSpace ) );
+
+#if CACHED_CONTAINER_TEST > 0
+ prof_end( &totalTime );
+
+ wxLogDebug( wxT( "Defragmented the container storing %d vertices / %.1f ms" ),
+ m_currentSize - m_freeSpace, totalTime.msecs() );
+#endif
+
+ return true;
+}
+
+
+void CACHED_CONTAINER::mergeFreeChunks()
+{
+ if( m_freeChunks.size() <= 1 ) // There are no chunks that can be merged
+ return;
+
+#if CACHED_CONTAINER_TEST > 0
+ prof_counter totalTime;
+ prof_start( &totalTime );
+#endif
+
+ // Reversed free chunks map - this one stores chunk size with its offset as the key
+ std::list<CHUNK> freeChunks;
+
+ FREE_CHUNK_MAP::const_iterator it, it_end;
+
+ for( it = m_freeChunks.begin(), it_end = m_freeChunks.end(); it != it_end; ++it )
+ {
+ freeChunks.push_back( std::make_pair( it->second, it->first ) );
+ }
+
+ m_freeChunks.clear();
+ freeChunks.sort();
+
+ std::list<CHUNK>::const_iterator itf, itf_end;
+ unsigned int offset = freeChunks.front().first;
+ unsigned int size = freeChunks.front().second;
+ freeChunks.pop_front();
+
+ for( itf = freeChunks.begin(), itf_end = freeChunks.end(); itf != itf_end; ++itf )
+ {
+ if( itf->first == offset + size )
+ {
+ // These chunks can be merged, so just increase the current chunk size and go on
+ size += itf->second;
+ }
+ else
+ {
+ // These chunks cannot be merged
+ // So store the previous one
+ m_freeChunks.insert( std::make_pair( size, offset ) );
+ // and let's check the next chunk
+ offset = itf->first;
+ size = itf->second;
+
+ }
+ }
+
+ // Add the last one
+ m_freeChunks.insert( std::make_pair( size, offset ) );
+
+#if CACHED_CONTAINER_TEST > 0
+ prof_end( &totalTime );
+
+ wxLogDebug( wxT( "Merged free chunks / %.1f ms" ), totalTime.msecs() );
+#endif
+
+ test();
+}
+
+
+bool CACHED_CONTAINER::resizeContainer( unsigned int aNewSize )
+{
+ wxASSERT( aNewSize != m_currentSize );
+
+#if CACHED_CONTAINER_TEST > 0
+ wxLogDebug( wxT( "Resizing container from %d to %d" ), m_currentSize, aNewSize );
+#endif
+
+ VERTEX* newContainer;
+
+ if( aNewSize < m_currentSize )
+ {
+ // Shrinking container
+ // Sanity check, no shrinking if we cannot fit all the data
+ if( reservedSpace() > aNewSize )
+ return false;
+
+ int size = aNewSize * sizeof( VERTEX );
+ newContainer = static_cast<VERTEX*>( malloc( size ) );
+
+ if( newContainer == NULL )
+ {
+ DisplayError( NULL, wxString::Format(
+ wxT( "CACHED_CONTAINER::resizeContainer:\n"
+ "Run out of memory (malloc %d bytes)" ),
+ size ) );
+ return false;
+ }
+
+ // Defragment directly to the new, smaller container
+ defragment( newContainer );
+
+ // We have to correct freeChunks after defragmentation
+ m_freeChunks.clear();
+ wxASSERT( aNewSize - reservedSpace() > 0 );
+ m_freeChunks.insert( CHUNK( aNewSize - reservedSpace(), reservedSpace() ) );
+ }
+ else
+ {
+ // Enlarging container
+ int size = aNewSize * sizeof( VERTEX );
+ newContainer = static_cast<VERTEX*>( realloc( m_vertices, size ) );
+
+ if( newContainer == NULL )
+ {
+ DisplayError( NULL, wxString::Format(
+ wxT( "CACHED_CONTAINER::resizeContainer:\n"
+ "Run out of memory (realloc from %d to %d bytes)" ),
+ m_currentSize * sizeof( VERTEX ), size ) );
+ return false;
+ }
+
+ // Add an entry for the new memory chunk at the end of the container
+ m_freeChunks.insert( CHUNK( aNewSize - m_currentSize, m_currentSize ) );
+ }
+
+ m_vertices = newContainer;
+
+ m_freeSpace += ( aNewSize - m_currentSize );
+ m_currentSize = aNewSize;
+
+ return true;
+}
+
+
+#ifdef CACHED_CONTAINER_TEST
+void CACHED_CONTAINER::showFreeChunks()
+{
+ FREE_CHUNK_MAP::iterator it;
+
+ wxLogDebug( wxT( "Free chunks:" ) );
+
+ for( it = m_freeChunks.begin(); it != m_freeChunks.end(); ++it )
+ {
+ unsigned int offset = getChunkOffset( *it );
+ unsigned int size = getChunkSize( *it );
+ wxASSERT( size > 0 );
+
+ wxLogDebug( wxT( "[0x%08x-0x%08x] (size %d)" ),
+ offset, offset + size - 1, size );
+ }
+}
+
+
+void CACHED_CONTAINER::showReservedChunks()
+{
+ ITEMS::iterator it;
+
+ wxLogDebug( wxT( "Reserved chunks:" ) );
+
+ for( it = m_items.begin(); it != m_items.end(); ++it )
+ {
+ VERTEX_ITEM* item = *it;
+ unsigned int offset = item->GetOffset();
+ unsigned int size = item->GetSize();
+ wxASSERT( size > 0 );
+
+ wxLogDebug( wxT( "[0x%08x-0x%08x] @ 0x%08lx (size %d)" ),
+ offset, offset + size - 1, (long) item, size );
+ }
+}
+
+
+void CACHED_CONTAINER::test()
+{
+ // Free space check
+ unsigned int freeSpace = 0;
+ FREE_CHUNK_MAP::iterator itf;
+
+ for( itf = m_freeChunks.begin(); itf != m_freeChunks.end(); ++itf )
+ freeSpace += getChunkSize( *itf );
+
+ wxASSERT( freeSpace == m_freeSpace );
+
+ // Reserved space check
+ /*unsigned int reservedSpace = 0;
+ ITEMS::iterator itr;
+ for( itr = m_items.begin(); itr != m_items.end(); ++itr )
+ reservedSpace += ( *itr )->GetSize();
+ reservedSpace += m_itemSize; // Add the current chunk size
+
+ wxASSERT( ( freeSpace + reservedSpace ) == m_currentSize );*/
+
+ // Overlapping check TBD
+}
+
+#endif /* CACHED_CONTAINER_TEST */
diff --git a/common/gal/opengl/gpu_manager.cpp b/common/gal/opengl/gpu_manager.cpp
new file mode 100644
index 0000000..be34a56
--- /dev/null
+++ b/common/gal/opengl/gpu_manager.cpp
@@ -0,0 +1,302 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file gpu_manager.cpp
+ * @brief Class to handle uploading vertices and indices to GPU in drawing purposes.
+ */
+
+#include <gal/opengl/gpu_manager.h>
+#include <gal/opengl/cached_container.h>
+#include <gal/opengl/noncached_container.h>
+#include <gal/opengl/shader.h>
+#include <typeinfo>
+#include <wx/msgdlg.h>
+#include <confirm.h>
+#ifdef PROFILE
+#include <profile.h>
+#include <wx/debug.h>
+#include <wx/log.h>
+#endif /* PROFILE */
+
+using namespace KIGFX;
+
+GPU_MANAGER* GPU_MANAGER::MakeManager( VERTEX_CONTAINER* aContainer )
+{
+ if( typeid( *aContainer ) == typeid( CACHED_CONTAINER ) )
+ return new GPU_CACHED_MANAGER( aContainer );
+ else if( typeid( *aContainer ) == typeid( NONCACHED_CONTAINER ) )
+ return new GPU_NONCACHED_MANAGER( aContainer );
+
+ wxASSERT_MSG( false, wxT( "Not handled container type" ) );
+ return NULL;
+}
+
+
+GPU_MANAGER::GPU_MANAGER( VERTEX_CONTAINER* aContainer ) :
+ m_isDrawing( false ), m_container( aContainer ), m_shader( NULL ), m_shaderAttrib( 0 )
+{
+}
+
+
+GPU_MANAGER::~GPU_MANAGER()
+{
+}
+
+
+void GPU_MANAGER::SetShader( SHADER& aShader )
+{
+ m_shader = &aShader;
+
+ m_shaderAttrib = m_shader->GetAttribute( "attrShaderParams" );
+
+ if( m_shaderAttrib == -1 )
+ {
+ DisplayError( NULL, wxT( "Could not get the shader attribute location" ) );
+ }
+}
+
+
+// Cached manager
+GPU_CACHED_MANAGER::GPU_CACHED_MANAGER( VERTEX_CONTAINER* aContainer ) :
+ GPU_MANAGER( aContainer ), m_buffersInitialized( false ), m_indicesPtr( NULL ),
+ m_verticesBuffer( 0 ), m_indicesBuffer( 0 ), m_indicesSize( 0 ), m_indicesCapacity( 0 )
+{
+ // Allocate the biggest possible buffer for indices
+ resizeIndices( aContainer->GetSize() );
+}
+
+
+GPU_CACHED_MANAGER::~GPU_CACHED_MANAGER()
+{
+ if( m_buffersInitialized )
+ {
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDeleteBuffers( 1, &m_verticesBuffer );
+ glDeleteBuffers( 1, &m_indicesBuffer );
+ }
+}
+
+
+void GPU_CACHED_MANAGER::Initialize()
+{
+ wxASSERT( !m_buffersInitialized );
+
+ if( !m_buffersInitialized )
+ {
+ glGenBuffers( 1, &m_verticesBuffer );
+ glGenBuffers( 1, &m_indicesBuffer );
+ m_buffersInitialized = true;
+ }
+}
+
+
+void GPU_CACHED_MANAGER::BeginDrawing()
+{
+ wxASSERT( !m_isDrawing );
+
+ if( m_container->IsDirty() )
+ uploadToGpu();
+
+ // Number of vertices to be drawn in the EndDrawing()
+ m_indicesSize = 0;
+ // Set the indices pointer to the beginning of the indices-to-draw buffer
+ m_indicesPtr = m_indices.get();
+
+ m_isDrawing = true;
+}
+
+
+void GPU_CACHED_MANAGER::DrawIndices( unsigned int aOffset, unsigned int aSize )
+{
+ wxASSERT( m_isDrawing );
+
+ // Copy indices of items that should be drawn to GPU memory
+ for( unsigned int i = aOffset; i < aOffset + aSize; *m_indicesPtr++ = i++ );
+
+ m_indicesSize += aSize;
+}
+
+
+void GPU_CACHED_MANAGER::DrawAll()
+{
+ wxASSERT( m_isDrawing );
+
+ m_indicesSize = m_container->GetSize();
+ for( unsigned int i = 0; i < m_indicesSize; *m_indicesPtr++ = i++ );
+}
+
+
+void GPU_CACHED_MANAGER::EndDrawing()
+{
+ wxASSERT( m_isDrawing );
+
+ // Prepare buffers
+ glEnableClientState( GL_VERTEX_ARRAY );
+ glEnableClientState( GL_COLOR_ARRAY );
+
+ // Bind vertices data buffers
+ glBindBuffer( GL_ARRAY_BUFFER, m_verticesBuffer );
+ glVertexPointer( CoordStride, GL_FLOAT, VertexSize, 0 );
+ glColorPointer( ColorStride, GL_UNSIGNED_BYTE, VertexSize, (GLvoid*) ColorOffset );
+
+ if( m_shader != NULL ) // Use shader if applicable
+ {
+ m_shader->Use();
+ glEnableVertexAttribArray( m_shaderAttrib );
+ glVertexAttribPointer( m_shaderAttrib, ShaderStride, GL_FLOAT, GL_FALSE,
+ VertexSize, (GLvoid*) ShaderOffset );
+ }
+
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indicesBuffer );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indicesSize * sizeof(int), (GLvoid*) m_indices.get(), GL_DYNAMIC_DRAW );
+
+ glDrawElements( GL_TRIANGLES, m_indicesSize, GL_UNSIGNED_INT, 0 );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
+
+ // Deactivate vertex array
+ glDisableClientState( GL_COLOR_ARRAY );
+ glDisableClientState( GL_VERTEX_ARRAY );
+
+ if( m_shader != NULL )
+ {
+ glDisableVertexAttribArray( m_shaderAttrib );
+ m_shader->Deactivate();
+ }
+
+ m_isDrawing = false;
+}
+
+
+void GPU_CACHED_MANAGER::uploadToGpu()
+{
+#ifdef PROFILE
+ prof_counter totalTime;
+ prof_start( &totalTime );
+#endif /* PROFILE */
+
+ if( !m_buffersInitialized )
+ Initialize();
+
+ int bufferSize = m_container->GetSize();
+ GLfloat* vertices = (GLfloat*) m_container->GetAllVertices();
+
+ // Upload vertices coordinates and shader types to GPU memory
+ glBindBuffer( GL_ARRAY_BUFFER, m_verticesBuffer );
+ glBufferData( GL_ARRAY_BUFFER, bufferSize * VertexSize, vertices, GL_STATIC_DRAW );
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ // Allocate the biggest possible buffer for indices
+ resizeIndices( bufferSize );
+
+ if( glGetError() != GL_NO_ERROR )
+ DisplayError( NULL, wxT( "Error during data upload to the GPU memory" ) );
+
+#ifdef PROFILE
+ prof_end( &totalTime );
+
+ wxLogDebug( wxT( "Uploading %d vertices to GPU / %.1f ms" ), bufferSize, totalTime.msecs() );
+#endif /* PROFILE */
+}
+
+
+void GPU_CACHED_MANAGER::resizeIndices( unsigned int aNewSize )
+{
+ if( aNewSize > m_indicesCapacity )
+ {
+ m_indicesCapacity = aNewSize;
+ m_indices.reset( new GLuint[m_indicesCapacity] );
+ }
+}
+
+
+// Noncached manager
+GPU_NONCACHED_MANAGER::GPU_NONCACHED_MANAGER( VERTEX_CONTAINER* aContainer ) :
+ GPU_MANAGER( aContainer )
+{
+}
+
+
+void GPU_NONCACHED_MANAGER::Initialize()
+{
+ // Nothing has to be intialized
+}
+
+
+void GPU_NONCACHED_MANAGER::BeginDrawing()
+{
+ // Nothing has to be prepared
+}
+
+
+void GPU_NONCACHED_MANAGER::DrawIndices( unsigned int aOffset, unsigned int aSize )
+{
+ wxASSERT_MSG( false, wxT( "Not implemented yet" ) );
+}
+
+
+void GPU_NONCACHED_MANAGER::DrawAll()
+{
+ // This is the default use case, nothing has to be done
+ // The real rendering takes place in the EndDrawing() function
+}
+
+
+void GPU_NONCACHED_MANAGER::EndDrawing()
+{
+ VERTEX* vertices = m_container->GetAllVertices();
+ GLfloat* coordinates = (GLfloat*) ( vertices );
+ GLubyte* colors = (GLubyte*) ( vertices ) + ColorOffset;
+
+ // Prepare buffers
+ glEnableClientState( GL_VERTEX_ARRAY );
+ glEnableClientState( GL_COLOR_ARRAY );
+
+ glVertexPointer( CoordStride, GL_FLOAT, VertexSize, coordinates );
+ glColorPointer( ColorStride, GL_UNSIGNED_BYTE, VertexSize, colors );
+
+ if( m_shader != NULL ) // Use shader if applicable
+ {
+ GLfloat* shaders = (GLfloat*) ( vertices ) + ShaderOffset / sizeof(GLfloat);
+
+ m_shader->Use();
+ glEnableVertexAttribArray( m_shaderAttrib );
+ glVertexAttribPointer( m_shaderAttrib, ShaderStride, GL_FLOAT, GL_FALSE,
+ VertexSize, shaders );
+ }
+
+ glDrawArrays( GL_TRIANGLES, 0, m_container->GetSize() );
+
+ // Deactivate vertex array
+ glDisableClientState( GL_COLOR_ARRAY );
+ glDisableClientState( GL_VERTEX_ARRAY );
+
+ if( m_shader != NULL )
+ {
+ glDisableVertexAttribArray( m_shaderAttrib );
+ m_shader->Deactivate();
+ }
+}
diff --git a/common/gal/opengl/noncached_container.cpp b/common/gal/opengl/noncached_container.cpp
new file mode 100644
index 0000000..c902c47
--- /dev/null
+++ b/common/gal/opengl/noncached_container.cpp
@@ -0,0 +1,89 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file noncached_container.cpp
+ * @brief Class to store instances of VERTEX without caching. It allows a fast one-frame drawing
+ * and then clearing the buffer and starting from scratch.
+ */
+
+#include <gal/opengl/noncached_container.h>
+#include <cstdlib>
+
+using namespace KIGFX;
+
+NONCACHED_CONTAINER::NONCACHED_CONTAINER( unsigned int aSize ) :
+ VERTEX_CONTAINER( aSize ), m_freePtr( 0 )
+{
+}
+
+
+NONCACHED_CONTAINER::~NONCACHED_CONTAINER()
+{
+}
+
+
+void NONCACHED_CONTAINER::SetItem( VERTEX_ITEM* aItem )
+{
+ // Nothing has to be done, as the noncached container
+ // does not care about VERTEX_ITEMs ownership
+}
+
+
+VERTEX* NONCACHED_CONTAINER::Allocate( unsigned int aSize )
+{
+ if( m_freeSpace < aSize )
+ {
+ // Double the space
+ VERTEX* newVertices = static_cast<VERTEX*>( realloc( m_vertices,
+ m_currentSize * 2 *
+ sizeof(VERTEX) ) );
+
+ if( newVertices != NULL )
+ {
+ m_vertices = newVertices;
+ m_freeSpace += m_currentSize;
+ m_currentSize *= 2;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ VERTEX* freeVertex = &m_vertices[m_freePtr];
+
+ // Move to the next free chunk
+ m_freePtr += aSize;
+ m_freeSpace -= aSize;
+
+ return freeVertex;
+}
+
+
+void NONCACHED_CONTAINER::Clear()
+{
+ m_freePtr = 0;
+ m_freeSpace = m_currentSize;
+}
diff --git a/common/gal/opengl/opengl_compositor.cpp b/common/gal/opengl/opengl_compositor.cpp
new file mode 100644
index 0000000..9f35d9e
--- /dev/null
+++ b/common/gal/opengl/opengl_compositor.cpp
@@ -0,0 +1,291 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013-2015 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file opengl_compositor.cpp
+ * @brief Class that handles multitarget rendering (i.e. to different textures/surfaces) and
+ * later compositing into a single image (OpenGL flavour).
+ */
+
+#include <gal/opengl/opengl_compositor.h>
+
+#include <stdexcept>
+#include <cassert>
+
+using namespace KIGFX;
+
+OPENGL_COMPOSITOR::OPENGL_COMPOSITOR() :
+ m_initialized( false ), m_current( 0 ), m_currentFbo( DIRECT_RENDERING )
+{
+ // Avoid not initialized members:
+ m_framebuffer = 0;
+ m_depthBuffer = 0;
+}
+
+
+OPENGL_COMPOSITOR::~OPENGL_COMPOSITOR()
+{
+ if( m_initialized )
+ clean();
+}
+
+
+void OPENGL_COMPOSITOR::Initialize()
+{
+ if( m_initialized )
+ return;
+
+ // We need framebuffer objects for drawing the screen contents
+ // Generate framebuffer and a depth buffer
+ glGenFramebuffersEXT( 1, &m_framebuffer );
+ glBindFramebufferEXT( GL_FRAMEBUFFER, m_framebuffer );
+ m_currentFbo = m_framebuffer;
+
+ // Allocate memory for the depth buffer
+ // Attach the depth buffer to the framebuffer
+ glGenRenderbuffersEXT( 1, &m_depthBuffer );
+ glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_depthBuffer );
+
+ // Use here a size of 24 bits for the depth buffer, 8 bits for the stencil buffer
+ // this is required later for anti-aliasing
+ glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, m_width, m_height );
+ glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER_EXT, m_depthBuffer );
+
+ // Unbind the framebuffer, so by default all the rendering goes directly to the display
+ glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, DIRECT_RENDERING );
+ m_currentFbo = DIRECT_RENDERING;
+
+ m_initialized = true;
+}
+
+
+void OPENGL_COMPOSITOR::Resize( unsigned int aWidth, unsigned int aHeight )
+{
+ if( m_initialized )
+ clean();
+
+ m_width = aWidth;
+ m_height = aHeight;
+}
+
+
+unsigned int OPENGL_COMPOSITOR::CreateBuffer()
+{
+ assert( m_initialized );
+
+ unsigned int maxBuffers;
+
+ // Get the maximum number of buffers
+ glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS, (GLint*) &maxBuffers );
+
+ if( usedBuffers() >= maxBuffers )
+ {
+ throw std::runtime_error("Cannot create more framebuffers. OpenGL rendering "
+ "backend requires at least 3 framebuffers. You may try to update/change "
+ "your graphic drivers.");
+ }
+
+ // GL_COLOR_ATTACHMENTn are consecutive integers
+ GLuint attachmentPoint = GL_COLOR_ATTACHMENT0 + usedBuffers();
+ GLuint textureTarget;
+
+ // Generate the texture for the pixel storage
+ glGenTextures( 1, &textureTarget );
+ glBindTexture( GL_TEXTURE_2D, textureTarget );
+
+ // Set texture parameters
+ glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, NULL );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+
+ // Bind the texture to the specific attachment point, clear and rebind the screen
+ glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_framebuffer );
+ m_currentFbo = m_framebuffer;
+ glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, attachmentPoint,
+ GL_TEXTURE_2D, textureTarget, 0 );
+
+ // Check the status, exit if the framebuffer can't be created
+ GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
+
+ if( status != GL_FRAMEBUFFER_COMPLETE_EXT )
+ {
+ switch( status )
+ {
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
+ throw std::runtime_error( "Cannot create the framebuffer." );
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
+ throw std::runtime_error( "The framebuffer attachment points are incomplete." );
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
+ throw std::runtime_error( "The framebuffer does not have at least one "
+ "image attached to it." );
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
+ throw std::runtime_error( "The framebuffer read buffer is incomplete." );
+ break;
+
+ case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
+ throw std::runtime_error( "The combination of internal formats of the attached "
+ "images violates an implementation-dependent set of restrictions." );
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
+ throw std::runtime_error( "GL_RENDERBUFFER_SAMPLES is not the same for "
+ "all attached renderbuffers" );
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT:
+ throw std::runtime_error( "Framebuffer incomplete layer targets errors." );
+ break;
+
+ default:
+ throw std::runtime_error( "Cannot create the framebuffer." );
+ break;
+ }
+
+ return 0;
+ }
+
+ ClearBuffer();
+
+ // Return to direct rendering (we were asked only to create a buffer, not switch to one)
+ glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, DIRECT_RENDERING );
+ m_currentFbo = DIRECT_RENDERING;
+
+ // Store the new buffer
+ OPENGL_BUFFER buffer = { textureTarget, attachmentPoint };
+ m_buffers.push_back( buffer );
+
+ return usedBuffers();
+}
+
+
+void OPENGL_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
+{
+ if( aBufferHandle > usedBuffers() )
+ return;
+
+ // Change the rendering destination to the selected attachment point
+ if( aBufferHandle == DIRECT_RENDERING )
+ {
+ m_currentFbo = DIRECT_RENDERING;
+ }
+ else if( m_currentFbo != m_framebuffer )
+ {
+ glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_framebuffer );
+ m_currentFbo = m_framebuffer;
+ }
+
+ if( m_currentFbo != DIRECT_RENDERING )
+ {
+ m_current = aBufferHandle - 1;
+ glDrawBuffer( m_buffers[m_current].attachmentPoint );
+ }
+}
+
+
+void OPENGL_COMPOSITOR::ClearBuffer()
+{
+ assert( m_initialized );
+
+ glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+}
+
+
+void OPENGL_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle )
+{
+ assert( m_initialized );
+ assert( aBufferHandle != 0 && aBufferHandle <= usedBuffers() );
+
+ // Switch to the main framebuffer and blit the scene
+ glBindFramebufferEXT( GL_FRAMEBUFFER, DIRECT_RENDERING );
+ m_currentFbo = DIRECT_RENDERING;
+
+ // Depth test has to be disabled to make transparency working
+ glDisable( GL_DEPTH_TEST );
+ glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
+
+ // Enable texturing and bind the main texture
+ glEnable( GL_TEXTURE_2D );
+ glBindTexture( GL_TEXTURE_2D, m_buffers[aBufferHandle - 1].textureTarget );
+
+ // Draw a full screen quad with the texture
+ glMatrixMode( GL_MODELVIEW );
+ glPushMatrix();
+ glLoadIdentity();
+ glMatrixMode( GL_PROJECTION );
+ glPushMatrix();
+ glLoadIdentity();
+
+ glBegin( GL_TRIANGLES );
+ glTexCoord2f( 0.0f, 1.0f );
+ glVertex2f( -1.0f, -1.0f );
+ glTexCoord2f( 1.0f, 1.0f );
+ glVertex2f( 1.0f, -1.0f );
+ glTexCoord2f( 1.0f, 0.0f );
+ glVertex2f( 1.0f, 1.0f );
+
+ glTexCoord2f( 0.0f, 1.0f );
+ glVertex2f( -1.0f, -1.0f );
+ glTexCoord2f( 1.0f, 0.0f );
+ glVertex2f( 1.0f, 1.0f );
+ glTexCoord2f( 0.0f, 0.0f );
+ glVertex2f( -1.0f, 1.0f );
+ glEnd();
+
+ glPopMatrix();
+ glMatrixMode( GL_MODELVIEW );
+ glPopMatrix();
+}
+
+
+void OPENGL_COMPOSITOR::clean()
+{
+ assert( m_initialized );
+
+ glBindFramebufferEXT( GL_FRAMEBUFFER, DIRECT_RENDERING );
+ m_currentFbo = DIRECT_RENDERING;
+
+ OPENGL_BUFFERS::const_iterator it;
+
+ for( it = m_buffers.begin(); it != m_buffers.end(); ++it )
+ {
+ glDeleteTextures( 1, &it->textureTarget );
+ }
+
+ m_buffers.clear();
+
+ glDeleteFramebuffersEXT( 1, &m_framebuffer );
+ glDeleteRenderbuffersEXT( 1, &m_depthBuffer );
+
+ m_initialized = false;
+}
diff --git a/common/gal/opengl/opengl_gal.cpp b/common/gal/opengl/opengl_gal.cpp
new file mode 100644
index 0000000..945161f
--- /dev/null
+++ b/common/gal/opengl/opengl_gal.cpp
@@ -0,0 +1,1204 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ * Copyright (C) 2013-2016 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * Graphics Abstraction Layer (GAL) for OpenGL
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <gal/opengl/opengl_gal.h>
+#include <gal/definitions.h>
+
+#include <wx/log.h>
+#include <macros.h>
+#ifdef __WXDEBUG__
+#include <profile.h>
+#endif /* __WXDEBUG__ */
+
+#include <limits>
+#include <boost/bind.hpp>
+
+using namespace KIGFX;
+
+static void InitTesselatorCallbacks( GLUtesselator* aTesselator );
+const int glAttributes[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 8, 0 };
+wxGLContext* OPENGL_GAL::glContext = NULL;
+
+OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
+ wxEvtHandler* aPaintListener, const wxString& aName ) :
+ wxGLCanvas( aParent, wxID_ANY, (int*) glAttributes, wxDefaultPosition, wxDefaultSize,
+ wxEXPAND, aName ),
+ mouseListener( aMouseListener ),
+ paintListener( aPaintListener ),
+ cachedManager( true ),
+ nonCachedManager( false ),
+ overlayManager( false )
+{
+ if( glContext == NULL )
+ glContext = new wxGLContext( this );
+
+ // Check if OpenGL requirements are met
+ runTest();
+
+ // Make VBOs use shaders
+ cachedManager.SetShader( shader );
+ nonCachedManager.SetShader( shader );
+ overlayManager.SetShader( shader );
+
+ // Initialize the flags
+ isFramebufferInitialized = false;
+ isGrouping = false;
+ groupCounter = 0;
+
+#ifdef RETINA_OPENGL_PATCH
+ SetViewWantsBestResolution( true );
+#endif
+
+ // Connecting the event handlers
+ Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::onPaint ) );
+
+ // Mouse events are skipped to the parent
+ Connect( wxEVT_MOTION, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+ Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+#ifdef USE_OSX_MAGNIFY_EVENT
+ Connect( wxEVT_MAGNIFY, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+#endif
+#if defined _WIN32 || defined _WIN64
+ Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
+#endif
+
+ SetSize( aParent->GetSize() );
+ screenSize = VECTOR2I( aParent->GetSize() );
+
+ // Grid color settings are different in Cairo and OpenGL
+ SetGridColor( COLOR4D( 0.8, 0.8, 0.8, 0.1 ) );
+
+ // Tesselator initialization
+ tesselator = gluNewTess();
+ InitTesselatorCallbacks( tesselator );
+
+ if( tesselator == NULL )
+ throw std::runtime_error( "Could not create the tesselator" );
+
+ gluTessProperty( tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
+
+ currentManager = &nonCachedManager;
+}
+
+
+OPENGL_GAL::~OPENGL_GAL()
+{
+ glFlush();
+
+ gluDeleteTess( tesselator );
+ ClearCache();
+}
+
+
+void OPENGL_GAL::BeginDrawing()
+{
+ if( !IsShownOnScreen() )
+ return;
+
+ SetCurrent( *glContext );
+ clientDC = new wxClientDC( this );
+
+#ifdef RETINA_OPENGL_PATCH
+ const float scaleFactor = GetBackingScaleFactor();
+#else
+ const float scaleFactor = 1.0f;
+#endif
+
+ // Set up the view port
+ glMatrixMode( GL_PROJECTION );
+ glLoadIdentity();
+ glViewport( 0, 0, (GLsizei) screenSize.x * scaleFactor, (GLsizei) screenSize.y * scaleFactor );
+
+ // Create the screen transformation
+ glOrtho( 0, (GLint) screenSize.x, 0, (GLsizei) screenSize.y,
+ -depthRange.x, -depthRange.y );
+
+ if( !isFramebufferInitialized )
+ {
+ // Prepare rendering target buffers
+ compositor.Initialize();
+ mainBuffer = compositor.CreateBuffer();
+ overlayBuffer = compositor.CreateBuffer();
+
+ isFramebufferInitialized = true;
+ }
+
+ // Disable 2D Textures
+ glDisable( GL_TEXTURE_2D );
+
+ // Enable the depth buffer
+ glEnable( GL_DEPTH_TEST );
+ glDepthFunc( GL_LESS );
+
+ // Setup blending, required for transparent objects
+ glEnable( GL_BLEND );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ glMatrixMode( GL_MODELVIEW );
+
+ // Set up the world <-> screen transformation
+ ComputeWorldScreenMatrix();
+ GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
+ matrixData[0] = worldScreenMatrix.m_data[0][0];
+ matrixData[1] = worldScreenMatrix.m_data[1][0];
+ matrixData[2] = worldScreenMatrix.m_data[2][0];
+ matrixData[4] = worldScreenMatrix.m_data[0][1];
+ matrixData[5] = worldScreenMatrix.m_data[1][1];
+ matrixData[6] = worldScreenMatrix.m_data[2][1];
+ matrixData[12] = worldScreenMatrix.m_data[0][2];
+ matrixData[13] = worldScreenMatrix.m_data[1][2];
+ matrixData[14] = worldScreenMatrix.m_data[2][2];
+ glLoadMatrixd( matrixData );
+
+ // Set defaults
+ SetFillColor( fillColor );
+ SetStrokeColor( strokeColor );
+
+ // Unbind buffers - set compositor for direct drawing
+ compositor.SetBuffer( OPENGL_COMPOSITOR::DIRECT_RENDERING );
+
+ // Remove all previously stored items
+ nonCachedManager.Clear();
+ overlayManager.Clear();
+
+ cachedManager.BeginDrawing();
+ nonCachedManager.BeginDrawing();
+ overlayManager.BeginDrawing();
+}
+
+
+void OPENGL_GAL::EndDrawing()
+{
+ // Cached & non-cached containers are rendered to the same buffer
+ compositor.SetBuffer( mainBuffer );
+ nonCachedManager.EndDrawing();
+ cachedManager.EndDrawing();
+
+ // Overlay container is rendered to a different buffer
+ compositor.SetBuffer( overlayBuffer );
+ overlayManager.EndDrawing();
+
+ // Be sure that the framebuffer is not colorized (happens on specific GPU&drivers combinations)
+ glColor4d( 1.0, 1.0, 1.0, 1.0 );
+
+ // Draw the remaining contents, blit the rendering targets to the screen, swap the buffers
+ compositor.DrawBuffer( mainBuffer );
+ compositor.DrawBuffer( overlayBuffer );
+ blitCursor();
+
+ glFlush();
+ SwapBuffers();
+
+ delete clientDC;
+}
+
+
+void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ const VECTOR2D startEndVector = aEndPoint - aStartPoint;
+ double lineAngle = startEndVector.Angle();
+
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+
+ drawLineQuad( aStartPoint, aEndPoint );
+
+ // Line caps
+ if( lineWidth > 1.0 )
+ {
+ drawFilledSemiCircle( aStartPoint, lineWidth / 2, lineAngle + M_PI / 2 );
+ drawFilledSemiCircle( aEndPoint, lineWidth / 2, lineAngle - M_PI / 2 );
+ }
+}
+
+
+void OPENGL_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
+ double aWidth )
+{
+ VECTOR2D startEndVector = aEndPoint - aStartPoint;
+ double lineAngle = startEndVector.Angle();
+
+ if( isFillEnabled )
+ {
+ // Filled tracks
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+
+ SetLineWidth( aWidth );
+ drawLineQuad( aStartPoint, aEndPoint );
+
+ // Draw line caps
+ drawFilledSemiCircle( aStartPoint, aWidth / 2, lineAngle + M_PI / 2 );
+ drawFilledSemiCircle( aEndPoint, aWidth / 2, lineAngle - M_PI / 2 );
+ }
+ else
+ {
+ // Outlined tracks
+ double lineLength = startEndVector.EuclideanNorm();
+
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+
+ Save();
+
+ currentManager->Translate( aStartPoint.x, aStartPoint.y, 0.0 );
+ currentManager->Rotate( lineAngle, 0.0f, 0.0f, 1.0f );
+
+ drawLineQuad( VECTOR2D( 0.0, aWidth / 2.0 ),
+ VECTOR2D( lineLength, aWidth / 2.0 ) );
+
+ drawLineQuad( VECTOR2D( 0.0, -aWidth / 2.0 ),
+ VECTOR2D( lineLength, -aWidth / 2.0 ) );
+
+ // Draw line caps
+ drawStrokedSemiCircle( VECTOR2D( 0.0, 0.0 ), aWidth / 2, M_PI / 2 );
+ drawStrokedSemiCircle( VECTOR2D( lineLength, 0.0 ), aWidth / 2, -M_PI / 2 );
+
+ Restore();
+ }
+}
+
+
+void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
+{
+ if( isFillEnabled )
+ {
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.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]).
+ * Shader uses this coordinates to determine if fragments are inside the circle or not.
+ * v2
+ * /\
+ * //\\
+ * v0 /_\/_\ v1
+ */
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0 );
+ currentManager->Vertex( aCenterPoint.x - aRadius * sqrt( 3.0f ), // v0
+ aCenterPoint.y - aRadius, layerDepth );
+
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0 );
+ currentManager->Vertex( aCenterPoint.x + aRadius * sqrt( 3.0f ), // v1
+ aCenterPoint.y - aRadius, layerDepth );
+
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0 );
+ currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + aRadius * 2.0f, // v2
+ 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 );
+ }
+}
+
+
+void OPENGL_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle,
+ double aEndAngle )
+{
+ if( aRadius <= 0 )
+ return;
+
+ // Swap the angles, if start angle is greater than end angle
+ SWAP( aStartAngle, >, aEndAngle );
+
+ Save();
+ currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
+
+ if( isStrokeEnabled )
+ {
+ const 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 )
+ {
+ 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 );
+ }
+ }
+
+ if( isFillEnabled )
+ {
+ const double alphaIncrement = 2 * M_PI / CIRCLE_POINTS;
+ double alpha;
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+ currentManager->Shader( SHADER_NONE );
+
+ // Triangle fan
+ for( alpha = aStartAngle; ( alpha + alphaIncrement ) < aEndAngle; )
+ {
+ currentManager->Vertex( 0.0, 0.0, 0.0 );
+ currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 );
+ alpha += alphaIncrement;
+ currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 );
+ }
+
+ // The last missing triangle
+ const VECTOR2D endPoint( cos( aEndAngle ) * aRadius, sin( aEndAngle ) * aRadius );
+ currentManager->Vertex( 0.0, 0.0, 0.0 );
+ currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 );
+ currentManager->Vertex( endPoint.x, endPoint.y, 0.0 );
+ }
+
+ Restore();
+}
+
+
+void OPENGL_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ // Compute the diagonal points of the rectangle
+ VECTOR2D diagonalPointA( aEndPoint.x, aStartPoint.y );
+ VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );
+
+ // Stroke the outline
+ if( isStrokeEnabled )
+ {
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+
+ std::deque<VECTOR2D> pointList;
+ pointList.push_back( aStartPoint );
+ pointList.push_back( diagonalPointA );
+ pointList.push_back( aEndPoint );
+ pointList.push_back( diagonalPointB );
+ pointList.push_back( aStartPoint );
+ DrawPolyline( pointList );
+ }
+
+ // Fill the rectangle
+ if( isFillEnabled )
+ {
+ currentManager->Shader( SHADER_NONE );
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+
+ currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth );
+ currentManager->Vertex( diagonalPointA.x, diagonalPointA.y, layerDepth );
+ currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth );
+
+ currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth );
+ currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth );
+ currentManager->Vertex( diagonalPointB.x, diagonalPointB.y, layerDepth );
+ }
+}
+
+
+void OPENGL_GAL::DrawPolyline( const std::deque<VECTOR2D>& aPointList )
+{
+ if( aPointList.size() < 2 )
+ return;
+
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+
+ std::deque<VECTOR2D>::const_iterator it = aPointList.begin();
+
+ // Start from the second point
+ for( ++it; it != aPointList.end(); ++it )
+ {
+ const VECTOR2D startEndVector = ( *it - *( it - 1 ) );
+ double lineAngle = startEndVector.Angle();
+
+ drawLineQuad( *( it - 1 ), *it );
+
+ // There is no need to draw line caps on both ends of polyline's segments
+ drawFilledSemiCircle( *( it - 1 ), lineWidth / 2, lineAngle + M_PI / 2 );
+ }
+
+ // ..and now - draw the ending cap
+ const VECTOR2D startEndVector = ( *( it - 1 ) - *( it - 2 ) );
+ double lineAngle = startEndVector.Angle();
+ drawFilledSemiCircle( *( it - 1 ), lineWidth / 2, lineAngle - M_PI / 2 );
+}
+
+
+void OPENGL_GAL::DrawPolyline( const VECTOR2D aPointList[], int aListSize )
+{
+ if( aListSize < 2 )
+ return;
+
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+
+ // Start from the second point
+ for( int i = 1; i < aListSize; ++i )
+ {
+ const VECTOR2D startEndVector = ( aPointList[i] - aPointList[i - 1] );
+ double lineAngle = startEndVector.Angle();
+
+ drawLineQuad( aPointList[i - 1], aPointList[i] );
+
+ // There is no need to draw line caps on both ends of polyline's segments
+ drawFilledSemiCircle( aPointList[i - 1], lineWidth / 2, lineAngle + M_PI / 2 );
+ }
+
+ // ..and now - draw the ending cap
+ const VECTOR2D startEndVector = ( aPointList[aListSize - 1] - aPointList[aListSize - 2] );
+ double lineAngle = startEndVector.Angle();
+ drawFilledSemiCircle( aPointList[aListSize - 1], lineWidth / 2, lineAngle - M_PI / 2 );
+}
+
+
+void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
+{
+ currentManager->Shader( SHADER_NONE );
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+
+ // Any non convex polygon needs to be tesselated
+ // for this purpose the GLU standard functions are used
+ TessParams params = { currentManager, tessIntersects };
+ gluTessBeginPolygon( tesselator, &params );
+ gluTessBeginContour( tesselator );
+
+ boost::shared_array<GLdouble> points( new GLdouble[3 * aPointList.size()] );
+ int v = 0;
+
+ for( std::deque<VECTOR2D>::const_iterator it = aPointList.begin(); it != aPointList.end(); ++it )
+ {
+ points[v] = it->x;
+ points[v + 1] = it->y;
+ points[v + 2] = layerDepth;
+ gluTessVertex( tesselator, &points[v], &points[v] );
+ v += 3;
+ }
+
+ gluTessEndContour( tesselator );
+ gluTessEndPolygon( tesselator );
+
+ // Free allocated intersecting points
+ tessIntersects.clear();
+
+ // vertexList destroyed here
+}
+
+
+void OPENGL_GAL::DrawPolygon( const VECTOR2D aPointList[], int aListSize )
+{
+ currentManager->Shader( SHADER_NONE );
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+
+ // Any non convex polygon needs to be tesselated
+ // for this purpose the GLU standard functions are used
+ TessParams params = { currentManager, tessIntersects };
+ gluTessBeginPolygon( tesselator, &params );
+ gluTessBeginContour( tesselator );
+
+ boost::shared_array<GLdouble> points( new GLdouble[3 * aListSize] );
+ int v = 0;
+ const VECTOR2D* ptr = aPointList;
+
+ for( int i = 0; i < aListSize; ++i )
+ {
+ points[v] = ptr->x;
+ points[v + 1] = ptr->y;
+ points[v + 2] = layerDepth;
+ gluTessVertex( tesselator, &points[v], &points[v] );
+ ++ptr;
+ v += 3;
+ }
+
+ gluTessEndContour( tesselator );
+ gluTessEndPolygon( tesselator );
+
+ // Free allocated intersecting points
+ tessIntersects.clear();
+
+ // vertexList destroyed here
+}
+
+
+void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
+ const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint )
+{
+ // FIXME The drawing quality needs to be improved
+ // FIXME Perhaps choose a quad/triangle strip instead?
+ // FIXME Brute force method, use a better (recursive?) algorithm
+
+ std::deque<VECTOR2D> pointList;
+
+ double t = 0.0;
+ double dt = 1.0 / (double) CURVE_POINTS;
+
+ for( int i = 0; i <= CURVE_POINTS; i++ )
+ {
+ double omt = 1.0 - t;
+ double omt2 = omt * omt;
+ double omt3 = omt * omt2;
+ double t2 = t * t;
+ double t3 = t * t2;
+
+ VECTOR2D vertex = omt3 * aStartPoint + 3.0 * t * omt2 * aControlPointA
+ + 3.0 * t2 * omt * aControlPointB + t3 * aEndPoint;
+
+ pointList.push_back( vertex );
+
+ t += dt;
+ }
+
+ DrawPolyline( pointList );
+}
+
+
+void OPENGL_GAL::ResizeScreen( int aWidth, int aHeight )
+{
+ screenSize = VECTOR2I( aWidth, aHeight );
+
+#ifdef RETINA_OPENGL_PATCH
+ const float scaleFactor = GetBackingScaleFactor();
+#else
+ const float scaleFactor = 1.0f;
+#endif
+
+ // Resize framebuffers
+ compositor.Resize( aWidth * scaleFactor, aHeight * scaleFactor );
+ isFramebufferInitialized = false;
+
+ wxGLCanvas::SetSize( aWidth, aHeight );
+}
+
+
+bool OPENGL_GAL::Show( bool aShow )
+{
+ bool s = wxGLCanvas::Show( aShow );
+
+ if( aShow )
+ wxGLCanvas::Raise();
+
+ return s;
+}
+
+
+void OPENGL_GAL::Flush()
+{
+ glFlush();
+}
+
+
+void OPENGL_GAL::ClearScreen( const COLOR4D& aColor )
+{
+ // Clear screen
+ glClearColor( aColor.r, aColor.g, aColor.b, aColor.a );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+}
+
+
+void OPENGL_GAL::Transform( const MATRIX3x3D& aTransformation )
+{
+ GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
+
+ matrixData[0] = aTransformation.m_data[0][0];
+ matrixData[1] = aTransformation.m_data[1][0];
+ matrixData[2] = aTransformation.m_data[2][0];
+ matrixData[4] = aTransformation.m_data[0][1];
+ matrixData[5] = aTransformation.m_data[1][1];
+ matrixData[6] = aTransformation.m_data[2][1];
+ matrixData[12] = aTransformation.m_data[0][2];
+ matrixData[13] = aTransformation.m_data[1][2];
+ matrixData[14] = aTransformation.m_data[2][2];
+
+ glMultMatrixd( matrixData );
+}
+
+
+void OPENGL_GAL::Rotate( double aAngle )
+{
+ currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
+}
+
+
+void OPENGL_GAL::Translate( const VECTOR2D& aVector )
+{
+ currentManager->Translate( aVector.x, aVector.y, 0.0f );
+}
+
+
+void OPENGL_GAL::Scale( const VECTOR2D& aScale )
+{
+ currentManager->Scale( aScale.x, aScale.y, 0.0f );
+}
+
+
+void OPENGL_GAL::Save()
+{
+ currentManager->PushMatrix();
+}
+
+
+void OPENGL_GAL::Restore()
+{
+ currentManager->PopMatrix();
+}
+
+
+int OPENGL_GAL::BeginGroup()
+{
+ isGrouping = true;
+
+ boost::shared_ptr<VERTEX_ITEM> newItem( new VERTEX_ITEM( cachedManager ) );
+ int groupNumber = getNewGroupNumber();
+ groups.insert( std::make_pair( groupNumber, newItem ) );
+
+ return groupNumber;
+}
+
+
+void OPENGL_GAL::EndGroup()
+{
+ cachedManager.FinishItem();
+ isGrouping = false;
+}
+
+
+void OPENGL_GAL::DrawGroup( int aGroupNumber )
+{
+ cachedManager.DrawItem( *groups[aGroupNumber] );
+}
+
+
+void OPENGL_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
+{
+ cachedManager.ChangeItemColor( *groups[aGroupNumber], aNewColor );
+}
+
+
+void OPENGL_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
+{
+ cachedManager.ChangeItemDepth( *groups[aGroupNumber], aDepth );
+}
+
+
+void OPENGL_GAL::DeleteGroup( int aGroupNumber )
+{
+ // Frees memory in the container as well
+ groups.erase( aGroupNumber );
+}
+
+
+void OPENGL_GAL::ClearCache()
+{
+ groups.clear();
+ cachedManager.Clear();
+}
+
+
+void OPENGL_GAL::SaveScreen()
+{
+ wxASSERT_MSG( false, wxT( "Not implemented yet" ) );
+}
+
+
+void OPENGL_GAL::RestoreScreen()
+{
+ wxASSERT_MSG( false, wxT( "Not implemented yet" ) );
+}
+
+
+void OPENGL_GAL::SetTarget( RENDER_TARGET aTarget )
+{
+ switch( aTarget )
+ {
+ default:
+ case TARGET_CACHED:
+ currentManager = &cachedManager;
+ break;
+
+ case TARGET_NONCACHED:
+ currentManager = &nonCachedManager;
+ break;
+
+ case TARGET_OVERLAY:
+ currentManager = &overlayManager;
+ break;
+ }
+
+ currentTarget = aTarget;
+}
+
+
+RENDER_TARGET OPENGL_GAL::GetTarget() const
+{
+ return currentTarget;
+}
+
+
+void OPENGL_GAL::ClearTarget( RENDER_TARGET aTarget )
+{
+ // Save the current state
+ unsigned int oldTarget = compositor.GetBuffer();
+
+ switch( aTarget )
+ {
+ // Cached and noncached items are rendered to the same buffer
+ default:
+ case TARGET_CACHED:
+ case TARGET_NONCACHED:
+ compositor.SetBuffer( mainBuffer );
+ break;
+
+ case TARGET_OVERLAY:
+ compositor.SetBuffer( overlayBuffer );
+ break;
+ }
+
+ compositor.ClearBuffer();
+
+ // Restore the previous state
+ compositor.SetBuffer( oldTarget );
+}
+
+
+void OPENGL_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
+{
+ // Now we should only store the position of the mouse cursor
+ // The real drawing routines are in blitCursor()
+ VECTOR2D screenCursor = worldScreenMatrix * aCursorPosition;
+
+ cursorPosition = screenWorldMatrix * VECTOR2D( screenCursor.x, screenSize.y - screenCursor.y );
+}
+
+
+void OPENGL_GAL::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ compositor.SetBuffer( mainBuffer );
+
+ // We do not need a very precise comparison here (the lineWidth is set by GAL::DrawGrid())
+ if( fabs( lineWidth - 2.0 * gridLineWidth / worldScale ) < 0.1 )
+ glLineWidth( 1.0 );
+ else
+ glLineWidth( 2.0 );
+
+ glColor4d( gridColor.r, gridColor.g, gridColor.b, gridColor.a );
+
+ glBegin( GL_LINES );
+ glVertex3d( aStartPoint.x, aStartPoint.y, layerDepth );
+ glVertex3d( aEndPoint.x, aEndPoint.y, layerDepth );
+ glEnd();
+
+ // Restore the default color, so textures will be drawn properly
+ glColor4d( 1.0, 1.0, 1.0, 1.0 );
+}
+
+
+void OPENGL_GAL::drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
+{
+ /* Helper drawing: ____--- v3 ^
+ * ____---- ... \ \
+ * ____---- ... \ end \
+ * v1 ____---- ... ____---- \ width
+ * ---- ...___---- \ \
+ * \ ___...-- \ v
+ * \ ____----... ____---- v2
+ * ---- ... ____----
+ * start \ ... ____----
+ * \... ____----
+ * ----
+ * v0
+ * dots mark triangles' hypotenuses
+ */
+
+ VECTOR2D startEndVector = aEndPoint - aStartPoint;
+ double lineLength = startEndVector.EuclideanNorm();
+
+ if( lineLength <= 0.0 )
+ return;
+
+ double scale = 0.5 * lineWidth / lineLength;
+
+ // The perpendicular vector also needs transformations
+ glm::vec4 vector = currentManager->GetTransformation() *
+ glm::vec4( -startEndVector.y * scale, startEndVector.x * scale, 0.0, 0.0 );
+
+ // Line width is maintained by the vertex shader
+ currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
+ currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
+
+ currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
+ currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v1
+
+ currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
+ currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
+
+ currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
+ currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0
+
+ currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth );
+ currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3
+
+ currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth );
+ currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v2
+}
+
+
+void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle )
+{
+ if( isFillEnabled )
+ {
+ currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
+ drawFilledSemiCircle( aCenterPoint, aRadius, aAngle );
+ }
+
+ if( isStrokeEnabled )
+ {
+ currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a );
+ drawStrokedSemiCircle( aCenterPoint, aRadius, aAngle );
+ }
+}
+
+
+void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
+ double aAngle )
+{
+ Save();
+ 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
+ * 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]).
+ * Shader uses these coordinates to determine if fragments are inside the semicircle or not.
+ * v2
+ * /\
+ * /__\
+ * v0 //__\\ v1
+ */
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f );
+ currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
+
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f );
+ currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
+
+ currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f );
+ currentManager->Vertex( 0.0f, aRadius * 2.0f, layerDepth ); // v2
+
+ Restore();
+}
+
+
+void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius,
+ double aAngle )
+{
+ double outerRadius = aRadius + ( lineWidth / 2 );
+
+ Save();
+ 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
+ * 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
+ * radius and the line width. Shader uses these coordinates to determine if fragments are
+ * inside the semicircle or not.
+ * v2
+ * /\
+ * /__\
+ * v0 //__\\ v1
+ */
+ currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, lineWidth );
+ currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0
+
+ currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, lineWidth );
+ currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1
+
+ currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, lineWidth );
+ currentManager->Vertex( 0.0f, outerRadius * 2.0f, layerDepth ); // v2
+
+ Restore();
+}
+
+
+void OPENGL_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
+{
+ PostPaint();
+}
+
+
+void OPENGL_GAL::skipMouseEvent( wxMouseEvent& aEvent )
+{
+ // Post the mouse event to the event listener registered in constructor, if any
+ if( mouseListener )
+ wxPostEvent( mouseListener, aEvent );
+}
+
+
+void OPENGL_GAL::blitCursor()
+{
+ if( !isCursorEnabled )
+ return;
+
+ compositor.SetBuffer( OPENGL_COMPOSITOR::DIRECT_RENDERING );
+
+ VECTOR2D cursorBegin = cursorPosition - cursorSize / ( 2 * worldScale );
+ VECTOR2D cursorEnd = cursorPosition + cursorSize / ( 2 * worldScale );
+ VECTOR2D cursorCenter = ( cursorBegin + cursorEnd ) / 2;
+
+ glDisable( GL_TEXTURE_2D );
+ glLineWidth( 1.0 );
+ glColor4d( cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a );
+
+ glBegin( GL_LINES );
+ glVertex2d( cursorCenter.x, cursorBegin.y );
+ glVertex2d( cursorCenter.x, cursorEnd.y );
+
+ glVertex2d( cursorBegin.x, cursorCenter.y );
+ glVertex2d( cursorEnd.x, cursorCenter.y );
+ glEnd();
+}
+
+
+unsigned int OPENGL_GAL::getNewGroupNumber()
+{
+ wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(),
+ wxT( "There are no free slots to store a group" ) );
+
+ while( groups.find( groupCounter ) != groups.end() )
+ {
+ groupCounter++;
+ }
+
+ return groupCounter++;
+}
+
+
+bool OPENGL_GAL::runTest()
+{
+ wxDialog dlgtest( GetParent(), -1, wxT( "opengl test" ), wxPoint( 50, 50 ),
+ wxDLG_UNIT( GetParent(), wxSize( 50, 50 ) ) );
+ OPENGL_TEST* test = new OPENGL_TEST( &dlgtest, this );
+
+ dlgtest.Raise(); // on Linux, on some windows managers (Unity for instance) this is needed to actually show the dialog
+ dlgtest.ShowModal();
+ bool result = test->IsOk();
+
+ if( !result )
+ throw std::runtime_error( test->GetError() );
+
+ return result;
+}
+
+
+OPENGL_GAL::OPENGL_TEST::OPENGL_TEST( wxDialog* aParent, OPENGL_GAL* aGal ) :
+ wxGLCanvas( aParent, wxID_ANY, glAttributes, wxDefaultPosition,
+ wxDefaultSize, 0, wxT( "GLCanvas" ) ),
+ m_parent( aParent ), m_gal( aGal ), m_tested( false ), m_result( false )
+{
+ m_timeoutTimer.SetOwner( this );
+ Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) );
+ Connect( wxEVT_TIMER, wxTimerEventHandler( OPENGL_GAL::OPENGL_TEST::OnTimeout ) );
+ m_parent->Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::OnDialogPaint ), NULL, this );
+}
+
+
+void OPENGL_GAL::OPENGL_TEST::Render( wxPaintEvent& WXUNUSED( aEvent ) )
+{
+ if( !m_tested )
+ {
+ if( !IsShownOnScreen() )
+ return;
+
+ m_timeoutTimer.Stop();
+ m_result = true; // Assume everything is fine, until proven otherwise
+
+ // One test is enough - close the testing dialog when the test is finished
+ Disconnect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) );
+ CallAfter( boost::bind( &wxDialog::EndModal, m_parent, wxID_NONE ) );
+
+ SetCurrent( *OPENGL_GAL::glContext );
+ GLenum err = glewInit();
+
+ if( GLEW_OK != err )
+ {
+ error( (const char*) glewGetErrorString( err ) );
+ return;
+ }
+ else
+ {
+ wxLogDebug( wxString( wxT( "Status: Using GLEW " ) ) +
+ FROM_UTF8( (char*) glewGetString( GLEW_VERSION ) ) );
+ }
+
+ // Check the OpenGL version (minimum 2.1 is required)
+ if( GLEW_VERSION_2_1 )
+ {
+ wxLogInfo( wxT( "OpenGL 2.1 supported." ) );
+ }
+ else
+ {
+ error( "OpenGL 2.1 or higher is required!" );
+ return;
+ }
+
+ // Framebuffers have to be supported
+ if( !GLEW_EXT_framebuffer_object )
+ {
+ error( "Framebuffer objects are not supported!" );
+ return;
+ }
+
+ // Vertex buffer has to be supported
+ if( !GLEW_ARB_vertex_buffer_object )
+ {
+ error( "Vertex buffer objects are not supported!" );
+ return;
+ }
+
+ // Prepare shaders
+ if( !m_gal->shader.LoadBuiltinShader( 0, SHADER_TYPE_VERTEX ) )
+ {
+ error( "Cannot compile vertex shader!" );
+ return;
+ }
+
+ if( !m_gal->shader.LoadBuiltinShader( 1, SHADER_TYPE_FRAGMENT ) )
+ {
+ error( "Cannot compile fragment shader!" );
+ return;
+ }
+
+ if( !m_gal->shader.Link() )
+ {
+ error( "Cannot link the shaders!" );
+ return;
+ }
+
+ m_tested = true;
+ }
+}
+
+
+void OPENGL_GAL::OPENGL_TEST::OnTimeout( wxTimerEvent& aEvent )
+{
+ error( "Could not create OpenGL canvas" );
+ m_parent->EndModal( wxID_NONE );
+}
+
+
+void OPENGL_GAL::OPENGL_TEST::OnDialogPaint( wxPaintEvent& aEvent )
+{
+ // GL canvas may never appear on the screen (e.g. due to missing GL extensions), and the test
+ // will not be run. Therefore give at most a second to perform the test, otherwise we conclude
+ // it has failed.
+ // Also, wxWidgets OnShow event is triggered before a window is shown, therefore here we use
+ // OnPaint event, which is executed when a window is actually visible.
+ m_timeoutTimer.StartOnce( 1000 );
+}
+
+
+void OPENGL_GAL::OPENGL_TEST::error( const std::string& aError )
+{
+ m_timeoutTimer.Stop();
+ m_result = false;
+ m_tested = true;
+ m_error = aError;
+}
+
+// ------------------------------------- // Callback functions for the tesselator // ------------------------------------- // Compare Redbook Chapter 11
+void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
+{
+ GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr );
+ OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
+ VERTEX_MANAGER* vboManager = param->vboManager;
+
+ if( vboManager )
+ vboManager->Vertex( vertex[0], vertex[1], vertex[2] );
+}
+
+
+void CALLBACK CombineCallback( GLdouble coords[3],
+ GLdouble* vertex_data[4],
+ GLfloat weight[4], GLdouble** dataOut, void* aData )
+{
+ GLdouble* vertex = new GLdouble[3];
+ OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
+
+ // Save the pointer so we can delete it later
+ param->intersectPoints.push_back( boost::shared_array<GLdouble>( vertex ) );
+
+ memcpy( vertex, coords, 3 * sizeof(GLdouble) );
+
+ *dataOut = vertex;
+}
+
+
+void CALLBACK EdgeCallback( GLboolean aEdgeFlag )
+{
+ // This callback is needed to force GLU tesselator to use triangles only
+}
+
+
+void CALLBACK ErrorCallback( GLenum aErrorCode )
+{
+ //throw std::runtime_error( std::string( "Tessellation error: " ) +
+ //std::string( (const char*) gluErrorString( aErrorCode ) );
+}
+
+
+static void InitTesselatorCallbacks( GLUtesselator* aTesselator )
+{
+ gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, ( void (CALLBACK*)() )VertexCallback );
+ gluTessCallback( aTesselator, GLU_TESS_COMBINE_DATA, ( void (CALLBACK*)() )CombineCallback );
+ gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, ( void (CALLBACK*)() )EdgeCallback );
+ gluTessCallback( aTesselator, GLU_TESS_ERROR, ( void (CALLBACK*)() )ErrorCallback );
+}
diff --git a/common/gal/opengl/shader.cpp b/common/gal/opengl/shader.cpp
new file mode 100644
index 0000000..228d53d
--- /dev/null
+++ b/common/gal/opengl/shader.cpp
@@ -0,0 +1,272 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ *
+ * Graphics Abstraction Layer (GAL) for OpenGL
+ *
+ * Shader class
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+
+#include <cstring>
+#include <cassert>
+
+#include <gal/opengl/shader.h>
+#include "shader_src.h"
+
+using namespace KIGFX;
+
+SHADER::SHADER() :
+ isProgramCreated( false ),
+ isShaderLinked( false ),
+ active( false ),
+ maximumVertices( 4 ),
+ geomInputType( GL_LINES ),
+ geomOutputType( GL_LINES )
+
+{
+ // Do not have uninitialized members:
+ programNumber = 0;
+}
+
+
+SHADER::~SHADER()
+{
+ if( isProgramCreated )
+ {
+ // Delete the shaders and the program
+ for( std::deque<GLuint>::iterator it = shaderNumbers.begin(); it != shaderNumbers.end();
+ ++it )
+ {
+ glDeleteShader( *it );
+ }
+
+ glDeleteProgram( programNumber );
+ }
+}
+
+
+bool SHADER::LoadBuiltinShader( unsigned int aShaderNumber, SHADER_TYPE aShaderType )
+{
+ if( aShaderNumber >= shaders_number )
+ return false;
+
+ return addSource( std::string( shaders_src[aShaderNumber] ), aShaderType );
+}
+
+
+bool SHADER::LoadShaderFromFile( const std::string& aShaderSourceName, SHADER_TYPE aShaderType )
+{
+ // Load shader sources
+ const std::string shaderSource = readSource( aShaderSourceName );
+
+ return addSource( shaderSource, aShaderType );
+}
+
+
+void SHADER::ConfigureGeometryShader( GLuint maxVertices, GLuint geometryInputType,
+ GLuint geometryOutputType )
+{
+ maximumVertices = maxVertices;
+ geomInputType = geometryInputType;
+ geomOutputType = geometryOutputType;
+}
+
+
+bool SHADER::Link()
+{
+ // Shader linking
+ glLinkProgram( programNumber );
+ programInfo( programNumber );
+
+ // Check the Link state
+ glGetObjectParameterivARB( programNumber, GL_OBJECT_LINK_STATUS_ARB,
+ (GLint*) &isShaderLinked );
+
+#ifdef DEBUG
+ if( !isShaderLinked )
+ {
+ int maxLength;
+ glGetProgramiv( programNumber, GL_INFO_LOG_LENGTH, &maxLength );
+ maxLength = maxLength + 1;
+ char* linkInfoLog = new char[maxLength];
+ glGetProgramInfoLog( programNumber, maxLength, &maxLength, linkInfoLog );
+ std::cerr << "Shader linking error:" << std::endl;
+ std::cerr << linkInfoLog;
+ delete[] linkInfoLog;
+ }
+#endif /* DEBUG */
+
+ return isShaderLinked;
+}
+
+
+int SHADER::AddParameter( const std::string& aParameterName )
+{
+ GLint location = glGetUniformLocation( programNumber, aParameterName.c_str() );
+
+ if( location != -1 )
+ parameterLocation.push_back( location );
+
+ return location;
+}
+
+
+void SHADER::SetParameter( int parameterNumber, float value ) const
+{
+ glUniform1f( parameterLocation[parameterNumber], value );
+}
+
+
+void SHADER::SetParameter( int parameterNumber, int value ) const
+{
+ glUniform1i( parameterLocation[parameterNumber], value );
+}
+
+
+int SHADER::GetAttribute( std::string aAttributeName ) const
+{
+ return glGetAttribLocation( programNumber, aAttributeName.c_str() );
+}
+
+
+void SHADER::programInfo( GLuint aProgram )
+{
+ GLint glInfoLogLength = 0;
+ GLint writtenChars = 0;
+
+ // Get the length of the info string
+ glGetProgramiv( aProgram, GL_INFO_LOG_LENGTH, &glInfoLogLength );
+
+ // Print the information
+ if( glInfoLogLength > 2 )
+ {
+ GLchar* glInfoLog = new GLchar[glInfoLogLength];
+ glGetProgramInfoLog( aProgram, glInfoLogLength, &writtenChars, glInfoLog );
+
+ std::cerr << glInfoLog << std::endl;
+
+ delete[] glInfoLog;
+ }
+}
+
+
+void SHADER::shaderInfo( GLuint aShader )
+{
+ GLint glInfoLogLength = 0;
+ GLint writtenChars = 0;
+
+ // Get the length of the info string
+ glGetShaderiv( aShader, GL_INFO_LOG_LENGTH, &glInfoLogLength );
+
+ // Print the information
+ if( glInfoLogLength > 2 )
+ {
+ GLchar* glInfoLog = new GLchar[glInfoLogLength];
+ glGetShaderInfoLog( aShader, glInfoLogLength, &writtenChars, glInfoLog );
+
+ std::cerr << glInfoLog << std::endl;
+
+ delete[] glInfoLog;
+ }
+}
+
+
+std::string SHADER::readSource( std::string aShaderSourceName )
+{
+ // Open the shader source for reading
+ std::ifstream inputFile( aShaderSourceName.c_str(), std::ifstream::in );
+ std::string shaderSource;
+
+ if( !inputFile )
+ throw std::runtime_error( "Can't read the shader source: " + aShaderSourceName );
+
+ std::string shaderSourceLine;
+
+ // Read all lines from the text file
+ while( getline( inputFile, shaderSourceLine ) )
+ {
+ shaderSource += shaderSourceLine;
+ shaderSource += "\n";
+ }
+
+ return shaderSource;
+}
+
+
+bool SHADER::addSource( const std::string& aShaderSource, SHADER_TYPE aShaderType )
+{
+ assert( !isShaderLinked );
+
+ // Create the program
+ if( !isProgramCreated )
+ {
+ programNumber = glCreateProgram();
+ isProgramCreated = true;
+ }
+
+ // Create a shader
+ GLuint shaderNumber = glCreateShader( aShaderType );
+ shaderNumbers.push_back( shaderNumber );
+
+ // Get the program info
+ programInfo( programNumber );
+
+ // Copy to char array
+ char* source = new char[aShaderSource.size() + 1];
+ strncpy( source, aShaderSource.c_str(), aShaderSource.size() + 1 );
+ const char** source_ = (const char**) ( &source );
+
+ // Attach the source
+ glShaderSource( shaderNumber, 1, source_, NULL );
+ programInfo( programNumber );
+
+ // Delete the allocated char array
+ delete[] source;
+
+ // Compile and attach shader to the program
+ glCompileShader( shaderNumber );
+ GLint status;
+ glGetShaderiv( shaderNumber, GL_COMPILE_STATUS, &status );
+
+ if( status != GL_TRUE )
+ {
+ shaderInfo( shaderNumber );
+ throw std::runtime_error( "Shader compilation error" );
+ }
+
+ glAttachShader( programNumber, shaderNumber );
+ programInfo( programNumber );
+
+ // Special handling for the geometry shader
+ if( aShaderType == SHADER_TYPE_GEOMETRY )
+ {
+ glProgramParameteriEXT( programNumber, GL_GEOMETRY_VERTICES_OUT_EXT, maximumVertices );
+ glProgramParameteriEXT( programNumber, GL_GEOMETRY_INPUT_TYPE_EXT, geomInputType );
+ glProgramParameteriEXT( programNumber, GL_GEOMETRY_OUTPUT_TYPE_EXT, geomOutputType );
+ }
+
+ return true;
+}
diff --git a/common/gal/opengl/shader.frag b/common/gal/opengl/shader.frag
new file mode 100644
index 0000000..7d7d96c
--- /dev/null
+++ b/common/gal/opengl/shader.frag
@@ -0,0 +1,75 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * Fragment shader
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#version 120
+
+// Shader types
+const float SHADER_LINE = 1.0;
+const float SHADER_FILLED_CIRCLE = 2.0;
+const float SHADER_STROKED_CIRCLE = 3.0;
+
+varying vec4 shaderParams;
+varying vec2 circleCoords;
+
+void filledCircle( vec2 aCoord )
+{
+ if( dot( aCoord, aCoord ) < 1.0 )
+ gl_FragColor = gl_Color;
+ else
+ discard;
+}
+
+
+void strokedCircle( vec2 aCoord, float aRadius, float aWidth )
+{
+ float outerRadius = aRadius + ( aWidth / 2 );
+ float innerRadius = aRadius - ( aWidth / 2 );
+ float relWidth = innerRadius / outerRadius;
+
+ if( ( dot( aCoord, aCoord ) < 1.0 ) &&
+ ( dot( aCoord, aCoord ) > relWidth * relWidth ) )
+ gl_FragColor = gl_Color;
+ else
+ discard;
+}
+
+
+void main()
+{
+ if( shaderParams[0] == SHADER_FILLED_CIRCLE )
+ {
+ filledCircle( circleCoords );
+ }
+ else if( shaderParams[0] == SHADER_STROKED_CIRCLE )
+ {
+ strokedCircle( circleCoords, shaderParams[2], shaderParams[3] );
+ }
+ else
+ {
+ // Simple pass-through
+ gl_FragColor = gl_Color;
+ }
+}
diff --git a/common/gal/opengl/shader.vert b/common/gal/opengl/shader.vert
new file mode 100644
index 0000000..24521a4
--- /dev/null
+++ b/common/gal/opengl/shader.vert
@@ -0,0 +1,95 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * Vertex shader
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#version 120
+
+// Shader types
+const float SHADER_LINE = 1.0;
+const float SHADER_FILLED_CIRCLE = 2.0;
+const float SHADER_STROKED_CIRCLE = 3.0;
+
+// Minimum line width
+const float MIN_WIDTH = 1.0;
+
+attribute vec4 attrShaderParams;
+varying vec4 shaderParams;
+varying vec2 circleCoords;
+
+void main()
+{
+ // Pass attributes to the fragment shader
+ shaderParams = attrShaderParams;
+
+ if( shaderParams[0] == SHADER_LINE )
+ {
+ float lineWidth = shaderParams[3];
+ float worldScale = gl_ModelViewMatrix[0][0];
+
+ // Make lines appear to be at least 1 pixel wide
+ if( worldScale * lineWidth < MIN_WIDTH )
+ gl_Position = gl_ModelViewProjectionMatrix *
+ ( gl_Vertex + vec4( shaderParams.yz * MIN_WIDTH / ( worldScale * lineWidth ), 0.0, 0.0 ) );
+ else
+ gl_Position = gl_ModelViewProjectionMatrix *
+ ( gl_Vertex + vec4( shaderParams.yz, 0.0, 0.0 ) );
+ }
+ else if( ( shaderParams[0] == SHADER_STROKED_CIRCLE ) ||
+ ( shaderParams[0] == SHADER_FILLED_CIRCLE ) )
+ {
+ // Compute relative circle coordinates basing on indices
+ // Circle
+ if( shaderParams[1] == 1.0 )
+ circleCoords = vec2( -sqrt( 3.0 ), -1.0 );
+ else if( shaderParams[1] == 2.0 )
+ circleCoords = vec2( sqrt( 3.0 ), -1.0 );
+ else if( shaderParams[1] == 3.0 )
+ circleCoords = vec2( 0.0, 2.0 );
+
+ // Semicircle
+ else if( shaderParams[1] == 4.0 )
+ circleCoords = vec2( -3.0 / sqrt( 3.0 ), 0.0 );
+ else if( shaderParams[1] == 5.0 )
+ circleCoords = vec2( 3.0 / sqrt( 3.0 ), 0.0 );
+ else if( shaderParams[1] == 6.0 )
+ circleCoords = vec2( 0.0, 2.0 );
+
+ // Make the line appear to be at least 1 pixel wide
+ float lineWidth = shaderParams[3];
+ float worldScale = gl_ModelViewMatrix[0][0];
+
+ if( worldScale * lineWidth < MIN_WIDTH )
+ shaderParams[3] = shaderParams[3] / ( worldScale * lineWidth );
+
+ gl_Position = ftransform();
+ }
+ else
+ {
+ // Pass through the coordinates like in the fixed pipeline
+ gl_Position = ftransform();
+ }
+
+ gl_FrontColor = gl_Color;
+}
diff --git a/common/gal/opengl/vertex_container.cpp b/common/gal/opengl/vertex_container.cpp
new file mode 100644
index 0000000..fa41ecb
--- /dev/null
+++ b/common/gal/opengl/vertex_container.cpp
@@ -0,0 +1,60 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file vertex_container.cpp
+ * @brief Class to store vertices and handle transfers between system memory and GPU memory.
+ */
+
+#include <gal/opengl/vertex_container.h>
+#include <gal/opengl/cached_container.h>
+#include <gal/opengl/noncached_container.h>
+#include <gal/opengl/shader.h>
+#include <cstdlib>
+#include <cstring>
+
+using namespace KIGFX;
+
+VERTEX_CONTAINER* VERTEX_CONTAINER::MakeContainer( bool aCached )
+{
+ if( aCached )
+ return new CACHED_CONTAINER;
+ else
+ return new NONCACHED_CONTAINER;
+}
+
+
+VERTEX_CONTAINER::VERTEX_CONTAINER( unsigned int aSize ) :
+ m_freeSpace( aSize ), m_currentSize( aSize ), m_initialSize( aSize ),
+ m_failed( false ), m_dirty( true )
+{
+ m_vertices = static_cast<VERTEX*>( malloc( aSize * sizeof( VERTEX ) ) );
+ memset( m_vertices, 0x00, aSize * sizeof( VERTEX ) );
+}
+
+
+VERTEX_CONTAINER::~VERTEX_CONTAINER()
+{
+ free( m_vertices );
+}
diff --git a/common/gal/opengl/vertex_item.cpp b/common/gal/opengl/vertex_item.cpp
new file mode 100644
index 0000000..a6fd352
--- /dev/null
+++ b/common/gal/opengl/vertex_item.cpp
@@ -0,0 +1,53 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file vertex_item.cpp
+ * @brief Class to handle an item held in a container.
+ */
+
+#include <gal/opengl/vertex_item.h>
+#include <gal/opengl/vertex_manager.h>
+#include <cstring>
+
+using namespace KIGFX;
+
+VERTEX_ITEM::VERTEX_ITEM( const VERTEX_MANAGER& aManager ) :
+ m_manager( aManager ), m_offset( 0 ), m_size( 0 )
+{
+ // As the item is created, we are going to modify it, so call to SetItem() is needed
+ m_manager.SetItem( *this );
+}
+
+
+VERTEX_ITEM::~VERTEX_ITEM()
+{
+ m_manager.FreeItem( *this );
+}
+
+
+VERTEX* VERTEX_ITEM::GetVertices() const
+{
+ return m_manager.GetVertices( *this );
+}
diff --git a/common/gal/opengl/vertex_manager.cpp b/common/gal/opengl/vertex_manager.cpp
new file mode 100644
index 0000000..d055b52
--- /dev/null
+++ b/common/gal/opengl/vertex_manager.cpp
@@ -0,0 +1,234 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file vertex_manager.cpp
+ * @brief Class to control vertex container and GPU with possibility of emulating old-style OpenGL
+ * 1.0 state machine using modern OpenGL methods.
+ */
+
+#include <gal/opengl/vertex_manager.h>
+#include <gal/opengl/cached_container.h>
+#include <gal/opengl/noncached_container.h>
+#include <gal/opengl/gpu_manager.h>
+#include <gal/opengl/vertex_item.h>
+#include <confirm.h>
+
+using namespace KIGFX;
+
+VERTEX_MANAGER::VERTEX_MANAGER( bool aCached ) :
+ m_noTransform( true ), m_transform( 1.0f )
+{
+ m_container.reset( VERTEX_CONTAINER::MakeContainer( aCached ) );
+ m_gpu.reset( GPU_MANAGER::MakeManager( m_container.get() ) );
+
+ // There is no shader used by default
+ for( unsigned int i = 0; i < ShaderStride; ++i )
+ m_shader[i] = 0.0f;
+}
+
+
+void VERTEX_MANAGER::Vertex( GLfloat aX, GLfloat aY, GLfloat aZ ) const
+{
+ // flag to avoid hanging by calling DisplayError too many times:
+ static bool show_err = true;
+
+ // Obtain the pointer to the vertex in the currently used container
+ VERTEX* newVertex = m_container->Allocate( 1 );
+
+ if( newVertex == NULL )
+ {
+ if( show_err )
+ {
+ DisplayError( NULL, wxT( "VERTEX_MANAGER::Vertex: Vertex allocation error" ) );
+ show_err = false;
+ }
+
+ return;
+ }
+
+ putVertex( *newVertex, aX, aY, aZ );
+}
+
+
+void VERTEX_MANAGER::Vertices( const VERTEX aVertices[], unsigned int aSize ) const
+{
+ // flag to avoid hanging by calling DisplayError too many times:
+ static bool show_err = true;
+
+ // Obtain pointer to the vertex in currently used container
+ VERTEX* newVertex = m_container->Allocate( aSize );
+
+ if( newVertex == NULL )
+ {
+ if( show_err )
+ {
+ DisplayError( NULL, wxT( "VERTEX_MANAGER::Vertices: Vertex allocation error" ) );
+ show_err = false;
+ }
+
+ return;
+ }
+
+ // Put vertices in already allocated memory chunk
+ for( unsigned int i = 0; i < aSize; ++i )
+ {
+ putVertex( newVertex[i], aVertices[i].x, aVertices[i].y, aVertices[i].z );
+ }
+}
+
+
+void VERTEX_MANAGER::SetItem( VERTEX_ITEM& aItem ) const
+{
+ m_container->SetItem( &aItem );
+}
+
+
+void VERTEX_MANAGER::FinishItem() const
+{
+ m_container->FinishItem();
+}
+
+
+void VERTEX_MANAGER::FreeItem( VERTEX_ITEM& aItem ) const
+{
+ m_container->Delete( &aItem );
+}
+
+
+void VERTEX_MANAGER::ChangeItemColor( const VERTEX_ITEM& aItem, const COLOR4D& aColor ) const
+{
+ unsigned int size = aItem.GetSize();
+ unsigned int offset = aItem.GetOffset();
+
+ VERTEX* vertex = m_container->GetVertices( offset );
+
+ for( unsigned int i = 0; i < size; ++i )
+ {
+ vertex->r = aColor.r * 255.0;
+ vertex->g = aColor.g * 255.0;
+ vertex->b = aColor.b * 255.0;
+ vertex->a = aColor.a * 255.0;
+ vertex++;
+ }
+
+ m_container->SetDirty();
+}
+
+
+void VERTEX_MANAGER::ChangeItemDepth( const VERTEX_ITEM& aItem, GLfloat aDepth ) const
+{
+ unsigned int size = aItem.GetSize();
+ unsigned int offset = aItem.GetOffset();
+
+ VERTEX* vertex = m_container->GetVertices( offset );
+
+ for( unsigned int i = 0; i < size; ++i )
+ {
+ vertex->z = aDepth;
+ vertex++;
+ }
+
+ m_container->SetDirty();
+}
+
+
+VERTEX* VERTEX_MANAGER::GetVertices( const VERTEX_ITEM& aItem ) const
+{
+ if( aItem.GetSize() == 0 )
+ return NULL; // The item is not stored in the container
+
+ return m_container->GetVertices( aItem.GetOffset() );
+}
+
+
+void VERTEX_MANAGER::SetShader( SHADER& aShader ) const
+{
+ m_gpu->SetShader( aShader );
+}
+
+
+void VERTEX_MANAGER::Clear() const
+{
+ m_container->Clear();
+}
+
+
+void VERTEX_MANAGER::BeginDrawing() const
+{
+ m_gpu->BeginDrawing();
+}
+
+
+void VERTEX_MANAGER::DrawItem( const VERTEX_ITEM& aItem ) const
+{
+ int size = aItem.GetSize();
+
+ if( size > 0 )
+ {
+ int offset = aItem.GetOffset();
+ m_gpu->DrawIndices( offset, size );
+ }
+}
+
+
+void VERTEX_MANAGER::EndDrawing() const
+{
+ m_gpu->EndDrawing();
+}
+
+
+void VERTEX_MANAGER::putVertex( VERTEX& aTarget, GLfloat aX, GLfloat aY, GLfloat aZ ) const
+{
+ // Modify the vertex according to the currently used transformations
+ if( m_noTransform )
+ {
+ // Simply copy coordinates, when the transform matrix is the identity matrix
+ aTarget.x = aX;
+ aTarget.y = aY;
+ aTarget.z = aZ;
+ }
+ else
+ {
+ // Apply transformations
+ glm::vec4 transVertex( aX, aY, aZ, 1.0f );
+ transVertex = m_transform * transVertex;
+
+ aTarget.x = transVertex.x;
+ aTarget.y = transVertex.y;
+ aTarget.z = transVertex.z;
+ }
+
+ // Apply currently used color
+ aTarget.r = m_color[0];
+ aTarget.g = m_color[1];
+ aTarget.b = m_color[2];
+ aTarget.a = m_color[3];
+
+ // Apply currently used shader
+ for( unsigned int j = 0; j < ShaderStride; ++j )
+ {
+ aTarget.shader[j] = m_shader[j];
+ }
+}
diff --git a/common/gal/stroke_font.cpp b/common/gal/stroke_font.cpp
new file mode 100644
index 0000000..636f75e
--- /dev/null
+++ b/common/gal/stroke_font.cpp
@@ -0,0 +1,403 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
+ * Copyright (C) 2013 CERN
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * Stroke font class
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <gal/stroke_font.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <wx/string.h>
+
+using namespace KIGFX;
+
+const double STROKE_FONT::INTERLINE_PITCH_RATIO = 1.5;
+const double STROKE_FONT::OVERBAR_HEIGHT = 1.22;
+const double STROKE_FONT::BOLD_FACTOR = 1.3;
+const double STROKE_FONT::STROKE_FONT_SCALE = 1.0 / 21.0;
+const double STROKE_FONT::ITALIC_TILT = 1.0 / 8;
+
+STROKE_FONT::STROKE_FONT( GAL* aGal ) :
+ m_gal( aGal ),
+ m_bold( false ),
+ m_italic( false ),
+ m_mirrored( false ),
+ m_overbar( false )
+{
+ // Default values
+ m_glyphSize = VECTOR2D( 10.0, 10.0 );
+ m_verticalJustify = GR_TEXT_VJUSTIFY_BOTTOM;
+ m_horizontalJustify = GR_TEXT_HJUSTIFY_LEFT;
+}
+
+
+bool STROKE_FONT::LoadNewStrokeFont( const char* const aNewStrokeFont[], int aNewStrokeFontSize )
+{
+ m_glyphs.clear();
+ m_glyphBoundingBoxes.clear();
+ m_glyphs.resize( aNewStrokeFontSize );
+ m_glyphBoundingBoxes.resize( aNewStrokeFontSize );
+
+ for( int j = 0; j < aNewStrokeFontSize; j++ )
+ {
+ GLYPH glyph;
+ double glyphStartX = 0.0;
+ double glyphEndX = 0.0;
+ VECTOR2D glyphBoundingX;
+
+ std::deque<VECTOR2D> pointList;
+
+ int i = 0;
+
+ while( aNewStrokeFont[j][i] )
+ {
+ VECTOR2D point( 0.0, 0.0 );
+ char coordinate[2] = { 0, };
+
+ for( int k = 0; k < 2; k++ )
+ {
+ coordinate[k] = aNewStrokeFont[j][i + k];
+ }
+
+ if( i < 2 )
+ {
+ // The first two values contain the width of the char
+ glyphStartX = ( coordinate[0] - 'R' ) * STROKE_FONT_SCALE;
+ glyphEndX = ( coordinate[1] - 'R' ) * STROKE_FONT_SCALE;
+ glyphBoundingX = VECTOR2D( 0, glyphEndX - glyphStartX );
+ }
+ else if( ( coordinate[0] == ' ' ) && ( coordinate[1] == 'R' ) )
+ {
+ // Raise pen
+ if( pointList.size() > 0 )
+ glyph.push_back( pointList );
+
+ pointList.clear();
+ }
+ else
+ {
+ // Every coordinate description of the Hershey format has an offset,
+ // it has to be subtracted
+ point.x = (double) ( coordinate[0] - 'R' ) * STROKE_FONT_SCALE - glyphStartX;
+ // -10 is here to keep GAL rendering consistent with the legacy gfx stuff
+ point.y = (double) ( coordinate[1] - 'R' - 10) * STROKE_FONT_SCALE;
+ pointList.push_back( point );
+ }
+
+ i += 2;
+ }
+
+ if( pointList.size() > 0 )
+ glyph.push_back( pointList );
+
+ m_glyphs[j] = glyph;
+
+ // Compute the bounding box of the glyph
+ m_glyphBoundingBoxes[j] = computeBoundingBox( glyph, glyphBoundingX );
+ }
+
+ return true;
+}
+
+
+int STROKE_FONT::getInterline() const
+{
+ return KiROUND( m_glyphSize.y * INTERLINE_PITCH_RATIO ) + m_gal->GetLineWidth();
+}
+
+
+BOX2D STROKE_FONT::computeBoundingBox( const GLYPH& aGLYPH, const VECTOR2D& aGLYPHBoundingX ) const
+{
+ BOX2D boundingBox;
+
+ std::deque<VECTOR2D> boundingPoints;
+
+ boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.x, 0 ) );
+ boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.y, 0 ) );
+
+ for( GLYPH::const_iterator pointListIt = aGLYPH.begin(); pointListIt != aGLYPH.end(); ++pointListIt )
+ {
+ for( std::deque<VECTOR2D>::const_iterator pointIt = pointListIt->begin();
+ pointIt != pointListIt->end(); ++pointIt )
+ {
+ boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.x, pointIt->y ) );
+ }
+ }
+
+ boundingBox.Compute( boundingPoints );
+
+ return boundingBox;
+}
+
+
+void STROKE_FONT::Draw( const UTF8& aText, const VECTOR2D& aPosition, double aRotationAngle )
+{
+ if( aText.empty() )
+ return;
+
+ // Context needs to be saved before any transformations
+ m_gal->Save();
+
+ m_gal->Translate( aPosition );
+ m_gal->Rotate( -aRotationAngle );
+
+ // Single line height
+ int lineHeight = getInterline( );
+ int lineCount = linesCount( aText );
+
+ // align the 1st line of text
+ switch( m_verticalJustify )
+ {
+ case GR_TEXT_VJUSTIFY_TOP:
+ m_gal->Translate( VECTOR2D( 0, m_glyphSize.y ) );
+ break;
+
+ case GR_TEXT_VJUSTIFY_CENTER:
+ m_gal->Translate( VECTOR2D( 0, m_glyphSize.y / 2.0 ) );
+ break;
+
+ case GR_TEXT_VJUSTIFY_BOTTOM:
+ break;
+
+ default:
+ break;
+ }
+
+ if( lineCount > 1 )
+ {
+ switch( m_verticalJustify )
+ {
+ case GR_TEXT_VJUSTIFY_TOP:
+ break;
+
+ case GR_TEXT_VJUSTIFY_CENTER:
+ m_gal->Translate( VECTOR2D(0, -( lineCount - 1 ) * lineHeight / 2) );
+ break;
+
+ case GR_TEXT_VJUSTIFY_BOTTOM:
+ m_gal->Translate( VECTOR2D(0, -( lineCount - 1 ) * lineHeight ) );
+ break;
+ }
+ }
+
+ m_gal->SetIsStroke( true );
+ m_gal->SetIsFill( false );
+
+ if( m_bold )
+ m_gal->SetLineWidth( m_gal->GetLineWidth() * BOLD_FACTOR );
+
+ // Split multiline strings into separate ones and draw them line by line
+ size_t begin = 0;
+ size_t newlinePos = aText.find( '\n' );
+
+ while( newlinePos != aText.npos )
+ {
+ size_t length = newlinePos - begin;
+
+ drawSingleLineText( aText.substr( begin, length ) );
+ m_gal->Translate( VECTOR2D( 0.0, lineHeight ) );
+
+ begin = newlinePos + 1;
+ newlinePos = aText.find( '\n', begin );
+ }
+
+ // Draw the last (or the only one) line
+ if( !aText.empty() )
+ drawSingleLineText( aText.substr( begin ) );
+
+ m_gal->Restore();
+}
+
+
+void STROKE_FONT::drawSingleLineText( const UTF8& aText )
+{
+ // By default the overbar is turned off
+ m_overbar = false;
+
+ double xOffset;
+ VECTOR2D glyphSize( m_glyphSize );
+ double overbar_italic_comp = 0.0;
+
+ // Compute the text size
+ VECTOR2D textSize = computeTextSize( aText );
+
+ m_gal->Save();
+
+ // Adjust the text position to the given alignment
+ switch( m_horizontalJustify )
+ {
+ case GR_TEXT_HJUSTIFY_CENTER:
+ m_gal->Translate( VECTOR2D( -textSize.x / 2.0, 0 ) );
+ break;
+
+ case GR_TEXT_HJUSTIFY_RIGHT:
+ if( !m_mirrored )
+ m_gal->Translate( VECTOR2D( -textSize.x, 0 ) );
+ break;
+
+ case GR_TEXT_HJUSTIFY_LEFT:
+ if( m_mirrored )
+ m_gal->Translate( VECTOR2D( -textSize.x, 0 ) );
+ break;
+
+ default:
+ break;
+ }
+
+ if( m_mirrored )
+ {
+ // In case of mirrored text invert the X scale of points and their X direction
+ // (m_glyphSize.x) and start drawing from the position where text normally should end
+ // (textSize.x)
+ xOffset = textSize.x;
+ glyphSize.x = -m_glyphSize.x;
+ }
+ else
+ {
+ xOffset = 0.0;
+ }
+
+ // The overbar is indented inward at the beginning of an italicized section, but
+ // must not be indented on subsequent letters to ensure that the bar segments
+ // overlap.
+ bool last_had_overbar = false;
+
+ for( UTF8::uni_iter chIt = aText.ubegin(), end = aText.uend(); chIt < end; ++chIt )
+ {
+ // Toggle overbar
+ if( *chIt == '~' )
+ {
+ if( ++chIt >= end )
+ break;
+
+ if( *chIt != '~' ) // It was a single tilda, it toggles overbar
+ m_overbar = !m_overbar;
+
+ // If it is a double tilda, just process the second one
+ }
+
+ int dd = *chIt - ' ';
+
+ if( dd >= (int) m_glyphBoundingBoxes.size() || dd < 0 )
+ dd = '?' - ' ';
+
+ GLYPH& glyph = m_glyphs[dd];
+ BOX2D& bbox = m_glyphBoundingBoxes[dd];
+
+ if( m_overbar && m_italic )
+ {
+ if( m_mirrored )
+ {
+ overbar_italic_comp = (-m_glyphSize.y * OVERBAR_HEIGHT) / ITALIC_TILT;
+ }
+ else
+ {
+ overbar_italic_comp = (m_glyphSize.y * OVERBAR_HEIGHT) / ITALIC_TILT;
+ }
+ }
+
+ if( m_overbar )
+ {
+ double overbar_start_x = xOffset;
+ double overbar_start_y = -m_glyphSize.y * OVERBAR_HEIGHT;
+ double overbar_end_x = xOffset + glyphSize.x * bbox.GetEnd().x;
+ double overbar_end_y = -m_glyphSize.y * OVERBAR_HEIGHT;
+
+ if( !last_had_overbar )
+ {
+ overbar_start_x += overbar_italic_comp;
+ last_had_overbar = true;
+ }
+
+ VECTOR2D startOverbar( overbar_start_x, overbar_start_y );
+ VECTOR2D endOverbar( overbar_end_x, overbar_end_y );
+
+ m_gal->DrawLine( startOverbar, endOverbar );
+ }
+ else
+ {
+ last_had_overbar = false;
+ }
+
+ for( GLYPH::iterator pointListIt = glyph.begin(); pointListIt != glyph.end();
+ ++pointListIt )
+ {
+ std::deque<VECTOR2D> pointListScaled;
+
+ for( std::deque<VECTOR2D>::iterator pointIt = pointListIt->begin();
+ pointIt != pointListIt->end(); ++pointIt )
+ {
+ VECTOR2D pointPos( pointIt->x * glyphSize.x + xOffset, pointIt->y * glyphSize.y );
+
+ if( m_italic )
+ {
+ // FIXME should be done other way - referring to the lowest Y value of point
+ // because now italic fonts are translated a bit
+ if( m_mirrored )
+ pointPos.x += pointPos.y * 0.1;
+ else
+ pointPos.x -= pointPos.y * 0.1;
+ }
+
+ pointListScaled.push_back( pointPos );
+ }
+
+ m_gal->DrawPolyline( pointListScaled );
+ }
+
+ xOffset += glyphSize.x * bbox.GetEnd().x;
+ }
+
+ m_gal->Restore();
+}
+
+
+VECTOR2D STROKE_FONT::computeTextSize( const UTF8& aText ) const
+{
+ VECTOR2D result = VECTOR2D( 0.0, m_glyphSize.y );
+
+ for( UTF8::uni_iter it = aText.ubegin(), end = aText.uend(); it < end; ++it )
+ {
+ wxASSERT_MSG( *it != '\n',
+ wxT( "This function is intended to work with single line strings" ) );
+
+ // If it is double tilda, then it is displayed as a single tilda
+ // If it is single tilda, then it is toggling overbar, so we need to skip it
+ if( *it == '~' )
+ {
+ if( ++it >= end )
+ break;
+ }
+
+ // Index in the bounding boxes table
+ int dd = *it - ' ';
+
+ if( dd >= (int) m_glyphBoundingBoxes.size() || dd < 0 )
+ dd = '?' - ' ';
+
+ result.x += m_glyphSize.x * m_glyphBoundingBoxes[dd].GetEnd().x;
+ }
+
+ return result;
+}