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: