diff --git a/common/draw_panel_gal.cpp b/common/draw_panel_gal.cpp
index 68530a8820..38af449f3a 100644
--- a/common/draw_panel_gal.cpp
+++ b/common/draw_panel_gal.cpp
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2013-2017 CERN
- * Copyright (C) 2013-2023, 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2013-2024, 2024 KiCad Developers, see AUTHORS.txt for contributors.
  *
  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
  * @author Maciej Suminski <maciej.suminski@cern.ch>
@@ -281,20 +281,10 @@ 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
-            if( m_backend == GAL_TYPE_OPENGL )
-                m_gal->ClearScreen();
+            m_gal->ClearScreen();
 
             if( m_view->IsDirty() )
             {
-                if( m_backend != GAL_TYPE_OPENGL  // Already called in opengl
-                        && m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED ) )
-                {
-                    m_gal->ClearScreen();
-                }
-
                 m_view->ClearTargets();
 
                 // Grid has to be redrawn only when the NONCACHED target is redrawn
diff --git a/common/gal/cairo/cairo_gal.cpp b/common/gal/cairo/cairo_gal.cpp
index 0910cc7cc1..ff84d294a1 100644
--- a/common/gal/cairo/cairo_gal.cpp
+++ b/common/gal/cairo/cairo_gal.cpp
@@ -2,7 +2,7 @@
  * This program source code file is part of KICAD, a free EDA CAD application.
  *
  * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
- * Copyright (C) 2012-2023 Kicad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2012-2024 Kicad Developers, see AUTHORS.txt for contributors.
  * Copyright (C) 2017-2018 CERN
  *
  * @author Maciej Suminski <maciej.suminski@cern.ch>
@@ -29,6 +29,7 @@
 
 #include <wx/image.h>
 #include <wx/log.h>
+#include <wx/rawbmp.h>
 
 #include <gal/cairo/cairo_gal.h>
 #include <gal/cairo/cairo_compositor.h>
@@ -583,6 +584,9 @@ void CAIRO_GAL_BASE::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
 void CAIRO_GAL_BASE::ResizeScreen( int aWidth, int aHeight )
 {
     m_screenSize = VECTOR2I( aWidth, aHeight );
+
+    m_bitmapSize = VECTOR2I( std::ceil( m_screenSize.x * getScalingFactor() ),
+                             std::ceil( m_screenSize.y * getScalingFactor() ) );
 }
 
 
@@ -594,9 +598,9 @@ void CAIRO_GAL_BASE::Flush()
 
 void CAIRO_GAL_BASE::ClearScreen()
 {
-    cairo_set_source_rgb( m_currentContext, m_clearColor.r, m_clearColor.g, m_clearColor.b );
-    cairo_rectangle( m_currentContext, 0.0, 0.0, m_screenSize.x, m_screenSize.y );
-    cairo_fill( m_currentContext );
+    cairo_set_source_rgb( m_context, m_clearColor.r, m_clearColor.g, m_clearColor.b );
+    cairo_rectangle( m_context, 0.0, 0.0, m_bitmapSize.x, m_bitmapSize.y );
+    cairo_fill( m_context );
 }
 
 
@@ -1021,15 +1025,15 @@ void CAIRO_GAL_BASE::resetContext()
 
     m_imageSurfaces.clear();
 
-    ClearScreen();
-
     // Compute the world <-> screen transformations
     ComputeWorldScreenMatrix();
 
-    cairo_matrix_init( &m_cairoWorldScreenMatrix, m_worldScreenMatrix.m_data[0][0],
-                       m_worldScreenMatrix.m_data[1][0], m_worldScreenMatrix.m_data[0][1],
-                       m_worldScreenMatrix.m_data[1][1], m_worldScreenMatrix.m_data[0][2],
-                       m_worldScreenMatrix.m_data[1][2] );
+    double sf = getScalingFactor();
+
+    cairo_matrix_init( &m_cairoWorldScreenMatrix,
+                       m_worldScreenMatrix.m_data[0][0] * sf, m_worldScreenMatrix.m_data[1][0] * sf,
+                       m_worldScreenMatrix.m_data[0][1] * sf, m_worldScreenMatrix.m_data[1][1] * sf,
+                       m_worldScreenMatrix.m_data[0][2] * sf, m_worldScreenMatrix.m_data[1][2] * sf );
 
     // we work in screen-space coordinates and do the transforms outside.
     cairo_identity_matrix( m_context );
@@ -1337,8 +1341,10 @@ CAIRO_GAL::CAIRO_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
     m_currentTarget = TARGET_NONCACHED;
     SetTarget( TARGET_NONCACHED );
 
+    SetBackgroundStyle( wxBG_STYLE_PAINT );
+
     m_bitmapBuffer = nullptr;
-    m_wxOutput = nullptr;
+    m_wxBitmap = nullptr;
 
     m_parentWindow = aParent;
     m_mouseListener = aMouseListener;
@@ -1374,10 +1380,6 @@ CAIRO_GAL::CAIRO_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
 #endif
 
     SetSize( aParent->GetClientSize() );
-    m_screenSize = ToVECTOR2I( aParent->GetClientSize() );
-
-    // Allocate memory for pixel storage
-    allocateBitmaps();
 
     m_isInitialized = false;
 }
@@ -1413,45 +1415,44 @@ void CAIRO_GAL::EndDrawing()
 
     // Now translate the raw context data from the format stored
     // by cairo into a format understood by wxImage.
-    int height = m_screenSize.y;
+    int height = m_bitmapSize.y;
     int stride = m_stride;
 
     unsigned char* srcRow = m_bitmapBuffer;
-    unsigned char* dst = m_wxOutput;
+
+    wxNativePixelData           dstData( *m_wxBitmap );
+    wxNativePixelData::Iterator di( dstData );
 
     for( int y = 0; y < height; y++ )
     {
-        for( int x = 0; x < stride; x += 4 )
+        wxNativePixelData::Iterator rowStart = di;
+
+        for( int x = 0; x < stride; x += 4, ++di )
         {
             const unsigned char* src = srcRow + x;
 
 #if defined( __BYTE_ORDER__ ) && ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
             // XRGB
-            dst[0] = src[1];
-            dst[1] = src[2];
-            dst[2] = src[3];
+            di.Red() = src[1];
+            di.Green() = src[2];
+            di.Blue() = src[3];
 #else
             // BGRX
-            dst[0] = src[2];
-            dst[1] = src[1];
-            dst[2] = src[0];
+            di.Red() = src[2];
+            di.Green() = src[1];
+            di.Blue() = src[0];
 #endif
-            dst += 3;
         }
 
         srcRow += stride;
+
+        di = rowStart;
+        di.OffsetY( dstData, 1 );
     }
 
-    wxImage    img( m_wxBufferWidth, m_screenSize.y, m_wxOutput, true );
-    wxBitmap   bmp( img );
-    wxMemoryDC mdc( bmp );
-    wxClientDC clientDC( this );
-
-    // Now it is the time to blit the mouse cursor
-    blitCursor( mdc );
-    clientDC.Blit( 0, 0, m_screenSize.x, m_screenSize.y, &mdc, 0, 0, wxCOPY );
-
     deinitSurface();
+
+    Refresh(); // Trigger repaint
 }
 
 
@@ -1472,7 +1473,7 @@ void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
     allocateBitmaps();
 
     if( m_validCompositor )
-        m_compositor->Resize( aWidth, aHeight );
+        m_compositor->Resize( m_bitmapSize.x, m_bitmapSize.y );
 
     m_validCompositor = false;
 
@@ -1563,7 +1564,7 @@ void CAIRO_GAL::initSurface()
         return;
 
     m_surface = cairo_image_surface_create_for_data( m_bitmapBuffer, GAL_FORMAT, m_wxBufferWidth,
-                                                     m_screenSize.y, m_stride );
+                                                     m_bitmapSize.y, m_stride );
 
     m_context = cairo_create( m_surface );
 
@@ -1594,17 +1595,18 @@ void CAIRO_GAL::deinitSurface()
 
 void CAIRO_GAL::allocateBitmaps()
 {
-    m_wxBufferWidth = m_screenSize.x;
+    m_wxBufferWidth = m_bitmapSize.x;
 
     // Create buffer, use the system independent Cairo context backend
     m_stride = cairo_format_stride_for_width( GAL_FORMAT, m_wxBufferWidth );
-    m_bufferSize = m_stride * m_screenSize.y;
+    m_bufferSize = m_stride * m_bitmapSize.y;
 
     wxASSERT( m_bitmapBuffer == nullptr );
     m_bitmapBuffer = new unsigned char[m_bufferSize];
 
-    wxASSERT( m_wxOutput == nullptr );
-    m_wxOutput = new unsigned char[m_wxBufferWidth * 3 * m_screenSize.y];
+    wxASSERT( m_wxBitmap == nullptr );
+    m_wxBitmap = new wxBitmap( m_wxBufferWidth, m_bitmapSize.y, 24 );
+    m_wxBitmap->SetScaleFactor( getScalingFactor() );
 }
 
 
@@ -1613,8 +1615,8 @@ void CAIRO_GAL::deleteBitmaps()
     delete[] m_bitmapBuffer;
     m_bitmapBuffer = nullptr;
 
-    delete[] m_wxOutput;
-    m_wxOutput = nullptr;
+    delete m_wxBitmap;
+    m_wxBitmap = nullptr;
 }
 
 
@@ -1622,7 +1624,7 @@ void CAIRO_GAL::setCompositor()
 {
     // Recreate the compositor with the new Cairo context
     m_compositor.reset( new CAIRO_COMPOSITOR( &m_currentContext ) );
-    m_compositor->Resize( m_screenSize.x, m_screenSize.y );
+    m_compositor->Resize( m_bitmapSize.x, m_bitmapSize.y );
     m_compositor->SetAntialiasingMode( m_options.cairo_antialiasing_mode );
 
     // Prepare buffers
@@ -1636,7 +1638,22 @@ void CAIRO_GAL::setCompositor()
 
 void CAIRO_GAL::onPaint( wxPaintEvent& aEvent )
 {
-    PostPaint( aEvent );
+    // We should have the rendered image in m_wxBitmap after EDA_DRAW_PANEL_GAL::onPaint
+
+    if( !m_wxBitmap )
+    {
+        wxLogDebug( "CAIRO_GAL::onPaint null output bitmap buffer" );
+        return;
+    }
+
+    {
+        // Now it is the time to blit the mouse cursor
+        wxMemoryDC mdc( *m_wxBitmap );
+        blitCursor( mdc );
+    }
+
+    wxPaintDC paintDC( this );
+    paintDC.DrawBitmap( *m_wxBitmap, 0, 0 );
 }
 
 
@@ -1672,6 +1689,12 @@ bool CAIRO_GAL::updatedGalDisplayOptions( const GAL_DISPLAY_OPTIONS& aOptions )
 }
 
 
+double CAIRO_GAL::getScalingFactor()
+{
+    return GetContentScaleFactor();
+};
+
+
 bool CAIRO_GAL::SetNativeCursorStyle( KICURSOR aCursor, bool aHiDPI )
 {
     // Store the current cursor type and get the wxCursor for it
diff --git a/include/gal/cairo/cairo_gal.h b/include/gal/cairo/cairo_gal.h
index de622863f6..8afb3f7529 100644
--- a/include/gal/cairo/cairo_gal.h
+++ b/include/gal/cairo/cairo_gal.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KICAD, a free EDA CAD application.
  *
  * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
- * Copyright (C) 2012-2021 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors.
  * Copyright (C) 2017-2018 CERN
  * @author Maciej Suminski <maciej.suminski@cern.ch>
  *
@@ -252,6 +252,9 @@ protected:
     const VECTOR2D xform( double x, double y ); // rotation, scale and offset
     const VECTOR2D xform( const VECTOR2D& aP ); // rotation, scale and offset
 
+    // Return the scaling factor for current window.
+    virtual double getScalingFactor() { return 1.0; }
+
     /**
      * Transform according to the rotation from m_currentWorld2Screen transform matrix.
      *
@@ -452,6 +455,8 @@ public:
     /// @copydoc GAL::EndDrawing()
     void EndDrawing() override;
 
+protected:
+
     /// Prepare Cairo surfaces for drawing
     void initSurface();
 
@@ -492,6 +497,9 @@ public:
     ///< Cairo-specific update handlers
     bool updatedGalDisplayOptions( const GAL_DISPLAY_OPTIONS& aOptions ) override;
 
+    ///< For HiDPI support
+    double getScalingFactor() override;
+
 protected:
     // Compositor related variables
     std::shared_ptr<CAIRO_COMPOSITOR> m_compositor;  ///< Object for layers compositing
@@ -507,7 +515,7 @@ protected:
     wxEvtHandler*       m_mouseListener;       ///< Mouse listener
     wxEvtHandler*       m_paintListener;       ///< Paint listener
     unsigned int        m_bufferSize;          ///< Size of buffers cairoOutput, bitmapBuffers
-    unsigned char*      m_wxOutput;            ///< wxImage compatible buffer
+    wxBitmap*           m_wxBitmap;            ///< Output buffer bitmap
 
     // Variables related to Cairo <-> wxWidgets
     unsigned char*      m_bitmapBuffer;        ///< Storage of the Cairo image
diff --git a/include/gal/graphics_abstraction_layer.h b/include/gal/graphics_abstraction_layer.h
index f318d115dc..8b5a16fa29 100644
--- a/include/gal/graphics_abstraction_layer.h
+++ b/include/gal/graphics_abstraction_layer.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KICAD, a free EDA CAD application.
  *
  * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
- * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2024 KiCad Developers, see AUTHORS.txt for contributors.
  *
  * Graphics Abstraction Layer (GAL) - base class
  *
@@ -1038,7 +1038,8 @@ protected:
     UTIL::LINK           m_observerLink;
 
     std::stack<double>   m_depthStack;         ///< Stored depth values
-    VECTOR2I             m_screenSize;         ///< Screen size in screen coordinates
+    VECTOR2I             m_screenSize;         ///< Screen size in screen (wx logical) coordinates
+    VECTOR2I             m_bitmapSize;         ///< Bitmap size, in physical pixels
 
     double               m_worldUnitLength;    ///< The unit length of the world coordinates [inch]
     double               m_screenDPI;          ///< The dots per inch of the screen