From 8e90063258cb457f43db78334ba5bf52ad78e083 Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Sat, 8 Jun 2024 12:45:46 +0300 Subject: [PATCH] Support hidpi in Cairo GAL canvas; performance improvements. We now draw onto wxBitmap directly, reducing the amount of copying. Also moves the bitmap blit into paint event handler. Modifies ClearScreen to work universally between Cairo/OpenGL backends. --- common/draw_panel_gal.cpp | 14 +-- common/gal/cairo/cairo_gal.cpp | 111 ++++++++++++++--------- include/gal/cairo/cairo_gal.h | 12 ++- include/gal/graphics_abstraction_layer.h | 5 +- 4 files changed, 82 insertions(+), 60 deletions(-) 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 * @author Maciej Suminski @@ -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 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 @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -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 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 * @@ -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 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 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 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