KiCad PCB EDA Suite
Loading...
Searching...
No Matches
opengl_gal.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Copyright (C) 2013-2017 CERN
7 * @author Maciej Suminski <[email protected]>
8 *
9 * Graphics Abstraction Layer (GAL) for OpenGL
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25#include <kicad_gl/kiglu.h> // Must be included first
26#include <kicad_gl/gl_utils.h>
27
28#include <advanced_config.h>
29#include <build_version.h>
31#include <gal/opengl/utils.h>
32#include <gal/definitions.h>
35#include <math/vector2wx.h>
36#include <bitmap_base.h>
37#include <bezier_curves.h>
38#include <math/util.h> // for KiROUND
39#include <pgm_base.h>
40#include <trace_helpers.h>
41
42#include <wx/app.h>
43#include <wx/frame.h>
44#include <wx/image.h>
45
46#include <macros.h>
47#include <optional>
49#include <thread_pool.h>
50
51#include <core/profile.h>
52#include <trace_helpers.h>
53
54#include <functional>
55#include <limits>
56#include <memory>
57#include <list>
58#include <vector>
59using namespace std::placeholders;
60using namespace KIGFX;
61
62//#define DISABLE_BITMAP_CACHE
63
64// The current font is "Ubuntu Mono" available under Ubuntu Font Licence 1.0
65// (see ubuntu-font-licence-1.0.txt for details)
66#include "gl_resources.h"
67#include <glsl_kicad_frag.h>
68#include <glsl_kicad_vert.h>
69using namespace KIGFX::BUILTIN_FONT;
70
71static void InitTesselatorCallbacks( GLUtesselator* aTesselator );
72
73// Trace mask for XOR/difference mode debugging
74static const wxChar* const traceGalXorMode = wxT( "KICAD_GAL_XOR_MODE" );
75
76static wxGLAttributes getGLAttribs()
77{
78 wxGLAttributes attribs;
79 attribs.RGBA().DoubleBuffer().Depth( 8 ).EndList();
80
81 return attribs;
82}
83
84wxGLContext* OPENGL_GAL::m_glMainContext = nullptr;
88
89namespace KIGFX
90{
92{
93public:
95 m_cacheSize( 0 )
96 {}
97
99
100 GLuint RequestBitmap( const BITMAP_BASE* aBitmap );
101
102private:
104 {
105 GLuint id;
106 int w, h;
107 size_t size;
108 long long int accessTime;
109 };
110
111 GLuint cacheBitmap( const BITMAP_BASE* aBitmap );
112
113 const size_t m_cacheMaxElements = 50;
114 const size_t m_cacheMaxSize = 256 * 1024 * 1024;
115
116 std::map<const KIID, CACHED_BITMAP> m_bitmaps;
117 std::list<KIID> m_cacheLru;
119 std::list<GLuint> m_freedTextureIds;
120};
121
122}; // namespace KIGFX
123
124
126{
127 for( auto& bitmap : m_bitmaps )
128 glDeleteTextures( 1, &bitmap.second.id );
129}
130
131
133{
134#ifndef DISABLE_BITMAP_CACHE
135 auto it = m_bitmaps.find( aBitmap->GetImageID() );
136
137 if( it != m_bitmaps.end() )
138 {
139 // A bitmap is found in cache bitmap. Ensure the associated texture is still valid.
140 if( glIsTexture( it->second.id ) )
141 {
142 it->second.accessTime = wxGetUTCTimeMillis().GetValue();
143 return it->second.id;
144 }
145 else
146 {
147 // Delete the invalid bitmap cache and its data
148 glDeleteTextures( 1, &it->second.id );
149 m_freedTextureIds.emplace_back( it->second.id );
150
151 auto listIt = std::find( m_cacheLru.begin(), m_cacheLru.end(), it->first );
152
153 if( listIt != m_cacheLru.end() )
154 m_cacheLru.erase( listIt );
155
156 m_cacheSize -= it->second.size;
157
158 m_bitmaps.erase( it );
159 }
160
161 // the cached bitmap is not valid and deleted, it will be recreated.
162 }
163
164#endif
165 return cacheBitmap( aBitmap );
166}
167
168
170{
171 CACHED_BITMAP bmp;
172
173 const wxImage* imgPtr = aBitmap->GetOriginalImageData();
174
175 if( !imgPtr )
176 return std::numeric_limits< GLuint >::max();
177
178 const wxImage& imgData = *imgPtr;
179
180 bmp.w = imgData.GetSize().x;
181 bmp.h = imgData.GetSize().y;
182
183 GLuint textureID;
184
185 if( m_freedTextureIds.empty() )
186 {
187 glGenTextures( 1, &textureID );
188 }
189 else
190 {
191 textureID = m_freedTextureIds.front();
192 m_freedTextureIds.pop_front();
193 }
194
195 glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
196
197 if( imgData.HasAlpha() || imgData.HasMask() )
198 {
199 bmp.size = bmp.w * bmp.h * 4;
200 auto buf = std::make_unique<uint8_t[]>( bmp.size );
201
202 uint8_t* dstP = buf.get();
203 uint8_t* srcP = imgData.GetData();
204
205 long long pxCount = static_cast<long long>( bmp.w ) * bmp.h;
206
207 if( imgData.HasAlpha() )
208 {
209 uint8_t* srcAlpha = imgData.GetAlpha();
210
211 for( long long px = 0; px < pxCount; px++ )
212 {
213 memcpy( dstP, srcP, 3 );
214 dstP[3] = *srcAlpha;
215
216 srcAlpha += 1;
217 srcP += 3;
218 dstP += 4;
219 }
220 }
221 else if( imgData.HasMask() )
222 {
223 uint8_t maskRed = imgData.GetMaskRed();
224 uint8_t maskGreen = imgData.GetMaskGreen();
225 uint8_t maskBlue = imgData.GetMaskBlue();
226
227 for( long long px = 0; px < pxCount; px++ )
228 {
229 memcpy( dstP, srcP, 3 );
230
231 if( srcP[0] == maskRed && srcP[1] == maskGreen && srcP[2] == maskBlue )
232 dstP[3] = wxALPHA_TRANSPARENT;
233 else
234 dstP[3] = wxALPHA_OPAQUE;
235
236 srcP += 3;
237 dstP += 4;
238 }
239 }
240
241 glBindTexture( GL_TEXTURE_2D, textureID );
242 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, bmp.w, bmp.h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
243 buf.get() );
244 }
245 else
246 {
247 bmp.size = bmp.w * bmp.h * 3;
248
249 uint8_t* srcP = imgData.GetData();
250
251 glBindTexture( GL_TEXTURE_2D, textureID );
252 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, bmp.w, bmp.h, 0, GL_RGB, GL_UNSIGNED_BYTE, srcP );
253 }
254
255 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
256 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
257
258 long long currentTime = wxGetUTCTimeMillis().GetValue();
259
260 bmp.id = textureID;
261 bmp.accessTime = currentTime;
262
263#ifndef DISABLE_BITMAP_CACHE
264 if( ( m_cacheLru.size() + 1 > m_cacheMaxElements || m_cacheSize + bmp.size > m_cacheMaxSize )
265 && !m_cacheLru.empty() )
266 {
267 KIID toRemove( 0 );
268 auto toRemoveLru = m_cacheLru.end();
269
270 // Remove entries accessed > 1s ago first
271 for( const auto& [kiid, cachedBmp] : m_bitmaps )
272 {
273 const int cacheTimeoutMillis = 1000L;
274
275 if( currentTime - cachedBmp.accessTime > cacheTimeoutMillis )
276 {
277 toRemove = kiid;
278 toRemoveLru = std::find( m_cacheLru.begin(), m_cacheLru.end(), toRemove );
279 break;
280 }
281 }
282
283 // Otherwise, remove the latest entry (it's less likely to be needed soon)
284 if( toRemove == niluuid )
285 {
286 toRemoveLru = m_cacheLru.end();
287 toRemoveLru--;
288
289 toRemove = *toRemoveLru;
290 }
291
292 CACHED_BITMAP& cachedBitmap = m_bitmaps[toRemove];
293
294 m_cacheSize -= cachedBitmap.size;
295 glDeleteTextures( 1, &cachedBitmap.id );
296 m_freedTextureIds.emplace_back( cachedBitmap.id );
297
298 m_bitmaps.erase( toRemove );
299 m_cacheLru.erase( toRemoveLru );
300 }
301
302 m_cacheLru.emplace_back( aBitmap->GetImageID() );
303 m_cacheSize += bmp.size;
304 m_bitmaps.emplace( aBitmap->GetImageID(), std::move( bmp ) );
305#endif
306
307 return textureID;
308}
309
310
312 wxWindow* aParent,
313 wxEvtHandler* aMouseListener, wxEvtHandler* aPaintListener,
314 const wxString& aName ) :
315 GAL( aDisplayOptions ),
316 HIDPI_GL_CANVAS( aVcSettings, aParent, getGLAttribs(), wxID_ANY, wxDefaultPosition,
317 wxDefaultSize,
318 wxEXPAND, aName ),
319 m_mouseListener( aMouseListener ),
320 m_paintListener( aPaintListener ),
321 m_currentManager( nullptr ),
322 m_cachedManager( nullptr ),
323 m_nonCachedManager( nullptr ),
324 m_overlayManager( nullptr ),
325 m_tempManager( nullptr ),
326 m_mainBuffer( 0 ),
327 m_overlayBuffer( 0 ),
328 m_tempBuffer( 0 ),
329 m_isContextLocked( false ),
331{
332 if( m_glMainContext == nullptr )
333 {
335
336 if( !m_glMainContext )
337 throw std::runtime_error( "Could not create the main OpenGL context" );
338
340 }
341 else
342 {
344
345 if( !m_glPrivContext )
346 throw std::runtime_error( "Could not create a private OpenGL context" );
347 }
348
349 m_shader = new SHADER();
351
352 m_bitmapCache = std::make_unique<GL_BITMAP_CACHE>();
353
355 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
356
357 // Initialize the flags
360 m_isInitialized = false;
361 m_isGrouping = false;
362 m_groupCounter = 0;
363
364 // Connect the native cursor handler
365 Connect( wxEVT_SET_CURSOR, wxSetCursorEventHandler( OPENGL_GAL::onSetNativeCursor ), nullptr,
366 this );
367
368 // Connecting the event handlers
369 Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::onPaint ) );
370
371 // Mouse events are skipped to the parent
372 Connect( wxEVT_MOTION, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
373 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
374 Connect( wxEVT_LEFT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
375 Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
376 Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
377 Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
378 Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
379 Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
380 Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
381 Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
382 Connect( wxEVT_AUX1_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
383 Connect( wxEVT_AUX1_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
384 Connect( wxEVT_AUX1_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
385 Connect( wxEVT_AUX2_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
386 Connect( wxEVT_AUX2_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
387 Connect( wxEVT_AUX2_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
388 Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
389 Connect( wxEVT_MAGNIFY, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
390
391#if defined _WIN32 || defined _WIN64
392 Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
393#endif
394
395 Bind( wxEVT_GESTURE_ZOOM, &OPENGL_GAL::skipGestureEvent, this );
396 Bind( wxEVT_GESTURE_PAN, &OPENGL_GAL::skipGestureEvent, this );
397
398 SetSize( aParent->GetClientSize() );
400
401 // Grid color settings are different in Cairo and OpenGL
402 SetGridColor( COLOR4D( 0.8, 0.8, 0.8, 0.1 ) );
404
405 // Tesselator initialization
406 m_tesselator = gluNewTess();
408
409 gluTessProperty( m_tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
410
412
413 // Avoid uninitialized variables:
419 ufm_fontTexture = -1;
421 m_swapInterval = 0;
422}
423
424
426{
428 wxASSERT( gl_mgr );
429
430 if( gl_mgr )
431 {
432 gl_mgr->LockCtx( m_glPrivContext, this );
433
435 if( m_isInitialized )
436 glFlush();
437 gluDeleteTess( m_tesselator );
438 ClearCache();
439
440 delete m_compositor;
441
442 if( m_isInitialized )
443 {
444 delete m_cachedManager;
445 delete m_nonCachedManager;
446 delete m_overlayManager;
447 delete m_tempManager;
448 }
449
450 gl_mgr->UnlockCtx( m_glPrivContext );
451
452 // If it was the main context, then it will be deleted
453 // when the last OpenGL GAL instance is destroyed (a few lines below)
455 gl_mgr->DestroyCtx( m_glPrivContext );
456
457 delete m_shader;
458
459 // Are we destroying the last GAL instance?
460 if( m_instanceCounter == 0 )
461 {
462 gl_mgr->LockCtx( m_glMainContext, this );
463
465 {
466 glDeleteTextures( 1, &g_fontTexture );
467 m_isBitmapFontLoaded = false;
468 }
469
470 gl_mgr->UnlockCtx( m_glMainContext );
471 gl_mgr->DestroyCtx( m_glMainContext );
472 m_glMainContext = nullptr;
473 }
474 }
475}
476
477
479{
480 static std::optional<wxString> cached;
481
482 if( cached.has_value() )
483 return *cached;
484
485 wxString retVal = wxEmptyString;
486
487 wxFrame* testFrame = new wxFrame( nullptr, wxID_ANY, wxT( "" ), wxDefaultPosition,
488 wxSize( 1, 1 ), wxFRAME_TOOL_WINDOW | wxNO_BORDER );
489
490 KIGFX::OPENGL_GAL* opengl_gal = nullptr;
491
492 try
493 {
495 opengl_gal = new KIGFX::OPENGL_GAL( dummy, aOptions, testFrame );
496
497 testFrame->Raise();
498 testFrame->Show();
499
500#ifdef __WXGTK__
501 // On GTK, Show() only queues realization. The GDK drawing window
502 // needed by SetCurrent() may not exist yet. Yield to let the event
503 // loop process the realize signal before we try to lock the context.
504 wxYield();
505#endif
506
507 GAL_CONTEXT_LOCKER lock( opengl_gal );
508 opengl_gal->init();
509 }
510 catch( std::runtime_error& err )
511 {
512 //Test failed
513 retVal = wxString( err.what() );
514 }
515
516 delete opengl_gal;
517 delete testFrame;
518
519 cached = retVal;
520 return retVal;
521}
522
523
524void OPENGL_GAL::PostPaint( wxPaintEvent& aEvent )
525{
526 // posts an event to m_paint_listener to ask for redraw the canvas.
527 if( m_paintListener )
528 wxPostEvent( m_paintListener, aEvent );
529}
530
531
533{
534 GAL_CONTEXT_LOCKER lock( this );
535
536 bool refresh = false;
537
538 if( m_options.antialiasing_mode != m_compositor->GetAntialiasingMode() )
539 {
540 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
542 refresh = true;
543 }
544
545 if( super::updatedGalDisplayOptions( aOptions ) || refresh )
546 {
547 Refresh();
548 refresh = true;
549 }
550
551 return refresh;
552}
553
554
556{
558 return std::min( std::abs( matrix.GetScale().x ), std::abs( matrix.GetScale().y ) );
559}
560
561
563{
564 double sf = GetScaleFactor();
565 return VECTOR2D( 2.0 / (double) ( m_screenSize.x * sf ), 2.0 /
566 (double) ( m_screenSize.y * sf ) );
567}
568
569
571{
572#ifdef KICAD_GAL_PROFILE
573 PROF_TIMER totalRealTime( "OPENGL_GAL::beginDrawing()", true );
574#endif /* KICAD_GAL_PROFILE */
575
576 wxASSERT_MSG( m_isContextLocked, "GAL_DRAWING_CONTEXT RAII object should have locked context. "
577 "Calling GAL::beginDrawing() directly is not allowed." );
578
579 wxASSERT_MSG( IsVisible(), "GAL::beginDrawing() must not be entered when GAL is not visible. "
580 "Other drawing routines will expect everything to be initialized "
581 "which will not be the case." );
582
583 if( !m_isInitialized )
584 init();
585
586 // Set up the view port
587 glMatrixMode( GL_PROJECTION );
588 glLoadIdentity();
589
590 // Create the screen transformation (Do the RH-LH conversion here)
591 glOrtho( 0, (GLint) m_screenSize.x, (GLsizei) m_screenSize.y, 0,
592 -m_depthRange.x, -m_depthRange.y );
593
595 {
596 // Prepare rendering target buffers
597 m_compositor->Initialize();
598 m_mainBuffer = m_compositor->CreateBuffer();
599 try
600 {
601 m_tempBuffer = m_compositor->CreateBuffer();
602 }
603 catch( const std::runtime_error& )
604 {
605 wxLogVerbose( "Could not create a framebuffer for diff mode blending.\n" );
606 m_tempBuffer = 0;
607 }
608 try
609 {
610 m_overlayBuffer = m_compositor->CreateBuffer();
611 }
612 catch( const std::runtime_error& )
613 {
614 wxLogVerbose( "Could not create a framebuffer for overlays.\n" );
615 m_overlayBuffer = 0;
616 }
617
619 }
620
621 m_compositor->Begin();
622
623 // Disable 2D Textures
624 glDisable( GL_TEXTURE_2D );
625
626 glShadeModel( GL_FLAT );
627
628 // Enable the depth buffer
629 glEnable( GL_DEPTH_TEST );
630 glDepthFunc( GL_LESS );
631
632 // Setup blending, required for transparent objects
633 glEnable( GL_BLEND );
634 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
635
636 glMatrixMode( GL_MODELVIEW );
637
638 // Set up the world <-> screen transformation
640 GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
641 matrixData[0] = m_worldScreenMatrix.m_data[0][0];
642 matrixData[1] = m_worldScreenMatrix.m_data[1][0];
643 matrixData[2] = m_worldScreenMatrix.m_data[2][0];
644 matrixData[4] = m_worldScreenMatrix.m_data[0][1];
645 matrixData[5] = m_worldScreenMatrix.m_data[1][1];
646 matrixData[6] = m_worldScreenMatrix.m_data[2][1];
647 matrixData[12] = m_worldScreenMatrix.m_data[0][2];
648 matrixData[13] = m_worldScreenMatrix.m_data[1][2];
649 matrixData[14] = m_worldScreenMatrix.m_data[2][2];
650 glLoadMatrixd( matrixData );
651
652 // Set defaults
655
656 // Remove all previously stored items
657 m_nonCachedManager->Clear();
658 m_overlayManager->Clear();
659 m_tempManager->Clear();
660
661 m_cachedManager->BeginDrawing();
662 m_nonCachedManager->BeginDrawing();
663 m_overlayManager->BeginDrawing();
664 m_tempManager->BeginDrawing();
665
667 {
668 // Keep bitmap font texture always bound to the second texturing unit
669 const GLint FONT_TEXTURE_UNIT = 2;
670
671 // Either load the font atlas to video memory, or simply bind it to a texture unit
673 {
674 glActiveTexture( GL_TEXTURE0 + FONT_TEXTURE_UNIT );
675 glGenTextures( 1, &g_fontTexture );
676 glBindTexture( GL_TEXTURE_2D, g_fontTexture );
677 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, font_image.width, font_image.height, 0, GL_RGB,
678 GL_UNSIGNED_BYTE, font_image.pixels );
679 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
680 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
681 checkGlError( "loading bitmap font", __FILE__, __LINE__ );
682
683 glActiveTexture( GL_TEXTURE0 );
684
686 }
687 else
688 {
689 glActiveTexture( GL_TEXTURE0 + FONT_TEXTURE_UNIT );
690 glBindTexture( GL_TEXTURE_2D, g_fontTexture );
691 glActiveTexture( GL_TEXTURE0 );
692 }
693
694 m_shader->Use();
695 m_shader->SetParameter( ufm_fontTexture, (int) FONT_TEXTURE_UNIT );
696 m_shader->SetParameter( ufm_fontTextureWidth, (int) font_image.width );
697 m_shader->Deactivate();
698 checkGlError( "setting bitmap font sampler as shader parameter", __FILE__, __LINE__ );
699
701 }
702
703 m_shader->Use();
704 m_shader->SetParameter( ufm_worldPixelSize,
705 (float) ( getWorldPixelSize() / GetScaleFactor() ) );
706 const VECTOR2D& screenPixelSize = getScreenPixelSize();
707 m_shader->SetParameter( ufm_screenPixelSize, screenPixelSize );
708 double pixelSizeMultiplier = m_compositor->GetAntialiasSupersamplingFactor();
709 m_shader->SetParameter( ufm_pixelSizeMultiplier, (float) pixelSizeMultiplier );
710 VECTOR2D renderingOffset = m_compositor->GetAntialiasRenderingOffset();
711 renderingOffset.x *= screenPixelSize.x;
712 renderingOffset.y *= screenPixelSize.y;
713 m_shader->SetParameter( ufm_antialiasingOffset, renderingOffset );
715 m_shader->Deactivate();
716
717 // Something between BeginDrawing and EndDrawing seems to depend on
718 // this texture unit being active, but it does not assure it itself.
719 glActiveTexture( GL_TEXTURE0 );
720
721 // Unbind buffers - set compositor for direct drawing
723
724#ifdef KICAD_GAL_PROFILE
725 totalRealTime.Stop();
726 wxLogTrace( traceGalProfile, wxT( "OPENGL_GAL::beginDrawing(): %.1f ms" ),
727 totalRealTime.msecs() );
728#endif /* KICAD_GAL_PROFILE */
729}
730
731void OPENGL_GAL::SetMinLineWidth( float aLineWidth )
732{
733 GAL::SetMinLineWidth( aLineWidth );
734
735 if( m_shader && ufm_minLinePixelWidth != -1 )
736 {
737 m_shader->Use();
738 m_shader->SetParameter( ufm_minLinePixelWidth, aLineWidth );
739 m_shader->Deactivate();
740 }
741}
742
743
745{
746 wxASSERT_MSG( m_isContextLocked, "What happened to the context lock?" );
747
748 PROF_TIMER cntTotal( "gl-end-total" );
749 PROF_TIMER cntEndCached( "gl-end-cached" );
750 PROF_TIMER cntEndNoncached( "gl-end-noncached" );
751 PROF_TIMER cntEndOverlay( "gl-end-overlay" );
752 PROF_TIMER cntComposite( "gl-composite" );
753 PROF_TIMER cntSwap( "gl-swap" );
754
755 cntTotal.Start();
756
757 // Cached & non-cached containers are rendered to the same buffer
758 m_compositor->SetBuffer( m_mainBuffer );
759
760 cntEndNoncached.Start();
761 m_nonCachedManager->EndDrawing();
762 cntEndNoncached.Stop();
763
764 cntEndCached.Start();
765 m_cachedManager->EndDrawing();
766 cntEndCached.Stop();
767
768 cntEndOverlay.Start();
769 // Overlay container is rendered to a different buffer
770 if( m_overlayBuffer )
771 m_compositor->SetBuffer( m_overlayBuffer );
772
773 m_overlayManager->EndDrawing();
774 cntEndOverlay.Stop();
775
776 cntComposite.Start();
777
778 // Be sure that the framebuffer is not colorized (happens on specific GPU&drivers combinations)
779 glColor4d( 1.0, 1.0, 1.0, 1.0 );
780
781 // Draw the remaining contents, blit the rendering targets to the screen, swap the buffers
782 m_compositor->DrawBuffer( m_mainBuffer );
783
784 if( m_overlayBuffer )
785 m_compositor->DrawBuffer( m_overlayBuffer );
786
787 m_compositor->Present();
788 blitCursor();
789
790 cntComposite.Stop();
791
792 cntSwap.Start();
793 SwapBuffers();
794 cntSwap.Stop();
795
796 cntTotal.Stop();
797
798#ifdef KICAD_GAL_PROFILE
799 wxLogTrace( traceGalProfile, "Timing: %s %s %s %s %s %s", cntTotal.to_string(),
800 cntEndCached.to_string(), cntEndNoncached.to_string(), cntEndOverlay.to_string(),
801 cntComposite.to_string(), cntSwap.to_string() );
802#endif
803}
804
805
806bool OPENGL_GAL::GetScreenshot( wxImage& aDstImage )
807{
808 if( !IsInitialized() || !m_compositor )
809 return false;
810
811 GAL_CONTEXT_LOCKER locker( this );
812
813 m_compositor->SetBuffer( m_mainBuffer );
814
815 GLint viewport[4];
816 glGetIntegerv( GL_VIEWPORT, viewport );
817
818 const int w = viewport[2];
819 const int h = viewport[3];
820
821 GLint readBuffer = GL_COLOR_ATTACHMENT0;
822 glGetIntegerv( GL_DRAW_BUFFER, &readBuffer );
823
824 bool ok = false;
825
826 if( w > 0 && h > 0 )
827 {
828 std::vector<unsigned char> rgba( (size_t) w * h * 4 );
829
830 glFinish();
831 glPixelStorei( GL_PACK_ALIGNMENT, 1 );
832 glReadBuffer( (GLenum) readBuffer );
833 glReadPixels( 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data() );
834
835 // wxImage wants separate RGB and alpha buffers and takes ownership of them.
836 unsigned char* rgb = (unsigned char*) malloc( (size_t) w * h * 3 );
837 unsigned char* alpha = (unsigned char*) malloc( (size_t) w * h );
838
839 for( int i = 0; i < w * h; ++i )
840 {
841 rgb[i * 3 + 0] = rgba[i * 4 + 0];
842 rgb[i * 3 + 1] = rgba[i * 4 + 1];
843 rgb[i * 3 + 2] = rgba[i * 4 + 2];
844 alpha[i] = rgba[i * 4 + 3];
845 }
846
847 aDstImage.SetData( rgb, w, h, false );
848 aDstImage.SetAlpha( alpha, false );
849
850 aDstImage = aDstImage.Mirror( false );
851 ok = true;
852 }
853
855
856 return ok;
857}
858
859
860void OPENGL_GAL::LockContext( int aClientCookie )
861{
862 wxASSERT_MSG( !m_isContextLocked, "Context already locked." );
863 m_isContextLocked = true;
864 m_lockClientCookie = aClientCookie;
865
867
868 if( !mgr )
869 return;
870
871 mgr->LockCtx( m_glPrivContext, this );
872}
873
874
875void OPENGL_GAL::UnlockContext( int aClientCookie )
876{
877 wxASSERT_MSG( m_isContextLocked, "Context not locked. A GAL_CONTEXT_LOCKER RAII object must "
878 "be stacked rather than making separate lock/unlock calls." );
879
880 wxASSERT_MSG( m_lockClientCookie == aClientCookie,
881 "Context was locked by a different client. "
882 "Should not be possible with RAII objects." );
883
884 m_isContextLocked = false;
885
887
888 if( !mgr )
889 return;
890
892}
893
894
896{
897 wxASSERT_MSG( m_isContextLocked, "GAL_UPDATE_CONTEXT RAII object should have locked context. "
898 "Calling this from anywhere else is not allowed." );
899
900 wxASSERT_MSG( IsVisible(), "GAL::beginUpdate() must not be entered when GAL is not visible. "
901 "Other update routines will expect everything to be initialized "
902 "which will not be the case." );
903
904 if( !m_isInitialized )
905 init();
906
907 m_cachedManager->Map();
908}
909
910
912{
913 if( !m_isInitialized )
914 return;
915
916 m_cachedManager->Unmap();
917}
918
919
920void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
921{
923
924 drawLineQuad( aStartPoint, aEndPoint );
925}
926
927
928void OPENGL_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
929 double aWidth )
930{
931 drawSegment( aStartPoint, aEndPoint, aWidth );
932}
933
934
935void OPENGL_GAL::drawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint, double aWidth,
936 bool aReserve )
937{
938 VECTOR2D startEndVector = aEndPoint - aStartPoint;
939 double lineLength = startEndVector.EuclideanNorm();
940
941 // Be careful about floating point rounding. As we draw segments in larger and larger
942 // coordinates, the shader (which uses floats) will lose precision and stop drawing small
943 // segments. In this case, we need to draw a circle for the minimal segment.
944 // Check if the coordinate differences can be accurately represented as floats
945 float startX = static_cast<float>( aStartPoint.x );
946 float startY = static_cast<float>( aStartPoint.y );
947 float endX = static_cast<float>( aEndPoint.x );
948 float endY = static_cast<float>( aEndPoint.y );
949
950 if( startX == endX && startY == endY )
951 {
952 drawCircle( aStartPoint, aWidth / 2, aReserve );
953 return;
954 }
955
956 if( m_isFillEnabled || aWidth == 1.0 )
957 {
959
960 SetLineWidth( aWidth );
961 drawLineQuad( aStartPoint, aEndPoint, aReserve );
962 }
963 else
964 {
965 EDA_ANGLE lineAngle( startEndVector );
966
967 // Outlined tracks
968 SetLineWidth( 1.0 );
970 m_strokeColor.a );
971
972 Save();
973
974 if( aReserve )
975 m_currentManager->Reserve( 6 + 6 + 3 + 3 ); // Two line quads and two semicircles
976
977 m_currentManager->Translate( aStartPoint.x, aStartPoint.y, 0.0 );
978 m_currentManager->Rotate( lineAngle.AsRadians(), 0.0f, 0.0f, 1.0f );
979
980 drawLineQuad( VECTOR2D( 0.0, aWidth / 2.0 ), VECTOR2D( lineLength, aWidth / 2.0 ), false );
981
982 drawLineQuad( VECTOR2D( 0.0, -aWidth / 2.0 ), VECTOR2D( lineLength, -aWidth / 2.0 ),
983 false );
984
985 // Draw line caps
986 drawStrokedSemiCircle( VECTOR2D( 0.0, 0.0 ), aWidth / 2, M_PI / 2, false );
987 drawStrokedSemiCircle( VECTOR2D( lineLength, 0.0 ), aWidth / 2, -M_PI / 2, false );
988
989 Restore();
990 }
991}
992
993
994void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
995{
996 drawCircle( aCenterPoint, aRadius );
997}
998
999
1000void OPENGL_GAL::DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
1001 double aWallWidth )
1002{
1003 if( m_isFillEnabled )
1004 {
1006
1007 m_currentManager->Shader( SHADER_HOLE_WALL, 1.0, aHoleRadius, aWallWidth );
1008 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1009
1010 m_currentManager->Shader( SHADER_HOLE_WALL, 2.0, aHoleRadius, aWallWidth );
1011 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1012
1013 m_currentManager->Shader( SHADER_HOLE_WALL, 3.0, aHoleRadius, aWallWidth );
1014 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1015 }
1016}
1017
1018
1019void OPENGL_GAL::drawCircle( const VECTOR2D& aCenterPoint, double aRadius, bool aReserve )
1020{
1021 if( m_isFillEnabled )
1022 {
1023 if( aReserve )
1024 m_currentManager->Reserve( 3 );
1025
1027
1028 /* Draw a triangle that contains the circle, then shade it leaving only the circle.
1029 * Parameters given to Shader() are indices of the triangle's vertices
1030 * (if you want to understand more, check the vertex shader source [shader.vert]).
1031 * Shader uses this coordinates to determine if fragments are inside the circle or not.
1032 * Does the calculations in the vertex shader now (pixel alignment)
1033 * v2
1034 * /\
1035 * //\\
1036 * v0 /_\/_\ v1
1037 */
1038 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0, aRadius );
1039 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1040
1041 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0, aRadius );
1042 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1043
1044 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0, aRadius );
1045 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1046 }
1047
1048 if( m_isStrokeEnabled )
1049 {
1050 if( aReserve )
1051 m_currentManager->Reserve( 3 );
1052
1054 m_strokeColor.a );
1055
1056 /* Draw a triangle that contains the circle, then shade it leaving only the circle.
1057 * Parameters given to Shader() are indices of the triangle's vertices
1058 * (if you want to understand more, check the vertex shader source [shader.vert]).
1059 * and the line width. Shader uses this coordinates to determine if fragments are
1060 * inside the circle or not.
1061 * v2
1062 * /\
1063 * //\\
1064 * v0 /_\/_\ v1
1065 */
1066 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 1.0, aRadius, m_lineWidth );
1067 m_currentManager->Vertex( aCenterPoint.x, // v0
1068 aCenterPoint.y, m_layerDepth );
1069
1070 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 2.0, aRadius, m_lineWidth );
1071 m_currentManager->Vertex( aCenterPoint.x, // v1
1072 aCenterPoint.y, m_layerDepth );
1073
1074 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 3.0, aRadius, m_lineWidth );
1075 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, // v2
1076 m_layerDepth );
1077 }
1078}
1079
1080
1081void OPENGL_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius,
1082 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle )
1083{
1084 if( aRadius <= 0 )
1085 return;
1086
1087 double startAngle = aStartAngle.AsRadians();
1088 double endAngle = startAngle + aAngle.AsRadians();
1089
1090 // Normalize arc angles
1091 normalize( startAngle, endAngle );
1092
1093 const double alphaIncrement = calcAngleStep( aRadius );
1094
1095 Save();
1096 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1097
1098 if( m_isFillEnabled )
1099 {
1100 double alpha;
1102 m_currentManager->Shader( SHADER_NONE );
1103
1104 // Triangle fan
1105 for( alpha = startAngle; ( alpha + alphaIncrement ) < endAngle; )
1106 {
1107 m_currentManager->Reserve( 3 );
1108 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1109 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius,
1110 m_layerDepth );
1111 alpha += alphaIncrement;
1112 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius,
1113 m_layerDepth );
1114 }
1115
1116 // The last missing triangle
1117 const VECTOR2D endPoint( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1118
1119 m_currentManager->Reserve( 3 );
1120 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1121 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, m_layerDepth );
1122 m_currentManager->Vertex( endPoint.x, endPoint.y, m_layerDepth );
1123 }
1124
1125 if( m_isStrokeEnabled )
1126 {
1128 m_strokeColor.a );
1129
1130 VECTOR2D p( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1131 double alpha;
1132 unsigned int lineCount = 0;
1133
1134 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1135 lineCount++;
1136
1137 if( alpha != endAngle )
1138 lineCount++;
1139
1140 reserveLineQuads( lineCount );
1141
1142 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1143 {
1144 VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
1145 drawLineQuad( p, p_next, false );
1146
1147 p = p_next;
1148 }
1149
1150 // Draw the last missing part
1151 if( alpha != endAngle )
1152 {
1153 VECTOR2D p_last( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1154 drawLineQuad( p, p_last, false );
1155 }
1156 }
1157
1158 Restore();
1159}
1160
1161
1162void OPENGL_GAL::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
1163 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
1164 double aWidth, double aMaxError )
1165{
1166 if( aRadius <= 0 )
1167 {
1168 // Arcs of zero radius are a circle of aWidth diameter
1169 if( aWidth > 0 )
1170 DrawCircle( aCenterPoint, aWidth / 2.0 );
1171
1172 return;
1173 }
1174
1175 double startAngle = aStartAngle.AsRadians();
1176 double endAngle = startAngle + aAngle.AsRadians();
1177
1178 // Swap the angles, if start angle is greater than end angle
1179 normalize( startAngle, endAngle );
1180
1181 // Calculate the seg count to approximate the arc with aMaxError or less
1182 int segCount360 = GetArcToSegmentCount( aRadius, aMaxError, FULL_CIRCLE );
1183 segCount360 = std::max( SEG_PER_CIRCLE_COUNT, segCount360 );
1184 double alphaIncrement = 2.0 * M_PI / segCount360;
1185
1186 // Refinement: Use a segment count multiple of 2, because we have a control point
1187 // on the middle of the arc, and the look is better if it is on a segment junction
1188 // because there is no approx error
1189 int seg_count = KiROUND( ( endAngle - startAngle ) / alphaIncrement );
1190
1191 if( seg_count % 2 != 0 )
1192 seg_count += 1;
1193
1194 // Our shaders have trouble rendering null line quads, so delegate this task to DrawSegment.
1195 if( seg_count == 0 )
1196 {
1197 VECTOR2D p_start( aCenterPoint.x + cos( startAngle ) * aRadius,
1198 aCenterPoint.y + sin( startAngle ) * aRadius );
1199
1200 VECTOR2D p_end( aCenterPoint.x + cos( endAngle ) * aRadius,
1201 aCenterPoint.y + sin( endAngle ) * aRadius );
1202
1203 DrawSegment( p_start, p_end, aWidth );
1204 return;
1205 }
1206
1207 // Recalculate alphaIncrement with a even integer number of segment
1208 alphaIncrement = ( endAngle - startAngle ) / seg_count;
1209
1210 Save();
1211 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1212
1213 if( m_isStrokeEnabled )
1214 {
1216 m_strokeColor.a );
1217
1218 double width = aWidth / 2.0;
1219 VECTOR2D startPoint( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1220 VECTOR2D endPoint( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1221
1222 drawStrokedSemiCircle( startPoint, width, startAngle + M_PI );
1223 drawStrokedSemiCircle( endPoint, width, endAngle );
1224
1225 VECTOR2D pOuter( cos( startAngle ) * ( aRadius + width ),
1226 sin( startAngle ) * ( aRadius + width ) );
1227
1228 VECTOR2D pInner( cos( startAngle ) * ( aRadius - width ),
1229 sin( startAngle ) * ( aRadius - width ) );
1230
1231 double alpha;
1232
1233 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1234 {
1235 VECTOR2D pNextOuter( cos( alpha ) * ( aRadius + width ),
1236 sin( alpha ) * ( aRadius + width ) );
1237 VECTOR2D pNextInner( cos( alpha ) * ( aRadius - width ),
1238 sin( alpha ) * ( aRadius - width ) );
1239
1240 DrawLine( pOuter, pNextOuter );
1241 DrawLine( pInner, pNextInner );
1242
1243 pOuter = pNextOuter;
1244 pInner = pNextInner;
1245 }
1246
1247 // Draw the last missing part
1248 if( alpha != endAngle )
1249 {
1250 VECTOR2D pLastOuter( cos( endAngle ) * ( aRadius + width ),
1251 sin( endAngle ) * ( aRadius + width ) );
1252 VECTOR2D pLastInner( cos( endAngle ) * ( aRadius - width ),
1253 sin( endAngle ) * ( aRadius - width ) );
1254
1255 DrawLine( pOuter, pLastOuter );
1256 DrawLine( pInner, pLastInner );
1257 }
1258 }
1259
1260 if( m_isFillEnabled )
1261 {
1263 SetLineWidth( aWidth );
1264
1265 VECTOR2D p( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1266 double alpha;
1267
1268 int lineCount = 0;
1269
1270 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1271 {
1272 lineCount++;
1273 }
1274
1275 // The last missing part
1276 if( alpha != endAngle )
1277 {
1278 lineCount++;
1279 }
1280
1281 reserveLineQuads( lineCount );
1282
1283 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1284 {
1285 VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
1286 drawLineQuad( p, p_next, false );
1287
1288 p = p_next;
1289 }
1290
1291 // Draw the last missing part
1292 if( alpha != endAngle )
1293 {
1294 VECTOR2D p_last( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1295 drawLineQuad( p, p_last, false );
1296 }
1297 }
1298
1299 Restore();
1300}
1301
1302
1303void OPENGL_GAL::DrawEllipse( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1304 const EDA_ANGLE& aRotation )
1305{
1306 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1307 return;
1308
1309 const double alphaIncrement = calcAngleStep( aMajorRadius );
1310 const double cosPhi = std::cos( aRotation.AsRadians() );
1311 const double sinPhi = std::sin( aRotation.AsRadians() );
1312
1313 auto eval = [&]( double theta ) -> VECTOR2D
1314 {
1315 const double lx = aMajorRadius * std::cos( theta );
1316 const double ly = aMinorRadius * std::sin( theta );
1317 return VECTOR2D( lx * cosPhi - ly * sinPhi, lx * sinPhi + ly * cosPhi );
1318 };
1319
1320 Save();
1321 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1322
1323 if( m_isFillEnabled )
1324 {
1326 m_currentManager->Shader( SHADER_NONE );
1327
1328 // Triangle fan from origin out to the ellipse boundary
1329 double alpha;
1330 for( alpha = 0.0; ( alpha + alphaIncrement ) < 2.0 * M_PI; )
1331 {
1332 const VECTOR2D p1 = eval( alpha );
1333 alpha += alphaIncrement;
1334 const VECTOR2D p2 = eval( alpha );
1335
1336 m_currentManager->Reserve( 3 );
1337 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1338 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1339 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1340 }
1341
1342 // Last wedge back to the start.
1343 const VECTOR2D p1 = eval( alpha );
1344 const VECTOR2D p2 = eval( 0.0 );
1345
1346 m_currentManager->Reserve( 3 );
1347 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1348 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1349 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1350 }
1351
1352 if( m_isStrokeEnabled )
1353 {
1355
1356 // Count quads for reservation.
1357 unsigned int lineCount = 0;
1358 double countAlpha;
1359
1360 for( countAlpha = alphaIncrement; countAlpha < 2.0 * M_PI; countAlpha += alphaIncrement )
1361 lineCount++;
1362
1363 lineCount++; // closing segment back to alpha = 0
1364
1365 reserveLineQuads( lineCount );
1366
1367 VECTOR2D p = eval( 0.0 );
1368 double alpha;
1369
1370 for( alpha = alphaIncrement; alpha < 2.0 * M_PI; alpha += alphaIncrement )
1371 {
1372 const VECTOR2D p_next = eval( alpha );
1373 drawLineQuad( p, p_next, false );
1374 p = p_next;
1375 }
1376
1377 // Closing segment
1378 drawLineQuad( p, eval( 0.0 ), false );
1379 }
1380
1381 Restore();
1382}
1383
1384
1385void OPENGL_GAL::DrawEllipseArc( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1386 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle )
1387{
1388 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1389 return;
1390
1391 double startAngle = aStartAngle.AsRadians();
1392 double endAngle = aEndAngle.AsRadians();
1393 normalize( startAngle, endAngle );
1394
1395 const double alphaIncrement = calcAngleStep( aMajorRadius );
1396 const double cosPhi = std::cos( aRotation.AsRadians() );
1397 const double sinPhi = std::sin( aRotation.AsRadians() );
1398
1399 auto eval = [&]( double theta ) -> VECTOR2D
1400 {
1401 const double lx = aMajorRadius * std::cos( theta );
1402 const double ly = aMinorRadius * std::sin( theta );
1403 return VECTOR2D( lx * cosPhi - ly * sinPhi, lx * sinPhi + ly * cosPhi );
1404 };
1405
1406 Save();
1407 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1408
1409 if( m_isFillEnabled )
1410 {
1412 m_currentManager->Shader( SHADER_NONE );
1413
1414 // Pie slice fan from origin out to the arc curve.
1415 double alpha;
1416
1417 for( alpha = startAngle; ( alpha + alphaIncrement ) < endAngle; )
1418 {
1419 const VECTOR2D p1 = eval( alpha );
1420 alpha += alphaIncrement;
1421 const VECTOR2D p2 = eval( alpha );
1422
1423 m_currentManager->Reserve( 3 );
1424 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1425 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1426 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1427 }
1428
1429 // Last wedge to endAngle.
1430 const VECTOR2D p1 = eval( alpha );
1431 const VECTOR2D p2 = eval( endAngle );
1432
1433 m_currentManager->Reserve( 3 );
1434 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1435 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1436 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1437 }
1438
1439 if( m_isStrokeEnabled )
1440 {
1442
1443 unsigned int lineCount = 0;
1444 double countAlpha;
1445
1446 for( countAlpha = startAngle + alphaIncrement; countAlpha <= endAngle; countAlpha += alphaIncrement )
1447 lineCount++;
1448
1449 if( countAlpha != endAngle )
1450 lineCount++; // trailing partial segment
1451
1452 reserveLineQuads( lineCount );
1453
1454 VECTOR2D p = eval( startAngle );
1455 double alpha;
1456
1457 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1458 {
1459 const VECTOR2D p_next = eval( alpha );
1460 drawLineQuad( p, p_next, false );
1461 p = p_next;
1462 }
1463
1464 // Trailing partial segment, if any
1465 if( alpha != endAngle )
1466 {
1467 const VECTOR2D p_last = eval( endAngle );
1468 drawLineQuad( p, p_last, false );
1469 }
1470 }
1471
1472 Restore();
1473}
1474
1475
1476void OPENGL_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1477{
1478 // Compute the diagonal points of the rectangle
1479 VECTOR2D diagonalPointA( aEndPoint.x, aStartPoint.y );
1480 VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );
1481
1482 // Fill the rectangle
1483 if( m_isFillEnabled )
1484 {
1485 m_currentManager->Reserve( 6 );
1486 m_currentManager->Shader( SHADER_NONE );
1488
1489 m_currentManager->Vertex( aStartPoint.x, aStartPoint.y, m_layerDepth );
1490 m_currentManager->Vertex( diagonalPointA.x, diagonalPointA.y, m_layerDepth );
1491 m_currentManager->Vertex( aEndPoint.x, aEndPoint.y, m_layerDepth );
1492
1493 m_currentManager->Vertex( aStartPoint.x, aStartPoint.y, m_layerDepth );
1494 m_currentManager->Vertex( aEndPoint.x, aEndPoint.y, m_layerDepth );
1495 m_currentManager->Vertex( diagonalPointB.x, diagonalPointB.y, m_layerDepth );
1496 }
1497
1498 // Stroke the outline
1499 if( m_isStrokeEnabled )
1500 {
1502 m_strokeColor.a );
1503
1504 // DrawLine (and DrawPolyline )
1505 // has problem with 0 length lines so enforce minimum
1506 if( aStartPoint == aEndPoint )
1507 {
1508 DrawLine( aStartPoint + VECTOR2D( 1.0, 0.0 ), aEndPoint );
1509 }
1510 else
1511 {
1512 std::deque<VECTOR2D> pointList;
1513
1514 pointList.push_back( aStartPoint );
1515 pointList.push_back( diagonalPointA );
1516 pointList.push_back( aEndPoint );
1517 pointList.push_back( diagonalPointB );
1518 pointList.push_back( aStartPoint );
1519 DrawPolyline( pointList );
1520 }
1521 }
1522}
1523
1524
1525void OPENGL_GAL::DrawSegmentChain( const std::vector<VECTOR2D>& aPointList, double aWidth )
1526{
1528 [&]( int idx )
1529 {
1530 return aPointList[idx];
1531 },
1532 aPointList.size(), aWidth );
1533}
1534
1535
1536void OPENGL_GAL::DrawSegmentChain( const SHAPE_LINE_CHAIN& aLineChain, double aWidth )
1537{
1538 auto numPoints = aLineChain.PointCount();
1539
1540 if( aLineChain.IsClosed() )
1541 numPoints += 1;
1542
1544 [&]( int idx )
1545 {
1546 return aLineChain.CPoint( idx );
1547 },
1548 numPoints, aWidth );
1549}
1550
1551
1552void OPENGL_GAL::DrawPolyline( const std::deque<VECTOR2D>& aPointList )
1553{
1555 [&]( int idx )
1556 {
1557 return aPointList[idx];
1558 },
1559 aPointList.size() );
1560}
1561
1562
1563void OPENGL_GAL::DrawPolyline( const std::vector<VECTOR2D>& aPointList )
1564{
1566 [&]( int idx )
1567 {
1568 return aPointList[idx];
1569 },
1570 aPointList.size() );
1571}
1572
1573
1574void OPENGL_GAL::DrawPolyline( const VECTOR2D aPointList[], int aListSize )
1575{
1577 [&]( int idx )
1578 {
1579 return aPointList[idx];
1580 },
1581 aListSize );
1582}
1583
1584
1586{
1587 auto numPoints = aLineChain.PointCount();
1588
1589 if( aLineChain.IsClosed() )
1590 numPoints += 1;
1591
1593 [&]( int idx )
1594 {
1595 return aLineChain.CPoint( idx );
1596 },
1597 numPoints );
1598}
1599
1600
1601void OPENGL_GAL::DrawPolylines( const std::vector<std::vector<VECTOR2D>>& aPointList )
1602{
1603 int lineQuadCount = 0;
1604
1605 for( const std::vector<VECTOR2D>& points : aPointList )
1606 lineQuadCount += points.size() - 1;
1607
1608 reserveLineQuads( lineQuadCount );
1609
1610 for( const std::vector<VECTOR2D>& points : aPointList )
1611 {
1613 [&]( int idx )
1614 {
1615 return points[idx];
1616 },
1617 points.size(), false );
1618 }
1619}
1620
1621
1622void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
1623{
1624 wxCHECK( aPointList.size() >= 2, /* void */ );
1625 auto points = std::unique_ptr<GLdouble[]>( new GLdouble[3 * aPointList.size()] );
1626 GLdouble* ptr = points.get();
1627
1628 for( const VECTOR2D& p : aPointList )
1629 {
1630 *ptr++ = p.x;
1631 *ptr++ = p.y;
1632 *ptr++ = m_layerDepth;
1633 }
1634
1635 drawPolygon( points.get(), aPointList.size() );
1636}
1637
1638
1639void OPENGL_GAL::DrawPolygon( const VECTOR2D aPointList[], int aListSize )
1640{
1641 wxCHECK( aListSize >= 2, /* void */ );
1642 auto points = std::unique_ptr<GLdouble[]>( new GLdouble[3 * aListSize] );
1643 GLdouble* target = points.get();
1644 const VECTOR2D* src = aPointList;
1645
1646 for( int i = 0; i < aListSize; ++i )
1647 {
1648 *target++ = src->x;
1649 *target++ = src->y;
1650 *target++ = m_layerDepth;
1651 ++src;
1652 }
1653
1654 drawPolygon( points.get(), aListSize );
1655}
1656
1657
1659 bool aStrokeTriangulation )
1660{
1661 m_currentManager->Shader( SHADER_NONE );
1663
1664 if( m_isFillEnabled )
1665 {
1666 int totalTriangleCount = 0;
1667
1668 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1669 {
1670 auto triPoly = aPolySet.TriangulatedPolygon( j );
1671
1672 totalTriangleCount += triPoly->GetTriangleCount();
1673 }
1674
1675 m_currentManager->Reserve( 3 * totalTriangleCount );
1676
1677 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1678 {
1679 auto triPoly = aPolySet.TriangulatedPolygon( j );
1680
1681 for( size_t i = 0; i < triPoly->GetTriangleCount(); i++ )
1682 {
1683 VECTOR2I a, b, c;
1684 triPoly->GetTriangle( i, a, b, c );
1685 m_currentManager->Vertex( a.x, a.y, m_layerDepth );
1686 m_currentManager->Vertex( b.x, b.y, m_layerDepth );
1687 m_currentManager->Vertex( c.x, c.y, m_layerDepth );
1688 }
1689 }
1690 }
1691
1692 if( m_isStrokeEnabled )
1693 {
1694 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1695 {
1696 const auto& poly = aPolySet.Polygon( j );
1697
1698 for( const auto& lc : poly )
1699 {
1700 DrawPolyline( lc );
1701 }
1702 }
1703 }
1704
1705 if( ADVANCED_CFG::GetCfg().m_DrawTriangulationOutlines )
1706 {
1707 aStrokeTriangulation = true;
1708 SetStrokeColor( COLOR4D( 0.0, 1.0, 0.2, 1.0 ) );
1709 }
1710
1711 if( aStrokeTriangulation )
1712 {
1715
1716 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1717 {
1718 auto triPoly = aPolySet.TriangulatedPolygon( j );
1719
1720 for( size_t i = 0; i < triPoly->GetTriangleCount(); i++ )
1721 {
1722 VECTOR2I a, b, c;
1723 triPoly->GetTriangle( i, a, b, c );
1724 DrawLine( a, b );
1725 DrawLine( b, c );
1726 DrawLine( c, a );
1727 }
1728 }
1729 }
1730}
1731
1732
1733void OPENGL_GAL::DrawPolygon( const SHAPE_POLY_SET& aPolySet, bool aStrokeTriangulation )
1734{
1735 if( aPolySet.IsTriangulationUpToDate() )
1736 {
1737 drawTriangulatedPolyset( aPolySet, aStrokeTriangulation );
1738 return;
1739 }
1740
1741 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1742 {
1743 const SHAPE_LINE_CHAIN& outline = aPolySet.COutline( j );
1744 DrawPolygon( outline );
1745 }
1746}
1747
1748
1750{
1751 if( aPolygon.PointCount() < 2 )
1752 return;
1753
1754 const int pointCount = aPolygon.SegmentCount() + 1;
1755 std::unique_ptr<GLdouble[]> points( new GLdouble[3 * pointCount] );
1756 GLdouble* ptr = points.get();
1757
1758 for( int i = 0; i < pointCount; ++i )
1759 {
1760 const VECTOR2I& p = aPolygon.CPoint( i );
1761 *ptr++ = p.x;
1762 *ptr++ = p.y;
1763 *ptr++ = m_layerDepth;
1764 }
1765
1766 drawPolygon( points.get(), pointCount );
1767}
1768
1769
1770void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
1771 const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint,
1772 double aFilterValue )
1773{
1774 std::vector<VECTOR2D> output;
1775 std::vector<VECTOR2D> pointCtrl;
1776
1777 pointCtrl.push_back( aStartPoint );
1778 pointCtrl.push_back( aControlPointA );
1779 pointCtrl.push_back( aControlPointB );
1780 pointCtrl.push_back( aEndPoint );
1781
1782 BEZIER_POLY converter( pointCtrl );
1783 converter.GetPoly( output, aFilterValue );
1784
1785 if( output.size() == 1 )
1786 output.push_back( output.front() );
1787
1788 DrawPolygon( &output[0], output.size() );
1789}
1790
1791
1792void OPENGL_GAL::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
1793{
1794 GLfloat alpha = std::clamp( alphaBlend, 0.0, 1.0 );
1795
1796 // We have to calculate the pixel size in users units to draw the image.
1797 // m_worldUnitLength is a factor used for converting IU to inches
1798 double scale = 1.0 / ( aBitmap.GetPPI() * m_worldUnitLength );
1799 double w = (double) aBitmap.GetSizePixels().x * scale;
1800 double h = (double) aBitmap.GetSizePixels().y * scale;
1801
1802 auto xform = m_currentManager->GetTransformation();
1803
1804 glm::vec4 v0 = xform * glm::vec4( -w / 2, -h / 2, 0.0, 0.0 );
1805 glm::vec4 v1 = xform * glm::vec4( w / 2, h / 2, 0.0, 0.0 );
1806 glm::vec4 trans = xform[3];
1807
1808 auto texture_id = m_bitmapCache->RequestBitmap( &aBitmap );
1809
1810 if( !glIsTexture( texture_id ) ) // ensure the bitmap texture is still valid
1811 return;
1812
1813 GLboolean depthMask = GL_TRUE;
1814 glGetBooleanv( GL_DEPTH_WRITEMASK, &depthMask );
1815
1816 if( alpha < 1.0f )
1817 glDepthMask( GL_FALSE );
1818
1819 glDepthFunc( GL_ALWAYS );
1820
1821 glAlphaFunc( GL_GREATER, 0.01f );
1822 glEnable( GL_ALPHA_TEST );
1823
1824 glMatrixMode( GL_TEXTURE );
1825 glPushMatrix();
1826 glTranslated( 0.5, 0.5, 0.5 );
1827 glRotated( aBitmap.Rotation().AsDegrees(), 0, 0, 1 );
1828 glTranslated( -0.5, -0.5, -0.5 );
1829
1830 glMatrixMode( GL_MODELVIEW );
1831 glPushMatrix();
1832 glTranslated( trans.x, trans.y, trans.z );
1833
1834 glEnable( GL_TEXTURE_2D );
1835 glActiveTexture( GL_TEXTURE0 );
1836 glBindTexture( GL_TEXTURE_2D, texture_id );
1837
1838 float texStartX = aBitmap.IsMirroredX() ? 1.0 : 0.0;
1839 float texEndX = aBitmap.IsMirroredX() ? 0.0 : 1.0;
1840 float texStartY = aBitmap.IsMirroredY() ? 1.0 : 0.0;
1841 float texEndY = aBitmap.IsMirroredY() ? 0.0 : 1.0;
1842
1843 glBegin( GL_QUADS );
1844 glColor4f( 1.0, 1.0, 1.0, alpha );
1845 glTexCoord2f( texStartX, texStartY );
1846 glVertex3f( v0.x, v0.y, m_layerDepth );
1847 glColor4f( 1.0, 1.0, 1.0, alpha );
1848 glTexCoord2f( texEndX, texStartY);
1849 glVertex3f( v1.x, v0.y, m_layerDepth );
1850 glColor4f( 1.0, 1.0, 1.0, alpha );
1851 glTexCoord2f( texEndX, texEndY);
1852 glVertex3f( v1.x, v1.y, m_layerDepth );
1853 glColor4f( 1.0, 1.0, 1.0, alpha );
1854 glTexCoord2f( texStartX, texEndY);
1855 glVertex3f( v0.x, v1.y, m_layerDepth );
1856 glEnd();
1857
1858 glBindTexture( GL_TEXTURE_2D, 0 );
1859
1860#ifdef DISABLE_BITMAP_CACHE
1861 glDeleteTextures( 1, &texture_id );
1862#endif
1863
1864 glPopMatrix();
1865
1866 glMatrixMode( GL_TEXTURE );
1867 glPopMatrix();
1868 glMatrixMode( GL_MODELVIEW );
1869
1870 glDisable( GL_ALPHA_TEST );
1871
1872 glDepthMask( depthMask );
1873
1874 glDepthFunc( GL_LESS );
1875}
1876
1877
1878void OPENGL_GAL::BitmapText( const wxString& aText, const VECTOR2I& aPosition,
1879 const EDA_ANGLE& aAngle )
1880{
1881 // Fallback to generic impl (which uses the stroke font) on cases we don't handle
1882 if( IsTextMirrored()
1883 || aText.Contains( wxT( "^{" ) )
1884 || aText.Contains( wxT( "_{" ) )
1885 || aText.Contains( wxT( "\n" ) ) )
1886 {
1887 return GAL::BitmapText( aText, aPosition, aAngle );
1888 }
1889
1890 const UTF8 text( aText );
1891 VECTOR2D textSize;
1892 float commonOffset;
1893 std::tie( textSize, commonOffset ) = computeBitmapTextSize( text );
1894
1895 const double SCALE = 1.4 * GetGlyphSize().y / textSize.y;
1896 double overbarHeight = textSize.y;
1897
1898 Save();
1899
1901 m_currentManager->Translate( aPosition.x, aPosition.y, m_layerDepth );
1902 m_currentManager->Rotate( aAngle.AsRadians(), 0.0f, 0.0f, -1.0f );
1903
1904 double sx = SCALE * ( m_globalFlipX ? -1.0 : 1.0 );
1905 double sy = SCALE * ( m_globalFlipY ? -1.0 : 1.0 );
1906
1907 m_currentManager->Scale( sx, sy, 0 );
1908 m_currentManager->Translate( 0, -commonOffset, 0 );
1909
1910 switch( GetHorizontalJustify() )
1911 {
1913 Translate( VECTOR2D( -textSize.x / 2.0, 0 ) );
1914 break;
1915
1917 //if( !IsTextMirrored() )
1918 Translate( VECTOR2D( -textSize.x, 0 ) );
1919 break;
1920
1922 //if( IsTextMirrored() )
1923 //Translate( VECTOR2D( -textSize.x, 0 ) );
1924 break;
1925
1927 wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
1928 break;
1929 }
1930
1931 switch( GetVerticalJustify() )
1932 {
1934 break;
1935
1937 Translate( VECTOR2D( 0, -textSize.y / 2.0 ) );
1938 overbarHeight = 0;
1939 break;
1940
1942 Translate( VECTOR2D( 0, -textSize.y ) );
1943 overbarHeight = -textSize.y / 2.0;
1944 break;
1945
1947 wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
1948 break;
1949 }
1950
1951 int overbarLength = 0;
1952 int overbarDepth = -1;
1953 int braceNesting = 0;
1954
1955 auto iterateString =
1956 [&]( const std::function<void( int aOverbarLength, int aOverbarHeight )>& overbarFn,
1957 const std::function<int( unsigned long aChar )>& bitmapCharFn )
1958 {
1959 for( UTF8::uni_iter chIt = text.ubegin(), end = text.uend(); chIt < end; ++chIt )
1960 {
1961 wxASSERT_MSG( *chIt != '\n' && *chIt != '\r',
1962 "No support for multiline bitmap text yet" );
1963
1964 if( *chIt == '~' && overbarDepth == -1 )
1965 {
1966 UTF8::uni_iter lookahead = chIt;
1967
1968 if( ++lookahead != end && *lookahead == '{' )
1969 {
1970 chIt = lookahead;
1971 overbarDepth = braceNesting;
1972 braceNesting++;
1973 continue;
1974 }
1975 }
1976 else if( *chIt == '{' )
1977 {
1978 braceNesting++;
1979 }
1980 else if( *chIt == '}' )
1981 {
1982 if( braceNesting > 0 )
1983 braceNesting--;
1984
1985 if( braceNesting == overbarDepth )
1986 {
1987 overbarFn( overbarLength, overbarHeight );
1988 overbarLength = 0;
1989
1990 overbarDepth = -1;
1991 continue;
1992 }
1993 }
1994
1995 if( overbarDepth != -1 )
1996 overbarLength += bitmapCharFn( *chIt );
1997 else
1998 bitmapCharFn( *chIt );
1999 }
2000 };
2001
2002 // First, calculate the amount of characters and overbars to reserve
2003
2004 int charsCount = 0;
2005 int overbarsCount = 0;
2006
2007 iterateString(
2008 [&overbarsCount]( int aOverbarLength, int aOverbarHeight )
2009 {
2010 overbarsCount++;
2011 },
2012 [&charsCount]( unsigned long aChar ) -> int
2013 {
2014 if( aChar != ' ' )
2015 charsCount++;
2016
2017 return 0;
2018 } );
2019
2020 m_currentManager->Reserve( 6 * charsCount + 6 * overbarsCount );
2021
2022 // Now reset the state and actually draw the characters and overbars
2023 overbarLength = 0;
2024 overbarDepth = -1;
2025 braceNesting = 0;
2026
2027 iterateString(
2028 [&]( int aOverbarLength, int aOverbarHeight )
2029 {
2030 drawBitmapOverbar( aOverbarLength, aOverbarHeight, false );
2031 },
2032 [&]( unsigned long aChar ) -> int
2033 {
2034 return drawBitmapChar( aChar, false );
2035 } );
2036
2037 // Handle the case when overbar is active till the end of the drawn text
2038 m_currentManager->Translate( 0, commonOffset, 0 );
2039
2040 if( overbarDepth != -1 && overbarLength > 0 )
2041 drawBitmapOverbar( overbarLength, overbarHeight );
2042
2043 Restore();
2044}
2045
2046
2048{
2050 m_compositor->SetBuffer( m_mainBuffer );
2051
2052 m_nonCachedManager->EnableDepthTest( false );
2053
2054 // sub-pixel lines all render the same
2055 float minorLineWidth = std::fmax( 1.0f,
2057 float majorLineWidth = minorLineWidth * 2.0f;
2058
2059 // Draw the axis and grid
2060 // For the drawing the start points, end points and increments have
2061 // to be calculated in world coordinates
2062 VECTOR2D worldStartPoint = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
2064
2065 // Draw axes if desired
2066 if( m_axesEnabled )
2067 {
2068 SetLineWidth( minorLineWidth );
2070
2071 DrawLine( VECTOR2D( worldStartPoint.x, 0 ), VECTOR2D( worldEndPoint.x, 0 ) );
2072 DrawLine( VECTOR2D( 0, worldStartPoint.y ), VECTOR2D( 0, worldEndPoint.y ) );
2073 }
2074
2075 // force flush
2076 m_nonCachedManager->EndDrawing();
2077
2078 if( !m_gridVisibility || m_gridSize.x == 0 || m_gridSize.y == 0 )
2079 return;
2080
2081 VECTOR2D gridScreenSize = GetVisibleGridSize();
2082
2083 // Compute grid starting and ending indexes to draw grid points on the
2084 // visible screen area
2085 // Note: later any point coordinate will be offset by m_gridOrigin
2086 int gridStartX = KiROUND( ( worldStartPoint.x - m_gridOrigin.x ) / gridScreenSize.x );
2087 int gridEndX = KiROUND( ( worldEndPoint.x - m_gridOrigin.x ) / gridScreenSize.x );
2088 int gridStartY = KiROUND( ( worldStartPoint.y - m_gridOrigin.y ) / gridScreenSize.y );
2089 int gridEndY = KiROUND( ( worldEndPoint.y - m_gridOrigin.y ) / gridScreenSize.y );
2090
2091 // Ensure start coordinate < end coordinate
2092 normalize( gridStartX, gridEndX );
2093 normalize( gridStartY, gridEndY );
2094
2095 // Ensure the grid fills the screen
2096 --gridStartX;
2097 ++gridEndX;
2098 --gridStartY;
2099 ++gridEndY;
2100
2101 glDisable( GL_DEPTH_TEST );
2102 glDisable( GL_TEXTURE_2D );
2103
2105 {
2106 glEnable( GL_STENCIL_TEST );
2107 glStencilFunc( GL_ALWAYS, 1, 1 );
2108 glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
2109 glColor4d( 0.0, 0.0, 0.0, 0.0 );
2110 SetStrokeColor( COLOR4D( 0.0, 0.0, 0.0, 0.0 ) );
2111 }
2112 else
2113 {
2114 glColor4d( m_gridColor.r, m_gridColor.g, m_gridColor.b, m_gridColor.a );
2116 }
2117
2119 {
2120 // Vertical positions
2121 for( int j = gridStartY; j <= gridEndY; j++ )
2122 {
2123 bool tickY = ( j % m_gridTick == 0 );
2124 const double posY = j * gridScreenSize.y + m_gridOrigin.y;
2125
2126 // Horizontal positions
2127 for( int i = gridStartX; i <= gridEndX; i++ )
2128 {
2129 bool tickX = ( i % m_gridTick == 0 );
2130 SetLineWidth( ( ( tickX && tickY ) ? majorLineWidth : minorLineWidth ) );
2131 auto lineLen = 2.0 * GetLineWidth();
2132 auto posX = i * gridScreenSize.x + m_gridOrigin.x;
2133
2134 DrawLine( VECTOR2D( posX - lineLen, posY ), VECTOR2D( posX + lineLen, posY ) );
2135 DrawLine( VECTOR2D( posX, posY - lineLen ), VECTOR2D( posX, posY + lineLen ) );
2136 }
2137 }
2138
2139 m_nonCachedManager->EndDrawing();
2140 }
2141 else
2142 {
2143 // Vertical lines
2144 for( int j = gridStartY; j <= gridEndY; j++ )
2145 {
2146 const double y = j * gridScreenSize.y + m_gridOrigin.y;
2147
2148 // If axes are drawn, skip the lines that would cover them
2149 if( m_axesEnabled && y == 0.0 )
2150 continue;
2151
2152 SetLineWidth( ( j % m_gridTick == 0 ) ? majorLineWidth : minorLineWidth );
2153 VECTOR2D a( gridStartX * gridScreenSize.x + m_gridOrigin.x, y );
2154 VECTOR2D b( gridEndX * gridScreenSize.x + m_gridOrigin.x, y );
2155
2156 DrawLine( a, b );
2157 }
2158
2159 m_nonCachedManager->EndDrawing();
2160
2162 {
2163 glStencilFunc( GL_NOTEQUAL, 0, 1 );
2164 glColor4d( m_gridColor.r, m_gridColor.g, m_gridColor.b, m_gridColor.a );
2166 }
2167
2168 // Horizontal lines
2169 for( int i = gridStartX; i <= gridEndX; i++ )
2170 {
2171 const double x = i * gridScreenSize.x + m_gridOrigin.x;
2172
2173 // If axes are drawn, skip the lines that would cover them
2174 if( m_axesEnabled && x == 0.0 )
2175 continue;
2176
2177 SetLineWidth( ( i % m_gridTick == 0 ) ? majorLineWidth : minorLineWidth );
2178 VECTOR2D a( x, gridStartY * gridScreenSize.y + m_gridOrigin.y );
2179 VECTOR2D b( x, gridEndY * gridScreenSize.y + m_gridOrigin.y );
2180 DrawLine( a, b );
2181 }
2182
2183 m_nonCachedManager->EndDrawing();
2184
2186 glDisable( GL_STENCIL_TEST );
2187 }
2188
2189 m_nonCachedManager->EnableDepthTest( true );
2190 glEnable( GL_DEPTH_TEST );
2191 glEnable( GL_TEXTURE_2D );
2192}
2193
2194
2195void OPENGL_GAL::ResizeScreen( int aWidth, int aHeight )
2196{
2197 m_screenSize = VECTOR2I( aWidth, aHeight );
2198
2199 // Resize framebuffers
2200 const float scaleFactor = GetScaleFactor();
2201 m_compositor->Resize( aWidth * scaleFactor, aHeight * scaleFactor );
2203
2204 wxGLCanvas::SetSize( aWidth, aHeight );
2205}
2206
2207
2208bool OPENGL_GAL::Show( bool aShow )
2209{
2210 bool s = wxGLCanvas::Show( aShow );
2211
2212 if( aShow )
2213 wxGLCanvas::Raise();
2214
2215 return s;
2216}
2217
2218
2220{
2221 glFlush();
2222}
2223
2224
2226{
2227 // Clear screen
2229
2230 // NOTE: Black used here instead of m_clearColor; it will be composited later
2231 glClearColor( 0, 0, 0, 1 );
2232 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
2233}
2234
2235
2236void OPENGL_GAL::Transform( const MATRIX3x3D& aTransformation )
2237{
2238 GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
2239
2240 matrixData[0] = aTransformation.m_data[0][0];
2241 matrixData[1] = aTransformation.m_data[1][0];
2242 matrixData[2] = aTransformation.m_data[2][0];
2243 matrixData[4] = aTransformation.m_data[0][1];
2244 matrixData[5] = aTransformation.m_data[1][1];
2245 matrixData[6] = aTransformation.m_data[2][1];
2246 matrixData[12] = aTransformation.m_data[0][2];
2247 matrixData[13] = aTransformation.m_data[1][2];
2248 matrixData[14] = aTransformation.m_data[2][2];
2249
2250 glMultMatrixd( matrixData );
2251}
2252
2253
2254void OPENGL_GAL::Rotate( double aAngle )
2255{
2256 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
2257}
2258
2259
2260void OPENGL_GAL::Translate( const VECTOR2D& aVector )
2261{
2262 m_currentManager->Translate( aVector.x, aVector.y, 0.0f );
2263}
2264
2265
2266void OPENGL_GAL::Scale( const VECTOR2D& aScale )
2267{
2268 m_currentManager->Scale( aScale.x, aScale.y, 1.0f );
2269}
2270
2271
2273{
2274 m_currentManager->PushMatrix();
2275}
2276
2277
2279{
2280 m_currentManager->PopMatrix();
2281}
2282
2283
2285{
2286 m_isGrouping = true;
2287
2288 std::shared_ptr<VERTEX_ITEM> newItem = std::make_shared<VERTEX_ITEM>( *m_cachedManager );
2289 int groupNumber = getNewGroupNumber();
2290 m_groups.insert( std::make_pair( groupNumber, newItem ) );
2291
2292 return groupNumber;
2293}
2294
2295
2297{
2298 m_cachedManager->FinishItem();
2299 m_isGrouping = false;
2300}
2301
2302
2303void OPENGL_GAL::DrawGroup( int aGroupNumber )
2304{
2305 auto group = m_groups.find( aGroupNumber );
2306
2307 if( group != m_groups.end() )
2308 m_cachedManager->DrawItem( *group->second );
2309}
2310
2311
2312void OPENGL_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
2313{
2314 auto group = m_groups.find( aGroupNumber );
2315
2316 if( group != m_groups.end() )
2317 m_cachedManager->ChangeItemColor( *group->second, aNewColor );
2318}
2319
2320
2321void OPENGL_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
2322{
2323 auto group = m_groups.find( aGroupNumber );
2324
2325 if( group != m_groups.end() )
2326 m_cachedManager->ChangeItemDepth( *group->second, aDepth );
2327}
2328
2329
2330void OPENGL_GAL::DeleteGroup( int aGroupNumber )
2331{
2332 // Frees memory in the container as well
2333 m_groups.erase( aGroupNumber );
2334}
2335
2336
2338{
2339 m_bitmapCache = std::make_unique<GL_BITMAP_CACHE>();
2340
2341 m_groups.clear();
2342
2343 if( m_isInitialized )
2344 m_cachedManager->Clear();
2345}
2346
2347
2349{
2350 switch( aTarget )
2351 {
2352 default:
2357 }
2358
2359 m_currentTarget = aTarget;
2360}
2361
2362
2364{
2365 return m_currentTarget;
2366}
2367
2368
2370{
2371 // Save the current state
2372 unsigned int oldTarget = m_compositor->GetBuffer();
2373
2374 switch( aTarget )
2375 {
2376 // Cached and noncached items are rendered to the same buffer
2377 default:
2378 case TARGET_CACHED:
2379 case TARGET_NONCACHED:
2380 m_compositor->SetBuffer( m_mainBuffer );
2381 break;
2382
2383 case TARGET_TEMP:
2384 if( m_tempBuffer )
2385 m_compositor->SetBuffer( m_tempBuffer );
2386 break;
2387
2388 case TARGET_OVERLAY:
2389 if( m_overlayBuffer )
2390 m_compositor->SetBuffer( m_overlayBuffer );
2391 break;
2392 }
2393
2394 if( aTarget != TARGET_OVERLAY )
2395 m_compositor->ClearBuffer( m_clearColor );
2396 else if( m_overlayBuffer )
2397 m_compositor->ClearBuffer( COLOR4D::BLACK );
2398
2399 // Restore the previous state
2400 m_compositor->SetBuffer( oldTarget );
2401}
2402
2403
2405{
2406 switch( aTarget )
2407 {
2408 default:
2409 case TARGET_CACHED:
2410 case TARGET_NONCACHED: return true;
2411 case TARGET_OVERLAY: return ( m_overlayBuffer != 0 );
2412 case TARGET_TEMP: return ( m_tempBuffer != 0 );
2413 }
2414}
2415
2416
2418{
2419 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::StartDiffLayer() called" ) );
2420 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): m_tempBuffer=%u" ), m_tempBuffer );
2421
2422 m_currentManager->EndDrawing();
2423
2424 if( m_tempBuffer )
2425 {
2426 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): setting target to TARGET_TEMP" ) );
2429
2430 // ClearTarget restores the previous compositor buffer, so we need to explicitly
2431 // set the compositor to render to m_tempBuffer for the layer drawing
2432 m_compositor->SetBuffer( m_tempBuffer );
2433 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): TARGET_TEMP set and cleared, compositor buffer=%u" ),
2434 m_tempBuffer );
2435 }
2436 else
2437 {
2438 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): WARNING - no temp buffer!" ) );
2439 }
2440}
2441
2442
2444{
2445 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::EndDiffLayer() called" ) );
2446 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): m_tempBuffer=%u, m_mainBuffer=%u" ),
2448
2449 if( m_tempBuffer )
2450 {
2451 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): using temp buffer path" ) );
2452
2453 // End drawing to the temp buffer
2454 m_currentManager->EndDrawing();
2455
2456 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): calling DrawBufferDifference" ) );
2457
2458 // Use difference compositing for true XOR/difference mode:
2459 // - Where only one layer has content: shows that layer's color
2460 // - Where both layers overlap with identical content: cancels out (black)
2461 // - Where layers overlap with different content: shows the absolute difference
2462 m_compositor->DrawBufferDifference( m_tempBuffer, m_mainBuffer );
2463
2464 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): DrawBufferDifference returned" ) );
2465 }
2466 else
2467 {
2468 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): NO temp buffer, using fallback path" ) );
2469
2470 // Fall back to imperfect alpha blending on single buffer
2471 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
2472 m_currentManager->EndDrawing();
2473 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
2474 }
2475
2476 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::EndDiffLayer() complete" ) );
2477}
2478
2479
2480bool OPENGL_GAL::SetNativeCursorStyle( KICURSOR aCursor, bool aHiDPI )
2481{
2482 // Store the current cursor type and get the wx cursor for it
2483 if( !GAL::SetNativeCursorStyle( aCursor, aHiDPI ) )
2484 return false;
2485
2487
2488#if wxCHECK_VERSION( 3, 3, 0 )
2489 wxWindow::SetCursorBundle( m_currentwxCursor );
2490#else
2491 wxWindow::SetCursor( m_currentwxCursor );
2492#endif
2493
2494 return true;
2495}
2496
2497
2498void OPENGL_GAL::onSetNativeCursor( wxSetCursorEvent& aEvent )
2499{
2500#if wxCHECK_VERSION( 3, 3, 0 )
2501 aEvent.SetCursor( m_currentwxCursor.GetCursorFor( this ) );
2502#else
2503 aEvent.SetCursor( m_currentwxCursor );
2504#endif
2505}
2506
2507
2508void OPENGL_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
2509{
2510 // Now we should only store the position of the mouse cursor
2511 // The real drawing routines are in blitCursor()
2512 //VECTOR2D screenCursor = m_worldScreenMatrix * aCursorPosition;
2513 //m_cursorPosition = m_screenWorldMatrix * VECTOR2D( screenCursor.x, screenCursor.y );
2514 m_cursorPosition = aCursorPosition;
2515}
2516
2517
2518void OPENGL_GAL::drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
2519 const bool aReserve )
2520{
2521 /* Helper drawing: ____--- v3 ^
2522 * ____---- ... \ \
2523 * ____---- ... \ end \
2524 * v1 ____---- ... ____---- \ width
2525 * ---- ...___---- \ \
2526 * \ ___...-- \ v
2527 * \ ____----... ____---- v2
2528 * ---- ... ____----
2529 * start \ ... ____----
2530 * \... ____----
2531 * ----
2532 * v0
2533 * dots mark triangles' hypotenuses
2534 */
2535
2536 auto v1 = m_currentManager->GetTransformation()
2537 * glm::vec4( aStartPoint.x, aStartPoint.y, 0.0, 0.0 );
2538 auto v2 = m_currentManager->GetTransformation()
2539 * glm::vec4( aEndPoint.x, aEndPoint.y, 0.0, 0.0 );
2540
2541 VECTOR2D vs( v2.x - v1.x, v2.y - v1.y );
2542
2543 if( aReserve )
2544 reserveLineQuads( 1 );
2545
2546 // Line width is maintained by the vertex shader
2547 m_currentManager->Shader( SHADER_LINE_A, m_lineWidth, vs.x, vs.y );
2548 m_currentManager->Vertex( aStartPoint, m_layerDepth );
2549
2550 m_currentManager->Shader( SHADER_LINE_B, m_lineWidth, vs.x, vs.y );
2551 m_currentManager->Vertex( aStartPoint, m_layerDepth );
2552
2553 m_currentManager->Shader( SHADER_LINE_C, m_lineWidth, vs.x, vs.y );
2554 m_currentManager->Vertex( aEndPoint, m_layerDepth );
2555
2556 m_currentManager->Shader( SHADER_LINE_D, m_lineWidth, vs.x, vs.y );
2557 m_currentManager->Vertex( aEndPoint, m_layerDepth );
2558
2559 m_currentManager->Shader( SHADER_LINE_E, m_lineWidth, vs.x, vs.y );
2560 m_currentManager->Vertex( aEndPoint, m_layerDepth );
2561
2562 m_currentManager->Shader( SHADER_LINE_F, m_lineWidth, vs.x, vs.y );
2563 m_currentManager->Vertex( aStartPoint, m_layerDepth );
2564}
2565
2566
2567void OPENGL_GAL::reserveLineQuads( const int aLineCount )
2568{
2569 m_currentManager->Reserve( 6 * aLineCount );
2570}
2571
2572
2573void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle )
2574{
2575 if( m_isFillEnabled )
2576 {
2578 drawFilledSemiCircle( aCenterPoint, aRadius, aAngle );
2579 }
2580
2581 if( m_isStrokeEnabled )
2582 {
2584 m_strokeColor.a );
2585 drawStrokedSemiCircle( aCenterPoint, aRadius, aAngle );
2586 }
2587}
2588
2589
2590void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle )
2591{
2592 Save();
2593
2594 m_currentManager->Reserve( 3 );
2595 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
2596 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
2597
2598 /* Draw a triangle that contains the semicircle, then shade it to leave only
2599 * the semicircle. Parameters given to Shader() are indices of the triangle's vertices
2600 * (if you want to understand more, check the vertex shader source [shader.vert]).
2601 * Shader uses these coordinates to determine if fragments are inside the semicircle or not.
2602 * v2
2603 * /\
2604 * /__\
2605 * v0 //__\\ v1
2606 */
2607 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f );
2608 m_currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v0
2609
2610 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f );
2611 m_currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v1
2612
2613 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f );
2614 m_currentManager->Vertex( 0.0f, aRadius * 2.0f, m_layerDepth ); // v2
2615
2616 Restore();
2617}
2618
2619
2620void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle,
2621 bool aReserve )
2622{
2623 double outerRadius = aRadius + ( m_lineWidth / 2 );
2624
2625 Save();
2626
2627 if( aReserve )
2628 m_currentManager->Reserve( 3 );
2629
2630 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
2631 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
2632
2633 /* Draw a triangle that contains the semicircle, then shade it to leave only
2634 * the semicircle. Parameters given to Shader() are indices of the triangle's vertices
2635 * (if you want to understand more, check the vertex shader source [shader.vert]), the
2636 * radius and the line width. Shader uses these coordinates to determine if fragments are
2637 * inside the semicircle or not.
2638 * v2
2639 * /\
2640 * /__\
2641 * v0 //__\\ v1
2642 */
2643 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, m_lineWidth );
2644 m_currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v0
2645
2646 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, m_lineWidth );
2647 m_currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v1
2648
2649 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, m_lineWidth );
2650 m_currentManager->Vertex( 0.0f, outerRadius * 2.0f, m_layerDepth ); // v2
2651
2652 Restore();
2653}
2654
2655
2656void OPENGL_GAL::drawPolygon( GLdouble* aPoints, int aPointCount )
2657{
2658 if( m_isFillEnabled )
2659 {
2660 m_currentManager->Shader( SHADER_NONE );
2662
2663 // Any non convex polygon needs to be tesselated
2664 // for this purpose the GLU standard functions are used
2666 gluTessBeginPolygon( m_tesselator, &params );
2667 gluTessBeginContour( m_tesselator );
2668
2669 GLdouble* point = aPoints;
2670
2671 for( int i = 0; i < aPointCount; ++i )
2672 {
2673 gluTessVertex( m_tesselator, point, point );
2674 point += 3; // 3 coordinates
2675 }
2676
2677 gluTessEndContour( m_tesselator );
2678 gluTessEndPolygon( m_tesselator );
2679
2680 // Free allocated intersecting points
2681 m_tessIntersects.clear();
2682 }
2683
2684 if( m_isStrokeEnabled )
2685 {
2687 [&]( int idx )
2688 {
2689 return VECTOR2D( aPoints[idx * 3], aPoints[idx * 3 + 1] );
2690 },
2691 aPointCount );
2692 }
2693}
2694
2695
2696void OPENGL_GAL::drawPolyline( const std::function<VECTOR2D( int )>& aPointGetter, int aPointCount,
2697 bool aReserve )
2698{
2699 wxCHECK( aPointCount > 0, /* return */ );
2700
2702
2703 if( aPointCount == 1 )
2704 {
2705 drawLineQuad( aPointGetter( 0 ), aPointGetter( 0 ), aReserve );
2706 return;
2707 }
2708
2709 if( aReserve )
2710 {
2711 reserveLineQuads( aPointCount - 1 );
2712 }
2713
2714 for( int i = 1; i < aPointCount; ++i )
2715 {
2716 auto start = aPointGetter( i - 1 );
2717 auto end = aPointGetter( i );
2718
2719 drawLineQuad( start, end, false );
2720 }
2721}
2722
2723
2724void OPENGL_GAL::drawSegmentChain( const std::function<VECTOR2D( int )>& aPointGetter,
2725 int aPointCount, double aWidth, bool aReserve )
2726{
2727 wxCHECK( aPointCount >= 2, /* return */ );
2728
2730
2731 int vertices = 0;
2732
2733 for( int i = 1; i < aPointCount; ++i )
2734 {
2735 auto start = aPointGetter( i - 1 );
2736 auto end = aPointGetter( i );
2737
2738 float startx = start.x;
2739 float starty = start.y;
2740 float endx = end.x;
2741 float endy = end.y;
2742
2743 // Be careful about floating point rounding. As we draw segments in larger and larger
2744 // coordinates, the shader (which uses floats) will lose precision and stop drawing small
2745 // segments. In this case, we need to draw a circle for the minimal segment.
2746 // Check if the coordinate differences can be accurately represented as floats
2747
2748 if( startx == endx && starty == endy )
2749 {
2750 vertices += 3; // One circle
2751 continue;
2752 }
2753
2754 if( m_isFillEnabled || aWidth == 1.0 )
2755 {
2756 vertices += 6; // One line
2757 }
2758 else
2759 {
2760 vertices += 6 + 6 + 3 + 3; // Two lines and two half-circles
2761 }
2762 }
2763
2764 m_currentManager->Reserve( vertices );
2765
2766 for( int i = 1; i < aPointCount; ++i )
2767 {
2768 auto start = aPointGetter( i - 1 );
2769 auto end = aPointGetter( i );
2770
2771 drawSegment( start, end, aWidth, false );
2772 }
2773}
2774
2775
2776int OPENGL_GAL::drawBitmapChar( unsigned long aChar, bool aReserve )
2777{
2778 const float TEX_X = font_image.width;
2779 const float TEX_Y = font_image.height;
2780
2781 // handle space
2782 if( aChar == ' ' )
2783 {
2784 const FONT_GLYPH_TYPE* g = LookupGlyph( 'x' );
2785 wxCHECK( g, 0 );
2786
2787 // Match stroke font as well as possible
2788 double spaceWidth = g->advance * 0.74;
2789
2790 Translate( VECTOR2D( spaceWidth, 0 ) );
2791 return KiROUND( spaceWidth );
2792 }
2793
2794 const FONT_GLYPH_TYPE* glyph = LookupGlyph( aChar );
2795
2796 // If the glyph is not found (happens for many esoteric unicode chars)
2797 // shows a '?' instead.
2798 if( !glyph )
2799 glyph = LookupGlyph( '?' );
2800
2801 if( !glyph ) // Should not happen.
2802 return 0;
2803
2804 const float X = glyph->atlas_x + font_information.smooth_pixels;
2805 const float Y = glyph->atlas_y + font_information.smooth_pixels;
2806 const float XOFF = glyph->minx;
2807
2808 // adjust for height rounding
2809 const float round_adjust = ( glyph->maxy - glyph->miny )
2810 - float( glyph->atlas_h - font_information.smooth_pixels * 2 );
2811 const float top_adjust = font_information.max_y - glyph->maxy;
2812 const float YOFF = round_adjust + top_adjust;
2813 const float W = glyph->atlas_w - font_information.smooth_pixels * 2;
2814 const float H = glyph->atlas_h - font_information.smooth_pixels * 2;
2815 const float B = 0;
2816
2817 if( aReserve )
2818 m_currentManager->Reserve( 6 );
2819
2820 Translate( VECTOR2D( XOFF, YOFF ) );
2821
2822 /* Glyph:
2823 * v0 v1
2824 * +--+
2825 * | /|
2826 * |/ |
2827 * +--+
2828 * v2 v3
2829 */
2830 m_currentManager->Shader( SHADER_FONT, X / TEX_X, ( Y + H ) / TEX_Y );
2831 m_currentManager->Vertex( -B, -B, 0 ); // v0
2832
2833 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, ( Y + H ) / TEX_Y );
2834 m_currentManager->Vertex( W + B, -B, 0 ); // v1
2835
2836 m_currentManager->Shader( SHADER_FONT, X / TEX_X, Y / TEX_Y );
2837 m_currentManager->Vertex( -B, H + B, 0 ); // v2
2838
2839
2840 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, ( Y + H ) / TEX_Y );
2841 m_currentManager->Vertex( W + B, -B, 0 ); // v1
2842
2843 m_currentManager->Shader( SHADER_FONT, X / TEX_X, Y / TEX_Y );
2844 m_currentManager->Vertex( -B, H + B, 0 ); // v2
2845
2846 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, Y / TEX_Y );
2847 m_currentManager->Vertex( W + B, H + B, 0 ); // v3
2848
2849 Translate( VECTOR2D( -XOFF + glyph->advance, -YOFF ) );
2850
2851 return glyph->advance;
2852}
2853
2854
2855void OPENGL_GAL::drawBitmapOverbar( double aLength, double aHeight, bool aReserve )
2856{
2857 // To draw an overbar, simply draw an overbar
2858 const FONT_GLYPH_TYPE* glyph = LookupGlyph( '_' );
2859 wxCHECK( glyph, /* void */ );
2860
2861 const float H = glyph->maxy - glyph->miny;
2862
2863 Save();
2864
2865 Translate( VECTOR2D( -aLength, -aHeight ) );
2866
2867 if( aReserve )
2868 m_currentManager->Reserve( 6 );
2869
2871
2872 m_currentManager->Shader( 0 );
2873
2874 m_currentManager->Vertex( 0, 0, 0 ); // v0
2875 m_currentManager->Vertex( aLength, 0, 0 ); // v1
2876 m_currentManager->Vertex( 0, H, 0 ); // v2
2877
2878 m_currentManager->Vertex( aLength, 0, 0 ); // v1
2879 m_currentManager->Vertex( 0, H, 0 ); // v2
2880 m_currentManager->Vertex( aLength, H, 0 ); // v3
2881
2882 Restore();
2883}
2884
2885
2886std::pair<VECTOR2D, float> OPENGL_GAL::computeBitmapTextSize( const UTF8& aText ) const
2887{
2888 static const FONT_GLYPH_TYPE* defaultGlyph = LookupGlyph( '(' ); // for strange chars
2889
2890 VECTOR2D textSize( 0, 0 );
2891 float commonOffset = std::numeric_limits<float>::max();
2892 float charHeight = font_information.max_y - defaultGlyph->miny;
2893 int overbarDepth = -1;
2894 int braceNesting = 0;
2895
2896 for( UTF8::uni_iter chIt = aText.ubegin(), end = aText.uend(); chIt < end; ++chIt )
2897 {
2898 if( *chIt == '~' && overbarDepth == -1 )
2899 {
2900 UTF8::uni_iter lookahead = chIt;
2901
2902 if( ++lookahead != end && *lookahead == '{' )
2903 {
2904 chIt = lookahead;
2905 overbarDepth = braceNesting;
2906 braceNesting++;
2907 continue;
2908 }
2909 }
2910 else if( *chIt == '{' )
2911 {
2912 braceNesting++;
2913 }
2914 else if( *chIt == '}' )
2915 {
2916 if( braceNesting > 0 )
2917 braceNesting--;
2918
2919 if( braceNesting == overbarDepth )
2920 {
2921 overbarDepth = -1;
2922 continue;
2923 }
2924 }
2925
2926 const FONT_GLYPH_TYPE* glyph = LookupGlyph( *chIt );
2927
2928 if( !glyph // Not coded in font
2929 || *chIt == '-' || *chIt == '_' ) // Strange size of these 2 chars
2930 {
2931 glyph = defaultGlyph;
2932 }
2933
2934 if( glyph )
2935 textSize.x += glyph->advance;
2936 }
2937
2938 textSize.y = std::max<float>( textSize.y, charHeight );
2939 commonOffset = std::min<float>( font_information.max_y - defaultGlyph->maxy, commonOffset );
2940 textSize.y -= commonOffset;
2941
2942 return std::make_pair( textSize, commonOffset );
2943}
2944
2945
2946void OPENGL_GAL::onPaint( wxPaintEvent& aEvent )
2947{
2948 PostPaint( aEvent );
2949}
2950
2951
2952void OPENGL_GAL::skipMouseEvent( wxMouseEvent& aEvent )
2953{
2954 // Post the mouse event to the event listener registered in constructor, if any
2955 if( m_mouseListener )
2956 wxPostEvent( m_mouseListener, aEvent );
2957}
2958
2959
2960void OPENGL_GAL::skipGestureEvent( wxGestureEvent& aEvent )
2961{
2962 // Post the gesture event to the event listener registered in constructor, if any
2963 if( m_mouseListener )
2964 wxPostEvent( m_mouseListener, aEvent );
2965}
2966
2967
2969{
2970 if( !IsCursorEnabled() )
2971 return;
2972
2974
2975 VECTOR2D cursorBegin;
2976 VECTOR2D cursorEnd;
2977 VECTOR2D cursorCenter = m_cursorPosition;
2978
2980 {
2981 cursorBegin = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
2982 cursorEnd = m_screenWorldMatrix * VECTOR2D( m_screenSize );
2983 }
2985 {
2986 const int cursorSize = 80;
2987 cursorBegin = m_cursorPosition - cursorSize / ( 2 * m_worldScale );
2988 cursorEnd = m_cursorPosition + cursorSize / ( 2 * m_worldScale );
2989 }
2990
2991 const COLOR4D color = getCursorColor();
2992
2993 GLboolean depthTestEnabled = glIsEnabled( GL_DEPTH_TEST );
2994 glDisable( GL_DEPTH_TEST );
2995
2996 glActiveTexture( GL_TEXTURE0 );
2997 glDisable( GL_TEXTURE_2D );
2998 glEnable( GL_BLEND );
2999 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
3000
3001 glLineWidth( 1.0 );
3002 glColor4d( color.r, color.g, color.b, color.a );
3003
3004 glMatrixMode( GL_PROJECTION );
3005 glPushMatrix();
3006 glTranslated( 0, 0, -0.5 );
3007
3008 glBegin( GL_LINES );
3009
3011 {
3012 // Calculate screen bounds in world coordinates
3013 VECTOR2D screenTopLeft = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
3014 VECTOR2D screenBottomRight = m_screenWorldMatrix * VECTOR2D( m_screenSize );
3015
3016 // For 45-degree lines passing through cursor position
3017 // Line equation: y = x + (cy - cx) for positive slope
3018 // Line equation: y = -x + (cy + cx) for negative slope
3019 double cx = m_cursorPosition.x;
3020 double cy = m_cursorPosition.y;
3021
3022 // Calculate intersections for positive slope diagonal (y = x + offset)
3023 double offset1 = cy - cx;
3024 VECTOR2D pos_start( screenTopLeft.x, screenTopLeft.x + offset1 );
3025 VECTOR2D pos_end( screenBottomRight.x, screenBottomRight.x + offset1 );
3026
3027 // Draw positive slope diagonal
3028 glVertex2d( pos_start.x, pos_start.y );
3029 glVertex2d( pos_end.x, pos_end.y );
3030
3031 // Calculate intersections for negative slope diagonal (y = -x + offset)
3032 double offset2 = cy + cx;
3033 VECTOR2D neg_start( screenTopLeft.x, offset2 - screenTopLeft.x );
3034 VECTOR2D neg_end( screenBottomRight.x, offset2 - screenBottomRight.x );
3035
3036 // Draw negative slope diagonal
3037 glVertex2d( neg_start.x, neg_start.y );
3038 glVertex2d( neg_end.x, neg_end.y );
3039 }
3040 else
3041 {
3042 glVertex2d( cursorCenter.x, cursorBegin.y );
3043 glVertex2d( cursorCenter.x, cursorEnd.y );
3044
3045 glVertex2d( cursorBegin.x, cursorCenter.y );
3046 glVertex2d( cursorEnd.x, cursorCenter.y );
3047 }
3048
3049 glEnd();
3050
3051 glPopMatrix();
3052
3053 if( depthTestEnabled )
3054 glEnable( GL_DEPTH_TEST );
3055}
3056
3057
3059{
3060 wxASSERT_MSG( m_groups.size() < std::numeric_limits<unsigned int>::max(),
3061 wxT( "There are no free slots to store a group" ) );
3062
3063 while( m_groups.find( m_groupCounter ) != m_groups.end() )
3065
3066 return m_groupCounter++;
3067}
3068
3069
3071{
3072 wxASSERT_MSG( m_isContextLocked, "This should only be called from within a locked context." );
3073
3074 // Check correct initialization from the constructor
3075 if( m_tesselator == nullptr )
3076 throw std::runtime_error( "Could not create the tesselator" );
3077
3079
3080 int glVersion = gladLoaderLoadGL();
3081
3082 if( glVersion == 0 )
3083 throw std::runtime_error( "Failed to load OpenGL via loader" );
3084
3085 const char* vendor = (const char*) glGetString( GL_VENDOR );
3086 const char* renderer = (const char*) glGetString( GL_RENDERER );
3087 const char* version = (const char*) glGetString( GL_VERSION );
3088
3089 if( !version )
3090 throw std::runtime_error( "No GL context is current (glGetString returned NULL)" );
3091
3092 SetOpenGLInfo( vendor, renderer, version );
3093
3094 // Check the OpenGL version (minimum 2.1 is required)
3095 if( !GLAD_GL_VERSION_2_1 )
3096 throw std::runtime_error( "OpenGL 2.1 or higher is required!" );
3097
3098#if defined( __LINUX__ ) // calling enableGlDebug crashes opengl on some OS (OSX and some Windows)
3099#ifdef DEBUG
3100 if( glDebugMessageCallback )
3101 enableGlDebug( true );
3102#endif
3103#endif
3104
3105 // Framebuffers have to be supported
3106 if( !GLAD_GL_ARB_framebuffer_object )
3107 throw std::runtime_error( "Framebuffer objects are not supported!" );
3108
3109 // Vertex buffer has to be supported
3110 if( !GLAD_GL_ARB_vertex_buffer_object )
3111 throw std::runtime_error( "Vertex buffer objects are not supported!" );
3112
3113 // Prepare shaders
3114 if( !m_shader->IsLinked()
3115 && !m_shader->LoadShaderFromStrings( SHADER_TYPE_VERTEX,
3116 BUILTIN_SHADERS::glsl_kicad_vert ) )
3117 {
3118 throw std::runtime_error( "Cannot compile vertex shader!" );
3119 }
3120
3121 if( !m_shader->IsLinked()
3122 && !m_shader->LoadShaderFromStrings( SHADER_TYPE_FRAGMENT,
3123 BUILTIN_SHADERS::glsl_kicad_frag ) )
3124 {
3125 throw std::runtime_error( "Cannot compile fragment shader!" );
3126 }
3127
3128 if( !m_shader->IsLinked() && !m_shader->Link() )
3129 throw std::runtime_error( "Cannot link the shaders!" );
3130
3131 // Set up shader parameters after linking
3133
3134 // Check if video card supports textures big enough to fit the font atlas
3135 int maxTextureSize;
3136 glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTextureSize );
3137
3138 if( maxTextureSize < (int) font_image.width || maxTextureSize < (int) font_image.height )
3139 {
3140 // TODO implement software texture scaling
3141 // for bitmap fonts and use a higher resolution texture?
3142 throw std::runtime_error( "Requested texture size is not supported" );
3143 }
3144
3145#if wxCHECK_VERSION( 3, 3, 3 )
3146 wxGLCanvas::SetSwapInterval( -1 );
3147 m_swapInterval = wxGLCanvas::GetSwapInterval();
3148#else
3150#endif
3151
3152 m_cachedManager = new VERTEX_MANAGER( true );
3153 m_nonCachedManager = new VERTEX_MANAGER( false );
3154 m_overlayManager = new VERTEX_MANAGER( false );
3155 m_tempManager = new VERTEX_MANAGER( false );
3156
3157 // Make VBOs use shaders
3158 m_cachedManager->SetShader( *m_shader );
3159 m_nonCachedManager->SetShader( *m_shader );
3160 m_overlayManager->SetShader( *m_shader );
3161 m_tempManager->SetShader( *m_shader );
3162
3163 m_isInitialized = true;
3164}
3165
3166
3168{
3169 // Initialize shader uniform parameter locations
3170 ufm_fontTexture = m_shader->AddParameter( "u_fontTexture" );
3171 ufm_fontTextureWidth = m_shader->AddParameter( "u_fontTextureWidth" );
3172 ufm_worldPixelSize = m_shader->AddParameter( "u_worldPixelSize" );
3173 ufm_screenPixelSize = m_shader->AddParameter( "u_screenPixelSize" );
3174 ufm_pixelSizeMultiplier = m_shader->AddParameter( "u_pixelSizeMultiplier" );
3175 ufm_antialiasingOffset = m_shader->AddParameter( "u_antialiasingOffset" );
3176 ufm_minLinePixelWidth = m_shader->AddParameter( "u_minLinePixelWidth" );
3177}
3178
3179
3180// Callback functions for the tesselator. Compare Redbook Chapter 11.
3181void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
3182{
3183 GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr );
3184 OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
3185 VERTEX_MANAGER* vboManager = param->vboManager;
3186
3187 assert( vboManager );
3188 vboManager->Vertex( vertex[0], vertex[1], vertex[2] );
3189}
3190
3191
3192void CALLBACK CombineCallback( GLdouble coords[3], GLdouble* vertex_data[4], GLfloat weight[4],
3193 GLdouble** dataOut, void* aData )
3194{
3195 GLdouble* vertex = new GLdouble[3];
3196 OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
3197
3198 // Save the pointer so we can delete it later
3199 // Note, we use the default_delete for an array because macOS
3200 // decides to bundle an ancient libc++ that mismatches the C++17 support of clang
3201 param->intersectPoints.emplace_back( vertex, std::default_delete<GLdouble[]>() );
3202
3203 memcpy( vertex, coords, 3 * sizeof( GLdouble ) );
3204
3205 *dataOut = vertex;
3206}
3207
3208
3209void CALLBACK EdgeCallback( GLboolean aEdgeFlag )
3210{
3211 // This callback is needed to force GLU tesselator to use triangles only
3212}
3213
3214
3215void CALLBACK ErrorCallback( GLenum aErrorCode )
3216{
3217 //throw std::runtime_error( std::string( "Tessellation error: " ) +
3218 //std::string( (const char*) gluErrorString( aErrorCode ) );
3219}
3220
3221
3222static void InitTesselatorCallbacks( GLUtesselator* aTesselator )
3223{
3224 gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, (void( CALLBACK* )()) VertexCallback );
3225 gluTessCallback( aTesselator, GLU_TESS_COMBINE_DATA, (void( CALLBACK* )()) CombineCallback );
3226 gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, (void( CALLBACK* )()) EdgeCallback );
3227 gluTessCallback( aTesselator, GLU_TESS_ERROR, (void( CALLBACK* )()) ErrorCallback );
3228}
3229
3230
3231void OPENGL_GAL::EnableDepthTest( bool aEnabled )
3232{
3233 m_cachedManager->EnableDepthTest( aEnabled );
3234 m_nonCachedManager->EnableDepthTest( aEnabled );
3235 m_overlayManager->EnableDepthTest( aEnabled );
3236}
3237
3238
3239inline double round_to_half_pixel( double f, double r )
3240{
3241 return ( ceil( f / r ) - 0.5 ) * r;
3242}
3243
3244
3246{
3248 auto pixelSize = m_worldScale;
3249
3250 // we need -m_lookAtPoint == -k * pixelSize + 0.5 * pixelSize for OpenGL
3251 // meaning m_lookAtPoint = (k-0.5)*pixelSize with integer k
3254
3256}
3257
3258
3259void OPENGL_GAL::DrawGlyph( const KIFONT::GLYPH& aGlyph, int aNth, int aTotal )
3260{
3261 if( aGlyph.IsStroke() )
3262 {
3263 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( aGlyph );
3264
3265 DrawPolylines( strokeGlyph );
3266 }
3267 else if( aGlyph.IsOutline() )
3268 {
3269 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
3270
3271 m_currentManager->Shader( SHADER_NONE );
3272 m_currentManager->Color( m_fillColor );
3273
3274 outlineGlyph.Triangulate(
3275 [&]( const VECTOR2D& aPt1, const VECTOR2D& aPt2, const VECTOR2D& aPt3 )
3276 {
3277 m_currentManager->Reserve( 3 );
3278
3279 m_currentManager->Vertex( aPt1.x, aPt1.y, m_layerDepth );
3280 m_currentManager->Vertex( aPt2.x, aPt2.y, m_layerDepth );
3281 m_currentManager->Vertex( aPt3.x, aPt3.y, m_layerDepth );
3282 } );
3283 }
3284}
3285
3286
3287void OPENGL_GAL::DrawGlyphs( const std::vector<std::unique_ptr<KIFONT::GLYPH>>& aGlyphs )
3288{
3289 if( aGlyphs.empty() )
3290 return;
3291
3292 bool allGlyphsAreStroke = true;
3293 bool allGlyphsAreOutline = true;
3294
3295 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3296 {
3297 if( !glyph->IsStroke() )
3298 {
3299 allGlyphsAreStroke = false;
3300 break;
3301 }
3302 }
3303
3304 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3305 {
3306 if( !glyph->IsOutline() )
3307 {
3308 allGlyphsAreOutline = false;
3309 break;
3310 }
3311 }
3312
3313 if( allGlyphsAreStroke )
3314 {
3315 // Optimized path for stroke fonts that pre-reserves line quads.
3316 int lineQuadCount = 0;
3317
3318 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3319 {
3320 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( *glyph );
3321
3322 for( const std::vector<VECTOR2D>& points : strokeGlyph )
3323 lineQuadCount += points.size() - 1;
3324 }
3325
3326 reserveLineQuads( lineQuadCount );
3327
3328 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3329 {
3330 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( *glyph );
3331
3332 for( const std::vector<VECTOR2D>& points : strokeGlyph )
3333 {
3335 [&]( int idx )
3336 {
3337 return points[idx];
3338 },
3339 points.size(), false );
3340 }
3341 }
3342
3343 return;
3344 }
3345 else if( allGlyphsAreOutline )
3346 {
3347 // Optimized path for outline fonts that pre-reserves glyph triangles.
3348 int triangleCount = 0;
3349
3350 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3351 {
3352 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );
3353
3354 for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ )
3355 {
3357 outlineGlyph.TriangulatedPolygon( i );
3358
3359 triangleCount += polygon->GetTriangleCount();
3360 }
3361 }
3362
3363 m_currentManager->Shader( SHADER_NONE );
3364 m_currentManager->Color( m_fillColor );
3365
3366 m_currentManager->Reserve( 3 * triangleCount );
3367
3368 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3369 {
3370 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );
3371
3372 for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ )
3373 {
3375 outlineGlyph.TriangulatedPolygon( i );
3376
3377 for( size_t j = 0; j < polygon->GetTriangleCount(); j++ )
3378 {
3379 VECTOR2I a, b, c;
3380 polygon->GetTriangle( j, a, b, c );
3381
3382 m_currentManager->Vertex( a.x, a.y, m_layerDepth );
3383 m_currentManager->Vertex( b.x, b.y, m_layerDepth );
3384 m_currentManager->Vertex( c.x, c.y, m_layerDepth );
3385 }
3386 }
3387 }
3388 }
3389 else
3390 {
3391 // Regular path
3392 for( size_t i = 0; i < aGlyphs.size(); i++ )
3393 DrawGlyph( *aGlyphs[i], i, aGlyphs.size() );
3394 }
3395}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
void SetOpenGLInfo(const char *aVendor, const char *aRenderer, const char *aVersion)
A setter for OpenGL info when it's initialized.
void SetOpenGLBackendInfo(wxString aBackend)
A setter for OpenGL backend info after the canvas is created.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:45
const wxImage * GetOriginalImageData() const
Definition bitmap_base.h:67
VECTOR2I GetSizePixels() const
EDA_ANGLE Rotation() const
bool IsMirroredX() const
bool IsMirroredY() const
KIID GetImageID() const
Definition bitmap_base.h:72
int GetPPI() const
static const WX_CURSOR_TYPE GetCursor(KICURSOR aCursorType, bool aHiDPI=false)
Get a cursor bundle (wx 3.3+) or appropriate cursor (older versions)
Definition cursors.cpp:399
double AsDegrees() const
Definition eda_angle.h:116
double AsRadians() const
Definition eda_angle.h:120
void UnlockCtx(wxGLContext *aContext)
Allow other canvases to bind an OpenGL context.
void DestroyCtx(wxGLContext *aContext)
Destroy a managed OpenGL context.
void LockCtx(wxGLContext *aContext, wxGLCanvas *aCanvas)
Set a context as current and prevents other canvases from switching it.
wxGLContext * CreateCtx(wxGLCanvas *aCanvas, const wxGLContext *aOther=nullptr)
Create a managed OpenGL context.
static int SetSwapInterval(wxGLCanvas *aCanvas, int aVal)
Attempt to set the OpenGL swap interval.
Definition gl_utils.cpp:78
static wxString DetectGLBackend(wxGLCanvas *aCanvas)
Definition gl_utils.cpp:50
HIDPI_GL_CANVAS(const KIGFX::VC_SETTINGS &aSettings, wxWindow *aParent, const wxGLAttributes &aGLAttribs, wxWindowID aId=wxID_ANY, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, long aStyle=0, const wxString &aName=wxGLCanvasName, const wxPalette &aPalette=wxNullPalette)
virtual wxSize GetNativePixelSize() const
double GetScaleFactor() const
Get the current scale factor.
virtual bool IsStroke() const
Definition glyph.h:47
virtual bool IsOutline() const
Definition glyph.h:46
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:389
double g
Green component.
Definition color4d.h:390
double a
Alpha component.
Definition color4d.h:392
static const COLOR4D BLACK
Definition color4d.h:402
double b
Blue component.
Definition color4d.h:391
void SetGridColor(const COLOR4D &aGridColor)
Set the grid color.
virtual void SetLayerDepth(double aLayerDepth)
Set the depth of the layer (position on the z-axis)
bool IsCursorEnabled() const
Return information about cursor visibility.
friend class GAL_CONTEXT_LOCKER
MATRIX3x3D m_worldScreenMatrix
World transformation.
VECTOR2D GetVisibleGridSize() const
Return the visible grid size in x and y directions.
double m_layerDepth
The actual layer depth.
MATRIX3x3D m_screenWorldMatrix
Screen transformation.
bool m_axesEnabled
Should the axes be drawn.
float m_gridLineWidth
Line width of the grid.
VECTOR2I m_screenSize
Screen size in screen (wx logical) coordinates.
GR_TEXT_H_ALIGN_T GetHorizontalJustify() const
void normalize(T &a, T &b)
Ensure that the first element is smaller than the second.
VECTOR2D m_depthRange
Range of the depth.
virtual void SetFillColor(const COLOR4D &aColor)
Set the fill color.
virtual bool SetNativeCursorStyle(KICURSOR aCursor, bool aHiDPI)
Set the cursor in the native panel.
GRID_STYLE m_gridStyle
Grid display style.
COLOR4D m_axesColor
Color of the axes.
const MATRIX3x3D & GetScreenWorldMatrix() const
Get the screen <-> world transformation matrix.
float m_lineWidth
The line width.
void computeWorldScale()
Compute the scaling factor for the world->screen matrix.
virtual void SetLineWidth(float aLineWidth)
Set the line width.
VECTOR2D m_gridSize
The grid size.
COLOR4D getCursorColor() const
Get the actual cursor color to draw.
COLOR4D m_fillColor
The fill color.
double m_worldUnitLength
The unit length of the world coordinates [inch].
virtual bool updatedGalDisplayOptions(const GAL_DISPLAY_OPTIONS &aOptions)
Handle updating display options.
void SetAxesColor(const COLOR4D &aAxesColor)
Set the axes color.
virtual void SetStrokeColor(const COLOR4D &aColor)
Set the stroke color.
VECTOR2D m_cursorPosition
Current cursor position (world coordinates)
const VECTOR2I & GetGlyphSize() const
int m_gridTick
Every tick line gets the double width.
double m_worldScale
The scale factor world->screen.
VECTOR2D m_gridOrigin
The grid origin.
virtual void SetMinLineWidth(float aLineWidth)
Set the minimum line width in pixels.
KICURSOR m_currentNativeCursor
Current cursor.
bool m_globalFlipY
Flag for Y axis flipping.
float GetMinLineWidth() const
Get the minimum line width in pixels.
bool m_isFillEnabled
Is filling of graphic objects enabled ?
virtual void ComputeWorldScreenMatrix()
Compute the world <-> screen transformation matrix.
COLOR4D m_gridColor
Color of the grid.
COLOR4D m_strokeColor
The color of the outlines.
bool m_isStrokeEnabled
Are the outlines stroked ?
GAL_DISPLAY_OPTIONS & m_options
bool m_gridVisibility
Should the grid be shown.
virtual void BitmapText(const wxString &aText, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle)
Draw a text using a bitmap font.
float GetLineWidth() const
Get the line width.
bool m_globalFlipX
Flag for X axis flipping.
GR_TEXT_V_ALIGN_T GetVerticalJustify() const
KIGFX::CROSS_HAIR_MODE m_crossHairMode
Crosshair drawing mode.
VECTOR2D m_lookAtPoint
Point to be looked at in world space.
GAL(GAL_DISPLAY_OPTIONS &aOptions)
GLuint cacheBitmap(const BITMAP_BASE *aBitmap)
const size_t m_cacheMaxElements
GLuint RequestBitmap(const BITMAP_BASE *aBitmap)
std::list< GLuint > m_freedTextureIds
const size_t m_cacheMaxSize
std::map< const KIID, CACHED_BITMAP > m_bitmaps
std::list< KIID > m_cacheLru
static const unsigned int DIRECT_RENDERING
OpenGL implementation of the Graphics Abstraction Layer.
Definition opengl_gal.h:70
void Transform(const MATRIX3x3D &aTransformation) override
Transform the context.
void drawPolygon(GLdouble *aPoints, int aPointCount)
Draw a filled polygon.
void ChangeGroupDepth(int aGroupNumber, int aDepth) override
Change the depth (Z-axis position) of the group.
void skipMouseEvent(wxMouseEvent &aEvent)
Skip the mouse event to the parent.
void drawSegment(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, double aWidth, bool aReserve=true)
Internal method for segment drawing.
unsigned int m_groupCounter
Counter used for generating keys for groups.
Definition opengl_gal.h:362
void EndDiffLayer() override
Ends rendering of a differential layer.
VERTEX_MANAGER * m_overlayManager
Container for storing overlaid VERTEX_ITEMs.
Definition opengl_gal.h:367
void Scale(const VECTOR2D &aScale) override
Scale the context.
bool m_isInitialized
Basic initialization flag, has to be done when the window is visible.
Definition opengl_gal.h:387
VERTEX_MANAGER * m_currentManager
Currently used VERTEX_MANAGER (for storing VERTEX_ITEMs).
Definition opengl_gal.h:363
void drawCircle(const VECTOR2D &aCenterPoint, double aRadius, bool aReserve=true)
Internal method for circle drawing.
std::deque< std::shared_ptr< GLdouble > > m_tessIntersects
Definition opengl_gal.h:407
void DrawEllipseArc(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle) override
Draw an elliptical arc in world coordinates.
void DrawCircle(const VECTOR2D &aCenterPoint, double aRadius) override
Draw a circle using world coordinates.
bool IsInitialized() const override
Return the initialization status for the canvas.
Definition opengl_gal.h:103
void LockContext(int aClientCookie) override
Use GAL_CONTEXT_LOCKER RAII object unless you know what you're doing.
WX_CURSOR_TYPE m_currentwxCursor
wx cursor showing the current native cursor.
Definition opengl_gal.h:401
unsigned int m_mainBuffer
Main rendering target.
Definition opengl_gal.h:374
std::unique_ptr< GL_BITMAP_CACHE > m_bitmapCache
Definition opengl_gal.h:403
std::pair< VECTOR2D, float > computeBitmapTextSize(const UTF8 &aText) const
Compute a size of text drawn using bitmap font with current text setting applied.
void SetTarget(RENDER_TARGET aTarget) override
Set the target for rendering.
void blitCursor()
Blit cursor into the current screen.
static wxString CheckFeatures(GAL_DISPLAY_OPTIONS &aOptions)
Checks OpenGL features.
void ClearTarget(RENDER_TARGET aTarget) override
Clear the target for rendering.
void drawBitmapOverbar(double aLength, double aHeight, bool aReserve=true)
Draw an overbar over the currently drawn text.
void EndGroup() override
End the group.
bool m_isBitmapFontInitialized
Is the shader set to use bitmap fonts?
Definition opengl_gal.h:386
void drawSegmentChain(const std::function< VECTOR2D(int)> &aPointGetter, int aPointCount, double aWidth, bool aReserve=true)
Generic way of drawing a chain of segments stored in different containers.
void DrawArcSegment(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aWidth, double aMaxError) override
Draw an arc segment.
void BitmapText(const wxString &aText, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle) override
Draw a text using a bitmap font.
bool updatedGalDisplayOptions(const GAL_DISPLAY_OPTIONS &aOptions) override
Handle updating display options.
unsigned int m_overlayBuffer
Auxiliary rendering target (for menus etc.)
Definition opengl_gal.h:375
void PostPaint(wxPaintEvent &aEvent)
Post an event to #m_paint_listener.
OPENGL_COMPOSITOR * m_compositor
Handles multiple rendering targets.
Definition opengl_gal.h:373
VERTEX_MANAGER * m_cachedManager
Container for storing cached VERTEX_ITEMs.
Definition opengl_gal.h:365
void Translate(const VECTOR2D &aTranslation) override
Translate the context.
void DrawPolyline(const std::deque< VECTOR2D > &aPointList) override
Draw a polyline.
bool SetNativeCursorStyle(KICURSOR aCursor, bool aHiDPI) override
Set the cursor in the native panel.
void DrawCurve(const VECTOR2D &startPoint, const VECTOR2D &controlPointA, const VECTOR2D &controlPointB, const VECTOR2D &endPoint, double aFilterValue=0.0) override
Draw a cubic bezier spline.
void drawFilledSemiCircle(const VECTOR2D &aCenterPoint, double aRadius, double aAngle)
Draw a filled semicircle.
void onPaint(wxPaintEvent &aEvent)
This is the OnPaint event handler.
void DrawGroup(int aGroupNumber) override
Draw the stored group.
GLint ufm_minLinePixelWidth
Definition opengl_gal.h:396
void Restore() override
Restore the context.
void DrawGrid() override
void DrawSegmentChain(const std::vector< VECTOR2D > &aPointList, double aWidth) override
Draw a chain of rounded segments.
void drawStrokedSemiCircle(const VECTOR2D &aCenterPoint, double aRadius, double aAngle, bool aReserve=true)
Draw a stroked semicircle.
unsigned int getNewGroupNumber()
Return a valid key that can be used as a new group number.
void Flush() override
Force all remaining objects to be drawn.
GLint ufm_antialiasingOffset
Definition opengl_gal.h:395
void DeleteGroup(int aGroupNumber) override
Delete the group from the memory.
bool IsVisible() const override
Return true if the GAL canvas is visible on the screen.
Definition opengl_gal.h:110
wxEvtHandler * m_mouseListener
Definition opengl_gal.h:353
int m_swapInterval
Used to store swap interval information.
Definition opengl_gal.h:351
void ClearCache() override
Delete all data created during caching of graphic items.
double getWorldPixelSize() const
void DrawSegment(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, double aWidth) override
Draw a rounded segment.
GROUPS_MAP m_groups
Stores information about VBO objects (groups)
Definition opengl_gal.h:361
void endUpdate() override
void ClearScreen() override
Clear the screen.
void ResizeScreen(int aWidth, int aHeight) override
Resizes the canvas.
GLint ufm_fontTextureWidth
Definition opengl_gal.h:398
void DrawPolygon(const std::deque< VECTOR2D > &aPointList) override
Draw a polygon.
void drawTriangulatedPolyset(const SHAPE_POLY_SET &aPoly, bool aStrokeTriangulation)
Draw a set of polygons with a cached triangulation.
void DrawCursor(const VECTOR2D &aCursorPosition) override
Draw the cursor.
void DrawRectangle(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a rectangle.
VERTEX_MANAGER * m_nonCachedManager
Container for storing non-cached VERTEX_ITEMs.
Definition opengl_gal.h:366
int drawBitmapChar(unsigned long aChar, bool aReserve=true)
Draw a single character using bitmap font.
GLUtesselator * m_tesselator
Definition opengl_gal.h:406
wxEvtHandler * m_paintListener
Definition opengl_gal.h:354
void StartDiffLayer() override
Begins rendering of a differential layer.
bool m_isContextLocked
Used for assertion checking.
Definition opengl_gal.h:390
void Save() override
Save the context.
void DrawHoleWall(const VECTOR2D &aCenterPoint, double aHoleRadius, double aWallWidth) override
Draw a hole wall ring.
bool m_isFramebufferInitialized
Are the framebuffers initialized?
Definition opengl_gal.h:384
void ComputeWorldScreenMatrix() override
Compute the world <-> screen transformation matrix.
bool Show(bool aShow) override
Shows/hides the GAL canvas.
void SetMinLineWidth(float aLineWidth) override
Set the minimum line width in pixels.
static GLuint g_fontTexture
Bitmap font texture handle (shared)
Definition opengl_gal.h:356
virtual bool HasTarget(RENDER_TARGET aTarget) override
Return true if the target exists.
bool GetScreenshot(wxImage &aDstImage)
Parameters passed to the GLU tesselator.
void reserveLineQuads(const int aLineCount)
Reserve specified number of line quads.
void DrawLine(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a line.
OPENGL_GAL(const KIGFX::VC_SETTINGS &aVcSettings, GAL_DISPLAY_OPTIONS &aDisplayOptions, wxWindow *aParent, wxEvtHandler *aMouseListener=nullptr, wxEvtHandler *aPaintListener=nullptr, const wxString &aName=wxT("GLCanvas"))
void beginUpdate() override
void DrawEllipse(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation) override
Draw a closed ellipse.
double calcAngleStep(double aRadius) const
Compute the angle step when drawing arcs/circles approximated with lines.
Definition opengl_gal.h:598
virtual void DrawGlyph(const KIFONT::GLYPH &aGlyph, int aNth, int aTotal) override
Draw a polygon representing a font glyph.
bool m_isGrouping
Was a group started?
Definition opengl_gal.h:389
void BeginDrawing() override
Start/end drawing functions, draw calls can be only made in between the calls to BeginDrawing()/EndDr...
GLint ufm_screenPixelSize
Definition opengl_gal.h:393
void Rotate(double aAngle) override
Rotate the context.
int BeginGroup() override
Begin a group.
GLint ufm_pixelSizeMultiplier
Definition opengl_gal.h:394
VECTOR2D getScreenPixelSize() const
void DrawBitmap(const BITMAP_BASE &aBitmap, double alphaBlend=1.0) override
Draw a bitmap image.
void drawPolyline(const std::function< VECTOR2D(int)> &aPointGetter, int aPointCount, bool aReserve=true)
Generic way of drawing a polyline stored in different containers.
RENDER_TARGET GetTarget() const override
Get the currently used target for rendering.
void skipGestureEvent(wxGestureEvent &aEvent)
Skip the gesture event to the parent.
void UnlockContext(int aClientCookie) override
void drawSemiCircle(const VECTOR2D &aCenterPoint, double aRadius, double aAngle)
Draw a semicircle.
void drawLineQuad(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, bool aReserve=true)
Draw a quad for the line.
VERTEX_MANAGER * m_tempManager
Container for storing temp (diff mode) VERTEX_ITEMs.
Definition opengl_gal.h:370
virtual void DrawGlyphs(const std::vector< std::unique_ptr< KIFONT::GLYPH > > &aGlyphs) override
Draw polygons representing font glyphs.
void onSetNativeCursor(wxSetCursorEvent &aEvent)
Give the correct cursor image when the native widget asks for it.
void EnableDepthTest(bool aEnabled=false) override
void EndDrawing() override
End the drawing, needs to be called for every new frame.
SHADER * m_shader
There is only one shader used for different objects.
Definition opengl_gal.h:381
void DrawArc(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle) override
Draw an arc.
void DrawPolylines(const std::vector< std::vector< VECTOR2D > > &aPointLists) override
Draw multiple polylines.
RENDER_TARGET m_currentTarget
Current rendering target.
Definition opengl_gal.h:377
wxGLContext * m_glPrivContext
Canvas-specific OpenGL context.
Definition opengl_gal.h:350
void ChangeGroupColor(int aGroupNumber, const COLOR4D &aNewColor) override
Change the color used to draw the group.
static bool m_isBitmapFontLoaded
Is the bitmap font texture loaded?
Definition opengl_gal.h:385
static wxGLContext * m_glMainContext
Parent OpenGL context.
Definition opengl_gal.h:349
void setupShaderParameters()
Set up the shader parameters for OpenGL rendering.
unsigned int m_tempBuffer
Temporary rendering target (for diffing etc.)
Definition opengl_gal.h:376
void init()
Basic OpenGL initialization and feature checks.
static int m_instanceCounter
GL GAL instance counter.
Definition opengl_gal.h:352
Provide the access to the OpenGL shaders.
Definition shader.h:73
Class to control vertex container and GPU with possibility of emulating old-style OpenGL 1....
bool Vertex(const VERTEX &aVertex)
Add a vertex with the given coordinates to the currently set item.
Definition kiid.h:44
VECTOR2< T > GetScale() const
Get the scale components of the matrix.
Definition matrix3x3.h:291
T m_data[3][3]
Definition matrix3x3.h:61
GL_CONTEXT_MANAGER * GetGLContextManager()
Definition pgm_base.h:114
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
void Start()
Start or restart the counter.
Definition profile.h:74
std::string to_string()
Definition profile.h:153
double msecs(bool aSinceLast=false)
Definition profile.h:147
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
void GetTriangle(int index, VECTOR2I &a, VECTOR2I &b, VECTOR2I &c) const
Represent a set of closed polygons.
bool IsTriangulationUpToDate() const
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
uni_iter is a non-mutating iterator that walks through unicode code points in the UTF8 encoded string...
Definition utf8.h:226
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
uni_iter uend() const
Return a uni_iter initialized to the end of "this" UTF8 byte sequence.
Definition utf8.h:309
uni_iter ubegin() const
Returns a uni_iter initialized to the start of "this" UTF8 byte sequence.
Definition utf8.h:301
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
@ BLUE
Definition color4d.h:52
KICURSOR
Definition cursors.h:40
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
KIID niluuid(0)
This file contains miscellaneous commonly used macros and functions.
MATRIX3x3< double > MATRIX3x3D
Definition matrix3x3.h:469
#define H(x, y, z)
Definition md5_hash.cpp:17
FONT_IMAGE_TYPE font_image
const FONT_GLYPH_TYPE * LookupGlyph(unsigned int aCodepoint)
FONT_INFO_TYPE font_information
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:29
@ SMALL_CROSS
Use small cross instead of dots for the grid.
@ DOTS
Use dots for the grid.
@ SHADER_NONE
@ SHADER_LINE_C
@ SHADER_LINE_B
@ SHADER_FONT
@ SHADER_LINE_F
@ SHADER_LINE_E
@ SHADER_STROKED_CIRCLE
@ SHADER_HOLE_WALL
@ SHADER_LINE_A
@ SHADER_LINE_D
@ SHADER_FILLED_CIRCLE
@ SHADER_TYPE_VERTEX
Vertex shader.
Definition shader.h:42
@ SHADER_TYPE_FRAGMENT
Fragment shader.
Definition shader.h:43
RENDER_TARGET
RENDER_TARGET: Possible rendering targets.
Definition definitions.h:32
@ TARGET_NONCACHED
Auxiliary rendering target (noncached)
Definition definitions.h:34
@ TARGET_TEMP
Temporary target for drawing in separate layer.
Definition definitions.h:36
@ TARGET_CACHED
Main rendering target (cached)
Definition definitions.h:33
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
static const wxChar *const traceGalXorMode
static void InitTesselatorCallbacks(GLUtesselator *aTesselator)
void CALLBACK CombineCallback(GLdouble coords[3], GLdouble *vertex_data[4], GLfloat weight[4], GLdouble **dataOut, void *aData)
void CALLBACK VertexCallback(GLvoid *aVertexPtr, void *aData)
void CALLBACK EdgeCallback(GLboolean aEdgeFlag)
static wxGLAttributes getGLAttribs()
void CALLBACK ErrorCallback(GLenum aErrorCode)
double round_to_half_pixel(double f, double r)
#define SEG_PER_CIRCLE_COUNT
Definition opengl_gal.h:49
#define CALLBACK
The default number of points for circle approximation.
Definition opengl_gal.h:45
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
const int scale
std::vector< FAB_LAYER_COLOR > dummy
VERTEX_MANAGER * vboManager
Manager used for storing new vertices.
Definition opengl_gal.h:339
std::deque< std::shared_ptr< GLdouble > > & intersectPoints
Intersect points, that have to be freed after tessellation.
Definition opengl_gal.h:342
Structure to keep VIEW_CONTROLS settings for easy store/restore operations.
VECTOR3I v1(5, 5, 5)
nlohmann::json output
VECTOR2I end
VECTOR2I v2(1, 0)
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_H_ALIGN_INDETERMINATE
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_INDETERMINATE
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
wxLogTrace helper definitions.
void enableGlDebug(bool aEnable)
Enable or disable OpenGL driver messages output.
Definition utils.cpp:187
int checkGlError(const std::string &aInfo, const char *aFile, int aLine, bool aThrow)
Check if a recent OpenGL operation has failed.
Definition utils.cpp:44
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
VECTOR2I ToVECTOR2I(const wxSize &aSize)
Definition vector2wx.h:26