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