MouseOver very slow when using QGraphicsView and many QGraphicsPixmapItem in QT6.4 and C++

128 views Asked by At

I render a QGraphicsScene with 4 million tiles (QGraphicsPixmapItem) and in the QGraphicsView I can scroll performantly. On mouseover I draw a rectangle over the tile, but this is shown VERY sluggishly and bounces back and forth. With a few tiles (10000) this behavior is super smooth. What can I improve/do here? Probably QT needs too long to find the corresponding tile at the mouse position? Is it possible to develop a custom functionality here to find the corresponding tile?

Minimal example: (You need a png-file (32x32) called tile_empty.png in your working-dir )

    #include <QApplication>
    #include <QFrame>
    #include <QGraphicsPixmapItem>
    #include <QGraphicsView>
    #include <QHBoxLayout>
    #include <QOpenGLWidget>
    #include <QStyleOption>
    
    class View : public QFrame
    {
        QGraphicsView * gView{ nullptr };
    public:
        explicit View( QWidget *parent = nullptr ) : QFrame( parent ),
                                                     gView( new QGraphicsView( this ) )
        {
            gView->setViewport( new QOpenGLWidget( ) );
            gView->setViewportUpdateMode( QGraphicsView::FullViewportUpdate );
            gView->setRenderHint( QPainter::Antialiasing, false );
            gView->setDragMode( QGraphicsView::RubberBandDrag );
            gView->setOptimizationFlags( QGraphicsView::DontSavePainterState );
            gView->setTransformationAnchor( QGraphicsView::AnchorUnderMouse );
            gView->setAlignment( Qt::AlignTop | Qt::AlignLeft );
    
            auto topLayout = new QGridLayout;
            topLayout->addWidget( gView, 1, 0 );
            setLayout( topLayout );
        }
    
        auto getView() const -> QGraphicsView& { return *gView; }
    };
    
    class Tile : public QGraphicsPixmapItem
    {
    public:
        explicit Tile( size_t x, size_t y, const QPixmap &graphic )
        {
            setZValue( 1 );
            setAcceptHoverEvents( true );
            setPixmap( graphic );
            setPos( QPointF( x * 32 , y * 32 ) );
        }
    protected:
        auto paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget ) -> void override
        {
            QGraphicsPixmapItem::paint( painter, option, widget );
    
            if ( !( option->state & QStyle::State_MouseOver ) )
                return;
    
            auto oldBrush = painter->brush();
            painter->setBrush( QColor( 128, 128, 128 ) );
            painter->drawRect( 0, 0, 32, 32 );
            painter->setBrush( oldBrush );
        }
    };
    
    class MainWindow : public QWidget
    {
        QGraphicsScene * scene{ nullptr };
    public:
        explicit MainWindow( QWidget *parent = nullptr ) : QWidget( parent ),
                                                           scene( new QGraphicsScene( this ) )
        {
            auto pixmap = new QPixmap( "tile_empty.png" );
            auto view   = new View();
            auto layout = new QHBoxLayout;
    
            for ( size_t  y=0; y < 2000; y++ )
                for ( size_t  x=0; x < 2000; x++ )
                    scene->addItem( new Tile( x, y, *pixmap ) );
    
            view->getView().setScene( scene );
            layout->addWidget( view );
            setLayout( layout );
            setWindowTitle( tr( "TileDemoSlow" ) );
        }
    };
    
    auto main( int argc, char *argv[] ) -> int
    {
        QApplication app( argc, argv );
        app.setAttribute( Qt::AA_DontCreateNativeWidgetSiblings );
        MainWindow window;
        window.show();
        return app.exec();
    }

CMake (Windows) :

    cmake_minimum_required(VERSION 3.21)
    
    project(TileDemoSlow VERSION 1.0
            DESCRIPTION "TileDemoSlow"
            LANGUAGES CXX)
    
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    set(CMAKE_AUTOUIC ON)
    
    add_executable(TileDemoSlow WIN32)
    
    if ( MSVC )
        set_target_properties(TileDemoSlow  PROPERTIES LINK_FLAGS "/ignore:4099")
        target_compile_options(TileDemoSlow  PRIVATE "/WX")
    endif()
    
    set_target_properties(TileDemoSlow  PROPERTIES
      CXX_STANDARD 20
      CXX_STANDARD_REQUIRED YES
      CXX_EXTENSIONS NO
    )
    set_target_properties(TileDemoSlow  PROPERTIES
      ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
      LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
      RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    )
    
    include(CheckIPOSupported)
    check_ipo_supported(RESULT result)
    if(result)
      set_target_properties(TileDemoSlow PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
    endif()
    
    find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets)
    qt_standard_project_setup()
    target_link_libraries(TileDemoSlow  PRIVATE Qt6::Core)
    target_link_libraries(TileDemoSlow  PRIVATE Qt6::Gui)
    target_link_libraries(TileDemoSlow  PRIVATE Qt6::OpenGL)
    target_link_libraries(TileDemoSlow  PRIVATE Qt6::OpenGLWidgets)
    target_link_libraries(TileDemoSlow  PRIVATE Qt6::Widgets)
    
    target_include_directories(TileDemoSlow  PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
    target_sources(TileDemoSlow  PRIVATE
      #Souces
      "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
    )

New insights 1:

I intercepted the events of the scene and measured the time and the GraphicsSceneMouseMove events seem to be performant. However, there is a very slow MetaCall in between, which is probably responsible for the jerking of the mouse pointer. Unfortunately I can't see in the debugger what kind of call it is. Does anyone have any ideas? Am I on the wrong track?

    template< typename EnumType >
    QString ToString( const EnumType& enumValue )
    {
        const char* enumName = qt_getEnumName( enumValue );
        const QMetaObject* metaObject = qt_getEnumMetaObject( enumValue );
        if (metaObject)
        {
            const int enumIndex = metaObject->indexOfEnumerator( enumName );
            return QString("%1::%2::%3").arg( metaObject->className(),
                                              enumName,
                                              metaObject->enumerator( enumIndex ).valueToKey( enumValue ) );
        }
    
        return QString("%1::%2").arg(enumName).arg(static_cast<int>(enumValue));
    }
    
    class Scene : public QGraphicsScene
    {
    public:
       explicit Scene( QWidget *parent = nullptr ) : QGraphicsScene( parent )
       {}
    
       bool event( QEvent *event ) override
       {
           auto start = std::chrono::high_resolution_clock::now();
            const bool ret = QGraphicsScene::event( event );
           auto stop = std::chrono::high_resolution_clock::now();
           auto duration = std::chrono::duration_cast< std::chrono::microseconds >( stop - start );
           std::cout << "EventType: " << ToString( event->type() ).toStdString() << " -> Duration: " << duration.count()
                     << " microseconds " << std::endl;
           return ret;
       }
    };

Output:

enter image description here

0

There are 0 answers