KiCad PCB EDA Suite
Loading...
Searching...
No Matches
opengl_gal.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Copyright (C) 2013-2017 CERN
7 * @author Maciej Suminski <[email protected]>
8 *
9 * Graphics Abstraction Layer (GAL) for OpenGL
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25#include <kicad_gl/kiglu.h> // Must be included first
26#include <kicad_gl/gl_utils.h>
27
28#include <advanced_config.h>
29#include <build_version.h>
31#include <gal/opengl/utils.h>
32#include <gal/definitions.h>
35#include <math/vector2wx.h>
36#include <bitmap_base.h>
37#include <bezier_curves.h>
38#include <math/util.h> // for KiROUND
39#include <pgm_base.h>
40#include <trace_helpers.h>
41
42#include <wx/app.h>
43#include <wx/frame.h>
44#include <wx/image.h>
45
46#include <macros.h>
47#include <optional>
49#include <thread_pool.h>
50
51#include <core/profile.h>
52
53#include <functional>
54#include <limits>
55#include <memory>
56#include <list>
57#include <vector>
58using namespace std::placeholders;
59using namespace KIGFX;
60
61//#define DISABLE_BITMAP_CACHE
62
63// The current font is "Ubuntu Mono" available under Ubuntu Font Licence 1.0
64// (see ubuntu-font-licence-1.0.txt for details)
65#include "gl_resources.h"
66#include <glsl_kicad_frag.h>
67#include <glsl_kicad_vert.h>
68using namespace KIGFX::BUILTIN_FONT;
69
70static void InitTesselatorCallbacks( GLUtesselator* aTesselator );
71
72// Trace mask for XOR/difference mode debugging
73static const wxChar* const traceGalXorMode = wxT( "KICAD_GAL_XOR_MODE" );
74
75// Stencil bit allocation used by OPENGL_GAL. Each independent use of the stencil
76// buffer claims a distinct bit so they can coexist within one frame.
77namespace
78{
79constexpr GLuint STENCIL_DOTS_MARKER = 0x01; // Set at every dot position by the
80 // display-grid DOTS rendering pass.
81constexpr GLuint STENCIL_GRID_COVERAGE = 0x80; // Set inside a PCB_GRIDITEM's coverage
82 // area to cut the display grid (and
83 // lower-priority grid-items) out.
84} // namespace
85
86static wxGLAttributes getGLAttribs()
87{
88 wxGLAttributes attribs;
89 attribs.RGBA().DoubleBuffer().Depth( 8 ).EndList();
90
91 return attribs;
92}
93
94wxGLContext* OPENGL_GAL::m_glMainContext = nullptr;
98
101bool OPENGL_GAL::m_glLoaded = false;
102GL_RESET_BUDGET OPENGL_GAL::m_resetBudget( 3, std::chrono::seconds( 60 ) );
103std::set<OPENGL_GAL*> OPENGL_GAL::m_instances;
105
106GL_RESET_BUDGET::CLOCK::time_point OPENGL_GAL::m_resetDetectedAt;
107
108namespace KIGFX
109{
111{
112public:
114 m_cacheSize( 0 )
115 {}
116
118
120 void Abandon() { m_bitmaps.clear(); }
121
122 GLuint RequestBitmap( const BITMAP_BASE* aBitmap );
123
124private:
126 {
127 GLuint id;
128 int w, h;
129 size_t size;
130 long long int accessTime;
131 };
132
133 GLuint cacheBitmap( const BITMAP_BASE* aBitmap );
134
135 const size_t m_cacheMaxElements = 50;
136 const size_t m_cacheMaxSize = 256 * 1024 * 1024;
137
138 std::map<const KIID, CACHED_BITMAP> m_bitmaps;
139 std::list<KIID> m_cacheLru;
141 std::list<GLuint> m_freedTextureIds;
142};
143
144}; // namespace KIGFX
145
146
148{
149 for( auto& bitmap : m_bitmaps )
150 glDeleteTextures( 1, &bitmap.second.id );
151}
152
153
155{
156#ifndef DISABLE_BITMAP_CACHE
157 auto it = m_bitmaps.find( aBitmap->GetImageID() );
158
159 if( it != m_bitmaps.end() )
160 {
161 // A bitmap is found in cache bitmap. Ensure the associated texture is still valid.
162 if( glIsTexture( it->second.id ) )
163 {
164 it->second.accessTime = wxGetUTCTimeMillis().GetValue();
165 return it->second.id;
166 }
167 else
168 {
169 // Delete the invalid bitmap cache and its data
170 glDeleteTextures( 1, &it->second.id );
171 m_freedTextureIds.emplace_back( it->second.id );
172
173 auto listIt = std::find( m_cacheLru.begin(), m_cacheLru.end(), it->first );
174
175 if( listIt != m_cacheLru.end() )
176 m_cacheLru.erase( listIt );
177
178 m_cacheSize -= it->second.size;
179
180 m_bitmaps.erase( it );
181 }
182
183 // the cached bitmap is not valid and deleted, it will be recreated.
184 }
185
186#endif
187 return cacheBitmap( aBitmap );
188}
189
190
192{
193 CACHED_BITMAP bmp;
194
195 const wxImage* imgPtr = aBitmap->GetOriginalImageData();
196
197 if( !imgPtr )
198 return std::numeric_limits< GLuint >::max();
199
200 wxImage imgData = *imgPtr;
201
202 // Check if the image exceeds the maximum texture size supported by the GPU
203 GLint maxTextureSize;
204 glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTextureSize );
205
206 int imgWidth = imgData.GetWidth();
207 int imgHeight = imgData.GetHeight();
208
209 if( imgWidth > maxTextureSize || imgHeight > maxTextureSize )
210 {
211 // Scale down the image to fit within the maximum texture size while preserving
212 // the aspect ratio
213 double scaleX = static_cast<double>( maxTextureSize ) / imgWidth;
214 double scaleY = static_cast<double>( maxTextureSize ) / imgHeight;
215 double scale = std::min( scaleX, scaleY );
216
217 int newWidth = std::clamp( KiROUND( imgWidth * scale ), 1, maxTextureSize );
218 int newHeight = std::clamp( KiROUND( imgHeight * scale ), 1, maxTextureSize );
219
220 imgData = imgData.Scale( newWidth, newHeight, wxIMAGE_QUALITY_HIGH );
221
222 if( !imgData.IsOk() )
223 return std::numeric_limits< GLuint >::max();
224 }
225
226 bmp.w = imgData.GetSize().x;
227 bmp.h = imgData.GetSize().y;
228
229 GLuint textureID;
230
231 if( m_freedTextureIds.empty() )
232 {
233 glGenTextures( 1, &textureID );
234 }
235 else
236 {
237 textureID = m_freedTextureIds.front();
238 m_freedTextureIds.pop_front();
239 }
240
241 glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
242
243 if( imgData.HasAlpha() || imgData.HasMask() )
244 {
245 bmp.size = static_cast<size_t>( bmp.w ) * bmp.h * 4;
246 auto buf = std::make_unique<uint8_t[]>( bmp.size );
247
248 uint8_t* dstP = buf.get();
249 uint8_t* srcP = imgData.GetData();
250
251 long long pxCount = static_cast<long long>( bmp.w ) * bmp.h;
252
253 if( imgData.HasAlpha() )
254 {
255 uint8_t* srcAlpha = imgData.GetAlpha();
256
257 for( long long px = 0; px < pxCount; px++ )
258 {
259 memcpy( dstP, srcP, 3 );
260 dstP[3] = *srcAlpha;
261
262 srcAlpha += 1;
263 srcP += 3;
264 dstP += 4;
265 }
266 }
267 else if( imgData.HasMask() )
268 {
269 uint8_t maskRed = imgData.GetMaskRed();
270 uint8_t maskGreen = imgData.GetMaskGreen();
271 uint8_t maskBlue = imgData.GetMaskBlue();
272
273 for( long long px = 0; px < pxCount; px++ )
274 {
275 memcpy( dstP, srcP, 3 );
276
277 if( srcP[0] == maskRed && srcP[1] == maskGreen && srcP[2] == maskBlue )
278 dstP[3] = wxALPHA_TRANSPARENT;
279 else
280 dstP[3] = wxALPHA_OPAQUE;
281
282 srcP += 3;
283 dstP += 4;
284 }
285 }
286
287 glBindTexture( GL_TEXTURE_2D, textureID );
288 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, bmp.w, bmp.h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
289 buf.get() );
290 }
291 else
292 {
293 bmp.size = static_cast<size_t>( bmp.w ) * bmp.h * 3;
294
295 uint8_t* srcP = imgData.GetData();
296
297 glBindTexture( GL_TEXTURE_2D, textureID );
298 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, bmp.w, bmp.h, 0, GL_RGB, GL_UNSIGNED_BYTE, srcP );
299 }
300
301 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
302 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
303
304 long long currentTime = wxGetUTCTimeMillis().GetValue();
305
306 bmp.id = textureID;
307 bmp.accessTime = currentTime;
308
309#ifndef DISABLE_BITMAP_CACHE
310 // A single oversized bitmap can exceed the whole cache budget, so evict until it fits or the
311 // cache is drained
312 while( ( m_cacheLru.size() + 1 > m_cacheMaxElements || m_cacheSize + bmp.size > m_cacheMaxSize )
313 && !m_cacheLru.empty() )
314 {
315 KIID toRemove( 0 );
316 auto toRemoveLru = m_cacheLru.end();
317
318 // Remove entries accessed > 1s ago first
319 for( const auto& [kiid, cachedBmp] : m_bitmaps )
320 {
321 const int cacheTimeoutMillis = 1000L;
322
323 if( currentTime - cachedBmp.accessTime > cacheTimeoutMillis )
324 {
325 toRemove = kiid;
326 toRemoveLru = std::find( m_cacheLru.begin(), m_cacheLru.end(), toRemove );
327 break;
328 }
329 }
330
331 // Otherwise, remove the latest entry (it's less likely to be needed soon)
332 if( toRemove == niluuid )
333 {
334 toRemoveLru = m_cacheLru.end();
335 toRemoveLru--;
336
337 toRemove = *toRemoveLru;
338 }
339
340 CACHED_BITMAP& cachedBitmap = m_bitmaps[toRemove];
341
342 m_cacheSize -= cachedBitmap.size;
343 glDeleteTextures( 1, &cachedBitmap.id );
344 m_freedTextureIds.emplace_back( cachedBitmap.id );
345
346 m_bitmaps.erase( toRemove );
347 m_cacheLru.erase( toRemoveLru );
348 }
349
350 m_cacheLru.emplace_back( aBitmap->GetImageID() );
351 m_cacheSize += bmp.size;
352 m_bitmaps.emplace( aBitmap->GetImageID(), std::move( bmp ) );
353#endif
354
355 return textureID;
356}
357
358
360 wxWindow* aParent,
361 wxEvtHandler* aMouseListener, wxEvtHandler* aPaintListener,
362 const wxString& aName ) :
363 GAL( aDisplayOptions ),
364 HIDPI_GL_CANVAS( aVcSettings, aParent, getGLAttribs(), wxID_ANY, wxDefaultPosition,
365 wxDefaultSize,
366 wxEXPAND, aName ),
368 m_mouseListener( aMouseListener ),
369 m_paintListener( aPaintListener ),
370 m_currentManager( nullptr ),
371 m_cachedManager( nullptr ),
372 m_nonCachedManager( nullptr ),
373 m_overlayManager( nullptr ),
374 m_tempManager( nullptr ),
375 m_mainBuffer( 0 ),
376 m_overlayBuffer( 0 ),
377 m_tempBuffer( 0 ),
378 m_isContextLocked( false ),
379 m_isContextValid( false ),
381{
382 if( m_glMainContext == nullptr )
383 {
385
386 if( !m_glMainContext )
387 throw std::runtime_error( "Could not create the main OpenGL context" );
388
390 }
391 else
392 {
394
395 if( !m_glPrivContext )
396 throw std::runtime_error( "Could not create a private OpenGL context" );
397 }
398
399 m_shader = new SHADER();
401 m_instances.insert( this );
402
403 m_bitmapCache = std::make_unique<GL_BITMAP_CACHE>();
404
406 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
407
408 // Initialize the flags
411 m_isInitialized = false;
412 m_isGrouping = false;
413 m_groupCounter = 0;
414
415 // Connect the native cursor handler
416 Connect( wxEVT_SET_CURSOR, wxSetCursorEventHandler( OPENGL_GAL::onSetNativeCursor ), nullptr,
417 this );
418
419 // Connecting the event handlers
420 Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::onPaint ) );
421
422 // Mouse events are skipped to the parent
423 Connect( wxEVT_MOTION, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
424 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
425 Connect( wxEVT_LEFT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
426 Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
427 Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
428 Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
429 Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
430 Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
431 Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
432 Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
433 Connect( wxEVT_AUX1_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
434 Connect( wxEVT_AUX1_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
435 Connect( wxEVT_AUX1_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
436 Connect( wxEVT_AUX2_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
437 Connect( wxEVT_AUX2_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
438 Connect( wxEVT_AUX2_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
439 Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
440 Connect( wxEVT_MAGNIFY, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
441
442#if defined _WIN32 || defined _WIN64
443 Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
444#endif
445
446 Bind( wxEVT_GESTURE_ZOOM, &OPENGL_GAL::skipGestureEvent, this );
447 Bind( wxEVT_GESTURE_PAN, &OPENGL_GAL::skipGestureEvent, this );
448
449 SetSize( aParent->GetClientSize() );
451
452 // Grid color settings are different in Cairo and OpenGL
453 SetGridColor( COLOR4D( 0.8, 0.8, 0.8, 0.1 ) );
455
456 // Tesselator initialization
457 m_tesselator = gluNewTess();
459
460 gluTessProperty( m_tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
461
463
464 // Avoid uninitialized variables:
470 ufm_fontTexture = -1;
472 m_swapInterval = 0;
473}
474
475
477{
479
480 if( gl_mgr )
481 {
482 // wxMSW destroys child windows before their C++ objects, so our own device context
483 // can already be gone and the teardown below would run against a sibling's context
484 m_isContextValid = gl_mgr->LockCtx( m_glPrivContext, this );
485
486 // A reset already destroyed our objects, and some drivers report every call as out of memory
488 m_isContextValid = false;
489
491 m_instances.erase( this );
492
493 if( !m_isContextValid )
494 {
495 // Whichever context is still current may belong to another share group, so every
496 // delete below would be aimed at an unrelated object of the same name
497 m_compositor->Abandon();
498 m_bitmapCache->Abandon();
499 m_shader->Abandon();
500
501 if( m_isInitialized )
502 {
503 m_cachedManager->Abandon();
504 m_nonCachedManager->Abandon();
505 m_overlayManager->Abandon();
506 m_tempManager->Abandon();
507 }
508 }
509 else if( m_isInitialized )
510 {
511 glFlush();
512 }
513
514 gluDeleteTess( m_tesselator );
515
516 if( m_isContextValid )
517 ClearCache();
518
519 // Groups free their vertices through the cached manager, so they must go before it does
520 m_groups.clear();
521
522 delete m_compositor;
523
524 if( m_isInitialized )
525 {
526 delete m_cachedManager;
527 delete m_nonCachedManager;
528 delete m_overlayManager;
529 delete m_tempManager;
530 }
531
532 gl_mgr->UnlockCtx( m_glPrivContext );
533
534 // If it was the main context, then it will be deleted
535 // when the last OpenGL GAL instance is destroyed (a few lines below)
537 gl_mgr->DestroyCtx( m_glPrivContext );
538
539 delete m_shader;
540
541 // Are we destroying the last GAL instance?
543 {
544 if( gl_mgr->LockCtx( m_glMainContext, this ) && m_isBitmapFontLoaded )
545 {
546 glDeleteTextures( 1, &g_fontTexture );
547 m_isBitmapFontLoaded = false;
548 }
549
550 gl_mgr->UnlockCtx( m_glMainContext );
551 gl_mgr->DestroyCtx( m_glMainContext );
552 m_glMainContext = nullptr;
553 }
554 }
555}
556
557
559{
560 static std::optional<wxString> cached;
561
562 if( cached.has_value() )
563 return *cached;
564
565 wxString retVal = wxEmptyString;
566
567 wxFrame* testFrame = new wxFrame( nullptr, wxID_ANY, wxT( "" ), wxDefaultPosition,
568 wxSize( 1, 1 ), wxFRAME_TOOL_WINDOW | wxNO_BORDER );
569
570 KIGFX::OPENGL_GAL* opengl_gal = nullptr;
571
572 try
573 {
575 opengl_gal = new KIGFX::OPENGL_GAL( dummy, aOptions, testFrame );
576
577 testFrame->Raise();
578 testFrame->Show();
579
580#ifdef __WXGTK__
581 // On GTK, Show() only queues realization. The GDK drawing window
582 // needed by SetCurrent() may not exist yet. Yield to let the event
583 // loop process the realize signal before we try to lock the context.
584 wxYield();
585#endif
586
587 GAL_CONTEXT_LOCKER lock( opengl_gal );
588 opengl_gal->init();
589 }
590 catch( std::runtime_error& err )
591 {
592 //Test failed
593 retVal = wxString( err.what() );
594 }
595
596 delete opengl_gal;
597 delete testFrame;
598
599 cached = retVal;
600 return retVal;
601}
602
603
604void OPENGL_GAL::PostPaint( wxPaintEvent& aEvent )
605{
606 // posts an event to m_paint_listener to ask for redraw the canvas.
607 if( m_paintListener )
608 wxPostEvent( m_paintListener, aEvent );
609}
610
611
613{
614 GAL_CONTEXT_LOCKER lock( this );
615
616 bool refresh = false;
617
618 // Options arrive before the canvas is realized, so the context is not current yet. Only
619 // the compositor needs it; BeginDrawing reconciles the mode once we can bind
620 if( m_isContextValid && m_options.antialiasing_mode != m_compositor->GetAntialiasingMode() )
621 {
622 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
624 refresh = true;
625 }
626
627 if( super::updatedGalDisplayOptions( aOptions ) || refresh )
628 {
629 Refresh();
630 refresh = true;
631 }
632
633 return refresh;
634}
635
636
638{
640 return std::min( std::abs( matrix.GetScale().x ), std::abs( matrix.GetScale().y ) );
641}
642
643
645{
646 double sf = GetScaleFactor();
647 return VECTOR2D( 2.0 / (double) ( m_screenSize.x * sf ), 2.0 /
648 (double) ( m_screenSize.y * sf ) );
649}
650
651
653{
654#ifdef KICAD_GAL_PROFILE
655 PROF_TIMER totalRealTime( "OPENGL_GAL::beginDrawing()", true );
656#endif /* KICAD_GAL_PROFILE */
657
658 wxASSERT_MSG( m_isContextLocked, "GAL_DRAWING_CONTEXT RAII object should have locked context. "
659 "Calling GAL::beginDrawing() directly is not allowed." );
660
661 wxASSERT_MSG( IsVisible(), "GAL::beginDrawing() must not be entered when GAL is not visible. "
662 "Other drawing routines will expect everything to be initialized "
663 "which will not be the case." );
664
665 if( !m_isInitialized )
666 init();
667
668 // Set up the view port
669 glMatrixMode( GL_PROJECTION );
670 glLoadIdentity();
671
672 // Create the screen transformation (Do the RH-LH conversion here)
673 glOrtho( 0, (GLint) m_screenSize.x, (GLsizei) m_screenSize.y, 0,
674 -m_depthRange.x, -m_depthRange.y );
675
676 // An antialiasing change that arrived without a current context never reached the compositor
677 if( m_options.antialiasing_mode != m_compositor->GetAntialiasingMode() )
678 {
679 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
681 }
682
684 {
685 // Prepare rendering target buffers
686 m_compositor->Initialize();
687 m_mainBuffer = m_compositor->CreateBuffer();
688 try
689 {
690 m_tempBuffer = m_compositor->CreateBuffer();
691 }
692 catch( const std::runtime_error& )
693 {
694 wxLogVerbose( "Could not create a framebuffer for diff mode blending.\n" );
695 m_tempBuffer = 0;
696 }
697 try
698 {
699 m_overlayBuffer = m_compositor->CreateBuffer();
700 }
701 catch( const std::runtime_error& )
702 {
703 wxLogVerbose( "Could not create a framebuffer for overlays.\n" );
704 m_overlayBuffer = 0;
705 }
706
708 }
709
710 m_compositor->Begin();
711
712 // Disable 2D Textures
713 glDisable( GL_TEXTURE_2D );
714
715 glShadeModel( GL_FLAT );
716
717 // Enable the depth buffer
718 glEnable( GL_DEPTH_TEST );
719 glDepthFunc( GL_LESS );
720
721 // Setup blending, required for transparent objects
722 glEnable( GL_BLEND );
723 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
724
725 glMatrixMode( GL_MODELVIEW );
726
727 // Set up the world <-> screen transformation
729 GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
730 matrixData[0] = m_worldScreenMatrix.m_data[0][0];
731 matrixData[1] = m_worldScreenMatrix.m_data[1][0];
732 matrixData[2] = m_worldScreenMatrix.m_data[2][0];
733 matrixData[4] = m_worldScreenMatrix.m_data[0][1];
734 matrixData[5] = m_worldScreenMatrix.m_data[1][1];
735 matrixData[6] = m_worldScreenMatrix.m_data[2][1];
736 matrixData[12] = m_worldScreenMatrix.m_data[0][2];
737 matrixData[13] = m_worldScreenMatrix.m_data[1][2];
738 matrixData[14] = m_worldScreenMatrix.m_data[2][2];
739 glLoadMatrixd( matrixData );
740
741 // Set defaults
744
745 // Remove all previously stored items
746 m_nonCachedManager->Clear();
747 m_overlayManager->Clear();
748 m_tempManager->Clear();
749
750 m_cachedManager->BeginDrawing();
751 m_nonCachedManager->BeginDrawing();
752 m_overlayManager->BeginDrawing();
753 m_tempManager->BeginDrawing();
754
756 {
757 // Keep bitmap font texture always bound to the second texturing unit
758 const GLint FONT_TEXTURE_UNIT = 2;
759
760 // Either load the font atlas to video memory, or simply bind it to a texture unit
762 {
763 glActiveTexture( GL_TEXTURE0 + FONT_TEXTURE_UNIT );
764 glGenTextures( 1, &g_fontTexture );
765 glBindTexture( GL_TEXTURE_2D, g_fontTexture );
766 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, font_image.width, font_image.height, 0, GL_RGB,
767 GL_UNSIGNED_BYTE, font_image.pixels );
768 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
769 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
770 checkGlError( "loading bitmap font", __FILE__, __LINE__ );
771
772 glActiveTexture( GL_TEXTURE0 );
773
775 }
776 else
777 {
778 glActiveTexture( GL_TEXTURE0 + FONT_TEXTURE_UNIT );
779 glBindTexture( GL_TEXTURE_2D, g_fontTexture );
780 glActiveTexture( GL_TEXTURE0 );
781 }
782
783 m_shader->Use();
784 m_shader->SetParameter( ufm_fontTexture, (int) FONT_TEXTURE_UNIT );
785 m_shader->SetParameter( ufm_fontTextureWidth, (int) font_image.width );
786 m_shader->Deactivate();
787 checkGlError( "setting bitmap font sampler as shader parameter", __FILE__, __LINE__ );
788
790 }
791
792 m_shader->Use();
793 m_shader->SetParameter( ufm_worldPixelSize,
794 (float) ( getWorldPixelSize() / GetScaleFactor() ) );
795 const VECTOR2D& screenPixelSize = getScreenPixelSize();
796 m_shader->SetParameter( ufm_screenPixelSize, screenPixelSize );
797 double pixelSizeMultiplier = m_compositor->GetAntialiasSupersamplingFactor();
798 m_shader->SetParameter( ufm_pixelSizeMultiplier, (float) pixelSizeMultiplier );
799 VECTOR2D renderingOffset = m_compositor->GetAntialiasRenderingOffset();
800 renderingOffset.x *= screenPixelSize.x;
801 renderingOffset.y *= screenPixelSize.y;
802 m_shader->SetParameter( ufm_antialiasingOffset, renderingOffset );
804 m_shader->Deactivate();
805
806 // Something between BeginDrawing and EndDrawing seems to depend on
807 // this texture unit being active, but it does not assure it itself.
808 glActiveTexture( GL_TEXTURE0 );
809
810 // Unbind buffers - set compositor for direct drawing
812
813#ifdef KICAD_GAL_PROFILE
814 totalRealTime.Stop();
815 wxLogTrace( traceGalProfile, wxT( "OPENGL_GAL::beginDrawing(): %.1f ms" ),
816 totalRealTime.msecs() );
817#endif /* KICAD_GAL_PROFILE */
818}
819
820void OPENGL_GAL::SetMinLineWidth( float aLineWidth )
821{
822 GAL::SetMinLineWidth( aLineWidth );
823
824 if( m_shader && ufm_minLinePixelWidth != -1 )
825 {
826 m_shader->Use();
827 m_shader->SetParameter( ufm_minLinePixelWidth, aLineWidth );
828 m_shader->Deactivate();
829 }
830}
831
832
834{
835 wxASSERT_MSG( m_isContextLocked, "What happened to the context lock?" );
836
837 PROF_TIMER cntTotal( "gl-end-total" );
838 PROF_TIMER cntEndCached( "gl-end-cached" );
839 PROF_TIMER cntEndNoncached( "gl-end-noncached" );
840 PROF_TIMER cntEndOverlay( "gl-end-overlay" );
841 PROF_TIMER cntComposite( "gl-composite" );
842 PROF_TIMER cntSwap( "gl-swap" );
843
844 cntTotal.Start();
845
846 // Cached & non-cached containers are rendered to the same buffer
847 m_compositor->SetBuffer( m_mainBuffer );
848
849 cntEndNoncached.Start();
850 m_nonCachedManager->EndDrawing();
851 cntEndNoncached.Stop();
852
853 cntEndCached.Start();
854 m_cachedManager->EndDrawing();
855 cntEndCached.Stop();
856
857 cntEndOverlay.Start();
858 // Overlay container is rendered to a different buffer
859 if( m_overlayBuffer )
860 m_compositor->SetBuffer( m_overlayBuffer );
861
862 m_overlayManager->EndDrawing();
863 cntEndOverlay.Stop();
864
865 cntComposite.Start();
866
867 // Be sure that the framebuffer is not colorized (happens on specific GPU&drivers combinations)
868 glColor4d( 1.0, 1.0, 1.0, 1.0 );
869
870 // Draw the remaining contents, blit the rendering targets to the screen, swap the buffers
871 m_compositor->DrawBuffer( m_mainBuffer );
872
873 if( m_overlayBuffer )
874 m_compositor->DrawBuffer( m_overlayBuffer );
875
876 m_compositor->Present();
877 blitCursor();
878
879 cntComposite.Stop();
880
881 cntSwap.Start();
882 SwapBuffers();
883 cntSwap.Stop();
884
885 cntTotal.Stop();
886
887#ifdef KICAD_GAL_PROFILE
888 wxLogTrace( traceGalProfile, "Timing: %s %s %s %s %s %s", cntTotal.to_string(),
889 cntEndCached.to_string(), cntEndNoncached.to_string(), cntEndOverlay.to_string(),
890 cntComposite.to_string(), cntSwap.to_string() );
891#endif
892}
893
894
895bool OPENGL_GAL::GetScreenshot( wxImage& aDstImage )
896{
897 // A canvas that has never drawn has no compositor buffers to read
899 return false;
900
901 GAL_CONTEXT_LOCKER locker( this );
902
903 if( !m_isContextValid )
904 return false;
905
906 m_compositor->SetBuffer( m_mainBuffer );
907
908 GLint viewport[4];
909 glGetIntegerv( GL_VIEWPORT, viewport );
910
911 const int w = viewport[2];
912 const int h = viewport[3];
913
914 GLint readBuffer = GL_COLOR_ATTACHMENT0;
915 glGetIntegerv( GL_DRAW_BUFFER, &readBuffer );
916
917 bool ok = false;
918
919 if( w > 0 && h > 0 )
920 {
921 std::vector<unsigned char> rgba( (size_t) w * h * 4 );
922
923 glFinish();
924 glPixelStorei( GL_PACK_ALIGNMENT, 1 );
925 glReadBuffer( (GLenum) readBuffer );
926 glReadPixels( 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data() );
927
928 // wxImage wants separate RGB and alpha buffers and takes ownership of them.
929 unsigned char* rgb = (unsigned char*) malloc( (size_t) w * h * 3 );
930 unsigned char* alpha = (unsigned char*) malloc( (size_t) w * h );
931
932 for( int i = 0; i < w * h; ++i )
933 {
934 rgb[i * 3 + 0] = rgba[i * 4 + 0];
935 rgb[i * 3 + 1] = rgba[i * 4 + 1];
936 rgb[i * 3 + 2] = rgba[i * 4 + 2];
937 alpha[i] = rgba[i * 4 + 3];
938 }
939
940 aDstImage.SetData( rgb, w, h, false );
941 aDstImage.SetAlpha( alpha, false );
942
943 aDstImage = aDstImage.Mirror( false );
944 ok = true;
945 }
946
948
949 return ok;
950}
951
952
953void OPENGL_GAL::LockContext( int aClientCookie )
954{
955 wxASSERT_MSG( !m_isContextLocked, "Context already locked." );
956 m_isContextLocked = true;
957 m_lockClientCookie = aClientCookie;
958
960
961 if( !mgr )
962 return;
963
965
966 // Entry points are process wide, so a canvas that has not drawn yet can still be polled
968 {
970 }
971
972 // A lost context accepts commands but ignores them, so treat it as unusable
974 m_isContextValid = false;
975}
976
977
985
986
988{
989 if( glGetGraphicsResetStatus )
990 return glGetGraphicsResetStatus();
991
992 if( glGetGraphicsResetStatusARB )
993 return glGetGraphicsResetStatusARB();
994
995 return GL_NO_ERROR;
996}
997
998
1000{
1001 // Only contexts created with the lose-on-reset strategy ever report a reset
1002 GLenum status = queryResetStatus();
1003
1004 if( status != GL_NO_ERROR )
1005 wxLogTrace( traceGalContext, wxS( "GL context %p reset, status 0x%x" ), m_glPrivContext, status );
1006
1007 return status != GL_NO_ERROR;
1008}
1009
1010
1012{
1014 m_resetDetectedAt = GL_RESET_BUDGET::CLOCK::now();
1015 m_resetSettled = false;
1017
1018 wxLogTrace( traceGalContext, wxS( "Orphaned GL context group, now %d, budget exhausted %d" ), m_contextGroupId,
1019 m_resetBudgetExhausted ? 1 : 0 );
1020
1021 bool mainOwned = false;
1022
1023 for( OPENGL_GAL* gal : m_instances )
1024 {
1025 if( gal->m_glPrivContext == m_glMainContext )
1026 mainOwned = true;
1027 }
1028
1029 // A live owner destroys the old main context itself; an ownerless one would otherwise leak
1030 if( !mainOwned && m_glMainContext )
1032
1033 // The dead group still owns these; the next canvas created starts a new group
1034 m_glMainContext = nullptr;
1035 g_fontTexture = 0;
1036 m_isBitmapFontLoaded = false;
1037
1038 for( OPENGL_GAL* gal : m_instances )
1039 gal->Refresh();
1040}
1041
1042
1044{
1046
1047 if( m_resetSettled || !mgr )
1048 return true;
1049
1050 if( GL_RESET_BUDGET::CLOCK::now() - m_resetDetectedAt >= std::chrono::seconds( 2 ) )
1051 {
1052 wxLogTrace( traceGalContext, wxS( "GL reset did not report completion, rebuilding anyway" ) );
1053 m_resetSettled = true;
1054 return true;
1055 }
1056
1057 // The lost context keeps reporting the reset until the hardware has finished it
1058 if( mgr->LockCtx( m_glPrivContext, this ) && queryResetStatus() == GL_NO_ERROR )
1059 {
1060 wxLogTrace( traceGalContext, wxS( "GL reset completed" ) );
1061 m_resetSettled = true;
1062 }
1063
1064 mgr->UnlockCtx( m_glPrivContext );
1065 return m_resetSettled;
1066}
1067
1068
1069void OPENGL_GAL::UnlockContext( int aClientCookie )
1070{
1071 wxASSERT_MSG( m_isContextLocked, "Context not locked. A GAL_CONTEXT_LOCKER RAII object must "
1072 "be stacked rather than making separate lock/unlock calls." );
1073
1074 wxASSERT_MSG( m_lockClientCookie == aClientCookie,
1075 "Context was locked by a different client. "
1076 "Should not be possible with RAII objects." );
1077
1078 m_isContextLocked = false;
1079
1081
1082 if( !mgr )
1083 return;
1084
1085 mgr->UnlockCtx( m_glPrivContext );
1086}
1087
1088
1090{
1091 wxASSERT_MSG( m_isContextLocked, "GAL_UPDATE_CONTEXT RAII object should have locked context. "
1092 "Calling this from anywhere else is not allowed." );
1093
1094 wxASSERT_MSG( IsVisible(), "GAL::beginUpdate() must not be entered when GAL is not visible. "
1095 "Other update routines will expect everything to be initialized "
1096 "which will not be the case." );
1097
1098 if( !m_isInitialized )
1099 init();
1100
1101 m_cachedManager->Map();
1102}
1103
1104
1106{
1107 if( !m_isInitialized )
1108 return;
1109
1110 m_cachedManager->Unmap();
1111}
1112
1113
1114void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1115{
1117
1118 drawLineQuad( aStartPoint, aEndPoint );
1119}
1120
1121
1122void OPENGL_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
1123 double aWidth )
1124{
1125 drawSegment( aStartPoint, aEndPoint, aWidth );
1126}
1127
1128
1129void OPENGL_GAL::drawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint, double aWidth,
1130 bool aReserve )
1131{
1132 VECTOR2D startEndVector = aEndPoint - aStartPoint;
1133 double lineLength = startEndVector.EuclideanNorm();
1134
1135 // Be careful about floating point rounding. As we draw segments in larger and larger
1136 // coordinates, the shader (which uses floats) will lose precision and stop drawing small
1137 // segments. In this case, we need to draw a circle for the minimal segment.
1138 // Check if the coordinate differences can be accurately represented as floats
1139 float startX = static_cast<float>( aStartPoint.x );
1140 float startY = static_cast<float>( aStartPoint.y );
1141 float endX = static_cast<float>( aEndPoint.x );
1142 float endY = static_cast<float>( aEndPoint.y );
1143
1144 if( startX == endX && startY == endY )
1145 {
1146 drawCircle( aStartPoint, aWidth / 2, aReserve );
1147 return;
1148 }
1149
1150 if( m_isFillEnabled || aWidth == 1.0 )
1151 {
1153
1154 SetLineWidth( aWidth );
1155 drawLineQuad( aStartPoint, aEndPoint, aReserve );
1156 }
1157 else
1158 {
1159 EDA_ANGLE lineAngle( startEndVector );
1160
1161 // Outlined tracks
1162 SetLineWidth( 1.0 );
1164 m_strokeColor.a );
1165
1166 Save();
1167
1168 if( aReserve )
1169 m_currentManager->Reserve( 6 + 6 + 3 + 3 ); // Two line quads and two semicircles
1170
1171 m_currentManager->Translate( aStartPoint.x, aStartPoint.y, 0.0 );
1172 m_currentManager->Rotate( lineAngle.AsRadians(), 0.0f, 0.0f, 1.0f );
1173
1174 drawLineQuad( VECTOR2D( 0.0, aWidth / 2.0 ), VECTOR2D( lineLength, aWidth / 2.0 ), false );
1175
1176 drawLineQuad( VECTOR2D( 0.0, -aWidth / 2.0 ), VECTOR2D( lineLength, -aWidth / 2.0 ),
1177 false );
1178
1179 // Draw line caps
1180 drawStrokedSemiCircle( VECTOR2D( 0.0, 0.0 ), aWidth / 2, M_PI / 2, false );
1181 drawStrokedSemiCircle( VECTOR2D( lineLength, 0.0 ), aWidth / 2, -M_PI / 2, false );
1182
1183 Restore();
1184 }
1185}
1186
1187
1188void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
1189{
1190 drawCircle( aCenterPoint, aRadius );
1191}
1192
1193
1194void OPENGL_GAL::DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
1195 double aWallWidth )
1196{
1197 if( m_isFillEnabled )
1198 {
1200
1201 m_currentManager->Shader( SHADER_HOLE_WALL, 1.0, aHoleRadius, aWallWidth );
1202 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1203
1204 m_currentManager->Shader( SHADER_HOLE_WALL, 2.0, aHoleRadius, aWallWidth );
1205 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1206
1207 m_currentManager->Shader( SHADER_HOLE_WALL, 3.0, aHoleRadius, aWallWidth );
1208 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1209 }
1210}
1211
1212
1213void OPENGL_GAL::drawCircle( const VECTOR2D& aCenterPoint, double aRadius, bool aReserve )
1214{
1215 if( m_isFillEnabled )
1216 {
1217 if( aReserve )
1218 m_currentManager->Reserve( 3 );
1219
1221
1222 /* Draw a triangle that contains the circle, then shade it leaving only the circle.
1223 * Parameters given to Shader() are indices of the triangle's vertices
1224 * (if you want to understand more, check the vertex shader source [shader.vert]).
1225 * Shader uses this coordinates to determine if fragments are inside the circle or not.
1226 * Does the calculations in the vertex shader now (pixel alignment)
1227 * v2
1228 * /\
1229 * //\\
1230 * v0 /_\/_\ v1
1231 */
1232 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0, aRadius );
1233 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1234
1235 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0, aRadius );
1236 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1237
1238 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0, aRadius );
1239 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, m_layerDepth );
1240 }
1241
1242 if( m_isStrokeEnabled )
1243 {
1244 if( aReserve )
1245 m_currentManager->Reserve( 3 );
1246
1248 m_strokeColor.a );
1249
1250 /* Draw a triangle that contains the circle, then shade it leaving only the circle.
1251 * Parameters given to Shader() are indices of the triangle's vertices
1252 * (if you want to understand more, check the vertex shader source [shader.vert]).
1253 * and the line width. Shader uses this coordinates to determine if fragments are
1254 * inside the circle or not.
1255 * v2
1256 * /\
1257 * //\\
1258 * v0 /_\/_\ v1
1259 */
1260 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 1.0, aRadius, m_lineWidth );
1261 m_currentManager->Vertex( aCenterPoint.x, // v0
1262 aCenterPoint.y, m_layerDepth );
1263
1264 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 2.0, aRadius, m_lineWidth );
1265 m_currentManager->Vertex( aCenterPoint.x, // v1
1266 aCenterPoint.y, m_layerDepth );
1267
1268 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 3.0, aRadius, m_lineWidth );
1269 m_currentManager->Vertex( aCenterPoint.x, aCenterPoint.y, // v2
1270 m_layerDepth );
1271 }
1272}
1273
1274
1275void OPENGL_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius,
1276 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle )
1277{
1278 if( aRadius <= 0 )
1279 return;
1280
1281 double startAngle = aStartAngle.AsRadians();
1282 double endAngle = startAngle + aAngle.AsRadians();
1283
1284 // Normalize arc angles
1285 normalize( startAngle, endAngle );
1286
1287 const double alphaIncrement = calcAngleStep( aRadius );
1288
1289 Save();
1290 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1291
1292 if( m_isFillEnabled )
1293 {
1294 double alpha;
1296 m_currentManager->Shader( SHADER_NONE );
1297
1298 // Triangle fan
1299 for( alpha = startAngle; ( alpha + alphaIncrement ) < endAngle; )
1300 {
1301 m_currentManager->Reserve( 3 );
1302 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1303 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius,
1304 m_layerDepth );
1305 alpha += alphaIncrement;
1306 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius,
1307 m_layerDepth );
1308 }
1309
1310 // The last missing triangle
1311 const VECTOR2D endPoint( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1312
1313 m_currentManager->Reserve( 3 );
1314 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1315 m_currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, m_layerDepth );
1316 m_currentManager->Vertex( endPoint.x, endPoint.y, m_layerDepth );
1317 }
1318
1319 if( m_isStrokeEnabled )
1320 {
1322 m_strokeColor.a );
1323
1324 VECTOR2D p( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1325 double alpha;
1326 unsigned int lineCount = 0;
1327
1328 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1329 lineCount++;
1330
1331 if( alpha != endAngle )
1332 lineCount++;
1333
1334 reserveLineQuads( lineCount );
1335
1336 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1337 {
1338 VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
1339 drawLineQuad( p, p_next, false );
1340
1341 p = p_next;
1342 }
1343
1344 // Draw the last missing part
1345 if( alpha != endAngle )
1346 {
1347 VECTOR2D p_last( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1348 drawLineQuad( p, p_last, false );
1349 }
1350 }
1351
1352 Restore();
1353}
1354
1355
1356void OPENGL_GAL::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
1357 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
1358 double aWidth, double aMaxError )
1359{
1360 if( aRadius <= 0 )
1361 {
1362 // Arcs of zero radius are a circle of aWidth diameter
1363 if( aWidth > 0 )
1364 DrawCircle( aCenterPoint, aWidth / 2.0 );
1365
1366 return;
1367 }
1368
1369 double startAngle = aStartAngle.AsRadians();
1370 double endAngle = startAngle + aAngle.AsRadians();
1371
1372 // Swap the angles, if start angle is greater than end angle
1373 normalize( startAngle, endAngle );
1374
1375 // Calculate the seg count to approximate the arc with aMaxError or less
1376 int segCount360 = GetArcToSegmentCount( aRadius, aMaxError, FULL_CIRCLE );
1377 segCount360 = std::max( SEG_PER_CIRCLE_COUNT, segCount360 );
1378 double alphaIncrement = 2.0 * M_PI / segCount360;
1379
1380 // Refinement: Use a segment count multiple of 2, because we have a control point
1381 // on the middle of the arc, and the look is better if it is on a segment junction
1382 // because there is no approx error
1383 int seg_count = KiROUND( ( endAngle - startAngle ) / alphaIncrement );
1384
1385 if( seg_count % 2 != 0 )
1386 seg_count += 1;
1387
1388 // Our shaders have trouble rendering null line quads, so delegate this task to DrawSegment.
1389 if( seg_count == 0 )
1390 {
1391 VECTOR2D p_start( aCenterPoint.x + cos( startAngle ) * aRadius,
1392 aCenterPoint.y + sin( startAngle ) * aRadius );
1393
1394 VECTOR2D p_end( aCenterPoint.x + cos( endAngle ) * aRadius,
1395 aCenterPoint.y + sin( endAngle ) * aRadius );
1396
1397 DrawSegment( p_start, p_end, aWidth );
1398 return;
1399 }
1400
1401 // Recalculate alphaIncrement with a even integer number of segment
1402 alphaIncrement = ( endAngle - startAngle ) / seg_count;
1403
1404 Save();
1405 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1406
1407 if( m_isStrokeEnabled )
1408 {
1410 m_strokeColor.a );
1411
1412 double width = aWidth / 2.0;
1413 VECTOR2D startPoint( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1414 VECTOR2D endPoint( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1415
1416 drawStrokedSemiCircle( startPoint, width, startAngle + M_PI );
1417 drawStrokedSemiCircle( endPoint, width, endAngle );
1418
1419 VECTOR2D pOuter( cos( startAngle ) * ( aRadius + width ),
1420 sin( startAngle ) * ( aRadius + width ) );
1421
1422 VECTOR2D pInner( cos( startAngle ) * ( aRadius - width ),
1423 sin( startAngle ) * ( aRadius - width ) );
1424
1425 double alpha;
1426
1427 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1428 {
1429 VECTOR2D pNextOuter( cos( alpha ) * ( aRadius + width ),
1430 sin( alpha ) * ( aRadius + width ) );
1431 VECTOR2D pNextInner( cos( alpha ) * ( aRadius - width ),
1432 sin( alpha ) * ( aRadius - width ) );
1433
1434 DrawLine( pOuter, pNextOuter );
1435 DrawLine( pInner, pNextInner );
1436
1437 pOuter = pNextOuter;
1438 pInner = pNextInner;
1439 }
1440
1441 // Draw the last missing part
1442 if( alpha != endAngle )
1443 {
1444 VECTOR2D pLastOuter( cos( endAngle ) * ( aRadius + width ),
1445 sin( endAngle ) * ( aRadius + width ) );
1446 VECTOR2D pLastInner( cos( endAngle ) * ( aRadius - width ),
1447 sin( endAngle ) * ( aRadius - width ) );
1448
1449 DrawLine( pOuter, pLastOuter );
1450 DrawLine( pInner, pLastInner );
1451 }
1452 }
1453
1454 if( m_isFillEnabled )
1455 {
1457 SetLineWidth( aWidth );
1458
1459 VECTOR2D p( cos( startAngle ) * aRadius, sin( startAngle ) * aRadius );
1460 double alpha;
1461
1462 int lineCount = 0;
1463
1464 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1465 {
1466 lineCount++;
1467 }
1468
1469 // The last missing part
1470 if( alpha != endAngle )
1471 {
1472 lineCount++;
1473 }
1474
1475 reserveLineQuads( lineCount );
1476
1477 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1478 {
1479 VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius );
1480 drawLineQuad( p, p_next, false );
1481
1482 p = p_next;
1483 }
1484
1485 // Draw the last missing part
1486 if( alpha != endAngle )
1487 {
1488 VECTOR2D p_last( cos( endAngle ) * aRadius, sin( endAngle ) * aRadius );
1489 drawLineQuad( p, p_last, false );
1490 }
1491 }
1492
1493 Restore();
1494}
1495
1496
1497void OPENGL_GAL::DrawEllipse( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1498 const EDA_ANGLE& aRotation )
1499{
1500 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1501 return;
1502
1503 const double alphaIncrement = calcAngleStep( aMajorRadius );
1504 const double cosPhi = std::cos( aRotation.AsRadians() );
1505 const double sinPhi = std::sin( aRotation.AsRadians() );
1506
1507 auto eval = [&]( double theta ) -> VECTOR2D
1508 {
1509 const double lx = aMajorRadius * std::cos( theta );
1510 const double ly = aMinorRadius * std::sin( theta );
1511 return VECTOR2D( lx * cosPhi - ly * sinPhi, lx * sinPhi + ly * cosPhi );
1512 };
1513
1514 Save();
1515 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1516
1517 if( m_isFillEnabled )
1518 {
1520 m_currentManager->Shader( SHADER_NONE );
1521
1522 // Triangle fan from origin out to the ellipse boundary
1523 double alpha;
1524 for( alpha = 0.0; ( alpha + alphaIncrement ) < 2.0 * M_PI; )
1525 {
1526 const VECTOR2D p1 = eval( alpha );
1527 alpha += alphaIncrement;
1528 const VECTOR2D p2 = eval( alpha );
1529
1530 m_currentManager->Reserve( 3 );
1531 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1532 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1533 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1534 }
1535
1536 // Last wedge back to the start.
1537 const VECTOR2D p1 = eval( alpha );
1538 const VECTOR2D p2 = eval( 0.0 );
1539
1540 m_currentManager->Reserve( 3 );
1541 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1542 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1543 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1544 }
1545
1546 if( m_isStrokeEnabled )
1547 {
1549
1550 // Count quads for reservation.
1551 unsigned int lineCount = 0;
1552 double countAlpha;
1553
1554 for( countAlpha = alphaIncrement; countAlpha < 2.0 * M_PI; countAlpha += alphaIncrement )
1555 lineCount++;
1556
1557 lineCount++; // closing segment back to alpha = 0
1558
1559 reserveLineQuads( lineCount );
1560
1561 VECTOR2D p = eval( 0.0 );
1562 double alpha;
1563
1564 for( alpha = alphaIncrement; alpha < 2.0 * M_PI; alpha += alphaIncrement )
1565 {
1566 const VECTOR2D p_next = eval( alpha );
1567 drawLineQuad( p, p_next, false );
1568 p = p_next;
1569 }
1570
1571 // Closing segment
1572 drawLineQuad( p, eval( 0.0 ), false );
1573 }
1574
1575 Restore();
1576}
1577
1578
1579void OPENGL_GAL::DrawEllipseArc( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1580 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle )
1581{
1582 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1583 return;
1584
1585 double startAngle = aStartAngle.AsRadians();
1586 double endAngle = aEndAngle.AsRadians();
1587 normalize( startAngle, endAngle );
1588
1589 const double alphaIncrement = calcAngleStep( aMajorRadius );
1590 const double cosPhi = std::cos( aRotation.AsRadians() );
1591 const double sinPhi = std::sin( aRotation.AsRadians() );
1592
1593 auto eval = [&]( double theta ) -> VECTOR2D
1594 {
1595 const double lx = aMajorRadius * std::cos( theta );
1596 const double ly = aMinorRadius * std::sin( theta );
1597 return VECTOR2D( lx * cosPhi - ly * sinPhi, lx * sinPhi + ly * cosPhi );
1598 };
1599
1600 Save();
1601 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 );
1602
1603 if( m_isFillEnabled )
1604 {
1606 m_currentManager->Shader( SHADER_NONE );
1607
1608 // Pie slice fan from origin out to the arc curve.
1609 double alpha;
1610
1611 for( alpha = startAngle; ( alpha + alphaIncrement ) < endAngle; )
1612 {
1613 const VECTOR2D p1 = eval( alpha );
1614 alpha += alphaIncrement;
1615 const VECTOR2D p2 = eval( alpha );
1616
1617 m_currentManager->Reserve( 3 );
1618 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1619 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1620 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1621 }
1622
1623 // Last wedge to endAngle.
1624 const VECTOR2D p1 = eval( alpha );
1625 const VECTOR2D p2 = eval( endAngle );
1626
1627 m_currentManager->Reserve( 3 );
1628 m_currentManager->Vertex( 0.0, 0.0, m_layerDepth );
1629 m_currentManager->Vertex( p1.x, p1.y, m_layerDepth );
1630 m_currentManager->Vertex( p2.x, p2.y, m_layerDepth );
1631 }
1632
1633 if( m_isStrokeEnabled )
1634 {
1636
1637 unsigned int lineCount = 0;
1638 double countAlpha;
1639
1640 for( countAlpha = startAngle + alphaIncrement; countAlpha <= endAngle; countAlpha += alphaIncrement )
1641 lineCount++;
1642
1643 if( countAlpha != endAngle )
1644 lineCount++; // trailing partial segment
1645
1646 reserveLineQuads( lineCount );
1647
1648 VECTOR2D p = eval( startAngle );
1649 double alpha;
1650
1651 for( alpha = startAngle + alphaIncrement; alpha <= endAngle; alpha += alphaIncrement )
1652 {
1653 const VECTOR2D p_next = eval( alpha );
1654 drawLineQuad( p, p_next, false );
1655 p = p_next;
1656 }
1657
1658 // Trailing partial segment, if any
1659 if( alpha != endAngle )
1660 {
1661 const VECTOR2D p_last = eval( endAngle );
1662 drawLineQuad( p, p_last, false );
1663 }
1664 }
1665
1666 Restore();
1667}
1668
1669
1670void OPENGL_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1671{
1672 // Compute the diagonal points of the rectangle
1673 VECTOR2D diagonalPointA( aEndPoint.x, aStartPoint.y );
1674 VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );
1675
1676 // Fill the rectangle
1677 if( m_isFillEnabled )
1678 {
1679 m_currentManager->Reserve( 6 );
1680 m_currentManager->Shader( SHADER_NONE );
1682
1683 m_currentManager->Vertex( aStartPoint.x, aStartPoint.y, m_layerDepth );
1684 m_currentManager->Vertex( diagonalPointA.x, diagonalPointA.y, m_layerDepth );
1685 m_currentManager->Vertex( aEndPoint.x, aEndPoint.y, m_layerDepth );
1686
1687 m_currentManager->Vertex( aStartPoint.x, aStartPoint.y, m_layerDepth );
1688 m_currentManager->Vertex( aEndPoint.x, aEndPoint.y, m_layerDepth );
1689 m_currentManager->Vertex( diagonalPointB.x, diagonalPointB.y, m_layerDepth );
1690 }
1691
1692 // Stroke the outline
1693 if( m_isStrokeEnabled )
1694 {
1696 m_strokeColor.a );
1697
1698 // DrawLine (and DrawPolyline )
1699 // has problem with 0 length lines so enforce minimum
1700 if( aStartPoint == aEndPoint )
1701 {
1702 DrawLine( aStartPoint + VECTOR2D( 1.0, 0.0 ), aEndPoint );
1703 }
1704 else
1705 {
1706 std::deque<VECTOR2D> pointList;
1707
1708 pointList.push_back( aStartPoint );
1709 pointList.push_back( diagonalPointA );
1710 pointList.push_back( aEndPoint );
1711 pointList.push_back( diagonalPointB );
1712 pointList.push_back( aStartPoint );
1713 DrawPolyline( pointList );
1714 }
1715 }
1716}
1717
1718
1719void OPENGL_GAL::DrawSegmentChain( const std::vector<VECTOR2D>& aPointList, double aWidth )
1720{
1722 [&]( int idx )
1723 {
1724 return aPointList[idx];
1725 },
1726 aPointList.size(), aWidth );
1727}
1728
1729
1730void OPENGL_GAL::DrawSegmentChain( const SHAPE_LINE_CHAIN& aLineChain, double aWidth )
1731{
1732 auto numPoints = aLineChain.PointCount();
1733
1734 if( aLineChain.IsClosed() )
1735 numPoints += 1;
1736
1738 [&]( int idx )
1739 {
1740 return aLineChain.CPoint( idx );
1741 },
1742 numPoints, aWidth );
1743}
1744
1745
1746void OPENGL_GAL::DrawPolyline( const std::deque<VECTOR2D>& aPointList )
1747{
1749 [&]( int idx )
1750 {
1751 return aPointList[idx];
1752 },
1753 aPointList.size() );
1754}
1755
1756
1757void OPENGL_GAL::DrawPolyline( const std::vector<VECTOR2D>& aPointList )
1758{
1760 [&]( int idx )
1761 {
1762 return aPointList[idx];
1763 },
1764 aPointList.size() );
1765}
1766
1767
1768void OPENGL_GAL::DrawPolyline( const VECTOR2D aPointList[], int aListSize )
1769{
1771 [&]( int idx )
1772 {
1773 return aPointList[idx];
1774 },
1775 aListSize );
1776}
1777
1778
1780{
1781 auto numPoints = aLineChain.PointCount();
1782
1783 if( aLineChain.IsClosed() )
1784 numPoints += 1;
1785
1787 [&]( int idx )
1788 {
1789 return aLineChain.CPoint( idx );
1790 },
1791 numPoints );
1792}
1793
1794
1795void OPENGL_GAL::DrawPolylines( const std::vector<std::vector<VECTOR2D>>& aPointList )
1796{
1797 int lineQuadCount = 0;
1798
1799 for( const std::vector<VECTOR2D>& points : aPointList )
1800 lineQuadCount += points.size() - 1;
1801
1802 reserveLineQuads( lineQuadCount );
1803
1804 for( const std::vector<VECTOR2D>& points : aPointList )
1805 {
1807 [&]( int idx )
1808 {
1809 return points[idx];
1810 },
1811 points.size(), false );
1812 }
1813}
1814
1815
1816void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
1817{
1818 wxCHECK( aPointList.size() >= 2, /* void */ );
1819 auto points = std::unique_ptr<GLdouble[]>( new GLdouble[3 * aPointList.size()] );
1820 GLdouble* ptr = points.get();
1821
1822 for( const VECTOR2D& p : aPointList )
1823 {
1824 *ptr++ = p.x;
1825 *ptr++ = p.y;
1826 *ptr++ = m_layerDepth;
1827 }
1828
1829 drawPolygon( points.get(), aPointList.size() );
1830}
1831
1832
1833void OPENGL_GAL::DrawPolygon( const VECTOR2D aPointList[], int aListSize )
1834{
1835 wxCHECK( aListSize >= 2, /* void */ );
1836 auto points = std::unique_ptr<GLdouble[]>( new GLdouble[3 * aListSize] );
1837 GLdouble* target = points.get();
1838 const VECTOR2D* src = aPointList;
1839
1840 for( int i = 0; i < aListSize; ++i )
1841 {
1842 *target++ = src->x;
1843 *target++ = src->y;
1844 *target++ = m_layerDepth;
1845 ++src;
1846 }
1847
1848 drawPolygon( points.get(), aListSize );
1849}
1850
1851
1853 bool aStrokeTriangulation )
1854{
1855 m_currentManager->Shader( SHADER_NONE );
1857
1858 if( m_isFillEnabled )
1859 {
1860 int totalTriangleCount = 0;
1861
1862 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1863 {
1864 auto triPoly = aPolySet.TriangulatedPolygon( j );
1865
1866 totalTriangleCount += triPoly->GetTriangleCount();
1867 }
1868
1869 m_currentManager->Reserve( 3 * totalTriangleCount );
1870
1871 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1872 {
1873 auto triPoly = aPolySet.TriangulatedPolygon( j );
1874
1875 for( size_t i = 0; i < triPoly->GetTriangleCount(); i++ )
1876 {
1877 VECTOR2I a, b, c;
1878 triPoly->GetTriangle( i, a, b, c );
1879 m_currentManager->Vertex( a.x, a.y, m_layerDepth );
1880 m_currentManager->Vertex( b.x, b.y, m_layerDepth );
1881 m_currentManager->Vertex( c.x, c.y, m_layerDepth );
1882 }
1883 }
1884 }
1885
1886 if( m_isStrokeEnabled )
1887 {
1888 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1889 {
1890 const auto& poly = aPolySet.Polygon( j );
1891
1892 for( const auto& lc : poly )
1893 {
1894 DrawPolyline( lc );
1895 }
1896 }
1897 }
1898
1899 if( ADVANCED_CFG::GetCfg().m_DrawTriangulationOutlines )
1900 {
1901 aStrokeTriangulation = true;
1902 SetStrokeColor( COLOR4D( 0.0, 1.0, 0.2, 1.0 ) );
1903 }
1904
1905 if( aStrokeTriangulation )
1906 {
1909
1910 for( unsigned int j = 0; j < aPolySet.TriangulatedPolyCount(); ++j )
1911 {
1912 auto triPoly = aPolySet.TriangulatedPolygon( j );
1913
1914 for( size_t i = 0; i < triPoly->GetTriangleCount(); i++ )
1915 {
1916 VECTOR2I a, b, c;
1917 triPoly->GetTriangle( i, a, b, c );
1918 DrawLine( a, b );
1919 DrawLine( b, c );
1920 DrawLine( c, a );
1921 }
1922 }
1923 }
1924}
1925
1926
1927void OPENGL_GAL::DrawPolygon( const SHAPE_POLY_SET& aPolySet, bool aStrokeTriangulation )
1928{
1929 if( aPolySet.IsTriangulationUpToDate() )
1930 {
1931 drawTriangulatedPolyset( aPolySet, aStrokeTriangulation );
1932 return;
1933 }
1934
1935 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1936 {
1937 const SHAPE_LINE_CHAIN& outline = aPolySet.COutline( j );
1938 DrawPolygon( outline );
1939 }
1940}
1941
1942
1944{
1945 if( aPolygon.PointCount() < 2 )
1946 return;
1947
1948 const int pointCount = aPolygon.SegmentCount() + 1;
1949 std::unique_ptr<GLdouble[]> points( new GLdouble[3 * pointCount] );
1950 GLdouble* ptr = points.get();
1951
1952 for( int i = 0; i < pointCount; ++i )
1953 {
1954 const VECTOR2I& p = aPolygon.CPoint( i );
1955 *ptr++ = p.x;
1956 *ptr++ = p.y;
1957 *ptr++ = m_layerDepth;
1958 }
1959
1960 drawPolygon( points.get(), pointCount );
1961}
1962
1963
1964void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
1965 const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint,
1966 double aFilterValue )
1967{
1968 std::vector<VECTOR2D> output;
1969 std::vector<VECTOR2D> pointCtrl;
1970
1971 pointCtrl.push_back( aStartPoint );
1972 pointCtrl.push_back( aControlPointA );
1973 pointCtrl.push_back( aControlPointB );
1974 pointCtrl.push_back( aEndPoint );
1975
1976 BEZIER_POLY converter( pointCtrl );
1977 converter.GetPoly( output, aFilterValue );
1978
1979 if( output.size() == 1 )
1980 output.push_back( output.front() );
1981
1982 DrawPolygon( &output[0], output.size() );
1983}
1984
1985
1986void OPENGL_GAL::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
1987{
1988 GLfloat alpha = std::clamp( alphaBlend, 0.0, 1.0 );
1989
1990 // We have to calculate the pixel size in users units to draw the image.
1991 // m_worldUnitLength is a factor used for converting IU to inches
1992 double scale = 1.0 / ( aBitmap.GetPPI() * m_worldUnitLength );
1993 double w = (double) aBitmap.GetSizePixels().x * scale;
1994 double h = (double) aBitmap.GetSizePixels().y * scale;
1995
1996 auto xform = m_currentManager->GetTransformation();
1997
1998 glm::vec4 v0 = xform * glm::vec4( -w / 2, -h / 2, 0.0, 0.0 );
1999 glm::vec4 v1 = xform * glm::vec4( w / 2, -h / 2, 0.0, 0.0 );
2000 glm::vec4 v2 = xform * glm::vec4( w / 2, h / 2, 0.0, 0.0 );
2001 glm::vec4 v3 = xform * glm::vec4( -w / 2, h / 2, 0.0, 0.0 );
2002 glm::vec4 trans = xform[3];
2003
2004 auto texture_id = m_bitmapCache->RequestBitmap( &aBitmap );
2005
2006 if( !glIsTexture( texture_id ) ) // ensure the bitmap texture is still valid
2007 return;
2008
2009 GLboolean depthMask = GL_TRUE;
2010 glGetBooleanv( GL_DEPTH_WRITEMASK, &depthMask );
2011
2012 if( alpha < 1.0f )
2013 glDepthMask( GL_FALSE );
2014
2015 glDepthFunc( GL_ALWAYS );
2016
2017 glAlphaFunc( GL_GREATER, 0.01f );
2018 glEnable( GL_ALPHA_TEST );
2019
2020 glMatrixMode( GL_TEXTURE );
2021 glPushMatrix();
2022 glTranslated( 0.5, 0.5, 0.5 );
2023 glRotated( aBitmap.Rotation().AsDegrees(), 0, 0, 1 );
2024 glTranslated( -0.5, -0.5, -0.5 );
2025
2026 glMatrixMode( GL_MODELVIEW );
2027 glPushMatrix();
2028 glTranslated( trans.x, trans.y, trans.z );
2029
2030 glEnable( GL_TEXTURE_2D );
2031 glActiveTexture( GL_TEXTURE0 );
2032 glBindTexture( GL_TEXTURE_2D, texture_id );
2033
2034 float texStartX = aBitmap.IsMirroredX() ? 1.0 : 0.0;
2035 float texEndX = aBitmap.IsMirroredX() ? 0.0 : 1.0;
2036 float texStartY = aBitmap.IsMirroredY() ? 1.0 : 0.0;
2037 float texEndY = aBitmap.IsMirroredY() ? 0.0 : 1.0;
2038
2039 glBegin( GL_QUADS );
2040 glColor4f( 1.0, 1.0, 1.0, alpha );
2041 glTexCoord2f( texStartX, texStartY );
2042 glVertex3f( v0.x, v0.y, m_layerDepth );
2043 glColor4f( 1.0, 1.0, 1.0, alpha );
2044 glTexCoord2f( texEndX, texStartY);
2045 glVertex3f( v1.x, v1.y, m_layerDepth );
2046 glColor4f( 1.0, 1.0, 1.0, alpha );
2047 glTexCoord2f( texEndX, texEndY);
2048 glVertex3f( v2.x, v2.y, m_layerDepth );
2049 glColor4f( 1.0, 1.0, 1.0, alpha );
2050 glTexCoord2f( texStartX, texEndY);
2051 glVertex3f( v3.x, v3.y, m_layerDepth );
2052 glEnd();
2053
2054 glBindTexture( GL_TEXTURE_2D, 0 );
2055
2056#ifdef DISABLE_BITMAP_CACHE
2057 glDeleteTextures( 1, &texture_id );
2058#endif
2059
2060 glPopMatrix();
2061
2062 glMatrixMode( GL_TEXTURE );
2063 glPopMatrix();
2064 glMatrixMode( GL_MODELVIEW );
2065
2066 glDisable( GL_ALPHA_TEST );
2067
2068 glDepthMask( depthMask );
2069
2070 glDepthFunc( GL_LESS );
2071}
2072
2073
2074void OPENGL_GAL::BitmapText( const wxString& aText, const VECTOR2I& aPosition,
2075 const EDA_ANGLE& aAngle )
2076{
2077 // Fallback to generic impl (which uses the stroke font) on cases we don't handle
2078 if( IsTextMirrored()
2079 || aText.Contains( wxT( "^{" ) )
2080 || aText.Contains( wxT( "_{" ) )
2081 || aText.Contains( wxT( "\n" ) ) )
2082 {
2083 return GAL::BitmapText( aText, aPosition, aAngle );
2084 }
2085
2086 const UTF8 text( aText );
2087 VECTOR2D textSize;
2088 float commonOffset;
2089 std::tie( textSize, commonOffset ) = computeBitmapTextSize( text );
2090
2091 const double SCALE = 1.4 * GetGlyphSize().y / textSize.y;
2092 double overbarHeight = textSize.y;
2093
2094 Save();
2095
2097 m_currentManager->Translate( aPosition.x, aPosition.y, m_layerDepth );
2098 m_currentManager->Rotate( aAngle.AsRadians(), 0.0f, 0.0f, -1.0f );
2099
2100 double sx = SCALE * ( m_globalFlipX ? -1.0 : 1.0 );
2101 double sy = SCALE * ( m_globalFlipY ? -1.0 : 1.0 );
2102
2103 m_currentManager->Scale( sx, sy, 0 );
2104 m_currentManager->Translate( 0, -commonOffset, 0 );
2105
2106 switch( GetHorizontalJustify() )
2107 {
2109 Translate( VECTOR2D( -textSize.x / 2.0, 0 ) );
2110 break;
2111
2113 //if( !IsTextMirrored() )
2114 Translate( VECTOR2D( -textSize.x, 0 ) );
2115 break;
2116
2118 //if( IsTextMirrored() )
2119 //Translate( VECTOR2D( -textSize.x, 0 ) );
2120 break;
2121
2123 wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
2124 break;
2125 }
2126
2127 switch( GetVerticalJustify() )
2128 {
2130 break;
2131
2133 Translate( VECTOR2D( 0, -textSize.y / 2.0 ) );
2134 overbarHeight = 0;
2135 break;
2136
2138 Translate( VECTOR2D( 0, -textSize.y ) );
2139 overbarHeight = -textSize.y / 2.0;
2140 break;
2141
2143 wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
2144 break;
2145 }
2146
2147 int overbarLength = 0;
2148 int overbarDepth = -1;
2149 int braceNesting = 0;
2150
2151 auto iterateString =
2152 [&]( const std::function<void( int aOverbarLength, int aOverbarHeight )>& overbarFn,
2153 const std::function<int( unsigned long aChar )>& bitmapCharFn )
2154 {
2155 for( UTF8::uni_iter chIt = text.ubegin(), end = text.uend(); chIt < end; ++chIt )
2156 {
2157 wxASSERT_MSG( *chIt != '\n' && *chIt != '\r',
2158 "No support for multiline bitmap text yet" );
2159
2160 if( *chIt == '~' && overbarDepth == -1 )
2161 {
2162 UTF8::uni_iter lookahead = chIt;
2163
2164 if( ++lookahead != end && *lookahead == '{' )
2165 {
2166 chIt = lookahead;
2167 overbarDepth = braceNesting;
2168 braceNesting++;
2169 continue;
2170 }
2171 }
2172 else if( *chIt == '{' )
2173 {
2174 braceNesting++;
2175 }
2176 else if( *chIt == '}' )
2177 {
2178 if( braceNesting > 0 )
2179 braceNesting--;
2180
2181 if( braceNesting == overbarDepth )
2182 {
2183 overbarFn( overbarLength, overbarHeight );
2184 overbarLength = 0;
2185
2186 overbarDepth = -1;
2187 continue;
2188 }
2189 }
2190
2191 if( overbarDepth != -1 )
2192 overbarLength += bitmapCharFn( *chIt );
2193 else
2194 bitmapCharFn( *chIt );
2195 }
2196 };
2197
2198 // First, calculate the amount of characters and overbars to reserve
2199
2200 int charsCount = 0;
2201 int overbarsCount = 0;
2202
2203 iterateString(
2204 [&overbarsCount]( int aOverbarLength, int aOverbarHeight )
2205 {
2206 overbarsCount++;
2207 },
2208 [&charsCount]( unsigned long aChar ) -> int
2209 {
2210 if( aChar != ' ' )
2211 charsCount++;
2212
2213 return 0;
2214 } );
2215
2216 m_currentManager->Reserve( 6 * charsCount + 6 * overbarsCount );
2217
2218 // Now reset the state and actually draw the characters and overbars
2219 overbarLength = 0;
2220 overbarDepth = -1;
2221 braceNesting = 0;
2222
2223 iterateString(
2224 [&]( int aOverbarLength, int aOverbarHeight )
2225 {
2226 drawBitmapOverbar( aOverbarLength, aOverbarHeight, false );
2227 },
2228 [&]( unsigned long aChar ) -> int
2229 {
2230 return drawBitmapChar( aChar, false );
2231 } );
2232
2233 // Handle the case when overbar is active till the end of the drawn text
2234 m_currentManager->Translate( 0, commonOffset, 0 );
2235
2236 if( overbarDepth != -1 && overbarLength > 0 )
2237 drawBitmapOverbar( overbarLength, overbarHeight );
2238
2239 Restore();
2240}
2241
2242
2244{
2246 m_compositor->SetBuffer( m_mainBuffer );
2247 m_nonCachedManager->EnableDepthTest( false );
2248
2249 const float minorLineWidth = std::fmax( 1.0f, m_gridLineWidth ) * getWorldPixelSize() / GetScaleFactor();
2250
2251 // Axes drawn first so grid lines at x/y=0 can skip on top of them.
2252 if( m_axesEnabled )
2253 {
2254 const VECTOR2D worldStartPoint = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
2255 const VECTOR2D worldEndPoint = m_screenWorldMatrix * VECTOR2D( m_screenSize );
2256
2257 SetLineWidth( minorLineWidth );
2259 DrawLine( VECTOR2D( worldStartPoint.x, 0 ), VECTOR2D( worldEndPoint.x, 0 ) );
2260 DrawLine( VECTOR2D( 0, worldStartPoint.y ), VECTOR2D( 0, worldEndPoint.y ) );
2261 m_nonCachedManager->EndDrawing();
2262 }
2263
2264 const bool renderGlobalGrid = m_gridVisibility && m_gridSize.x != 0 && m_gridSize.y != 0;
2265
2266 if( renderGlobalGrid )
2267 {
2268 GRID_SOURCE globalGrid;
2269 globalGrid.unbounded = true;
2270 globalGrid.axesEnabled = m_axesEnabled;
2272 globalGrid.origin = m_gridOrigin;
2273 globalGrid.pitch = GetVisibleGridSize();
2274 globalGrid.tick = static_cast<unsigned>( m_gridTick );
2275 globalGrid.style = m_gridStyle;
2276 globalGrid.color = m_gridColor;
2277 globalGrid.priority = 0;
2278
2279 // Appending keeps the precedence order SetGridSources established: every grid
2280 // item is bounded, and bounded beats unbounded, so the background grid belongs
2281 // last whatever its priority.
2282 m_gridSources.push_back( globalGrid );
2283 }
2284
2285 if( !m_gridSources.empty() )
2287
2288 if( renderGlobalGrid )
2289 m_gridSources.pop_back();
2290}
2291
2292
2294{
2295 Save();
2296 Translate( src.origin );
2297 Rotate( -src.orientation );
2298
2299 if( src.unbounded )
2300 {
2301 const BOX2D screen = gridScreenBBox( src );
2302
2303 DrawRectangle( screen.GetOrigin(), screen.GetEnd() );
2304 Restore();
2305 return;
2306 }
2307
2308 switch( src.kind )
2309 {
2311 {
2312 const double rMax = src.extent.x;
2313 const double phiMax = src.extent.y;
2314
2315 if( rMax > 0.0 && phiMax > 0.0 )
2316 {
2317 if( phiMax >= 2 * M_PI - 1e-6 )
2318 {
2319 DrawCircle( VECTOR2D( 0, 0 ), rMax );
2320 }
2321 else
2322 {
2323 const int kArcSegments = std::max( 16, (int) ( phiMax / ( M_PI / 16 ) ) );
2324 std::deque<VECTOR2D> poly;
2325 poly.emplace_back( 0.0, 0.0 );
2326
2327 for( int i = 0; i <= kArcSegments; ++i )
2328 {
2329 const double phi = phiMax * i / kArcSegments;
2330 poly.emplace_back( rMax * std::cos( phi ), rMax * std::sin( phi ) );
2331 }
2332
2333 poly.emplace_back( 0.0, 0.0 );
2334
2335 DrawPolygon( poly );
2336 }
2337 }
2338 break;
2339 }
2340
2342 DrawRectangle( VECTOR2D( -src.extent.x, -src.extent.y ), VECTOR2D( src.extent.x, src.extent.y ) );
2343 break;
2344
2345 default: wxFAIL_MSG( wxT( "drawGridCoverageShape: unhandled GRID_SOURCE::KIND" ) ); break;
2346 }
2347
2348 Restore();
2349}
2350
2351
2353{
2354 if( m_gridSources.empty() )
2355 return;
2356
2357 // Pre-sorted by precedence; each bounded source stencils its coverage, so first drawn
2358 // wins. Selected grids go last: they ignore the stencil and draw over everything.
2359 std::vector<const GRID_SOURCE*> ordered;
2360 ordered.reserve( m_gridSources.size() );
2361
2362 for( const GRID_SOURCE& src : m_gridSources )
2363 {
2364 if( !src.highlighted )
2365 ordered.push_back( &src );
2366 }
2367
2368 for( const GRID_SOURCE& src : m_gridSources )
2369 {
2370 if( src.highlighted )
2371 ordered.push_back( &src );
2372 }
2373
2374 const float minorLineWidth = std::fmax( 1.0f, m_gridLineWidth ) * getWorldPixelSize() / GetScaleFactor();
2375 const float majorLineWidth = minorLineWidth * 2.0f;
2376 const float hairLineWidth = getWorldPixelSize() / GetScaleFactor();
2377
2378 glDisable( GL_DEPTH_TEST );
2379 glDisable( GL_TEXTURE_2D );
2380 m_nonCachedManager->EnableDepthTest( false );
2381
2382 glEnable( GL_STENCIL_TEST );
2383 glStencilMask( 0xFF );
2384 glClear( GL_STENCIL_BUFFER_BIT );
2385
2386 for( const GRID_SOURCE* srcPtr : ordered )
2387 {
2388 const GRID_SOURCE& src = *srcPtr;
2389
2390 // Selected grids let you see the dimmed grid below and do not get stamped out.
2391 const GLuint coverageMask = src.highlighted ? 0 : STENCIL_GRID_COVERAGE;
2392
2393 glStencilMask( 0x00 );
2394 glStencilFunc( GL_EQUAL, 0, coverageMask );
2395 glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
2396
2397 if( src.highlighted )
2398 {
2399 COLOR4D dimming = m_clearColor;
2400 dimming.a = GRID_DIM_ALPHA;
2401
2402 SetIsFill( true );
2403 SetIsStroke( false );
2404 SetFillColor( dimming );
2405 drawGridCoverageShape( src );
2406 m_nonCachedManager->EndDrawing();
2407 }
2408
2409 COLOR4D color = src.color.a > 0 ? src.color : m_gridColor;
2410
2411 if( src.highlighted )
2413
2414 glColor4d( color.r, color.g, color.b, color.a );
2415 SetStrokeColor( color );
2416
2417 Save();
2418 Translate( src.origin );
2419 // GAL Rotate is math-convention; grid orientation is screen-convention.
2420 Rotate( -src.orientation );
2421
2422 const unsigned tick = ( src.tick > 0 ) ? src.tick : static_cast<unsigned>( m_gridTick );
2423 const double threshold =
2425
2426 // SMALL_CROSS marker.
2427 auto drawCrossAt = [&]( const VECTOR2D& pos, bool aMajor, double aArmAngle )
2428 {
2429 const float w = aMajor ? majorLineWidth : minorLineWidth;
2430 const double len = 2.0 * w;
2431 const double c = std::cos( aArmAngle );
2432 const double s = std::sin( aArmAngle );
2433 const VECTOR2D arm1( c * len, s * len );
2434 const VECTOR2D arm2( -s * len, c * len );
2435
2436 SetIsFill( false );
2437 SetIsStroke( true );
2438 SetLineWidth( w );
2439 DrawLine( pos - arm1, pos + arm1 );
2440 DrawLine( pos - arm2, pos + arm2 );
2441 };
2442
2443 // LINES stroke setup.
2444 auto beginLines = [&]()
2445 {
2446 SetIsFill( false );
2447 SetIsStroke( true );
2448 };
2449
2450 // DOTS via stencil intersection.
2451 auto drawDotsViaStencil = [&]( auto&& aMarkAxis, auto&& aRenderAxis )
2452 {
2453 // Drop the previous source's markers, keeping its coverage claim.
2454 glStencilMask( STENCIL_DOTS_MARKER );
2455 glClear( GL_STENCIL_BUFFER_BIT );
2456
2457 // Mark pass: set marker where claim is clear.
2458 glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
2459 glStencilFunc( GL_EQUAL, STENCIL_DOTS_MARKER, coverageMask );
2460 glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
2461 SetIsFill( false );
2462 SetIsStroke( true );
2463 aMarkAxis();
2464 m_nonCachedManager->EndDrawing();
2465
2466 // Render pass: stroke where marker set and claim clear.
2467 glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
2468 glStencilMask( 0x00 );
2469 glStencilFunc( GL_EQUAL, STENCIL_DOTS_MARKER, STENCIL_DOTS_MARKER | coverageMask );
2470 glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
2471 aRenderAxis();
2472 m_nonCachedManager->EndDrawing();
2473 };
2474
2475 switch( src.kind )
2476 {
2478 {
2479 double rMax;
2480 double phiMax;
2481 double dr = src.pitch.x;
2482 double dPhi = src.pitch.y;
2483
2484 if( src.unbounded )
2485 {
2486 const BOX2D screen = gridScreenBBox( src );
2487 const double farX = std::max( std::abs( screen.GetLeft() ), std::abs( screen.GetRight() ) );
2488 const double farY = std::max( std::abs( screen.GetTop() ), std::abs( screen.GetBottom() ) );
2489
2490 rMax = std::hypot( farX, farY ) * 1.01; // bleed past the farthest corner
2491 phiMax = 2 * M_PI;
2492 }
2493 else
2494 {
2495 rMax = src.extent.x;
2496 phiMax = src.extent.y;
2497 }
2498
2499 dr = AutoSparsePitch( dr, tick, threshold );
2500
2501 if( rMax > 0.0 )
2502 dPhi = AutoSparsePitch( dPhi, tick, threshold / rMax );
2503
2504 wxASSERT( dr > 0.0 && dPhi > 0.0 );
2505
2506 auto drawArcs = [&]()
2507 {
2508 int rIdx = 0;
2509 for( double r = 0; r <= rMax + 1e-6; r += dr, ++rIdx )
2510 {
2511 if( r == 0.0 )
2512 continue;
2513
2514 SetLineWidth( ( tick && rIdx % (int) tick == 0 ) ? majorLineWidth : minorLineWidth );
2515 DrawArc( VECTOR2D( 0, 0 ), r, EDA_ANGLE( 0, RADIANS_T ), EDA_ANGLE( phiMax, RADIANS_T ) );
2516 }
2517 };
2518
2519 auto drawSpokes = [&]()
2520 {
2521 int pIdx = 0;
2522 for( double phi = 0; phi <= phiMax + 1e-6; phi += dPhi, ++pIdx )
2523 {
2524 SetLineWidth( ( tick && pIdx % (int) tick == 0 ) ? majorLineWidth : minorLineWidth );
2525 const double cx = std::cos( phi );
2526 const double cy = std::sin( phi );
2527 DrawLine( VECTOR2D( 0, 0 ), VECTOR2D( rMax * cx, rMax * cy ) );
2528 }
2529 };
2530
2531 if( src.style == GRID_STYLE::LINES )
2532 {
2533 beginLines();
2534 drawArcs();
2535 drawSpokes();
2536 }
2537 else if( src.style == GRID_STYLE::DOTS )
2538 {
2539 drawDotsViaStencil( drawArcs, drawSpokes );
2540 }
2541 else // SMALL_CROSS
2542 {
2543 int rIdx = 0;
2544 for( double r = 0; r <= rMax + 1e-6; r += dr, ++rIdx )
2545 {
2546 int pIdx = 0;
2547 for( double phi = 0; phi <= phiMax + 1e-6; phi += dPhi, ++pIdx )
2548 {
2549 const bool major = tick && ( rIdx % (int) tick == 0 ) && ( pIdx % (int) tick == 0 );
2550 const VECTOR2D pos( r * std::cos( phi ), r * std::sin( phi ) );
2551 drawCrossAt( pos, major, phi );
2552 }
2553 }
2554 }
2555 break;
2556 }
2557
2559 {
2560 double dx = src.pitch.x;
2561 double dy = src.pitch.y;
2562
2563 // Sparse both axes by the same factor to preserve aspect ratio.
2564 const double minPitch = std::min( dx, dy );
2565 const double sparsed = AutoSparsePitch( minPitch, tick, threshold );
2566
2567 if( sparsed != minPitch )
2568 {
2569 const double scale = sparsed / minPitch;
2570 dx *= scale;
2571 dy *= scale;
2572 }
2573
2574 wxASSERT( dx > 0.0 && dy > 0.0 );
2575
2576 double xMin, xMax, yMin, yMax;
2577 int ixMin, ixMax, iyMin, iyMax;
2578
2579 BOX2D localBBox = gridScreenBBox( src );
2580
2581 // One-pitch bleed so off-screen grid lines still paint.
2582 localBBox.Inflate( dx, dy );
2583
2584 if( src.unbounded )
2585 {
2586 xMin = localBBox.GetLeft();
2587 xMax = localBBox.GetRight();
2588 yMin = localBBox.GetTop();
2589 yMax = localBBox.GetBottom();
2590
2591 ixMin = (int) std::floor( xMin / dx );
2592 ixMax = (int) std::ceil( xMax / dx );
2593 iyMin = (int) std::floor( yMin / dy );
2594 iyMax = (int) std::ceil( yMax / dy );
2595 }
2596 else
2597 {
2598 xMin = std::max( localBBox.GetLeft(), -src.extent.x );
2599 xMax = std::min( localBBox.GetRight(), src.extent.x );
2600 yMin = std::max( localBBox.GetTop(), -src.extent.y );
2601 yMax = std::min( localBBox.GetBottom(), src.extent.y );
2602 ixMin = (int) -( src.extent.x / dx );
2603 ixMax = (int) ( src.extent.x / dx );
2604 iyMin = (int) -( src.extent.y / dy );
2605 iyMax = (int) ( src.extent.y / dy );
2606 }
2607
2608 auto drawVerticals = [&]()
2609 {
2610 for( int ix = ixMin; ix <= ixMax; ++ix )
2611 {
2612 const double x = ix * dx;
2613
2614 // Skip line coincident with world Y axis when axes are drawn.
2615 if( src.axesEnabled && x + src.origin.x == 0.0 )
2616 continue;
2617
2618 SetLineWidth( ( tick && std::abs( ix ) % (int) tick == 0 ) ? majorLineWidth : minorLineWidth );
2619 DrawLine( VECTOR2D( x, yMin ), VECTOR2D( x, yMax ) );
2620 }
2621 };
2622
2623 auto drawHorizontals = [&]()
2624 {
2625 for( int iy = iyMin; iy <= iyMax; ++iy )
2626 {
2627 const double y = iy * dy;
2628
2629 if( src.axesEnabled && y + src.origin.y == 0.0 )
2630 continue;
2631
2632 SetLineWidth( ( tick && std::abs( iy ) % (int) tick == 0 ) ? majorLineWidth : minorLineWidth );
2633 DrawLine( VECTOR2D( xMin, y ), VECTOR2D( xMax, y ) );
2634 }
2635 };
2636
2637 if( src.style == GRID_STYLE::LINES )
2638 {
2639 beginLines();
2640 drawVerticals();
2641 drawHorizontals();
2642 }
2643 else if( src.style == GRID_STYLE::DOTS )
2644 {
2645 drawDotsViaStencil( drawVerticals, drawHorizontals );
2646 }
2647 else // SMALL_CROSS
2648 {
2649 for( int ix = ixMin; ix <= ixMax; ++ix )
2650 {
2651 for( int iy = iyMin; iy <= iyMax; ++iy )
2652 {
2653 const bool major =
2654 tick && ( std::abs( ix ) % (int) tick == 0 ) && ( std::abs( iy ) % (int) tick == 0 );
2655 drawCrossAt( VECTOR2D( ix * dx, iy * dy ), major, 0.0 );
2656 }
2657 }
2658 }
2659 break;
2660 }
2661
2662 default: wxFAIL_MSG( wxT( "drawGridSources: unhandled GRID_SOURCE::KIND" ) ); break;
2663 }
2664
2665 Restore();
2666 m_nonCachedManager->EndDrawing();
2667
2668 // outline the coverage, so grids don't get lost no matter the pitch
2669 if( !src.unbounded )
2670 {
2671 glStencilMask( 0x00 );
2672 glStencilFunc( GL_EQUAL, 0, coverageMask );
2673 glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
2674
2675 SetIsFill( false );
2676 SetIsStroke( true );
2678 SetLineWidth( hairLineWidth );
2679 drawGridCoverageShape( src );
2680 m_nonCachedManager->EndDrawing();
2681 }
2682
2683 // mask out the coverage unless unbounded (background) or highlighted (dimmed fill)
2684 if( !src.unbounded && !src.highlighted )
2685 {
2686 glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
2687 glStencilMask( STENCIL_GRID_COVERAGE );
2688 glStencilFunc( GL_ALWAYS, STENCIL_GRID_COVERAGE, STENCIL_GRID_COVERAGE );
2689 glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
2690
2691 SetIsFill( true );
2692 SetIsStroke( false );
2693 drawGridCoverageShape( src );
2694 m_nonCachedManager->EndDrawing();
2695
2696 glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
2697 }
2698 }
2699
2700 glDisable( GL_STENCIL_TEST );
2701 m_nonCachedManager->EnableDepthTest( true );
2702 glEnable( GL_DEPTH_TEST );
2703 glEnable( GL_TEXTURE_2D );
2704}
2705
2706
2707void OPENGL_GAL::ResizeScreen( int aWidth, int aHeight )
2708{
2709 m_screenSize = VECTOR2I( aWidth, aHeight );
2710
2711 // Resize framebuffers
2712 const float scaleFactor = GetScaleFactor();
2713 m_compositor->Resize( aWidth * scaleFactor, aHeight * scaleFactor );
2715
2716 wxGLCanvas::SetSize( aWidth, aHeight );
2717}
2718
2719
2720bool OPENGL_GAL::Show( bool aShow )
2721{
2722 bool s = wxGLCanvas::Show( aShow );
2723
2724 if( aShow )
2725 wxGLCanvas::Raise();
2726
2727 return s;
2728}
2729
2730
2732{
2733 glFlush();
2734}
2735
2736
2738{
2739 // Clear screen
2741
2742 // NOTE: Black used here instead of m_clearColor; it will be composited later
2743 glClearColor( 0, 0, 0, 1 );
2744 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
2745}
2746
2747
2748void OPENGL_GAL::Transform( const MATRIX3x3D& aTransformation )
2749{
2750 GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
2751
2752 matrixData[0] = aTransformation.m_data[0][0];
2753 matrixData[1] = aTransformation.m_data[1][0];
2754 matrixData[2] = aTransformation.m_data[2][0];
2755 matrixData[4] = aTransformation.m_data[0][1];
2756 matrixData[5] = aTransformation.m_data[1][1];
2757 matrixData[6] = aTransformation.m_data[2][1];
2758 matrixData[12] = aTransformation.m_data[0][2];
2759 matrixData[13] = aTransformation.m_data[1][2];
2760 matrixData[14] = aTransformation.m_data[2][2];
2761
2762 glMultMatrixd( matrixData );
2763}
2764
2765
2766void OPENGL_GAL::Rotate( double aAngle )
2767{
2768 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
2769}
2770
2771
2772void OPENGL_GAL::Translate( const VECTOR2D& aVector )
2773{
2774 m_currentManager->Translate( aVector.x, aVector.y, 0.0f );
2775}
2776
2777
2778void OPENGL_GAL::Scale( const VECTOR2D& aScale )
2779{
2780 m_currentManager->Scale( aScale.x, aScale.y, 1.0f );
2781}
2782
2783
2785{
2786 m_currentManager->PushMatrix();
2787}
2788
2789
2791{
2792 m_currentManager->PopMatrix();
2793}
2794
2795
2797{
2798 m_isGrouping = true;
2799
2800 std::shared_ptr<VERTEX_ITEM> newItem = std::make_shared<VERTEX_ITEM>( *m_cachedManager );
2801 int groupNumber = getNewGroupNumber();
2802 m_groups.insert( std::make_pair( groupNumber, newItem ) );
2803
2804 return groupNumber;
2805}
2806
2807
2809{
2810 m_cachedManager->FinishItem();
2811 m_isGrouping = false;
2812}
2813
2814
2815void OPENGL_GAL::DrawGroup( int aGroupNumber )
2816{
2817 auto group = m_groups.find( aGroupNumber );
2818
2819 if( group != m_groups.end() )
2820 m_cachedManager->DrawItem( *group->second );
2821}
2822
2823
2824void OPENGL_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
2825{
2826 auto group = m_groups.find( aGroupNumber );
2827
2828 if( group != m_groups.end() )
2829 m_cachedManager->ChangeItemColor( *group->second, aNewColor );
2830}
2831
2832
2833void OPENGL_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
2834{
2835 auto group = m_groups.find( aGroupNumber );
2836
2837 if( group != m_groups.end() )
2838 m_cachedManager->ChangeItemDepth( *group->second, aDepth );
2839}
2840
2841
2842void OPENGL_GAL::DeleteGroup( int aGroupNumber )
2843{
2844 // Frees memory in the container as well
2845 m_groups.erase( aGroupNumber );
2846}
2847
2848
2850{
2851 // Runs unlocked from VIEW::Clear, so deleting a lost group's texture names could hit another group's
2853 m_bitmapCache->Abandon();
2854
2855 m_bitmapCache = std::make_unique<GL_BITMAP_CACHE>();
2856
2857 m_groups.clear();
2858
2859 if( m_isInitialized )
2860 m_cachedManager->Clear();
2861}
2862
2863
2865{
2866 switch( aTarget )
2867 {
2868 default:
2873 }
2874
2875 m_currentTarget = aTarget;
2876}
2877
2878
2880{
2881 return m_currentTarget;
2882}
2883
2884
2886{
2887 // Save the current state
2888 unsigned int oldTarget = m_compositor->GetBuffer();
2889
2890 switch( aTarget )
2891 {
2892 // Cached and noncached items are rendered to the same buffer
2893 default:
2894 case TARGET_CACHED:
2895 case TARGET_NONCACHED:
2896 m_compositor->SetBuffer( m_mainBuffer );
2897 break;
2898
2899 case TARGET_TEMP:
2900 if( m_tempBuffer )
2901 m_compositor->SetBuffer( m_tempBuffer );
2902 break;
2903
2904 case TARGET_OVERLAY:
2905 if( m_overlayBuffer )
2906 m_compositor->SetBuffer( m_overlayBuffer );
2907 break;
2908 }
2909
2910 if( aTarget != TARGET_OVERLAY )
2911 m_compositor->ClearBuffer( m_clearColor );
2912 else if( m_overlayBuffer )
2913 m_compositor->ClearBuffer( COLOR4D::BLACK );
2914
2915 // Restore the previous state
2916 m_compositor->SetBuffer( oldTarget );
2917}
2918
2919
2921{
2922 switch( aTarget )
2923 {
2924 default:
2925 case TARGET_CACHED:
2926 case TARGET_NONCACHED: return true;
2927 case TARGET_OVERLAY: return ( m_overlayBuffer != 0 );
2928 case TARGET_TEMP: return ( m_tempBuffer != 0 );
2929 }
2930}
2931
2932
2934{
2935 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::StartDiffLayer() called" ) );
2936 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): m_tempBuffer=%u" ), m_tempBuffer );
2937
2938 m_currentManager->EndDrawing();
2939
2940 if( m_tempBuffer )
2941 {
2942 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): setting target to TARGET_TEMP" ) );
2945
2946 // ClearTarget restores the previous compositor buffer, so we need to explicitly
2947 // set the compositor to render to m_tempBuffer for the layer drawing
2948 m_compositor->SetBuffer( m_tempBuffer );
2949 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): TARGET_TEMP set and cleared, compositor buffer=%u" ),
2950 m_tempBuffer );
2951 }
2952 else
2953 {
2954 wxLogTrace( traceGalXorMode, wxT( "StartDiffLayer(): WARNING - no temp buffer!" ) );
2955 }
2956}
2957
2958
2960{
2961 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::EndDiffLayer() called" ) );
2962 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): m_tempBuffer=%u, m_mainBuffer=%u" ),
2964
2965 if( m_tempBuffer )
2966 {
2967 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): using temp buffer path" ) );
2968
2969 // End drawing to the temp buffer
2970 m_currentManager->EndDrawing();
2971
2972 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): calling DrawBufferDifference" ) );
2973
2974 // Use difference compositing for true XOR/difference mode:
2975 // - Where only one layer has content: shows that layer's color
2976 // - Where both layers overlap with identical content: cancels out (black)
2977 // - Where layers overlap with different content: shows the absolute difference
2978 m_compositor->DrawBufferDifference( m_tempBuffer, m_mainBuffer );
2979
2980 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): DrawBufferDifference returned" ) );
2981 }
2982 else
2983 {
2984 wxLogTrace( traceGalXorMode, wxT( "EndDiffLayer(): NO temp buffer, using fallback path" ) );
2985
2986 // Fall back to imperfect alpha blending on single buffer
2987 glBlendFunc( GL_SRC_ALPHA, GL_ONE );
2988 m_currentManager->EndDrawing();
2989 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
2990 }
2991
2992 wxLogTrace( traceGalXorMode, wxT( "OPENGL_GAL::EndDiffLayer() complete" ) );
2993}
2994
2995
2996bool OPENGL_GAL::SetNativeCursorStyle( KICURSOR aCursor, bool aHiDPI )
2997{
2998 // Store the current cursor type and get the wx cursor for it
2999 if( !GAL::SetNativeCursorStyle( aCursor, aHiDPI ) )
3000 return false;
3001
3003
3004#if wxCHECK_VERSION( 3, 3, 0 )
3005 wxWindow::SetCursorBundle( m_currentwxCursor );
3006#else
3007 wxWindow::SetCursor( m_currentwxCursor );
3008#endif
3009
3010 return true;
3011}
3012
3013
3014void OPENGL_GAL::onSetNativeCursor( wxSetCursorEvent& aEvent )
3015{
3016#if wxCHECK_VERSION( 3, 3, 0 )
3017 aEvent.SetCursor( m_currentwxCursor.GetCursorFor( this ) );
3018#else
3019 aEvent.SetCursor( m_currentwxCursor );
3020#endif
3021}
3022
3023
3024void OPENGL_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
3025{
3026 // Now we should only store the position of the mouse cursor
3027 // The real drawing routines are in blitCursor()
3028 //VECTOR2D screenCursor = m_worldScreenMatrix * aCursorPosition;
3029 //m_cursorPosition = m_screenWorldMatrix * VECTOR2D( screenCursor.x, screenCursor.y );
3030 m_cursorPosition = aCursorPosition;
3031}
3032
3033
3034void OPENGL_GAL::drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
3035 const bool aReserve )
3036{
3037 /* Helper drawing: ____--- v3 ^
3038 * ____---- ... \ \
3039 * ____---- ... \ end \
3040 * v1 ____---- ... ____---- \ width
3041 * ---- ...___---- \ \
3042 * \ ___...-- \ v
3043 * \ ____----... ____---- v2
3044 * ---- ... ____----
3045 * start \ ... ____----
3046 * \... ____----
3047 * ----
3048 * v0
3049 * dots mark triangles' hypotenuses
3050 */
3051
3052 auto v1 = m_currentManager->GetTransformation()
3053 * glm::vec4( aStartPoint.x, aStartPoint.y, 0.0, 0.0 );
3054 auto v2 = m_currentManager->GetTransformation()
3055 * glm::vec4( aEndPoint.x, aEndPoint.y, 0.0, 0.0 );
3056
3057 VECTOR2D vs( v2.x - v1.x, v2.y - v1.y );
3058
3059 if( aReserve )
3060 reserveLineQuads( 1 );
3061
3062 // Line width is maintained by the vertex shader
3063 m_currentManager->Shader( SHADER_LINE_A, m_lineWidth, vs.x, vs.y );
3064 m_currentManager->Vertex( aStartPoint, m_layerDepth );
3065
3066 m_currentManager->Shader( SHADER_LINE_B, m_lineWidth, vs.x, vs.y );
3067 m_currentManager->Vertex( aStartPoint, m_layerDepth );
3068
3069 m_currentManager->Shader( SHADER_LINE_C, m_lineWidth, vs.x, vs.y );
3070 m_currentManager->Vertex( aEndPoint, m_layerDepth );
3071
3072 m_currentManager->Shader( SHADER_LINE_D, m_lineWidth, vs.x, vs.y );
3073 m_currentManager->Vertex( aEndPoint, m_layerDepth );
3074
3075 m_currentManager->Shader( SHADER_LINE_E, m_lineWidth, vs.x, vs.y );
3076 m_currentManager->Vertex( aEndPoint, m_layerDepth );
3077
3078 m_currentManager->Shader( SHADER_LINE_F, m_lineWidth, vs.x, vs.y );
3079 m_currentManager->Vertex( aStartPoint, m_layerDepth );
3080}
3081
3082
3083void OPENGL_GAL::reserveLineQuads( const int aLineCount )
3084{
3085 m_currentManager->Reserve( 6 * aLineCount );
3086}
3087
3088
3089void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle )
3090{
3091 if( m_isFillEnabled )
3092 {
3094 drawFilledSemiCircle( aCenterPoint, aRadius, aAngle );
3095 }
3096
3097 if( m_isStrokeEnabled )
3098 {
3100 m_strokeColor.a );
3101 drawStrokedSemiCircle( aCenterPoint, aRadius, aAngle );
3102 }
3103}
3104
3105
3106void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle )
3107{
3108 Save();
3109
3110 m_currentManager->Reserve( 3 );
3111 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
3112 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
3113
3114 /* Draw a triangle that contains the semicircle, then shade it to leave only
3115 * the semicircle. Parameters given to Shader() are indices of the triangle's vertices
3116 * (if you want to understand more, check the vertex shader source [shader.vert]).
3117 * Shader uses these coordinates to determine if fragments are inside the semicircle or not.
3118 * v2
3119 * /\
3120 * /__\
3121 * v0 //__\\ v1
3122 */
3123 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f );
3124 m_currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v0
3125
3126 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f );
3127 m_currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v1
3128
3129 m_currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f );
3130 m_currentManager->Vertex( 0.0f, aRadius * 2.0f, m_layerDepth ); // v2
3131
3132 Restore();
3133}
3134
3135
3136void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle,
3137 bool aReserve )
3138{
3139 double outerRadius = aRadius + ( m_lineWidth / 2 );
3140
3141 Save();
3142
3143 if( aReserve )
3144 m_currentManager->Reserve( 3 );
3145
3146 m_currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f );
3147 m_currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f );
3148
3149 /* Draw a triangle that contains the semicircle, then shade it to leave only
3150 * the semicircle. Parameters given to Shader() are indices of the triangle's vertices
3151 * (if you want to understand more, check the vertex shader source [shader.vert]), the
3152 * radius and the line width. Shader uses these coordinates to determine if fragments are
3153 * inside the semicircle or not.
3154 * v2
3155 * /\
3156 * /__\
3157 * v0 //__\\ v1
3158 */
3159 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, m_lineWidth );
3160 m_currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v0
3161
3162 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, m_lineWidth );
3163 m_currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, m_layerDepth ); // v1
3164
3165 m_currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, m_lineWidth );
3166 m_currentManager->Vertex( 0.0f, outerRadius * 2.0f, m_layerDepth ); // v2
3167
3168 Restore();
3169}
3170
3171
3172void OPENGL_GAL::drawPolygon( GLdouble* aPoints, int aPointCount )
3173{
3174 if( m_isFillEnabled )
3175 {
3176 m_currentManager->Shader( SHADER_NONE );
3178
3179 // Any non convex polygon needs to be tesselated
3180 // for this purpose the GLU standard functions are used
3182 gluTessBeginPolygon( m_tesselator, &params );
3183 gluTessBeginContour( m_tesselator );
3184
3185 GLdouble* point = aPoints;
3186
3187 for( int i = 0; i < aPointCount; ++i )
3188 {
3189 gluTessVertex( m_tesselator, point, point );
3190 point += 3; // 3 coordinates
3191 }
3192
3193 gluTessEndContour( m_tesselator );
3194 gluTessEndPolygon( m_tesselator );
3195
3196 // Free allocated intersecting points
3197 m_tessIntersects.clear();
3198 }
3199
3200 if( m_isStrokeEnabled )
3201 {
3203 [&]( int idx )
3204 {
3205 return VECTOR2D( aPoints[idx * 3], aPoints[idx * 3 + 1] );
3206 },
3207 aPointCount );
3208 }
3209}
3210
3211
3212void OPENGL_GAL::drawPolyline( const std::function<VECTOR2D( int )>& aPointGetter, int aPointCount,
3213 bool aReserve )
3214{
3215 wxCHECK( aPointCount > 0, /* return */ );
3216
3218
3219 if( aPointCount == 1 )
3220 {
3221 drawLineQuad( aPointGetter( 0 ), aPointGetter( 0 ), aReserve );
3222 return;
3223 }
3224
3225 if( aReserve )
3226 {
3227 reserveLineQuads( aPointCount - 1 );
3228 }
3229
3230 for( int i = 1; i < aPointCount; ++i )
3231 {
3232 auto start = aPointGetter( i - 1 );
3233 auto end = aPointGetter( i );
3234
3235 drawLineQuad( start, end, false );
3236 }
3237}
3238
3239
3240void OPENGL_GAL::drawSegmentChain( const std::function<VECTOR2D( int )>& aPointGetter,
3241 int aPointCount, double aWidth, bool aReserve )
3242{
3243 wxCHECK( aPointCount >= 2, /* return */ );
3244
3246
3247 int vertices = 0;
3248
3249 for( int i = 1; i < aPointCount; ++i )
3250 {
3251 auto start = aPointGetter( i - 1 );
3252 auto end = aPointGetter( i );
3253
3254 float startx = start.x;
3255 float starty = start.y;
3256 float endx = end.x;
3257 float endy = end.y;
3258
3259 // Be careful about floating point rounding. As we draw segments in larger and larger
3260 // coordinates, the shader (which uses floats) will lose precision and stop drawing small
3261 // segments. In this case, we need to draw a circle for the minimal segment.
3262 // Check if the coordinate differences can be accurately represented as floats
3263
3264 if( startx == endx && starty == endy )
3265 {
3266 vertices += 3; // One circle
3267 continue;
3268 }
3269
3270 if( m_isFillEnabled || aWidth == 1.0 )
3271 {
3272 vertices += 6; // One line
3273 }
3274 else
3275 {
3276 vertices += 6 + 6 + 3 + 3; // Two lines and two half-circles
3277 }
3278 }
3279
3280 m_currentManager->Reserve( vertices );
3281
3282 for( int i = 1; i < aPointCount; ++i )
3283 {
3284 auto start = aPointGetter( i - 1 );
3285 auto end = aPointGetter( i );
3286
3287 drawSegment( start, end, aWidth, false );
3288 }
3289}
3290
3291
3292int OPENGL_GAL::drawBitmapChar( unsigned long aChar, bool aReserve )
3293{
3294 const float TEX_X = font_image.width;
3295 const float TEX_Y = font_image.height;
3296
3297 // handle space
3298 if( aChar == ' ' )
3299 {
3300 const FONT_GLYPH_TYPE* g = LookupGlyph( 'x' );
3301 wxCHECK( g, 0 );
3302
3303 // Match stroke font as well as possible
3304 double spaceWidth = g->advance * 0.74;
3305
3306 Translate( VECTOR2D( spaceWidth, 0 ) );
3307 return KiROUND( spaceWidth );
3308 }
3309
3310 const FONT_GLYPH_TYPE* glyph = LookupGlyph( aChar );
3311
3312 // If the glyph is not found (happens for many esoteric unicode chars)
3313 // shows a '?' instead.
3314 if( !glyph )
3315 glyph = LookupGlyph( '?' );
3316
3317 if( !glyph ) // Should not happen.
3318 return 0;
3319
3320 const float X = glyph->atlas_x + font_information.smooth_pixels;
3321 const float Y = glyph->atlas_y + font_information.smooth_pixels;
3322 const float XOFF = glyph->minx;
3323
3324 // adjust for height rounding
3325 const float round_adjust = ( glyph->maxy - glyph->miny )
3326 - float( glyph->atlas_h - font_information.smooth_pixels * 2 );
3327 const float top_adjust = font_information.max_y - glyph->maxy;
3328 const float YOFF = round_adjust + top_adjust;
3329 const float W = glyph->atlas_w - font_information.smooth_pixels * 2;
3330 const float H = glyph->atlas_h - font_information.smooth_pixels * 2;
3331 const float B = 0;
3332
3333 if( aReserve )
3334 m_currentManager->Reserve( 6 );
3335
3336 Translate( VECTOR2D( XOFF, YOFF ) );
3337
3338 /* Glyph:
3339 * v0 v1
3340 * +--+
3341 * | /|
3342 * |/ |
3343 * +--+
3344 * v2 v3
3345 */
3346 m_currentManager->Shader( SHADER_FONT, X / TEX_X, ( Y + H ) / TEX_Y );
3347 m_currentManager->Vertex( -B, -B, 0 ); // v0
3348
3349 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, ( Y + H ) / TEX_Y );
3350 m_currentManager->Vertex( W + B, -B, 0 ); // v1
3351
3352 m_currentManager->Shader( SHADER_FONT, X / TEX_X, Y / TEX_Y );
3353 m_currentManager->Vertex( -B, H + B, 0 ); // v2
3354
3355
3356 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, ( Y + H ) / TEX_Y );
3357 m_currentManager->Vertex( W + B, -B, 0 ); // v1
3358
3359 m_currentManager->Shader( SHADER_FONT, X / TEX_X, Y / TEX_Y );
3360 m_currentManager->Vertex( -B, H + B, 0 ); // v2
3361
3362 m_currentManager->Shader( SHADER_FONT, ( X + W ) / TEX_X, Y / TEX_Y );
3363 m_currentManager->Vertex( W + B, H + B, 0 ); // v3
3364
3365 Translate( VECTOR2D( -XOFF + glyph->advance, -YOFF ) );
3366
3367 return glyph->advance;
3368}
3369
3370
3371void OPENGL_GAL::drawBitmapOverbar( double aLength, double aHeight, bool aReserve )
3372{
3373 // To draw an overbar, simply draw an overbar
3374 const FONT_GLYPH_TYPE* glyph = LookupGlyph( '_' );
3375 wxCHECK( glyph, /* void */ );
3376
3377 const float H = glyph->maxy - glyph->miny;
3378
3379 Save();
3380
3381 Translate( VECTOR2D( -aLength, -aHeight ) );
3382
3383 if( aReserve )
3384 m_currentManager->Reserve( 6 );
3385
3387
3388 m_currentManager->Shader( 0 );
3389
3390 m_currentManager->Vertex( 0, 0, 0 ); // v0
3391 m_currentManager->Vertex( aLength, 0, 0 ); // v1
3392 m_currentManager->Vertex( 0, H, 0 ); // v2
3393
3394 m_currentManager->Vertex( aLength, 0, 0 ); // v1
3395 m_currentManager->Vertex( 0, H, 0 ); // v2
3396 m_currentManager->Vertex( aLength, H, 0 ); // v3
3397
3398 Restore();
3399}
3400
3401
3402std::pair<VECTOR2D, float> OPENGL_GAL::computeBitmapTextSize( const UTF8& aText ) const
3403{
3404 static const FONT_GLYPH_TYPE* defaultGlyph = LookupGlyph( '(' ); // for strange chars
3405
3406 VECTOR2D textSize( 0, 0 );
3407 float commonOffset = std::numeric_limits<float>::max();
3408 float charHeight = font_information.max_y - defaultGlyph->miny;
3409 int overbarDepth = -1;
3410 int braceNesting = 0;
3411
3412 for( UTF8::uni_iter chIt = aText.ubegin(), end = aText.uend(); chIt < end; ++chIt )
3413 {
3414 if( *chIt == '~' && overbarDepth == -1 )
3415 {
3416 UTF8::uni_iter lookahead = chIt;
3417
3418 if( ++lookahead != end && *lookahead == '{' )
3419 {
3420 chIt = lookahead;
3421 overbarDepth = braceNesting;
3422 braceNesting++;
3423 continue;
3424 }
3425 }
3426 else if( *chIt == '{' )
3427 {
3428 braceNesting++;
3429 }
3430 else if( *chIt == '}' )
3431 {
3432 if( braceNesting > 0 )
3433 braceNesting--;
3434
3435 if( braceNesting == overbarDepth )
3436 {
3437 overbarDepth = -1;
3438 continue;
3439 }
3440 }
3441
3442 const FONT_GLYPH_TYPE* glyph = LookupGlyph( *chIt );
3443
3444 if( !glyph // Not coded in font
3445 || *chIt == '-' || *chIt == '_' ) // Strange size of these 2 chars
3446 {
3447 glyph = defaultGlyph;
3448 }
3449
3450 if( glyph )
3451 textSize.x += glyph->advance;
3452 }
3453
3454 textSize.y = std::max<float>( textSize.y, charHeight );
3455 commonOffset = std::min<float>( font_information.max_y - defaultGlyph->maxy, commonOffset );
3456 textSize.y -= commonOffset;
3457
3458 return std::make_pair( textSize, commonOffset );
3459}
3460
3461
3462void OPENGL_GAL::onPaint( wxPaintEvent& aEvent )
3463{
3464 PostPaint( aEvent );
3465}
3466
3467
3468void OPENGL_GAL::skipMouseEvent( wxMouseEvent& aEvent )
3469{
3470 // Post the mouse event to the event listener registered in constructor, if any
3471 if( m_mouseListener )
3472 wxPostEvent( m_mouseListener, aEvent );
3473}
3474
3475
3476void OPENGL_GAL::skipGestureEvent( wxGestureEvent& aEvent )
3477{
3478 // Post the gesture event to the event listener registered in constructor, if any
3479 if( m_mouseListener )
3480 wxPostEvent( m_mouseListener, aEvent );
3481}
3482
3483
3485{
3486 if( !IsCursorEnabled() )
3487 return;
3488
3490
3491 VECTOR2D cursorBegin;
3492 VECTOR2D cursorEnd;
3493 VECTOR2D cursorCenter = m_cursorPosition;
3494
3496 {
3497 cursorBegin = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
3498 cursorEnd = m_screenWorldMatrix * VECTOR2D( m_screenSize );
3499 }
3501 {
3502 const int cursorSize = 80;
3503 cursorBegin = m_cursorPosition - cursorSize / ( 2 * m_worldScale );
3504 cursorEnd = m_cursorPosition + cursorSize / ( 2 * m_worldScale );
3505 }
3506
3507 const COLOR4D color = getCursorColor();
3508
3509 GLboolean depthTestEnabled = glIsEnabled( GL_DEPTH_TEST );
3510 glDisable( GL_DEPTH_TEST );
3511
3512 glActiveTexture( GL_TEXTURE0 );
3513 glDisable( GL_TEXTURE_2D );
3514 glEnable( GL_BLEND );
3515 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
3516
3517 glLineWidth( 1.0 );
3518 glColor4d( color.r, color.g, color.b, color.a );
3519
3520 glMatrixMode( GL_PROJECTION );
3521 glPushMatrix();
3522 glTranslated( 0, 0, -0.5 );
3523
3524 glBegin( GL_LINES );
3525
3527 {
3528 // Calculate screen bounds in world coordinates
3529 VECTOR2D screenTopLeft = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
3530 VECTOR2D screenBottomRight = m_screenWorldMatrix * VECTOR2D( m_screenSize );
3531
3532 // For 45-degree lines passing through cursor position
3533 // Line equation: y = x + (cy - cx) for positive slope
3534 // Line equation: y = -x + (cy + cx) for negative slope
3535 double cx = m_cursorPosition.x;
3536 double cy = m_cursorPosition.y;
3537
3538 // Calculate intersections for positive slope diagonal (y = x + offset)
3539 double offset1 = cy - cx;
3540 VECTOR2D pos_start( screenTopLeft.x, screenTopLeft.x + offset1 );
3541 VECTOR2D pos_end( screenBottomRight.x, screenBottomRight.x + offset1 );
3542
3543 // Draw positive slope diagonal
3544 glVertex2d( pos_start.x, pos_start.y );
3545 glVertex2d( pos_end.x, pos_end.y );
3546
3547 // Calculate intersections for negative slope diagonal (y = -x + offset)
3548 double offset2 = cy + cx;
3549 VECTOR2D neg_start( screenTopLeft.x, offset2 - screenTopLeft.x );
3550 VECTOR2D neg_end( screenBottomRight.x, offset2 - screenBottomRight.x );
3551
3552 // Draw negative slope diagonal
3553 glVertex2d( neg_start.x, neg_start.y );
3554 glVertex2d( neg_end.x, neg_end.y );
3555 }
3556 else
3557 {
3558 glVertex2d( cursorCenter.x, cursorBegin.y );
3559 glVertex2d( cursorCenter.x, cursorEnd.y );
3560
3561 glVertex2d( cursorBegin.x, cursorCenter.y );
3562 glVertex2d( cursorEnd.x, cursorCenter.y );
3563 }
3564
3565 glEnd();
3566
3567 glPopMatrix();
3568
3569 if( depthTestEnabled )
3570 glEnable( GL_DEPTH_TEST );
3571}
3572
3573
3575{
3576 wxASSERT_MSG( m_groups.size() < std::numeric_limits<unsigned int>::max(),
3577 wxT( "There are no free slots to store a group" ) );
3578
3579 while( m_groups.find( m_groupCounter ) != m_groups.end() )
3581
3582 return m_groupCounter++;
3583}
3584
3585
3587{
3588 wxASSERT_MSG( m_isContextLocked, "This should only be called from within a locked context." );
3589
3590 // Check correct initialization from the constructor
3591 if( m_tesselator == nullptr )
3592 throw std::runtime_error( "Could not create the tesselator" );
3593
3595
3596 int glVersion = gladLoaderLoadGL();
3597
3598 if( glVersion == 0 )
3599 throw std::runtime_error( "Failed to load OpenGL via loader" );
3600
3601 m_glLoaded = true;
3602
3603 const char* vendor = (const char*) glGetString( GL_VENDOR );
3604 const char* renderer = (const char*) glGetString( GL_RENDERER );
3605 const char* version = (const char*) glGetString( GL_VERSION );
3606
3607 if( !version )
3608 throw std::runtime_error( "No GL context is current (glGetString returned NULL)" );
3609
3610 SetOpenGLInfo( vendor, renderer, version );
3611
3612 wxLogTrace( traceGalContext, wxS( "GL context %p ready on canvas %p: %s | %s | %s" ),
3613 m_glPrivContext, this, wxString::FromUTF8( vendor ),
3614 wxString::FromUTF8( renderer ), wxString::FromUTF8( version ) );
3615
3616 GLint resetStrategy = 0;
3617
3618 if( GLAD_GL_VERSION_4_5 || GLAD_GL_ARB_robustness || GLAD_GL_KHR_robustness )
3619 glGetIntegerv( GL_RESET_NOTIFICATION_STRATEGY, &resetStrategy );
3620
3621 wxLogTrace( traceGalContext, wxS( "GL context %p reset strategy 0x%x" ), m_glPrivContext, resetStrategy );
3622
3623 // Check the OpenGL version (minimum 2.1 is required)
3624 if( !GLAD_GL_VERSION_2_1 )
3625 throw std::runtime_error( "OpenGL 2.1 or higher is required!" );
3626
3627#if defined( __LINUX__ ) // calling enableGlDebug crashes opengl on some OS (OSX and some Windows)
3628#ifdef DEBUG
3629 if( glDebugMessageCallback )
3630 enableGlDebug( true );
3631#endif
3632#endif
3633
3634 // Framebuffers have to be supported
3635 if( !GLAD_GL_ARB_framebuffer_object )
3636 throw std::runtime_error( "Framebuffer objects are not supported!" );
3637
3638 // Vertex buffer has to be supported
3639 if( !GLAD_GL_ARB_vertex_buffer_object )
3640 throw std::runtime_error( "Vertex buffer objects are not supported!" );
3641
3642 // Prepare shaders
3643 if( !m_shader->IsLinked()
3644 && !m_shader->LoadShaderFromStrings( SHADER_TYPE_VERTEX,
3645 BUILTIN_SHADERS::glsl_kicad_vert ) )
3646 {
3647 throw std::runtime_error( "Cannot compile vertex shader!" );
3648 }
3649
3650 if( !m_shader->IsLinked()
3651 && !m_shader->LoadShaderFromStrings( SHADER_TYPE_FRAGMENT,
3652 BUILTIN_SHADERS::glsl_kicad_frag ) )
3653 {
3654 throw std::runtime_error( "Cannot compile fragment shader!" );
3655 }
3656
3657 if( !m_shader->IsLinked() && !m_shader->Link() )
3658 throw std::runtime_error( "Cannot link the shaders!" );
3659
3660 // Set up shader parameters after linking
3662
3663 // Check if video card supports textures big enough to fit the font atlas
3664 int maxTextureSize;
3665 glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTextureSize );
3666
3667 if( maxTextureSize < (int) font_image.width || maxTextureSize < (int) font_image.height )
3668 {
3669 // TODO implement software texture scaling
3670 // for bitmap fonts and use a higher resolution texture?
3671 throw std::runtime_error( "Requested texture size is not supported" );
3672 }
3673
3674#if wxCHECK_VERSION( 3, 3, 3 )
3675 wxGLCanvas::SetSwapInterval( -1 );
3676 m_swapInterval = wxGLCanvas::GetSwapInterval();
3677#else
3679#endif
3680
3681 m_cachedManager = new VERTEX_MANAGER( true );
3682 m_nonCachedManager = new VERTEX_MANAGER( false );
3683 m_overlayManager = new VERTEX_MANAGER( false );
3684 m_tempManager = new VERTEX_MANAGER( false );
3685
3686 // Make VBOs use shaders
3687 m_cachedManager->SetShader( *m_shader );
3688 m_nonCachedManager->SetShader( *m_shader );
3689 m_overlayManager->SetShader( *m_shader );
3690 m_tempManager->SetShader( *m_shader );
3691
3692 m_isInitialized = true;
3693}
3694
3695
3697{
3698 // Initialize shader uniform parameter locations
3699 ufm_fontTexture = m_shader->AddParameter( "u_fontTexture" );
3700 ufm_fontTextureWidth = m_shader->AddParameter( "u_fontTextureWidth" );
3701 ufm_worldPixelSize = m_shader->AddParameter( "u_worldPixelSize" );
3702 ufm_screenPixelSize = m_shader->AddParameter( "u_screenPixelSize" );
3703 ufm_pixelSizeMultiplier = m_shader->AddParameter( "u_pixelSizeMultiplier" );
3704 ufm_antialiasingOffset = m_shader->AddParameter( "u_antialiasingOffset" );
3705 ufm_minLinePixelWidth = m_shader->AddParameter( "u_minLinePixelWidth" );
3706}
3707
3708
3709// Callback functions for the tesselator. Compare Redbook Chapter 11.
3710void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
3711{
3712 GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr );
3713 OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
3714 VERTEX_MANAGER* vboManager = param->vboManager;
3715
3716 assert( vboManager );
3717 vboManager->Vertex( vertex[0], vertex[1], vertex[2] );
3718}
3719
3720
3721void CALLBACK CombineCallback( GLdouble coords[3], GLdouble* vertex_data[4], GLfloat weight[4],
3722 GLdouble** dataOut, void* aData )
3723{
3724 GLdouble* vertex = new GLdouble[3];
3725 OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
3726
3727 // Save the pointer so we can delete it later
3728 // Note, we use the default_delete for an array because macOS
3729 // decides to bundle an ancient libc++ that mismatches the C++17 support of clang
3730 param->intersectPoints.emplace_back( vertex, std::default_delete<GLdouble[]>() );
3731
3732 memcpy( vertex, coords, 3 * sizeof( GLdouble ) );
3733
3734 *dataOut = vertex;
3735}
3736
3737
3738void CALLBACK EdgeCallback( GLboolean aEdgeFlag )
3739{
3740 // This callback is needed to force GLU tesselator to use triangles only
3741}
3742
3743
3745{
3746 //throw std::runtime_error( std::string( "Tessellation error: " ) +
3747 //std::string( (const char*) gluErrorString( aErrorCode ) );
3748}
3749
3750
3751static void InitTesselatorCallbacks( GLUtesselator* aTesselator )
3752{
3753#if defined( _MSC_VER )
3754#pragma warning( push )
3755#pragma warning( disable : 4191 )
3756#endif
3757 gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, (void( CALLBACK* )()) VertexCallback );
3758 gluTessCallback( aTesselator, GLU_TESS_COMBINE_DATA, (void( CALLBACK* )()) CombineCallback );
3759 gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, (void( CALLBACK* )()) EdgeCallback );
3760 gluTessCallback( aTesselator, GLU_TESS_ERROR, (void( CALLBACK* )()) ErrorCallback );
3761#if defined( _MSC_VER )
3762#pragma warning( pop )
3763#endif
3764}
3765
3766
3767void OPENGL_GAL::EnableDepthTest( bool aEnabled )
3768{
3769 m_cachedManager->EnableDepthTest( aEnabled );
3770 m_nonCachedManager->EnableDepthTest( aEnabled );
3771 m_overlayManager->EnableDepthTest( aEnabled );
3772}
3773
3774
3775inline double round_to_half_pixel( double f, double r )
3776{
3777 return ( ceil( f / r ) - 0.5 ) * r;
3778}
3779
3780
3782{
3784 auto pixelSize = m_worldScale;
3785
3786 // we need -m_lookAtPoint == -k * pixelSize + 0.5 * pixelSize for OpenGL
3787 // meaning m_lookAtPoint = (k-0.5)*pixelSize with integer k
3790
3792}
3793
3794
3795void OPENGL_GAL::DrawGlyph( const KIFONT::GLYPH& aGlyph, int aNth, int aTotal )
3796{
3797 if( aGlyph.IsStroke() )
3798 {
3799 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( aGlyph );
3800
3801 DrawPolylines( strokeGlyph );
3802 }
3803 else if( aGlyph.IsOutline() )
3804 {
3805 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
3806
3807 m_currentManager->Shader( SHADER_NONE );
3808 m_currentManager->Color( m_fillColor );
3809
3810 outlineGlyph.Triangulate(
3811 [&]( const VECTOR2D& aPt1, const VECTOR2D& aPt2, const VECTOR2D& aPt3 )
3812 {
3813 m_currentManager->Reserve( 3 );
3814
3815 m_currentManager->Vertex( aPt1.x, aPt1.y, m_layerDepth );
3816 m_currentManager->Vertex( aPt2.x, aPt2.y, m_layerDepth );
3817 m_currentManager->Vertex( aPt3.x, aPt3.y, m_layerDepth );
3818 } );
3819 }
3820}
3821
3822
3823void OPENGL_GAL::DrawGlyphs( const std::vector<std::unique_ptr<KIFONT::GLYPH>>& aGlyphs )
3824{
3825 if( aGlyphs.empty() )
3826 return;
3827
3828 bool allGlyphsAreStroke = true;
3829 bool allGlyphsAreOutline = true;
3830
3831 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3832 {
3833 if( !glyph->IsStroke() )
3834 {
3835 allGlyphsAreStroke = false;
3836 break;
3837 }
3838 }
3839
3840 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3841 {
3842 if( !glyph->IsOutline() )
3843 {
3844 allGlyphsAreOutline = false;
3845 break;
3846 }
3847 }
3848
3849 if( allGlyphsAreStroke )
3850 {
3851 // Optimized path for stroke fonts that pre-reserves line quads.
3852 int lineQuadCount = 0;
3853
3854 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3855 {
3856 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( *glyph );
3857
3858 for( const std::vector<VECTOR2D>& points : strokeGlyph )
3859 lineQuadCount += points.size() - 1;
3860 }
3861
3862 reserveLineQuads( lineQuadCount );
3863
3864 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3865 {
3866 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( *glyph );
3867
3868 for( const std::vector<VECTOR2D>& points : strokeGlyph )
3869 {
3871 [&]( int idx )
3872 {
3873 return points[idx];
3874 },
3875 points.size(), false );
3876 }
3877 }
3878
3879 return;
3880 }
3881 else if( allGlyphsAreOutline )
3882 {
3883 // Optimized path for outline fonts that pre-reserves glyph triangles.
3884 int triangleCount = 0;
3885
3886 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3887 {
3888 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );
3889
3890 for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ )
3891 {
3893 outlineGlyph.TriangulatedPolygon( i );
3894
3895 triangleCount += polygon->GetTriangleCount();
3896 }
3897 }
3898
3899 m_currentManager->Shader( SHADER_NONE );
3900 m_currentManager->Color( m_fillColor );
3901
3902 m_currentManager->Reserve( 3 * triangleCount );
3903
3904 for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
3905 {
3906 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );
3907
3908 for( unsigned int i = 0; i < outlineGlyph.TriangulatedPolyCount(); i++ )
3909 {
3911 outlineGlyph.TriangulatedPolygon( i );
3912
3913 for( size_t j = 0; j < polygon->GetTriangleCount(); j++ )
3914 {
3915 VECTOR2I a, b, c;
3916 polygon->GetTriangle( j, a, b, c );
3917
3918 m_currentManager->Vertex( a.x, a.y, m_layerDepth );
3919 m_currentManager->Vertex( b.x, b.y, m_layerDepth );
3920 m_currentManager->Vertex( c.x, c.y, m_layerDepth );
3921 }
3922 }
3923 }
3924 }
3925 else
3926 {
3927 // Regular path
3928 for( size_t i = 0; i < aGlyphs.size(); i++ )
3929 DrawGlyph( *aGlyphs[i], i, aGlyphs.size() );
3930 }
3931}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
void SetOpenGLInfo(const char *aVendor, const char *aRenderer, const char *aVersion)
A setter for OpenGL info when it's initialized.
void SetOpenGLBackendInfo(wxString aBackend)
A setter for OpenGL backend info after the canvas is created.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:45
const wxImage * GetOriginalImageData() const
Definition bitmap_base.h:67
VECTOR2I GetSizePixels() const
EDA_ANGLE Rotation() const
bool IsMirroredX() const
bool IsMirroredY() const
KIID GetImageID() const
Definition bitmap_base.h:72
int GetPPI() const
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
static const WX_CURSOR_TYPE GetCursor(KICURSOR aCursorType, bool aHiDPI=false)
Get a cursor bundle (wx 3.3+) or appropriate cursor (older versions)
Definition cursors.cpp:399
double AsDegrees() const
Definition eda_angle.h:115
double AsRadians() const
Definition eda_angle.h:119
void UnlockCtx(wxGLContext *aContext)
Allow other canvases to bind an OpenGL context.
void DestroyCtx(wxGLContext *aContext)
Destroy a managed OpenGL context.
bool LockCtx(wxGLContext *aContext, wxGLCanvas *aCanvas)
Set a context as current and prevents other canvases from switching it.
wxGLContext * CreateCtx(wxGLCanvas *aCanvas, const wxGLContext *aOther=nullptr)
Create a managed OpenGL context.
static int SetSwapInterval(wxGLCanvas *aCanvas, int aVal)
Attempt to set the OpenGL swap interval.
Definition gl_utils.cpp:78
static wxString DetectGLBackend(wxGLCanvas *aCanvas)
Definition gl_utils.cpp:50
HIDPI_GL_CANVAS(const KIGFX::VC_SETTINGS &aSettings, wxWindow *aParent, const wxGLAttributes &aGLAttribs, wxWindowID aId=wxID_ANY, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, long aStyle=0, const wxString &aName=wxGLCanvasName, const wxPalette &aPalette=wxNullPalette)
virtual wxSize GetNativePixelSize() const
double GetScaleFactor() const
Get the current scale factor.
virtual bool IsStroke() const
Definition glyph.h:47
virtual bool IsOutline() const
Definition glyph.h:46
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:391
double g
Green component.
Definition color4d.h:392
COLOR4D Darkened(double aFactor) const
Return a color that is darker by a given factor, without modifying object.
Definition color4d.h:279
COLOR4D & Brighten(double aFactor)
Makes the color brighter by a given factor.
Definition color4d.h:206
double a
Alpha component.
Definition color4d.h:394
static const COLOR4D BLACK
Definition color4d.h:404
double b
Blue component.
Definition color4d.h:393
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.
virtual void SetIsFill(bool aIsFillEnabled)
Enable/disable fill.
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.
BOX2D gridScreenBBox(const GRID_SOURCE &aSrc) const
VECTOR2D m_cursorPosition
Current cursor position (world coordinates)
virtual void SetIsStroke(bool aIsStrokeEnabled)
Enable/disable stroked outlines.
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.
double computeMinGridSpacing() const
Compute minimum grid spacing from the grid settings.
bool m_isStrokeEnabled
Are the outlines stroked ?
std::vector< GRID_SOURCE > m_gridSources
Sources overlayed on the display grid.
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.
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)
void Abandon()
Drop the cached texture names without deleting them.
std::list< GLuint > m_freedTextureIds
const size_t m_cacheMaxSize
std::map< const KIID, CACHED_BITMAP > m_bitmaps
std::list< KIID > m_cacheLru
Limit how often the OpenGL canvases are rebuilt after GPU resets.
static const unsigned int DIRECT_RENDERING
OpenGL implementation of the Graphics Abstraction Layer.
Definition opengl_gal.h:72
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:380
void EndDiffLayer() override
Ends rendering of a differential layer.
VERTEX_MANAGER * m_overlayManager
Container for storing overlaid VERTEX_ITEMs.
Definition opengl_gal.h:385
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:405
VERTEX_MANAGER * m_currentManager
Currently used VERTEX_MANAGER (for storing VERTEX_ITEMs).
Definition opengl_gal.h:381
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:426
void DrawEllipseArc(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle) override
Draw an elliptical arc in world coordinates.
void DrawCircle(const VECTOR2D &aCenterPoint, double aRadius) override
Draw a circle using world coordinates.
bool IsInitialized() const override
Return the initialization status for the canvas.
Definition opengl_gal.h:103
void LockContext(int aClientCookie) override
Use GAL_CONTEXT_LOCKER RAII object unless you know what you're doing.
WX_CURSOR_TYPE m_currentwxCursor
wx cursor showing the current native cursor.
Definition opengl_gal.h:420
unsigned int m_mainBuffer
Main rendering target.
Definition opengl_gal.h:392
static std::set< OPENGL_GAL * > m_instances
Canvases to repaint after a reset.
Definition opengl_gal.h:364
std::unique_ptr< GL_BITMAP_CACHE > m_bitmapCache
Definition opengl_gal.h:422
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)
Check 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:404
static GL_RESET_BUDGET m_resetBudget
Rate of resets tolerated before falling back.
Definition opengl_gal.h:362
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
Update handler for OpenGL settings.
unsigned int m_overlayBuffer
Auxiliary rendering target (for menus etc.)
Definition opengl_gal.h:393
void PostPaint(wxPaintEvent &aEvent)
Post an event to m_paintListener.
OPENGL_COMPOSITOR * m_compositor
Handles multiple rendering targets.
Definition opengl_gal.h:391
VERTEX_MANAGER * m_cachedManager
Container for storing cached VERTEX_ITEMs.
Definition opengl_gal.h:383
void Translate(const VECTOR2D &aTranslation) override
Translate the context.
static bool m_glLoaded
GL entry points are loaded, so resets can be queried.
Definition opengl_gal.h:363
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:415
void Restore() override
Restore the context.
int m_ownContextGroupId
Group this canvas' contexts were created in.
Definition opengl_gal.h:370
bool IsResetSettled() override
Check whether the driver has finished the reset that destroyed this canvas' context.
void DrawGrid() override
Draw the grid.
void DrawSegmentChain(const std::vector< VECTOR2D > &aPointList, double aWidth) override
Draw a chain of rounded segments.
static GL_RESET_BUDGET::CLOCK::time_point m_resetDetectedAt
When the last reset was detected, to stop waiting on a driver that never reports completion.
Definition opengl_gal.h:368
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:414
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:109
wxEvtHandler * m_mouseListener
Definition opengl_gal.h:371
int m_swapInterval
Used to store swap interval information.
Definition opengl_gal.h:358
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:379
void endUpdate() override
Disable item update mode.
void ClearScreen() override
Clear the screen.
void ResizeScreen(int aWidth, int aHeight) override
Resizes the canvas.
GLint ufm_fontTextureWidth
Definition opengl_gal.h:417
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:384
int drawBitmapChar(unsigned long aChar, bool aReserve=true)
Draw a single character using bitmap font.
GLUtesselator * m_tesselator
Definition opengl_gal.h:425
wxEvtHandler * m_paintListener
Definition opengl_gal.h:372
void StartDiffLayer() override
Begins rendering of a differential layer.
void drawGridCoverageShape(const GRID_SOURCE &aSrc)
Fill a source's coverage region into the current color/stencil state.
static bool m_resetSettled
Driver finished the last reset.
Definition opengl_gal.h:365
bool m_isContextLocked
Used for assertion checking.
Definition opengl_gal.h:408
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:402
void ComputeWorldScreenMatrix() override
Compute the world <-> screen transformation matrix.
void orphanContextGroup()
Abandon the shared context group so the next canvas created starts a fresh one.
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:374
virtual bool HasTarget(RENDER_TARGET aTarget) override
Return true if the target exists.
GAL_CONTEXT_LOSS GetContextLoss() const override
Check whether a GPU reset destroyed this canvas' rendering context.
bool GetScreenshot(wxImage &aDstImage)
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
Enable item update mode.
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:632
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:407
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:412
void Rotate(double aAngle) override
Rotate the context.
int BeginGroup() override
Begin a group.
GLint ufm_pixelSizeMultiplier
Definition opengl_gal.h:413
VECTOR2D getScreenPixelSize() const
void drawGridSources()
Render m_gridSources priority-descending; each writes its coverage into the stencil so lower-priority...
static int m_contextGroupId
Changes each time a reset kills the shared group.
Definition opengl_gal.h:360
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:388
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:399
void DrawArc(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle) override
Draw an arc.
bool m_isContextValid
Did the last lock make us current?
Definition opengl_gal.h:409
void DrawPolylines(const std::vector< std::vector< VECTOR2D > > &aPointLists) override
Draw multiple polylines.
RENDER_TARGET m_currentTarget
Current rendering target.
Definition opengl_gal.h:395
wxGLContext * m_glPrivContext
Canvas-specific OpenGL context.
Definition opengl_gal.h:357
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:403
static bool m_resetBudgetExhausted
Resets came too often to keep using OpenGL.
Definition opengl_gal.h:361
static wxGLContext * m_glMainContext
Parent OpenGL context.
Definition opengl_gal.h:356
void setupShaderParameters()
Set up the shader parameters for OpenGL rendering.
unsigned int m_tempBuffer
Temporary rendering target (for diffing etc.)
Definition opengl_gal.h:394
void init()
Basic OpenGL initialization and feature checks.
static int m_instanceCounter
GL GAL instance counter.
Definition opengl_gal.h:359
Provide the access to the OpenGL shaders.
Definition shader.h:73
Class to control vertex container and GPU with possibility of emulating old-style OpenGL 1....
bool Vertex(const VERTEX &aVertex)
Add a vertex with the given coordinates to the currently set item.
Definition kiid.h:46
VECTOR2< T > GetScale() const
Get the scale components of the matrix.
Definition matrix3x3.h:291
T m_data[3][3]
Definition matrix3x3.h:61
GL_CONTEXT_MANAGER * GetGLContextManager()
Definition pgm_base.h:113
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
void Start()
Start or restart the counter.
Definition profile.h:74
std::string to_string()
Definition profile.h:154
double msecs(bool aSinceLast=false)
Definition profile.h:148
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
void GetTriangle(int index, VECTOR2I &a, VECTOR2I &b, VECTOR2I &c) const
Represent a set of closed polygons.
bool IsTriangulationUpToDate() const
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
uni_iter is a non-mutating iterator that walks through unicode code points in the UTF8 encoded string...
Definition utf8.h:226
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
uni_iter uend() const
Return a uni_iter initialized to the end of "this" UTF8 byte sequence.
Definition utf8.h:309
uni_iter ubegin() const
Returns a uni_iter initialized to the start of "this" UTF8 byte sequence.
Definition utf8.h:301
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
@ BLUE
Definition color4d.h:52
KICURSOR
Definition cursors.h:40
@ RADIANS_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:446
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
const wxChar *const traceGalContext
Flag to enable debug output of GAL context binding.
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
KIID niluuid(0)
This file contains miscellaneous commonly used macros and functions.
MATRIX3x3< double > MATRIX3x3D
Definition matrix3x3.h:469
#define H(x, y, z)
Definition md5_hash.cpp:17
const FONT_GLYPH_TYPE * LookupGlyph(unsigned int aCodepoint)
FONT_INFO_TYPE font_information
GAL_API FONT_IMAGE_TYPE font_image
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:30
constexpr double GRID_DIM_ALPHA
Opacity of the background wash laid over a selected grid item's area, to fade the grids showing throu...
GAL_CONTEXT_LOSS
State of a canvas' rendering context after a GPU reset.
@ REPEATED
Contexts keep being lost; stop using this backend.
@ RECOVERABLE
Context was lost; rebuild the canvas.
@ SMALL_CROSS
Use small cross instead of dots for the grid.
@ DOTS
Use dots for the grid.
@ LINES
Use lines 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
constexpr double GRID_SELECTED_BRIGHTEN
How far the grid being edited is lifted above its own colour.
@ SHADER_TYPE_VERTEX
Vertex shader.
Definition shader.h:42
@ SHADER_TYPE_FRAGMENT
Fragment shader.
Definition shader.h:43
double AutoSparsePitch(double aPitch, unsigned aTick, double aThreshold)
Multiply aPitch by aTick until it exceeds aThreshold, so a sub-threshold grid still shows every Nth l...
RENDER_TARGET
RENDER_TARGET: Possible rendering targets.
Definition definitions.h:32
@ TARGET_NONCACHED
Auxiliary rendering target (noncached)
Definition definitions.h:34
@ TARGET_TEMP
Temporary target for drawing in separate layer.
Definition definitions.h:36
@ TARGET_CACHED
Main rendering target (cached)
Definition definitions.h:33
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
constexpr double GRID_EDGE_DARKEN
How far the hairline round a grid's coverage sits below its own colour.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
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)
static GLenum queryResetStatus()
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
The default number of points for circle approximation.
Definition opengl_gal.h:51
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
const int scale
std::vector< FAB_LAYER_COLOR > dummy
unsigned priority
Higher wins where grids overlap.
double orientation
Rotation about origin, radians CCW.
VECTOR2D extent
Cartesian: (dx, dy) = size/2 from origin.
VECTOR2D pitch
Cartesian: (dx, dy); polar: (dr, dPhi rad).
VECTOR2D origin
Render-time projection of a grid: GRID_GEOMETRY (kind/origin/pitch/orientation/extent) plus rendering...
unsigned tick
Major-tick interval (0 = inherit default).
bool axesEnabled
Skip grid lines coincident with the world axes (only meaningful for unbounded cartesian).
bool highlighted
Render with edit-mode emphasis (selected grid).
bool unbounded
No extent; visible range derived from screen corners.
Parameters passed to the GLU tesselator.
Definition opengl_gal.h:344
VERTEX_MANAGER * vboManager
Manager used for storing new vertices.
Definition opengl_gal.h:346
std::deque< std::shared_ptr< GLdouble > > & intersectPoints
Intersect points, that have to be freed after tessellation.
Definition opengl_gal.h:349
Structure to keep VIEW_CONTROLS settings for easy store/restore operations.
VECTOR3I v1(5, 5, 5)
VECTOR2I end
VECTOR2I v2(1, 0)
VECTOR2I v3(-2, 1)
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_H_ALIGN_INDETERMINATE
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_INDETERMINATE
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
wxLogTrace helper definitions.
void enableGlDebug(bool aEnable)
Enable or disable OpenGL driver messages output.
Definition utils.cpp:187
int checkGlError(const std::string &aInfo, const char *aFile, int aLine, bool aThrow)
Check if a recent OpenGL operation has failed.
Definition utils.cpp:44
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
VECTOR2I ToVECTOR2I(const wxSize &aSize)
Definition vector2wx.h:26
#define CALLBACK
unsigned int GLenum
Definition vrml_layer.h:43