diff options
author | saurabhb17 | 2020-02-26 16:11:59 +0530 |
---|---|---|
committer | GitHub | 2020-02-26 16:11:59 +0530 |
commit | e255d0622297488c1c52755be670733418c994cf (patch) | |
tree | 1392c90227aeea231c1d86371131e04c40382918 /common/gal | |
parent | 0db48f6533517ecebfd9f0693f89deca28408b76 (diff) | |
parent | c38609295ad4b617aef472b9c575aee18710a50f (diff) | |
download | KiCad-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.cpp | 159 | ||||
-rw-r--r-- | common/gal/cairo/cairo_gal.cpp | 1074 | ||||
-rw-r--r-- | common/gal/color4d.cpp | 179 | ||||
-rw-r--r-- | common/gal/graphics_abstraction_layer.cpp | 240 | ||||
-rw-r--r-- | common/gal/opengl/cached_container.cpp | 547 | ||||
-rw-r--r-- | common/gal/opengl/gpu_manager.cpp | 302 | ||||
-rw-r--r-- | common/gal/opengl/noncached_container.cpp | 89 | ||||
-rw-r--r-- | common/gal/opengl/opengl_compositor.cpp | 291 | ||||
-rw-r--r-- | common/gal/opengl/opengl_gal.cpp | 1204 | ||||
-rw-r--r-- | common/gal/opengl/shader.cpp | 272 | ||||
-rw-r--r-- | common/gal/opengl/shader.frag | 75 | ||||
-rw-r--r-- | common/gal/opengl/shader.vert | 95 | ||||
-rw-r--r-- | common/gal/opengl/vertex_container.cpp | 60 | ||||
-rw-r--r-- | common/gal/opengl/vertex_item.cpp | 53 | ||||
-rw-r--r-- | common/gal/opengl/vertex_manager.cpp | 234 | ||||
-rw-r--r-- | common/gal/stroke_font.cpp | 403 |
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( ¤tContext ) ); + 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, ¶ms ); + 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, ¶ms ); + 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; +} |