KiCad PCB EDA Suite
Loading...
Searching...
No Matches
draw_panel_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) 2013-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Maciej Suminski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23#include <wx/dcclient.h>
24
25#include <chrono>
26
27#include <eda_draw_frame.h>
28#include <kiface_base.h>
29#include <macros.h>
30#include <scoped_set_reset.h>
32#include <trace_helpers.h>
33
35#include <view/view.h>
37#include <gal/painter.h>
38#include <base_screen.h>
39#include <gal/cursors.h>
43#include <gal/cairo/cairo_gal.h>
44#include <gal/gal_modern.h>
45#include <rhi/render_thread.h>
48#include <rhi/frame_mailbox.h>
49#include <rhi/frame_packet.h>
50#include <rhi/rhi_surface.h>
51#ifdef __WXMAC__
52#include <rhi/rhi_metal_view.h>
53#endif
54#include <advanced_config.h>
55#include <frame_type.h>
56#include <math/vector2wx.h>
57
58
60#include <tool/tool_manager.h>
61
62#include <widgets/wx_infobar.h>
63
64#include <kiplatform/touchpad.h>
65#include <kiplatform/ui.h>
66
67#include <atomic>
68#include <stdexcept>
69
70#include <core/profile.h>
71
72#include <wx/display.h>
73#include <wx/utils.h>
74
75#include <pgm_base.h>
76#include <confirm.h>
77
78
84[[maybe_unused]] static const wxChar traceDrawPanel[] = wxT( "KICAD_DRAW_PANEL" );
85
86// Counts inputs to any canvas so the render thread never holds back a frame that answers one
87static std::atomic<uint64_t> s_inputSerial{ 0 };
88
89
91{
92 if( !aFrame )
94
95 switch( aFrame->GetFrameType() )
96 {
97 case FRAME_SCH:
100 case FRAME_SCH_DIFF:
102
103 default:
105 }
106}
107
108
110{
111 static_assert( static_cast<int>( RHI_DISPLAY_TYPE::COCOA ) == KIPLATFORM::UI::DISPLAY_COCOA );
112
114 int displayType = KIPLATFORM::UI::DISPLAY_UNKNOWN;
115
116 KIPLATFORM::UI::GetNativeWindowHandles( aWindow, &handle.nativeHandle, &handle.nativeDisplay, &displayType );
117 handle.displayType = static_cast<RHI_DISPLAY_TYPE>( displayType );
118 return handle;
119}
120
121
124{
125 // Fast relies on the shaders' analytic edge coverage, High Quality supersamples the layer FBOs
127}
128
129
131{
133 {
135
136#ifdef __WXMAC__
138 && m_cachedNativeHandle.nativeHandle )
139 {
141 m_cachedNativeHandle.nativeHandle = host.view;
142 m_cachedNativeHandle.nativeDisplay = host.layer;
143 }
144#endif
145
146 // A paint before the window is realized gets an invalid handle, so retry next frame
148 }
149
151}
152
153
155{
156#ifdef __WXMAC__
159#endif
160
162 m_nativeHandleValid = false;
163}
164
165
167{
168 if( !m_swapchainCtrl )
169 return;
170
171 const wxSize cs = backendScreenSize();
172 float dpiScale = static_cast<float>( GetContentScaleFactor() );
174
175 // An unrealized window's handle would fail surface creation on the render thread
176 if( nativeHandle.IsValid() )
177 {
178 // GTK drawing suppression needs the realized widget at its final layout size
180 {
181 wxLogTrace( traceRhi, wxS( "DrawPanel: deferred PrepareForExternalRendering "
182 "windowId=%u" ), m_windowId );
185 }
186
187 // Caps a window that presents without vsync at one frame per display refresh
188 m_swapchainCtrl->SetRefreshRate( displayRefreshRate() );
189 m_swapchainCtrl->OnPaint( nativeHandle,
190 static_cast<uint32_t>( cs.x ),
191 static_cast<uint32_t>( cs.y ),
192 dpiScale );
193 }
194 else if( IsShown() && !m_refreshTimer.IsRunning() )
195 {
196 // A timer rather than an idle refresh, so a surface that never maps cannot spin
197 m_refreshTimer.StartOnce( 100 );
198 }
199}
200
201
202EDA_DRAW_PANEL_GAL::EDA_DRAW_PANEL_GAL( wxWindow* aParentWindow, wxWindowID aWindowId,
203 const wxPoint& aPosition, const wxSize& aSize,
204 KIGFX::GAL_DISPLAY_OPTIONS& aOptions, GAL_TYPE aGalType ) :
205 wxScrolledCanvas( aParentWindow, aWindowId, aPosition, aSize ),
206 m_MouseCapturedLost( false ),
207 m_parent( aParentWindow ),
208 m_edaFrame( nullptr ),
209 m_drawing( false ),
210 m_drawingEnabled( false ),
211 m_needIdleRefresh( false ),
212 m_gal( nullptr ),
213 m_view( nullptr ),
214 m_painter( nullptr ),
215 m_viewControls( nullptr ),
217 m_options( aOptions ),
218 m_eventDispatcher( nullptr ),
219 m_lostFocus( false ),
220 m_recoveryAttempted( false ),
222 m_rebuiltAfterReset( false ),
223 m_pendingResize( false ),
224 m_stealsFocus( true ),
225 m_statusPopup( nullptr )
226{
227#ifdef _WIN32
228 // need to fix broken cairo rendering on Windows with wx 3.3
229 SetDoubleBuffered( false );
230#endif
231 m_PaintEventCounter = std::make_unique<PROF_COUNTER>( "Draw panel paint events" );
232
233 if( Pgm().GetCommonSettings()->m_Appearance.show_scrollbars )
234 ShowScrollbars( wxSHOW_SB_ALWAYS, wxSHOW_SB_ALWAYS );
235 else
236 ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER );
237
238 SetLayoutDirection( wxLayout_LeftToRight );
239
240 m_edaFrame = dynamic_cast<EDA_DRAW_FRAME*>( m_parent );
241
242 // If we're in a dialog, we have to go looking for our parent frame
243 if( !m_edaFrame )
244 {
245 wxWindow* ancestor = aParentWindow->GetParent();
246
247 while( ancestor && !dynamic_cast<EDA_DRAW_FRAME*>( ancestor ) )
248 ancestor = ancestor->GetParent();
249
250 if( ancestor )
251 m_edaFrame = dynamic_cast<EDA_DRAW_FRAME*>( ancestor );
252 }
253
255
256 // Callers ask for the modern canvas before the saved choice is known. Starting it only to
257 // switch away costs a GPU device, so other choices start on the fallback and the frame applies them.
258 if( aGalType == GAL_TYPE_MODERN )
259 {
261
263 aGalType = GAL_FALLBACK;
264 }
265
266 SwitchBackend( aGalType );
267 SetBackgroundStyle( wxBG_STYLE_CUSTOM );
268
269 EnableScrolling( false, false ); // otherwise Zoom Auto disables GAL canvas
270 KIPLATFORM::UI::SetOverlayScrolling( this, false ); // Prevent excessive repaint on GTK
271 KIPLATFORM::UI::ImmControl( this, false ); // Ensure our panel can't suck in IME events
272
273 Connect( wxEVT_SIZE, wxSizeEventHandler( EDA_DRAW_PANEL_GAL::onSize ), nullptr, this );
274 Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( EDA_DRAW_PANEL_GAL::onEnter ), nullptr, this );
275 Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( EDA_DRAW_PANEL_GAL::onLostFocus ), nullptr, this );
276
277 const wxEventType events[] = {
278 // Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events,
279 // especially special key like arrow keys, are handled by the GAL event dispatcher,
280 // and not sent to GUI without filtering, because they have a default action (scroll)
281 // that must not be called.
282 wxEVT_LEFT_UP,
283 wxEVT_LEFT_DOWN,
284 wxEVT_LEFT_DCLICK,
285 wxEVT_RIGHT_UP,
286 wxEVT_RIGHT_DOWN,
287 wxEVT_RIGHT_DCLICK,
288 wxEVT_MIDDLE_UP,
289 wxEVT_MIDDLE_DOWN,
290 wxEVT_MIDDLE_DCLICK,
291 wxEVT_AUX1_UP,
292 wxEVT_AUX1_DOWN,
293 wxEVT_AUX1_DCLICK,
294 wxEVT_AUX2_UP,
295 wxEVT_AUX2_DOWN,
296 wxEVT_AUX2_DCLICK,
297 wxEVT_MOTION,
298 wxEVT_MOUSEWHEEL,
299 wxEVT_CHAR,
300 wxEVT_CHAR_HOOK,
301 wxEVT_MAGNIFY,
303 };
304
305 for( wxEventType eventType : events )
306 Connect( eventType, wxEventHandler( EDA_DRAW_PANEL_GAL::OnEvent ), nullptr, m_eventDispatcher );
307
308 // Set up timer to detect when drawing starts
309 m_refreshTimer.SetOwner( this );
310 Connect( m_refreshTimer.GetId(), wxEVT_TIMER, wxTimerEventHandler( EDA_DRAW_PANEL_GAL::onRefreshTimer ),
311 nullptr, this );
312
313 Connect( wxEVT_SHOW, wxShowEventHandler( EDA_DRAW_PANEL_GAL::onShowEvent ), nullptr, this );
314}
315
316
318{
319 wxLogTrace( traceRhi, wxS( "DrawPanel: destructor windowId=%u backend=%d" ),
320 m_windowId, static_cast<int>( m_backend ) );
321
323
324 // Ensure EDA_DRAW_PANEL_GAL::onShowEvent is not fired during Dtor process
325 Disconnect( wxEVT_SHOW, wxShowEventHandler( EDA_DRAW_PANEL_GAL::onShowEvent ) );
326 StopDrawing();
327
328 wxASSERT( !m_drawing );
329
330 // Deleting the view can retire GAL groups, which needs the device proxy
331 delete m_viewControls;
332 delete m_view;
333
335
336 delete m_gal;
337 m_gal = nullptr;
338}
339
340
342{
344 wxScrolledCanvas::SetFocus();
345 m_lostFocus = false;
346}
347
348
349void EDA_DRAW_PANEL_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
350{
351 // Consuming the paint region stops GTK3 clearing the modern canvas between presents. The other
352 // backends repost their child's paint event here with no native paint context, where a wxPaintDC asserts.
353 std::optional<wxPaintDC> dc;
354
356 dc.emplace( this );
357
358 DoRePaint( false );
359}
360
361
363{
364 if( IsAccelerated( aFailedCanvasType ) && aFailedCanvasType != GAL_FALLBACK )
365 return GAL_FALLBACK;
366
367 return GAL_TYPE_NONE;
368}
369
370
371bool EDA_DRAW_PANEL_GAL::abandonRhiCanvas( const wxString& aDetail )
372{
374 {
376
377#ifdef __WXMAC__
378 showMessageLater( _( "Could not use Metal, switching to OpenGL" ), aDetail, false );
379#else
380 showMessageLater( _( "Could not use Vulkan, falling back to software rendering" ), aDetail, false );
381#endif
382 }
383
384 wxLogTrace( traceRhi, wxS( "DrawPanel: leaving the RHI canvas for backend %d" ), static_cast<int>( GAL_FALLBACK ) );
385
386 return SwitchBackend( GAL_FALLBACK );
387}
388
389
391{
392 if( m_swapchainCtrl )
393 {
394 m_swapchainCtrl->OnCloseSync();
395 m_swapchainCtrl.reset();
396 }
397
398 if( auto* galModern = dynamic_cast<KIGFX::GAL_MODERN*>( m_gal ) )
399 galModern->DestroyBuffers();
400
401 // The proxy references the render thread. DestroyBuffers() left no proxy call outstanding.
402 m_deviceProxy.reset();
403
404 if( m_renderThread )
405 {
406 m_renderThread->RemoveDeviceLostListener( this );
407
408 // Other canvases keep the thread running, so the uploads still queued from this one are applied
409 // before its GAL and geometry go. A thread that stopped will not read them.
410 while( !m_renderThread->WaitForFence() && m_renderThread->IsRunning() )
411 {
412 }
413
414 m_renderThread.reset();
415 }
416
417 m_frameMailbox.reset();
418 m_hasLastFullDrawDesc = false;
419
421}
422
423
424bool EDA_DRAW_PANEL_GAL::recoverFromGalError( const std::exception& aError )
425{
426 // Reinitializing would hit the same VRAM ceiling, so out-of-memory goes straight to the fallback
427 const bool gpuOutOfMemory = dynamic_cast<const KIGFX::GPU_OOM_ERROR*>( &aError ) != nullptr;
428
429 // The reinit below rewrites m_backend
430 const GAL_TYPE failedBackend = m_backend;
431
432 try
433 {
434 // Reset detection only runs inside LockContext, and this frame's lock came before the reset
435 if( m_gal && !m_gal->IsContextLocked() )
437
438 // A reset seen mid-frame surfaces as a GL error; rebuild rather than spend the one-shot recovery
439 if( handleContextLoss() )
440 return true;
441
442 // Sleep/wake and GPU resets can invalidate the entire GL/device context.
443 // Try a full reinit of the current backend before falling back.
444 if( !gpuOutOfMemory && !m_recoveryAttempted )
445 {
446 m_recoveryAttempted = true;
448
449 if( SwitchBackend( failedBackend ) )
450 {
451 StartDrawing();
452 return true;
453 }
454 }
455
456 if( failedBackend == GAL_TYPE_MODERN )
457 {
458 m_recoveryAttempted = false;
459 abandonRhiCanvas( wxString( aError.what() ) );
460 StartDrawing();
461 return true;
462 }
463
464 if( CanvasAfterFailure( failedBackend ) != GAL_TYPE_NONE )
465 {
466 m_recoveryAttempted = false;
468
470 {
471 // Some drivers refuse new framebuffers to a process for the rest of its life after a reset
472 m_rebuiltAfterReset = false;
474 showMessageLater( _( "The graphics driver was reset and OpenGL could not be restored" ),
475 _( "Restart KiCad to use accelerated graphics again." ), false );
476 }
477 else
478 {
479 showMessageLater( _( "Could not use OpenGL, falling back to software rendering" ),
480 wxString( aError.what() ), false );
481 }
482
483 StartDrawing();
484 return true;
485 }
486
487 showMessageLater( _( "Graphics error" ), wxString( aError.what() ), true );
488 }
489 catch( std::exception& recoveryErr )
490 {
491 showMessageLater( _( "Graphics error during recovery" ), wxString( recoveryErr.what() ), true );
492 }
493 catch( ... )
494 {
495 showMessageLater( _( "Graphics error during recovery" ), _( "Unknown exception during backend switch" ),
496 true );
497 }
498
499 return false;
500}
501
502
504{
506
508 return false;
509
510 // Deleting a GAL that someone still holds locked would leave them unlocking freed memory
511 if( m_gal->IsContextLocked() )
512 return true;
513
514 // Hidden canvases rebuild when next shown, since a new GAL cannot initialize off screen
515 if( !IsShownOnScreen() )
516 return true;
517
518 // Rebuilding while the driver is still resetting can fail or hang, so poll until it finishes
519 if( !m_gal->IsResetSettled() )
520 {
521 m_refreshTimer.StartOnce( 20 );
522 return true;
523 }
524
525 wxLogTrace( traceGalContext, wxS( "Rebuilding canvas %p after GPU reset, repeated %d" ), this,
526 loss == KIGFX::GAL_CONTEXT_LOSS::REPEATED ? 1 : 0 );
527
528 bool rebuilt = false;
529
531 {
532 GAL_TYPE backend = m_backend;
533
534 // Forces SwitchBackend to build a new GAL of the same type
536 rebuilt = SwitchBackend( backend );
537 m_rebuiltAfterReset = rebuilt;
538 }
539
540 // SwitchBackend already reported a failed rebuild, so only a repeated loss needs the notice
541 if( !rebuilt && GAL_FALLBACK_AVAILABLE )
542 {
543 static bool s_notified = false;
544
545 wxLogTrace( traceGalContext, wxS( "Canvas %p falling back to software rendering" ), this );
548
549 if( loss == KIGFX::GAL_CONTEXT_LOSS::REPEATED && !s_notified )
550 {
551 s_notified = true;
552 showMessageLater( _( "The graphics driver keeps resetting, falling back to software rendering" ),
553 wxEmptyString, false );
554 }
555 }
556
557 StartDrawing();
558 return true;
559}
560
561
562void EDA_DRAW_PANEL_GAL::showMessageLater( const wxString& aTitle, const wxString& aDetail, bool aError )
563{
564 wxWindow* parent = m_parent;
565
566 CallAfter(
567 [parent, aTitle, aDetail, aError]()
568 {
569 if( aError )
570 DisplayErrorMessage( parent, aTitle, aDetail );
571 else
572 DisplayInfoMessage( parent, aTitle, aDetail );
573 } );
574}
575
576
578static constexpr int MAX_CONTEXT_BIND_RETRIES = 2;
579
580
582{
583 auto* galModern = static_cast<KIGFX::GAL_MODERN*>( m_gal );
584 auto packet = std::make_unique<KIGFX::FRAME_PACKET>();
585
586 packet->drawDesc = m_lastFullStaticDesc;
587 packet->drawDesc.reuseStaticFBO = true;
588
589 // The cached desc holds the camera of the last full render, which would snap a pan or zoom back
590 galModern->PopulateCameraUBOs( packet->drawDesc );
591 galModern->PopulateCursorUBO( packet->drawDesc );
592
593 // Unchanged epochs tell the render thread to keep both geometry FBOs
594 packet->staticDrawDesc = m_lastFullStaticDesc;
595 packet->dynamicDrawDesc = m_lastFullDynamicDesc;
596 packet->staticChanged = true;
597 packet->dynamicChanged = true;
598 packet->staticEpoch = m_staticEpoch;
599 packet->dynamicEpoch = m_dynamicEpoch;
600
601 packet->inputSerial = s_inputSerial.load( std::memory_order_relaxed );
602 m_frameMailbox->Submit( std::move( packet ) );
603}
604
605
606bool EDA_DRAW_PANEL_GAL::DoRePaint( bool aAllowSkip )
607{
608 if( !m_refreshMutex.try_lock() )
609 {
610 wxLogTrace( traceRhi, wxS( "DrawPanel: DoRePaint SKIP (mutex locked)" ) );
611 return false;
612 }
613
614 std::lock_guard<std::mutex> lock( m_refreshMutex, std::adopt_lock );
615
616 if( !m_drawingEnabled )
617 return false;
618
619 // No surface may be created on an unmapped window. The other backends check visibility below.
620 if( m_backend == GAL_TYPE_MODERN && !IsShown() )
621 {
622 wxLogTrace( traceRhi, wxS( "DrawPanel: DoRePaint SKIP (hidden) windowId=%u" ), m_windowId );
623 return false;
624 }
625
626 if( handleContextLoss() )
627 return false;
628
629 if( m_backend == GAL_TYPE_MODERN && m_renderThread && m_renderThread->IsDeviceLost() )
630 {
631 // A device lost before it ever presented this canvas gives way to the fallback rather than retrying forever
632 if( m_renderThread->HasPresented( m_windowId ) )
633 {
635
637 {
638 StartDrawing();
639 return false;
640 }
641 }
642
643 abandonRhiCanvas( _( "The graphics device was lost and could not be restored." ) );
644 StartDrawing();
645 return false;
646 }
647
648 if( !m_gal->IsInitialized() || !m_gal->IsVisible() || m_gal->IsContextLocked() )
649 {
650 wxLogTrace( traceRhi, wxS( "DrawPanel: DoRePaint SKIP (init=%d vis=%d locked=%d)" ),
651 m_gal->IsInitialized(), m_gal->IsVisible(), m_gal->IsContextLocked() );
652 return false;
653 }
654
655 if( m_drawing )
656 {
657 wxLogTrace( traceRhi, wxS( "DrawPanel: DoRePaint SKIP (already drawing)" ) );
658 return false;
659 }
660
661 // The context may have become current since the size change was deferred
662 if( m_pendingResize )
663 ResizeGal();
664
665 m_lastRepaintStart = std::chrono::steady_clock::now();
666
667 // Update current zoom settings if the canvas is managed by a EDA frame
668 // (i.e. not by a preview panel in a dialog)
669 if( !IsDialogPreview() && GetParentEDAFrame() && GetParentEDAFrame()->GetScreen() )
671
672 if( Pgm().GetCommonSettings()->m_Appearance.show_scrollbars )
673 m_viewControls->UpdateScrollbars();
674
675#ifdef KICAD_GAL_PROFILE
676 latencyProbeZoomToRender.Checkpoint("do-repaint-start");
677#endif
678
679 SCOPED_SET_RESET<bool> drawing( m_drawing, true );
680
681 ( *m_PaintEventCounter )++;
682
683 wxASSERT( m_painter );
684
685 KIGFX::RENDER_SETTINGS* settings =
686 static_cast<KIGFX::RENDER_SETTINGS*>( m_painter->GetSettings() );
687
688 PROF_TIMER cntUpd("view-upd-items", false);
689 PROF_TIMER cntTotal("view-total", false);
690 PROF_TIMER cntCtx("view-context-create", false);
691 PROF_TIMER cntCtxDestroy("view-context-destroy", false);
692 PROF_TIMER cntRedraw("view-redraw-rects", false);
693
694 bool isDirty = false;
695
697 {
698 static_cast<KIGFX::GAL_MODERN*>( m_gal )->ReleaseRetiredBuffers(
699 m_frameMailbox->ConsumedBufferGeneration() );
700 }
701
702 cntTotal.Start();
703
704 try
705 {
706 beforeRedraw();
707
708 VECTOR2D cursorPos = m_viewControls->GetCursorPosition();
709 bool viewDirty = m_view->IsDirty();
710 bool cursorMoved = ( cursorPos != m_lastCursorPosition );
711 bool hasPendingItemUpdates = m_view->HasPendingItemUpdates();
712
713 // Skip all update work when nothing has changed since the previous frame.
714 // Never skip when responding to a native paint event or explicit ForceRefresh
715 // because the window content may have been invalidated by the OS.
716 if( aAllowSkip && !viewDirty && !cursorMoved && !hasPendingItemUpdates )
717 {
718 m_lastRepaintEnd = std::chrono::steady_clock::now();
719 return true;
720 }
721
722 if( hasPendingItemUpdates )
723 {
724 cntUpd.Start();
725
726 try
727 {
728 m_view->UpdateItems();
729 }
730 catch( std::out_of_range& err )
731 {
732 // Don't do anything here but don't fail
733 // This can happen when we don't catch `at()` calls
734 wxLogTrace( traceDrawPanel, wxS( "Out of Range error: %s" ), err.what() );
735 }
736 catch( std::runtime_error& err )
737 {
738 // Handle GL errors (e.g. glMapBuffer failure) that surface during UpdateItems().
739 // These can occur on macOS under memory pressure when embedding large 3D models.
740 // Log and continue so the outer handler can decide whether to switch backends.
741 wxLogTrace( traceDrawPanel, wxS( "Runtime error during UpdateItems: %s" ),
742 err.what() );
743 throw;
744 }
745
746 cntUpd.Stop();
747 viewDirty = m_view->IsDirty();
748 }
749
750 // After processing item updates, skip the GL cycle when neither the
751 // view targets nor the cursor position have changed.
752 if( aAllowSkip && !viewDirty && !cursorMoved )
753 {
754 m_lastRepaintEnd = std::chrono::steady_clock::now();
755 return true;
756 }
757
758 m_lastCursorPosition = cursorPos;
759
760 // A cursor move alone reuses the cached scene and skips the view traversal and upload
761 const bool useOverlayFastPath = m_backend == GAL_TYPE_MODERN
763 && !viewDirty
764 && cursorMoved;
765
766 // A zoom can carry groups across their level of detail
768 m_view->SyncRetainedScene();
769
770 // Grid items are thinned and sized for the zoom they were made at, so they need a full redraw
771 const bool useCameraFastPath = m_backend == GAL_TYPE_MODERN
773 && viewDirty
774 && m_view->IsCameraDirtyOnly()
775 && !m_gal->HasStaticRebuildPending()
776 && m_gal->GetGridSources().empty();
777
778 if( useOverlayFastPath && m_frameMailbox && m_swapchainCtrl )
779 {
780 m_gal->SetCursorColor( settings->GetCursorColor() );
781 m_gal->DrawCursor( cursorPos );
783 presentFrame();
784
785 m_lastRepaintEnd = std::chrono::steady_clock::now();
786
787 m_recoveryAttempted = false;
788 return true;
789 }
790
791 if( useCameraFastPath && m_frameMailbox && m_swapchainCtrl )
792 {
793 // GetDrawDesc() is unusable here because reused-scene frames overwrite the ring-buffered state
794 auto* galModern = static_cast<KIGFX::GAL_MODERN*>( m_gal );
795
796 m_gal->SetClearColor( settings->GetBackgroundColor() );
797 m_gal->SetGridColor( settings->GetGridColor() );
798 m_gal->SetCursorColor( settings->GetCursorColor() );
799 m_gal->DrawCursor( cursorPos );
800
801 if( galModern->SyncScene() )
802 {
803 KIGFX::FRAME_DRAW_DESC patched = galModern->GetStaticDrawDesc();
804 patched.linePipelineKind = m_lastFullStaticDesc.linePipelineKind;
805 patched.supersampleFactor = m_lastFullStaticDesc.supersampleFactor;
806 m_lastFullStaticDesc = patched;
808 }
809
810 auto packet = std::make_unique<KIGFX::FRAME_PACKET>();
811 packet->drawDesc = m_lastFullStaticDesc;
812 packet->drawDesc.reuseStaticFBO = false;
813 packet->drawDesc.linePipelineKind = m_linePipelineKind;
814 ApplyAntialiasingMode( packet->drawDesc, m_options.antialiasing_mode );
815
816 galModern->PopulateCameraUBOs( packet->drawDesc );
817 galModern->PopulateCursorUBO( packet->drawDesc );
818
819 packet->staticDrawDesc = m_lastFullStaticDesc;
820 packet->dynamicDrawDesc = m_lastFullDynamicDesc;
821
822 // The render thread decides reuse from each target desc's own flag and camera, not
823 // drawDesc's, so stale ones would revert the canvas to the old zoom
824 packet->staticDrawDesc.reuseStaticFBO = false;
825 packet->dynamicDrawDesc.reuseDynamicFBO = false;
826 galModern->PopulateCameraUBOs( packet->staticDrawDesc );
827 galModern->PopulateCameraUBOs( packet->dynamicDrawDesc );
828
829 packet->staticChanged = true;
830 packet->dynamicChanged = true;
831 packet->staticEpoch = m_staticEpoch;
832 packet->dynamicEpoch = m_dynamicEpoch;
833
834 // A following cursor-only frame reuses these descs, which must not keep the old view
835 m_lastFullStaticDesc.frameUBO = packet->staticDrawDesc.frameUBO;
836 m_lastFullStaticDesc.gridUBO = packet->staticDrawDesc.gridUBO;
837 m_lastFullStaticDesc.cursorUBO = packet->staticDrawDesc.cursorUBO;
838 m_lastFullDynamicDesc.frameUBO = packet->dynamicDrawDesc.frameUBO;
839 m_lastFullDynamicDesc.gridUBO = packet->dynamicDrawDesc.gridUBO;
840 m_lastFullDynamicDesc.cursorUBO = packet->dynamicDrawDesc.cursorUBO;
841
842 m_view->MarkClean();
843
844 packet->inputSerial = s_inputSerial.load( std::memory_order_relaxed );
845 m_frameMailbox->Submit( std::move( packet ) );
846
847 presentFrame();
848
849 m_lastRepaintEnd = std::chrono::steady_clock::now();
850 m_recoveryAttempted = false;
851 return true;
852 }
853
854 // GAL_DRAWING_CONTEXT can throw in the dtor, so we need to scope
855 // the full lifetime inside the try block
856 {
857 cntCtx.Start();
859 cntCtx.Stop();
860
861 if( !ctx.IsDrawing() )
862 {
863 // A canvas being torn down never reaches here; DoRePaint returns above once
864 // the window stops being visible. So repeated failures mean a live canvas
865 // whose context is gone for good, and retrying forever would leave it blank
867 throw std::runtime_error( "Could not make the OpenGL context current" );
868
870 return false;
871 }
872
873 if( m_view->IsTargetDirty( KIGFX::TARGET_OVERLAY )
874 && !m_gal->HasTarget( KIGFX::TARGET_OVERLAY ) )
875 {
876 m_view->MarkDirty();
877 }
878
879 m_gal->SetClearColor( settings->GetBackgroundColor() );
880 m_gal->SetGridColor( settings->GetGridColor() );
881 m_gal->SetCursorColor( settings->GetCursorColor() );
882
883 // The OpenGL back buffer is undefined after SwapBuffers, even on a cursor-only repaint
885 m_gal->ClearScreen();
886
887 if( m_view->IsDirty() )
888 {
889 // Cairo composites all targets into one surface, so a dirty NONCACHED needs a full clear
890 if( !IsAccelerated( m_backend ) && m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED ) )
891 {
892 m_gal->ClearScreen();
893 }
894
895 // A rebuild the backend asked for itself also redraws the whole non-cached target
896 if( m_gal->HasStaticRebuildPending() )
897 {
898 m_view->MarkTargetDirty( KIGFX::TARGET_NONCACHED );
899 m_view->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
900 }
901
902 // A retained scene keeps NONCACHED through a pan or zoom, but the grid is clipped to the old view
903 const bool viewMoved = m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED );
904
905 m_view->ClearTargets();
906
907 // Grid has to be redrawn only when the NONCACHED target is redrawn
908 if( viewMoved || m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED )
909 || m_gal->RefillsNonCachedTargets() )
910 {
912 m_gal->DrawGrid();
913 }
914
915 cntRedraw.Start();
916 m_view->Redraw();
917 cntRedraw.Stop();
918 isDirty = true;
919 }
920
921 m_gal->DrawCursor( cursorPos );
922
923 #ifdef KICAD_GAL_PROFILE
924 latencyProbeZoomToRender.Checkpoint("do-repaint-pre-ctx-destroy");
925 #endif
926
927
928 cntCtxDestroy.Start();
929 }
930
931 // ctx goes out of scope here so destructor would be called
932 cntCtxDestroy.Stop();
933
934#ifdef KICAD_GAL_PROFILE
935 latencyProbeZoomToRender.Checkpoint("do-repaint-ctx-done");
936#endif
937
938 // A clean view leaves GetDrawDesc() empty, which would blank the static FBO
940 {
941 auto* galModern = static_cast<KIGFX::GAL_MODERN*>( m_gal );
942
943 if( !isDirty && m_hasLastFullDrawDesc && m_backend == GAL_TYPE_MODERN )
944 {
946 }
947 else if( isDirty )
948 {
949 auto packet = std::make_unique<KIGFX::FRAME_PACKET>();
950
951 packet->drawDesc = galModern->GetDrawDesc();
952 packet->drawDesc.linePipelineKind = m_linePipelineKind;
953 ApplyAntialiasingMode( packet->drawDesc, m_options.antialiasing_mode );
954
955 // Lets the first frame show the cursor without waiting for a move
956 galModern->PopulateCursorUBO( packet->drawDesc );
957
958 packet->staticDrawDesc = galModern->GetStaticDrawDesc();
959 packet->dynamicDrawDesc = galModern->GetDynamicDrawDesc();
960 packet->staticDrawDesc.linePipelineKind = m_linePipelineKind;
961 packet->dynamicDrawDesc.linePipelineKind = m_linePipelineKind;
962 packet->staticDrawDesc.supersampleFactor = packet->drawDesc.supersampleFactor;
963 packet->dynamicDrawDesc.supersampleFactor = packet->drawDesc.supersampleFactor;
964
965 packet->staticChanged = galModern->TakeStaticChanged();
966 packet->dynamicChanged = true;
967
968 if( packet->staticChanged )
970
972 packet->staticEpoch = m_staticEpoch;
973 packet->dynamicEpoch = m_dynamicEpoch;
974
975 m_lastFullStaticDesc = packet->staticDrawDesc;
976 m_lastFullDynamicDesc = packet->dynamicDrawDesc;
978
979 packet->inputSerial = s_inputSerial.load( std::memory_order_relaxed );
980 m_frameMailbox->Submit( std::move( packet ) );
981 }
982
983 presentFrame();
984 }
985
986 m_recoveryAttempted = false;
988 m_rebuiltAfterReset = false;
989 }
990 catch( std::exception& err )
991 {
992 wxLogTrace( traceDrawPanel, wxS( "DoRePaint exception: %s" ), err.what() );
993
994 if( recoverFromGalError( err ) )
995 return true;
996
997 StopDrawing();
998 }
999 catch( ... )
1000 {
1001 showMessageLater( _( "Graphics error" ), _( "Unknown exception" ), true );
1002 StopDrawing();
1003 }
1004
1005 if( isDirty )
1006 {
1007#ifdef KICAD_GAL_PROFILE
1008 wxLogTrace( traceGalProfile, "View timing: %s %s %s %s %s",
1009 cntTotal.to_string(),
1010 cntUpd.to_string(),
1011 cntRedraw.to_string(),
1012 cntCtx.to_string(),
1013 cntCtxDestroy.to_string()
1014 );
1015#endif
1016 }
1017
1018 m_lastRepaintEnd = std::chrono::steady_clock::now();
1019
1020#ifdef KICAD_GAL_PROFILE
1021 wxLogTrace( traceGalProfile, "%s", latencyProbeZoomToRender.to_string() );
1022 latencyProbeRepaintToMotion.Reset();
1023 latencyProbeRepaintToMotion.Checkpoint("repaint-done");
1024#endif
1025
1026 return true;
1027}
1028
1029
1030void EDA_DRAW_PANEL_GAL::onSize( wxSizeEvent& aEvent )
1031{
1032 ResizeGal();
1033
1034 // The first fit often runs before the frame has its final layout, and window managers,
1035 // infobars and panels resize the canvas later. Until someone zooms or pans, fit again.
1036 wxSize clientSize = GetClientSize();
1037
1038 if( clientSize.x > 0 && clientSize.y > 0 && m_view
1039 && m_automaticFit.Holds( m_view->GetScale(), m_view->GetCenter() ) )
1040 {
1041 ZoomToFit( m_automaticFit.GetBox(), m_automaticFit.GetMarginScale() );
1042 }
1043}
1044
1045
1046void EDA_DRAW_PANEL_GAL::ZoomToFit( const BOX2I& aBox, std::optional<double> aMarginScale )
1047{
1048 KIGFX::VIEW* view = m_view;
1049 BOX2I bBox = aBox;
1050
1051 // The GAL's world units may have changed since the view was attached.
1052 m_gal->ComputeWorldScreenMatrix();
1053
1054 // Avoid a temporary zoom that dirties retained targets before the fitted scale is known.
1055 VECTOR2D screenSize = view->ToWorld( ToVECTOR2I( GetClientSize() ), false ) * view->GetScale();
1056
1057 // A box with no extent (an empty screen or a lone point) has nothing to fit
1058 if( bBox.GetWidth() == 0 && bBox.GetHeight() == 0 )
1059 bBox = GetDefaultViewBBox();
1060
1061 VECTOR2D vsize = bBox.GetSize();
1062 double scale = 1.0 / std::max( fabs( vsize.x / screenSize.x ),
1063 fabs( vsize.y / screenSize.y ) );
1064
1065 // if the scale isn't finite (most likely due to an empty canvas)
1066 // simply just make sure we are centered and quit out of trying to zoom to fit
1067 if( !std::isfinite( scale ) )
1068 {
1069 view->SetCenter( VECTOR2D( 0, 0 ) );
1070 m_automaticFit.Clear();
1071 Refresh();
1072 return;
1073 }
1074
1075 // Reserve enough margin to limit the amount of the view that might be obscured behind the
1076 // infobar.
1077 double margin_scale_factor = aMarginScale.value_or( GetClientSize().y < 768 ? 1.10 : 1.04 );
1078
1079 view->SetScale( scale / margin_scale_factor );
1080 view->SetCenter( bBox.Centre() );
1081 m_automaticFit.Record( aBox, aMarginScale, view->GetScale(), view->GetCenter() );
1082 Refresh();
1083}
1084
1085
1087{
1088 // A swapchain that does not match its surface rescales and blurs every frame
1089 if( aBackend == GAL_TYPE_MODERN )
1090 return 0;
1091
1092#ifdef __WXMAC__
1093 // The aligned GL child covers the client area without the offset pixel
1094 if( aBackend == GAL_TYPE_OPENGL )
1095 return 0;
1096#endif
1097
1098 // The ( +1, +1 ) prevents an ugly black line on right and bottom on Mac
1099 return 1;
1100}
1101
1102
1104{
1105 const int pad = ScreenSizePad( m_backend );
1106 wxSize size = GetClientSize();
1107
1108 // The backend window starts below what cannot be drawn over it
1109 size.y -= m_overlayInsetTop;
1110
1111 return wxSize( std::max( 10, size.x + pad ), std::max( 10, size.y + pad ) );
1112}
1113
1114
1116{
1117 // If we get a second wx update call before the first finishes, don't crash
1118 if( m_gal->IsContextLocked() )
1119 return;
1120
1122
1123 // Resizing reallocates the framebuffer, which only exists in this canvas' own context
1124 if( !m_gal->IsContextValid() )
1125 {
1126 // wx reports a given size once, so dropping it here would leave the canvas stuck at
1127 // whatever size the GAL was built with until something else resizes the window
1128 m_pendingResize = true;
1130 return;
1131 }
1132
1133 m_pendingResize = false;
1134
1135 const wxSize clientSize = backendScreenSize();
1136
1137 // A client area that grew by exactly the pad would otherwise match the old GAL size
1138 if( !aForce && ToVECTOR2I( clientSize ) == m_gal->GetScreenPixelSize() )
1139 return;
1140
1141 m_gal->ResizeScreen( clientSize.GetX(), clientSize.GetY() );
1142
1143 wxWindow* backendWindow = dynamic_cast<wxWindow*>( m_gal );
1144
1145 if( backendWindow && backendWindow->GetPosition().y != m_overlayInsetTop )
1146 backendWindow->Move( 0, m_overlayInsetTop );
1147
1148 if( m_backend == GAL_TYPE_MODERN )
1149 {
1150 static_cast<KIGFX::GAL_MODERN*>( m_gal )->SetDpiScale(
1151 static_cast<float>( GetContentScaleFactor() ) );
1152 }
1153
1154 if( m_swapchainCtrl )
1155 {
1156 float dpiScale = static_cast<float>( GetContentScaleFactor() );
1157 m_swapchainCtrl->OnResize( static_cast<uint32_t>( clientSize.x ),
1158 static_cast<uint32_t>( clientSize.y ),
1159 dpiScale );
1160 }
1161
1162 if( m_view )
1163 {
1164 // ResizeScreen reallocates every compositor buffer, so nothing survives the resize
1165 m_view->MarkDirty();
1166
1167 // New FBO sizes need a fresh upload, which the camera fast path would skip
1168 m_view->MarkGeometryDirty();
1169 }
1170
1171 if( m_backend == GAL_TYPE_MODERN )
1172 Refresh();
1173}
1174
1175
1177{
1178 std::vector<wxRect> rects;
1179 int infobarsBottom = 0;
1180
1181 for( wxWindow* child : GetChildren() )
1182 {
1183 if( dynamic_cast<WX_INFOBAR*>( child ) && child->IsShown() )
1184 {
1185 rects.push_back( child->GetRect() );
1186 infobarsBottom = std::max( infobarsBottom, child->GetRect().GetBottom() + 1 );
1187 }
1188 }
1189
1190 // On Wayland the OpenGL canvas is a subsurface stacked above everything the toolkit draws
1191 // in the frame, so an infobar overlaid on it cannot show. The canvas makes room instead.
1192 int inset = 0;
1193
1194#ifdef __WXGTK__
1195 if( m_backend == GAL_TYPE_OPENGL && wxGetDisplayInfo().type == wxDisplayType::wxDisplayWayland )
1196 inset = infobarsBottom;
1197#endif
1198
1199 if( inset != m_overlayInsetTop )
1200 {
1201 m_overlayInsetTop = inset;
1202 ResizeGal( true );
1203 Refresh();
1204 }
1205
1206 // Only the Cairo backend presents frames outside the paint cycle
1207 if( KIGFX::CAIRO_GAL* cairoGal = dynamic_cast<KIGFX::CAIRO_GAL*>( m_gal ) )
1208 cairoGal->SetOverlayExclusions( rects );
1209}
1210
1211
1216
1217
1219{
1220 const auto now = std::chrono::steady_clock::now();
1221
1222 // The window can move to another display
1223 if( now - m_refreshRateAsked >= std::chrono::seconds( 1 ) )
1224 {
1225 const double reported = KIPLATFORM::UI::GetDisplayRefreshRate( this );
1226
1227 // Headless, virtualized and some driver setups report 0 or nonsense
1228 m_refreshRate = reported >= 24.0 && reported <= 1000.0 ? static_cast<float>( reported ) : 0.0f;
1229 m_refreshRateAsked = now;
1230 }
1231
1232 return m_refreshRate;
1233}
1234
1235
1236void EDA_DRAW_PANEL_GAL::Refresh( bool aEraseBackground, const wxRect* aRect )
1237{
1238 auto now = std::chrono::steady_clock::now();
1239 auto delta = std::chrono::duration_cast<std::chrono::milliseconds>( now - m_lastRepaintStart ).count();
1240 bool galInitialized = m_gal && m_gal->IsInitialized();
1241
1242 // When vsync is available the driver throttles SwapBuffers, so we only need
1243 // a small guard to avoid queueing work faster than the GPU can consume it.
1244 // Without vsync, cap the render rate at the monitor refresh rate so the
1245 // GPU is not saturated producing frames that will never be shown.
1246 int minPeriodMs = 3;
1247
1248 if( galInitialized && m_gal->GetSwapInterval() == 0 )
1249 {
1250 const float displayHz = displayRefreshRate();
1251 int refreshHz = displayHz > 0.0f ? KiROUND( displayHz ) : 60;
1252
1253 refreshHz += 5; // Repaint slightly faster to avoid adding latency
1254
1255 minPeriodMs = 1000 / refreshHz;
1256 }
1257
1258 if( delta >= minPeriodMs )
1259 {
1260 if( !DoRePaint() && IsShownOnScreen() )
1262 }
1263 else if( !m_refreshTimer.IsRunning() )
1264 {
1265 m_refreshDeferred = true;
1266 m_refreshTimer.StartOnce( static_cast<int>( minPeriodMs - delta ) );
1267 }
1268}
1269
1270
1272{
1273 wxLogTrace( traceRhi, wxS( "DrawPanel: ForceRefresh windowId=%u drawEnabled=%d "
1274 "IsShown=%d IsShownOnScreen=%d galInit=%d" ),
1275 m_windowId, m_drawingEnabled, IsShown(), IsShownOnScreen(),
1276 m_gal ? m_gal->IsInitialized() : -1 );
1277
1278 if( !m_drawingEnabled )
1279 {
1280 if( m_gal && m_gal->IsInitialized() )
1281 {
1282 Connect( wxEVT_PAINT, wxPaintEventHandler( EDA_DRAW_PANEL_GAL::onPaint ), nullptr, this );
1283 Connect( wxEVT_IDLE, wxIdleEventHandler( EDA_DRAW_PANEL_GAL::onIdle ), nullptr, this );
1284
1285 m_drawingEnabled = true;
1286 }
1287 else
1288 {
1289 // Try again soon
1290 m_refreshTimer.StartOnce( 100 );
1291 return;
1292 }
1293 }
1294
1295 DoRePaint( false );
1296}
1297
1298
1299bool EDA_DRAW_PANEL_GAL::GetScreenshot( wxImage& aDstImage )
1300{
1301 if( !m_gal )
1302 return false;
1303
1304 if( m_backend == GAL_TYPE_OPENGL )
1305 {
1306 DoRePaint( false );
1307
1308 // The repaint may have replaced a GAL lost to a GPU reset
1309 if( m_backend != GAL_TYPE_OPENGL || !m_gal )
1310 return false;
1311
1312 return static_cast<KIGFX::OPENGL_GAL*>( m_gal )->GetScreenshot( aDstImage );
1313 }
1314
1316 {
1317 DoRePaint( false );
1318
1319 // The repaint may have torn down the render thread while recovering from an error
1321 return false;
1322
1323 return m_renderThread->ReadbackWindowPixels( m_windowId, aDstImage );
1324 }
1325
1326 // The caller falls back to a wxClientDC capture
1327 return false;
1328}
1329
1330
1332{
1333 m_eventDispatcher = aEventDispatcher;
1334}
1335
1336
1338{
1339 wxLogTrace( traceRhi, wxS( "DrawPanel: StartDrawing windowId=%u" ), m_windowId );
1340
1341 // Start querying GAL if it is ready
1342 m_refreshTimer.StartOnce( 100 );
1343}
1344
1345
1347{
1348 m_refreshTimer.Stop();
1349 m_drawingEnabled = false;
1350
1351 Disconnect( wxEVT_PAINT, wxPaintEventHandler( EDA_DRAW_PANEL_GAL::onPaint ), nullptr, this );
1352 Disconnect( wxEVT_IDLE, wxIdleEventHandler( EDA_DRAW_PANEL_GAL::onIdle ), nullptr, this );
1353}
1354
1355
1357{
1358 // Set display settings for high contrast mode
1359 KIGFX::RENDER_SETTINGS* rSettings = m_view->GetPainter()->GetSettings();
1360
1361 SetTopLayer( aLayer );
1362
1363 rSettings->ClearHighContrastLayers();
1364 rSettings->SetLayerIsHighContrast( aLayer );
1365
1366 m_view->UpdateAllLayersColor();
1367}
1368
1369
1371{
1372 m_view->ClearTopLayers();
1373 m_view->SetTopLayer( aLayer );
1374 m_view->UpdateAllLayersOrder();
1375}
1376
1377
1379{
1380 if( aStoredCanvasType == GAL_TYPE_CAIRO || aStoredCanvasType == GAL_TYPE_MODERN )
1381 return static_cast<GAL_TYPE>( aStoredCanvasType );
1382
1383 // The retired wxDC canvas, and any value another KiCad version may have written, leave the
1384 // user with the accelerated canvas rather than the do-nothing stub GAL
1385 return GAL_TYPE_OPENGL;
1386}
1387
1388
1390{
1391 // Do not do anything if the currently used GAL is correct
1392 if( aGalType == m_backend && m_gal != nullptr )
1393 return true;
1394
1395 VECTOR2D grid_origin = m_gal ? m_gal->GetGridOrigin() : VECTOR2D();
1396 VECTOR2D grid_size = m_gal ? m_gal->GetGridSize() : VECTOR2D();
1397 bool grid_visibility = m_gal ? m_gal->GetGridVisibility() : true;
1398 double worldUnitLength = m_gal ? m_gal->GetWorldUnitLength() : 0.0;
1399 bool result = true; // assume everything will be fine
1400
1401 // Prevent refreshing canvas during backend switch
1402 StopDrawing();
1403
1405 m_pendingResize = false;
1406
1407 KIGFX::GAL* new_gal = nullptr;
1408
1409 try
1410 {
1411 switch( aGalType )
1412 {
1413 case GAL_TYPE_OPENGL:
1414 {
1415 wxString errormsg = KIGFX::OPENGL_GAL::CheckFeatures( m_options );
1416
1417 if( errormsg.empty() )
1418 {
1419 new_gal = new KIGFX::OPENGL_GAL( GetVcSettings(), m_options, this, this, this );
1420 }
1421 else
1422 {
1423 if( GAL_FALLBACK != aGalType )
1424 {
1425 aGalType = GAL_FALLBACK;
1426 showMessageLater( _( "Could not use OpenGL, falling back to software rendering" ), errormsg,
1427 false );
1428 new_gal = new KIGFX::CAIRO_GAL( m_options, this, this, this );
1429 }
1430 else
1431 {
1432 // We're well and truly banjaxed if we get here without a fallback.
1433 showMessageLater( _( "Could not use OpenGL" ), errormsg, false );
1434 new_gal = new KIGFX::GAL( m_options );
1435 aGalType = GAL_TYPE_NONE;
1436 result = false;
1437 }
1438 }
1439
1440 break;
1441 }
1442
1443 case GAL_TYPE_CAIRO:
1444 new_gal = new KIGFX::CAIRO_GAL( m_options, this, this, this );
1445 break;
1446
1447 case GAL_TYPE_MODERN:
1448 {
1450
1451 // A device that failed once is not retried, so the user is told only once
1453 return abandonRhiCanvas( wxEmptyString );
1454
1456
1457 if( m_renderThread )
1458 {
1459 wxLogTrace( traceRhi, wxS( "DrawPanel: render thread acquired" ) );
1460
1461 // A repaint sees the loss and rebuilds the canvas. The listener is removed before the panel
1462 // is destroyed, and wx drops anything still queued for it.
1463 m_renderThread->AddDeviceLostListener( this,
1464 [this]()
1465 {
1466 CallAfter(
1467 [this]()
1468 {
1469 Refresh();
1470 } );
1471 } );
1472
1473 // The render thread owns the device, so the UI thread reaches it only through the proxy
1474 m_deviceProxy = std::make_unique<KIGFX::RHI_DEVICE_PROXY>( *m_renderThread );
1475
1476 new_gal = new KIGFX::GAL_MODERN( m_options, *m_deviceProxy );
1477
1478 auto* galModern = static_cast<KIGFX::GAL_MODERN*>( new_gal );
1479 galModern->SetRenderThread( m_renderThread.get() );
1480 galModern->SetPersistentTargets( true );
1481
1482 // Only the track shader draws the glyphs of the font atlas
1483 galModern->SetBitmapGlyphs( m_linePipelineKind == KIGFX::RHI_LINE_PIPELINE_KIND::PCB_TRACKS );
1484
1486 m_frameMailbox = std::make_unique<KIGFX::FRAME_MAILBOX>();
1487 m_swapchainCtrl = std::make_unique<KIGFX::SWAPCHAIN_CONTROLLER>( *m_renderThread, m_windowId );
1488 m_swapchainCtrl->SetFrameMailbox( m_frameMailbox.get() );
1489
1490 wxLogTrace( traceRhi, wxS( "DrawPanel: windowId=%u swapchainCtrl created" ), m_windowId );
1491
1492 // The GDK background is set on the first paint, once the widget is laid out
1495
1496 // The native surface is being recreated
1497 m_nativeHandleValid = false;
1498 }
1499 else
1500 {
1501 return abandonRhiCanvas( wxEmptyString );
1502 }
1503
1504 break;
1505 }
1506
1507 default:
1508 wxASSERT( false );
1510
1511 case GAL_TYPE_NONE:
1512 // KIGFX::GAL is a stub that prevents code relying on GAL canvas
1513 // existence from crashing, but cannot display anything
1514 new_gal = new KIGFX::GAL( m_options );
1515 break;
1516 }
1517 }
1518 catch( std::runtime_error& err )
1519 {
1520 // The GAL is deleted while the device proxy it releases its buffers through is still alive
1521 if( aGalType == GAL_TYPE_MODERN )
1522 {
1523 delete new_gal;
1525
1526 return abandonRhiCanvas( wxString( err.what() ) );
1527 }
1528
1529 // A context the driver refuses to create would otherwise leave every frame on the blank stub
1530 if( aGalType == GAL_TYPE_OPENGL && GAL_FALLBACK_AVAILABLE )
1531 {
1532 try
1533 {
1534 new_gal = new KIGFX::CAIRO_GAL( m_options, this, this, this );
1535 aGalType = GAL_FALLBACK;
1537 showMessageLater( _( "Could not use OpenGL, falling back to software rendering" ),
1538 wxString( err.what() ), false );
1539 }
1540 catch( std::runtime_error& )
1541 {
1542 new_gal = nullptr;
1543 }
1544 }
1545
1546 if( !new_gal )
1547 {
1548 // Create a dummy GAL
1549 new_gal = new KIGFX::GAL( m_options );
1550 aGalType = GAL_TYPE_NONE;
1551 showMessageLater( _( "Error switching GAL backend" ), wxString( err.what() ), true );
1552 result = false;
1553 }
1554 }
1555
1556 // trigger update of the gal options in case they differ from the defaults
1557 m_options.NotifyChanged();
1558
1559 // The native touchpad hook is attached to the window that takes the backend's input
1561
1562 // Queued uploads must not outlive the GAL_MODERN geometry they point at
1563 if( m_backend == GAL_TYPE_MODERN && aGalType != GAL_TYPE_MODERN )
1565
1566 delete m_gal;
1567 m_gal = nullptr;
1568
1569 m_gal = new_gal;
1570
1571 // The view lays itself out in the editor's world units as it attaches
1572 if( worldUnitLength > 0.0 )
1573 m_gal->SetWorldUnitLength( worldUnitLength );
1574
1575 if( aGalType == GAL_TYPE_MODERN )
1576 {
1577 KIGFX::GAL_MODERN* galModern = static_cast<KIGFX::GAL_MODERN*>( m_gal );
1578
1579 galModern->SetDpiScale( static_cast<float>( GetContentScaleFactor() ) );
1581 }
1582
1583 // Matches ResizeGal() so the picture does not move at the next resize
1584 wxSize clientSize = GetClientSize();
1585 int pad = ScreenSizePad( aGalType );
1586
1587 clientSize.x = std::max( 10, clientSize.x + pad );
1588 clientSize.y = std::max( 10, clientSize.y + pad );
1589 m_gal->ResizeScreen( clientSize.GetX(), clientSize.GetY() );
1590
1591 if( grid_origin != VECTOR2D() )
1592 m_gal->SetGridOrigin( grid_origin );
1593
1594 if( grid_size.x > 0 && grid_size.y > 0 )
1595 m_gal->SetGridSize( grid_size );
1596
1597 m_gal->SetGridVisibility( grid_visibility );
1598
1599 // Make sure the cursor is set on the new canvas
1601
1602 if( m_painter )
1603 m_painter->SetGAL( m_gal );
1604
1605 if( m_view )
1606 {
1607 m_view->SetGAL( m_gal );
1608 // OpenGL requires reverse draw order when draw priority is enabled.
1609 m_view->ReverseDrawOrder( aGalType == GAL_TYPE_OPENGL );
1610
1611 // The new GAL starts with empty geometry caches
1612 m_view->UpdateAllItems( KIGFX::ALL );
1613 }
1614
1615 m_backend = aGalType;
1616
1617 // A shown backend window raises itself, which would bury what is overlaid on this canvas
1618 for( wxWindow* child : GetChildren() )
1619 {
1620 if( child != dynamic_cast<wxWindow*>( m_gal ) )
1621 child->Raise();
1622 }
1623
1625
1627
1628 if( m_view )
1629 {
1630 wxLogTrace( traceRhi, wxS( "DrawPanel: backend %d view centre (%.17g, %.17g) scale %.17g screen %dx%d" ),
1631 static_cast<int>( m_backend ), m_view->GetCenter().x, m_view->GetCenter().y, m_view->GetScale(),
1632 m_gal->GetScreenPixelSize().x, m_gal->GetScreenPixelSize().y );
1633 }
1634
1635 return result;
1636}
1637
1638
1640{
1642
1643 if( Pgm().GetCommonSettings()->m_Input.touchpad_mode != TOUCHPAD_MODE::NATIVE_GESTURES )
1644 return;
1645
1646 // The modern canvas has no child window, so this panel receives the native pointer input
1647 wxWindow* inputWindow = m_backend == GAL_TYPE_MODERN ? static_cast<wxWindow*>( this )
1648 : dynamic_cast<wxWindow*>( m_gal );
1649
1650 if( inputWindow )
1651 {
1653 inputWindow,
1654 [this]( const KIPLATFORM::UI::TOUCHPAD_GESTURE& aGesture )
1655 {
1656 if( !m_viewControls )
1657 return;
1658
1659 m_viewControls->ApplyPanAndZoomGesture(
1660 VECTOR2D( aGesture.panX, aGesture.panY ), aGesture.zoomFactor,
1661 VECTOR2D( aGesture.zoomAnchor.x, aGesture.zoomAnchor.y ) );
1662 } );
1663 }
1664}
1665
1666
1668{
1669 try
1670 {
1671 // Check if the current rendering backend can be properly initialized
1672 m_view->UpdateItems();
1673 }
1674 catch( const std::runtime_error& e )
1675 {
1676 // recoverFromGalError() tells the user the outcome either way
1677 if( !recoverFromGalError( e ) )
1678 {
1679 StopDrawing();
1680 }
1681 else if( m_edaFrame && m_edaFrame->GetCanvas() == this )
1682 {
1683 // The frame's tools and view settings must follow the new GAL
1684 m_edaFrame->ActivateGalCanvas();
1685 }
1686 }
1687}
1688
1689
1691{
1692 s_inputSerial.fetch_add( 1, std::memory_order_relaxed );
1693}
1694
1695
1696void EDA_DRAW_PANEL_GAL::OnEvent( wxEvent& aEvent )
1697{
1698 if( aEvent.GetEventType() != KIGFX::WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE )
1699 NoteInput();
1700
1701 bool shouldSetFocus = m_lostFocus && m_stealsFocus
1702 && !KIUI::IsInputControlFocused() // Don't steal from input controls
1703 && !KIUI::IsModalDialogFocused() // Don't steal from dialogs
1704 && KIPLATFORM::UI::IsWindowActive( m_edaFrame ); // Don't steal from other windows
1705
1706 if( shouldSetFocus )
1707 SetFocus();
1708
1709 if( !m_eventDispatcher )
1710 aEvent.Skip();
1711 else
1712 m_eventDispatcher->DispatchWxEvent( aEvent );
1713
1714 Refresh();
1715}
1716
1717
1718void EDA_DRAW_PANEL_GAL::onEnter( wxMouseEvent& aEvent )
1719{
1720 bool shouldSetFocus = m_stealsFocus
1721 && !KIUI::IsInputControlFocused() // Don't steal from input controls
1722 && !KIUI::IsModalDialogFocused() // Don't steal from dialogs
1723 && KIPLATFORM::UI::IsWindowActive( m_edaFrame ); // Don't steal from other windows
1724
1725 // Getting focus is necessary in order to receive key events properly
1726 if( shouldSetFocus )
1727 SetFocus();
1728
1729 aEvent.Skip();
1730}
1731
1732
1733void EDA_DRAW_PANEL_GAL::onLostFocus( wxFocusEvent& aEvent )
1734{
1735 m_lostFocus = true;
1736
1737 m_viewControls->CancelDrag();
1738
1739 // Reset the tool dispatcher's button state when focus is lost. This prevents
1740 // the dispatcher from thinking the button is still pressed when focus returns,
1741 // which can cause selection and drag operations to stop working.
1742 if( m_eventDispatcher )
1743 m_eventDispatcher->ResetState();
1744
1745 aEvent.Skip();
1746}
1747
1748
1749void EDA_DRAW_PANEL_GAL::onIdle( wxIdleEvent& aEvent )
1750{
1751 if( m_needIdleRefresh )
1752 {
1753 wxLogTrace( traceRhi, wxS( "DrawPanel: onIdle refresh windowId=%u IsShown=%d "
1754 "IsShownOnScreen=%d" ),
1755 m_windowId, IsShown(), IsShownOnScreen() );
1756
1757 m_needIdleRefresh = false;
1758 Refresh();
1759 }
1760
1761 aEvent.Skip();
1762}
1763
1764
1765void EDA_DRAW_PANEL_GAL::onRefreshTimer( wxTimerEvent& aEvent )
1766{
1767 const bool deferred = m_refreshDeferred;
1768
1769 m_refreshDeferred = false;
1770
1771 // A postponed Refresh() is not an expose and must not present the frame already on screen
1772 if( deferred && m_backend == GAL_TYPE_MODERN && m_drawingEnabled )
1773 {
1774 if( !DoRePaint() && IsShownOnScreen() )
1776
1777 return;
1778 }
1779
1780 ForceRefresh();
1781}
1782
1783
1784void EDA_DRAW_PANEL_GAL::onShowEvent( wxShowEvent& aEvent )
1785{
1786 wxLogTrace( traceRhi, wxS( "DrawPanel: onShowEvent windowId=%u show=%d IsShown=%d "
1787 "IsShownOnScreen=%d galInit=%d" ),
1788 m_windowId, aEvent.IsShown(), IsShown(), IsShownOnScreen(),
1789 m_gal ? m_gal->IsInitialized() : -1 );
1790
1791 if( m_gal && m_gal->IsInitialized() && m_gal->IsVisible() )
1792 {
1793 OnShow();
1794 }
1795
1796}
1797
1798
1800{
1801 if( !m_gal )
1802 return;
1803
1804 DPI_SCALING_COMMON dpi( nullptr, m_parent );
1805
1806 bool hidpi = false;
1807
1808 // Cursor scaling factor cannot be set for a wxCursor on GTK and OSX (at least before wx 3.3),
1809 // resulting in 4x rendered size on 2x window scale.
1810 // MSW renders the bitmap as-is, without scaling, so this works here.
1811#ifdef __WXMSW__
1812 hidpi = dpi.GetContentScaleFactor() >= 2.0;
1813#endif
1814
1815 m_gal->SetNativeCursorStyle( aCursor, hidpi );
1816
1817 // The RHI canvas draws into the panel itself, and a new canvas window inherits the panel's cursor
1818#if wxCHECK_VERSION( 3, 3, 0 )
1819 SetCursorBundle( CURSOR_STORE::GetCursor( aCursor, hidpi ) );
1820#else
1821 SetCursor( CURSOR_STORE::GetCursor( aCursor, hidpi ) );
1822#endif
1823}
1824
1825
1826std::shared_ptr<KIGFX::VIEW_OVERLAY> EDA_DRAW_PANEL_GAL::DebugOverlay()
1827{
1828 if( !m_debugOverlay )
1829 {
1830 m_debugOverlay.reset( new KIGFX::VIEW_OVERLAY() );
1831 m_view->Add( m_debugOverlay.get() );
1832 }
1833
1834 return m_debugOverlay;
1835}
1836
1837
1839{
1840 if( m_debugOverlay )
1841 {
1842 m_view->Remove( m_debugOverlay.get() );
1843 m_debugOverlay = nullptr;
1844 }
1845}
1846
1847
1849{
1851
1852 KIGFX::VC_SETTINGS vcSettings;
1853 vcSettings.m_warpCursor = cfg->m_Input.center_on_zoom;
1855 vcSettings.m_autoPanSettingEnabled = cfg->m_Input.auto_pan;
1856 vcSettings.m_autoPanAcceleration = static_cast<float>( cfg->m_Input.auto_pan_acceleration );
1857 vcSettings.m_horizontalPan = cfg->m_Input.horizontal_pan;
1859 vcSettings.m_zoomSpeed = cfg->m_Input.zoom_speed;
1860 vcSettings.m_zoomSpeedAuto = cfg->m_Input.zoom_speed_auto;
1865 vcSettings.m_dragLeft = cfg->m_Input.drag_left;
1866 vcSettings.m_dragMiddle = cfg->m_Input.drag_middle;
1867 vcSettings.m_dragRight = cfg->m_Input.drag_right;
1870
1871 return vcSettings;
1872}
BASE_SCREEN class implementation.
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
VECTOR2D m_ScrollCenter
Current scroll center point in logical units.
Definition base_screen.h:96
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const SizeVec & GetSize() const
Definition box2.h:203
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
Class to handle configuration and automatic determination of the DPI scale to use for canvases.
double GetContentScaleFactor() const override
Get the content scale factor, which may be different from the scale factor on some platforms.
FRAME_T GetFrameType() const
The base class for create windows for drawing purpose.
static bool CanvasFailed(EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvas)
virtual BASE_SCREEN * GetScreen() const
Return a pointer to a BASE_SCREEN or one of its derivatives.
static void SetCanvasFailed(EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvas)
Keep every frame off a canvas for this session without changing the saved preference.
std::unique_ptr< PROF_COUNTER > m_PaintEventCounter
EDA_DRAW_FRAME * m_edaFrame
Parent EDA_DRAW_FRAME (if available)
static GAL_TYPE CanvasAfterFailure(GAL_TYPE aFailedCanvasType)
Return the canvas that takes over from one that could not be used.
virtual void OnShow()
Called when the window is shown for the first time.
void onLostFocus(wxFocusEvent &aEvent)
static constexpr GAL_TYPE GAL_FALLBACK
std::unique_ptr< KIGFX::PAINTER > m_painter
Contains information about how to draw items using GAL.
RHI_NATIVE_WINDOW_HANDLE m_cachedNativeHandle
Fetching the handle forces a display-server flush, so it is cached between realizations and fetched a...
bool m_rebuiltAfterReset
The GAL was rebuilt after a GPU reset and has not completed a frame yet.
void onSize(wxSizeEvent &aEvent)
bool m_pendingResize
Set when a size change could not be applied because the GL context was unavailable.
bool m_needIdleRefresh
True when canvas needs to be refreshed from idle handler.
std::unique_ptr< KIGFX::SWAPCHAIN_CONTROLLER > m_swapchainCtrl
bool abandonRhiCanvas(const wxString &aDetail)
Leave the RHI canvas for GAL_FALLBACK after its device could not be created or restored.
virtual void SetHighContrastLayer(int aLayer)
Take care of display settings for the given layer to be displayed in high contrast mode.
int m_overlayInsetTop
Rows at the top of the panel left to the infobars where the backend window would hide them.
bool m_stealsFocus
Flag to indicate whether the panel should take focus at certain times (when moused over,...
virtual BOX2I GetDefaultViewBBox() const
Return the bounding box of the view that should be used if model is not valid.
KIGFX::GAL_DISPLAY_OPTIONS & m_options
void StopDrawing()
Prevent the GAL canvas from further drawing until it is recreated or StartDrawing() is called.
KIGFX::RHI_LINE_PIPELINE_KIND m_linePipelineKind
Fixed in the constructor from the parent frame's editor.
static KIGFX::VC_SETTINGS GetVcSettings()
Gets a populated View Controls settings object dervived from our program settings.
static void NoteInput()
Count an input so the frame that answers it is never held back by the display-rate limit.
std::unique_ptr< KIGFX::FRAME_MAILBOX > m_frameMailbox
Delivers frame packets to the render thread.
virtual void SetTopLayer(int aLayer)
Move the selected layer to the top, so it is displayed above all others.
KIGFX::FRAME_DRAW_DESC m_lastFullStaticDesc
Draw descriptors of the last full redraw, reused by frames that need no new geometry.
void presentFrame()
Present the most recently submitted frame packet.
void ClearDebugOverlay()
Clear the contents of the debug overlay and removes it from the VIEW.
virtual KIGFX::VIEW * GetView() const
Return a pointer to the KIGFX::VIEW instance used in the panel.
static int ScreenSizePad(GAL_TYPE aBackend)
Return how many pixels larger than the client area a backend's GAL is made.
void ForceRefresh()
Force a redraw.
bool recoverFromGalError(const std::exception &aErr)
void ResizeGal(bool aForce=false)
Resize the GAL to the current client size of this panel.
bool m_drawing
True if GAL is currently redrawing the view.
KIGFX::GAL * m_gal
Interface for drawing objects on a 2D-surface.
EDA_DRAW_PANEL_GAL(wxWindow *aParentWindow, wxWindowID aWindowId, const wxPoint &aPosition, const wxSize &aSize, KIGFX::GAL_DISPLAY_OPTIONS &aOptions, GAL_TYPE aGalType=GAL_TYPE_OPENGL)
Create a drawing panel that is contained inside aParentWindow.
std::chrono::steady_clock::time_point m_lastRepaintStart
Timestamp of the last repaint start.
void onRefreshTimer(wxTimerEvent &aEvent)
float displayRefreshRate()
Refresh rate in Hz of the display showing the canvas, or 0 when unknown.
void releaseCachedNativeWindowHandle()
void SetCurrentCursor(KICURSOR aCursor)
Set the current cursor shape for this panel.
virtual void onPaint(wxPaintEvent &WXUNUSED(aEvent))
bool m_lostFocus
Flag to indicate that focus should be regained on the next mouse event.
std::unique_ptr< KIGFX::RHI_DEVICE_PROXY > m_deviceProxy
The UI thread's access to the device.
static bool IsAccelerated(GAL_TYPE aCanvasType)
True for a canvas drawn on the GPU, which keeps cached layers between frames.
wxSize backendScreenSize() const
The padded size of the backend window, which sits below the top overlay inset.
std::chrono::steady_clock::time_point m_lastRepaintEnd
Timestamp of the last repaint end.
std::shared_ptr< KIGFX::VIEW_OVERLAY > m_debugOverlay
Optional overlay for drawing transient debug objects.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
bool m_needsExternalRenderingSetup
True until the first paint completes the deferred GTK drawing suppression.
wxTimer m_refreshTimer
Timer to prevent too-frequent refreshing.
TOOL_DISPATCHER * m_eventDispatcher
Processes and forwards events to tools.
wxWindow * m_parent
Pointer to the parent window.
@ GAL_TYPE_OPENGL
OpenGL implementation.
@ GAL_TYPE_MODERN
RHI implementation, Vulkan or Metal on macOS.
@ GAL_TYPE_CAIRO
Cairo implementation.
@ GAL_TYPE_NONE
GAL not used (the legacy wxDC engine is used)
virtual void beforeRedraw()
Hook for subclasses to update view items whose inputs nothing else tracks.
KIGFX::FRAME_DRAW_DESC m_lastFullDynamicDesc
void teardownModernRenderInfra()
Stop the RHI render thread and release its swapchain, mailbox and buffers.
virtual void prepareGridSources()
Hook for subclasses to push per-frame grid sources onto the GAL.
KIGFX::VIEW * m_view
Stores view settings (scale, center, etc.) and items to be drawn.
bool m_MouseCapturedLost
used on wxMSW: true after a wxEVT_MOUSE_CAPTURE_LOST was received false after the mouse is recaptured...
KIGFX::WX_VIEW_CONTROLS * m_viewControls
Control for VIEW (moving, zooming, etc.)
const RHI_NATIVE_WINDOW_HANDLE & getCachedNativeWindowHandle()
Return the native window handle for RHI surface creation, or an invalid handle until the window is re...
void SetFocus() override
void onIdle(wxIdleEvent &aEvent)
void UpdateTouchpadGestureHandler()
Apply the current native touchpad gesture preference to the active backend window.
void SetEventDispatcher(TOOL_DISPATCHER *aEventDispatcher)
Set a dispatcher that processes events and forwards them to tools.
void UpdateOverlayExclusions()
Tell the backend which areas of this panel are covered by an overlaid infobar.
float m_refreshRate
What displayRefreshRate() last asked the window system.
void onEnter(wxMouseEvent &aEvent)
void submitCachedScene()
Submit the last full scene again with the current camera and cursor, keeping all targets.
uint32_t m_windowId
Routes this window's work on the render thread.
void showMessageLater(const wxString &aTitle, const wxString &aDetail, bool aError)
Show a recovery message once the current event finishes.
std::unique_ptr< KIPLATFORM::UI::TOUCHPAD_GESTURE_HANDLER > m_touchpadGestureHandler
uint64_t m_staticEpoch
Content generations of the static and dynamic pools last submitted, see FRAME_PACKET.
void RequestRefresh()
Make sure a refresh gets done on the next idle event if it hasn't already.
int m_contextBindFailures
Consecutive frames dropped because the GL context could not be made current.
std::shared_ptr< KIGFX::RENDER_THREAD > m_renderThread
The render thread that owns the RHI device, shared by every canvas, see GAL_MODERN::AcquireRenderThre...
std::mutex m_refreshMutex
Blocks multiple calls to the draw.
virtual bool SwitchBackend(GAL_TYPE aGalType)
Switch method of rendering graphics.
void StartDrawing()
Begin drawing if it was stopped previously.
static GAL_TYPE ResolveStoredCanvasType(int aStoredCanvasType)
Convert a stored canvas type setting into a backend that can actually be used.
bool DoRePaint(bool aAllowSkip=true)
Repaint the canvas, and fix scrollbar cursors.
void OnEvent(wxEvent &aEvent)
Used to forward events to the canvas from popups, etc.
bool m_recoveryAttempted
Set after a GPU recovery attempt to prevent infinite retry loops.
bool m_drawingEnabled
Flag that determines if VIEW may use GAL for redrawing the screen.
std::shared_ptr< KIGFX::VIEW_OVERLAY > DebugOverlay()
Create an overlay for rendering debug graphics.
KIGFX::AUTOMATIC_FIT m_automaticFit
What the last ZoomToFit() left, refitted on a resize until the view is zoomed or panned.
void onShowEvent(wxShowEvent &aEvent)
bool handleContextLoss()
Replace a GAL whose context was destroyed by a GPU reset.
static constexpr bool GAL_FALLBACK_AVAILABLE
GAL_TYPE m_backend
Currently used GAL.
std::chrono::steady_clock::time_point m_refreshRateAsked
void ZoomToFit(const BOX2I &aBox, std::optional< double > aMarginScale=std::nullopt)
Scale and centre the view on aBox, and fit it again whenever the canvas changes size until something ...
VECTOR2D m_lastCursorPosition
Last cursor position sent to GAL for drawing.
bool GetScreenshot(wxImage &aDstImage)
Capture the current canvas contents into aDstImage.
EDA_DRAW_FRAME * GetParentEDAFrame() const
Returns parent EDA_DRAW_FRAME, if available or NULL otherwise.
bool m_refreshDeferred
The refresh timer was armed by Refresh() for a repaint it postponed.
GAL that turns drawing commands into instanced primitives for the RHI device.
Definition gal_modern.h:50
void SetCentringPad(int aPad)
The other canvases lay the view out this many pixels larger than the client area.
Definition gal_modern.h:155
void SetRenderThread(RENDER_THREAD *aThread)
Render thread used for fence-based synchronization.
Definition gal_modern.h:143
static uint32_t AllocateWindowId()
static std::shared_ptr< RENDER_THREAD > AcquireRenderThread(bool aEnableValidation)
Return the render thread every RHI canvas in the process shares, starting it on first use and replaci...
void SetDpiScale(float aScale)
Backing-store scale factor, 2.0 on Retina.
Definition gal_modern.h:149
Abstract interface for drawing on a 2D-surface.
Raised when a GPU buffer allocation is predicted to exceed the available video memory.
OpenGL implementation of the Graphics Abstraction Layer.
Definition opengl_gal.h:69
static wxString CheckFeatures(GAL_DISPLAY_OPTIONS &aOptions)
Check OpenGL features.
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
void ClearHighContrastLayers()
Clear the list of active layers.
virtual const COLOR4D & GetGridColor()=0
Return current grid color settings.
virtual const COLOR4D & GetBackgroundColor() const =0
Return current background color settings.
void SetLayerIsHighContrast(int aLayerId, bool aEnabled=true)
Set the specified layer as high-contrast.
virtual const COLOR4D & GetCursorColor()=0
Return current cursor color settings.
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:68
double GetScale() const
Definition view.h:298
const VECTOR2D & GetCenter() const
Return the center point of this VIEW (in world space coordinates).
Definition view.h:377
virtual void SetScale(double aScale, VECTOR2D aAnchor={ 0, 0 })
Set the scaling factor, zooming around a given anchor point.
Definition view.cpp:901
VECTOR2D ToWorld(const VECTOR2D &aCoord, bool aAbsolute=true) const
Converts a screen space point/vector to a point/vector in world space coordinates.
Definition view.cpp:791
void SetCenter(const VECTOR2D &aCenter)
Set the center point of the VIEW (i.e.
Definition view.cpp:971
static const wxEventType EVT_REFRESH_MOUSE
Event that forces mouse move event in the dispatcher (eg.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:562
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
RAII class that sets an value at construction and resets it to the original value at destruction.
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:245
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
KICURSOR
Definition cursors.h:41
@ ARROW
Definition cursors.h:43
static RHI_NATIVE_WINDOW_HANDLE GetNativeWindowHandle(wxWindow *aWindow)
static constexpr int MAX_CONTEXT_BIND_RETRIES
Frames to drop before giving up on the GL context and letting the backend recover.
static void ApplyAntialiasingMode(KIGFX::FRAME_DRAW_DESC &aDesc, KIGFX::GAL_ANTIALIASING_MODE aMode)
static std::atomic< uint64_t > s_inputSerial
static KIGFX::RHI_LINE_PIPELINE_KIND DetermineLinePipelineKind(const EDA_DRAW_FRAME *aFrame)
#define _(s)
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:31
@ FRAME_SCH_VIEWER
Definition frame_type.h:32
@ FRAME_SCH
Definition frame_type.h:30
@ FRAME_SCH_DIFF
Definition frame_type.h:35
const wxChar *const traceGalContext
Flag to enable debug output of GAL context binding.
const wxChar *const traceRhi
Flag to enable debug output of the RHI rendering backend.
static const wxChar traceDrawPanel[]
Flag to enable drawing panel debugging output.
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
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.
RHI_LINE_PIPELINE_KIND
@ ALL
All except INITIAL_ADD.
Definition view_item.h:55
@ TARGET_NONCACHED
Auxiliary rendering target (noncached)
Definition definitions.h:34
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
double GetDisplayRefreshRate(const wxWindow *aWindow)
Return the refresh rate in Hz of the display showing most of the window, or 0 if unknown.
Definition wxgtk/ui.cpp:448
void GetNativeWindowHandles(wxWindow *aWindow, void **aHandle, void **aDisplay, int *aDisplayType=nullptr)
Get the native handles needed to create a GPU surface for aWindow.
Definition wxgtk/ui.cpp:967
void PrepareForExternalRendering(wxWindow *aWindow)
Prepare a realized widget for Vulkan or Metal rendering, before its first present.
void SetOverlayScrolling(const wxWindow *aWindow, bool overlay)
Used to set overlay/non-overlay scrolling mode in a window.
Definition wxgtk/ui.cpp:474
void ImmControl(wxWindow *aWindow, bool aEnable)
Configures the IME mode of a given control handle.
Definition wxgtk/ui.cpp:610
@ DISPLAY_UNKNOWN
Definition ui.h:273
@ DISPLAY_COCOA
Definition ui.h:277
std::unique_ptr< TOUCHPAD_GESTURE_HANDLER > CreateTouchpadGestureHandler(wxWindow *aInputWindow, TOUCHPAD_GESTURE_CALLBACK aCallback)
Register a window for native touchpad pan and pinch gestures when the port supports it.
void ImeNotifyCancelComposition(wxWindow *aWindow)
Asks the IME to cancel.
Definition wxgtk/ui.cpp:615
bool IsWindowActive(wxWindow *aWindow)
Check to see if the given window is the currently active window (e.g.
Definition wxgtk/ui.cpp:230
KICOMMON_API bool IsInputControlFocused(wxWindow *aFocus=nullptr)
Check if a input control has focus.
KICOMMON_API bool IsModalDialogFocused()
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
KICOMMON_API RHI_METAL_HOST_VIEW RhiMetalAttachHostView(void *aCanvasView)
KICOMMON_API void RhiMetalDetachHostView(void *aHostView)
RHI_DISPLAY_TYPE
Matches KIPLATFORM::UI::NATIVE_DISPLAY_TYPE as reported by GetNativeWindowHandles().
Definition rhi_surface.h:30
const int scale
int canvas_type
EDA_DRAW_PANEL_GAL::GAL_TYPE_* value, see gal_options_panel.cpp.
MOUSE_DRAG_ACTION drag_right
MOUSE_DRAG_ACTION drag_middle
MOUSE_DRAG_ACTION drag_left
GPU geometry of one frame, built by GAL_MODERN after EndDrawing() and passed through the FRAME_MAILBO...
RHI_LINE_PIPELINE_KIND linePipelineKind
uint32_t supersampleFactor
Layer FBO scale that the present blit downsamples.
Structure to keep VIEW_CONTROLS settings for easy store/restore operations.
MOUSE_DRAG_ACTION m_dragLeft
bool m_horizontalPan
Enable horizontal panning with the horizontal scroll/trackpad input.
bool m_scrollReversePanH
Whether to invert the scroll wheel movement for horizontal pan.
bool m_autoPanSettingEnabled
Flag for turning on autopanning.
bool m_focusFollowSchPcb
Flag for automatic focus switching between Schematic and PCB editors.
float m_autoPanAcceleration
How fast does panning accelerate when approaching the window boundary.
MOUSE_DRAG_ACTION m_dragMiddle
int m_zoomSpeed
Zoom speed for the non-accelerating zoom controller.
int m_scrollModifierZoom
What modifier key to enable zoom with the (vertical) scroll wheel.
int m_scrollModifierPanH
What modifier key to enable horizontal pan with the (vertical) scroll wheel.
bool m_warpCursor
If the cursor is allowed to be warped.
MOUSE_DRAG_ACTION m_dragRight
bool m_scrollReverseZoom
Whether to invert the scroll wheel movement for zoom.
int m_motionPanModifier
What modifier key pans the view when the mouse moves with it held.
bool m_zoomAcceleration
Enable the accelerating zoom controller.
bool m_zoomSpeedAuto
When true, ignore zoom_speed and pick a platform-specific default.
int m_scrollModifierPanV
What modifier key to enable vertical with the (vertical) scroll wheel.
A pan and zoom update produced by a native touchpad gesture recognizer.
Definition touchpad.h:38
Platform-agnostic wrapper for a native window handle.
Definition rhi_surface.h:45
RHI_DISPLAY_TYPE displayType
Definition rhi_surface.h:48
bool IsValid() const
A handle is usable only once the window is realized.
Definition rhi_surface.h:54
wxString result
Test unit parsing edge cases and error handling.
int delta
wxLogTrace helper definitions.
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
VECTOR2I ToVECTOR2I(const wxSize &aSize)
Definition vector2wx.h:26
WX_VIEW_CONTROLS class definition.