From b728c08b7aaca5899a25ed7c2516cccb39643d60 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Tue, 17 Feb 2026 10:53:23 -0800 Subject: [PATCH] Reduce OpenGL GPU utilization by rate-limiting redraws The GAL draw panel was rendering far more frames than needed, causing 100% GPU utilization on systems where vsync was unavailable or disabled. Three changes address this: Refresh() now enforces a minimum period between renders. With vsync active the guard is 3ms (the driver throttles via SwapBuffers). Without vsync a 16ms floor caps rendering at 60 FPS, preventing GPU saturation. DoRePaint() tracks the cursor position and skips the entire GL cycle when neither the view is clean and the cursor doesn't move OnEvent() delegates directly to the rate-limited Refresh() instead of maintaining its own adaptive throttle with a RequestRefresh() idle fallback that fired immediately and provided no actual throttling. Fixes https://gitlab.com/kicad/code/kicad/-/issues/19112 --- common/draw_panel_gal.cpp | 55 ++++++++++++++++++++++++---------- include/class_draw_panel_gal.h | 3 ++ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/common/draw_panel_gal.cpp b/common/draw_panel_gal.cpp index 23d27962a7..f53d07800d 100644 --- a/common/draw_panel_gal.cpp +++ b/common/draw_panel_gal.cpp @@ -263,6 +263,20 @@ bool EDA_DRAW_PANEL_GAL::DoRePaint() cntUpd.Stop(); + VECTOR2D cursorPos = m_viewControls->GetCursorPosition(); + bool viewDirty = m_view->IsDirty(); + bool cursorMoved = ( cursorPos != m_lastCursorPosition ); + + // Skip the entire GL cycle when nothing has changed. The front buffer + // still shows the previous frame so there is nothing to redraw. + if( !viewDirty && !cursorMoved ) + { + m_lastRepaintEnd = wxGetLocalTimeMillis(); + return true; + } + + m_lastCursorPosition = cursorPos; + // GAL_DRAWING_CONTEXT can throw in the dtor, so we need to scope // the full lifetime inside the try block { @@ -280,9 +294,9 @@ bool EDA_DRAW_PANEL_GAL::DoRePaint() m_gal->SetGridColor( settings->GetGridColor() ); m_gal->SetCursorColor( settings->GetCursorColor() ); - // TODO: find why ClearScreen() must be called here in opengl mode - // and only if m_view->IsDirty() in Cairo mode to avoid display artifacts - // when moving the mouse cursor + // OpenGL double-buffering leaves the back buffer undefined after + // SwapBuffers, so a full clear is always required before compositing. + // Cairo only needs to clear when NONCACHED content changed. if( m_backend == GAL_TYPE_OPENGL ) m_gal->ClearScreen(); @@ -306,7 +320,7 @@ bool EDA_DRAW_PANEL_GAL::DoRePaint() isDirty = true; } - m_gal->DrawCursor( m_viewControls->GetCursorPosition() ); + m_gal->DrawCursor( cursorPos ); cntCtxDestroy.Start(); } @@ -398,8 +412,26 @@ void EDA_DRAW_PANEL_GAL::RequestRefresh() void EDA_DRAW_PANEL_GAL::Refresh( bool aEraseBackground, const wxRect* aRect ) { - if( !DoRePaint() ) - RequestRefresh(); + wxLongLong now = wxGetLocalTimeMillis(); + wxLongLong delta = now - m_lastRepaintEnd; + + // When vsync is available the driver throttles SwapBuffers, so we only need + // a small guard to avoid queueing work faster than the GPU can consume it. + // Without vsync, enforce a 60 FPS ceiling to prevent saturating the GPU. + int minPeriodMs = 3; + + if( m_gal && m_gal->IsInitialized() && m_gal->GetSwapInterval() == 0 ) + minPeriodMs = 16; + + if( delta >= minPeriodMs ) + { + if( !DoRePaint() ) + RequestRefresh(); + } + else if( !m_refreshTimer.IsRunning() ) + { + m_refreshTimer.StartOnce( ( minPeriodMs - delta ).GetValue() ); + } } @@ -597,16 +629,7 @@ void EDA_DRAW_PANEL_GAL::OnEvent( wxEvent& aEvent ) else m_eventDispatcher->DispatchWxEvent( aEvent ); - // Give events time to process, based on last render duration - wxLongLong endDelta = wxGetLocalTimeMillis() - m_lastRepaintEnd; - long long timeLimit = ( m_lastRepaintEnd - m_lastRepaintStart ).GetValue() / 5; - - timeLimit = std::clamp( timeLimit, 3LL, 150LL ); - - if( endDelta > timeLimit ) - Refresh(); - else - RequestRefresh(); + Refresh(); } diff --git a/include/class_draw_panel_gal.h b/include/class_draw_panel_gal.h index 37ba950041..d2f582f94f 100644 --- a/include/class_draw_panel_gal.h +++ b/include/class_draw_panel_gal.h @@ -288,6 +288,9 @@ protected: /// True when canvas needs to be refreshed from idle handler bool m_needIdleRefresh; + /// Last cursor position sent to GAL for drawing + VECTOR2D m_lastCursorPosition; + /// Interface for drawing objects on a 2D-surface KIGFX::GAL* m_gal;