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