Performance issue
-
Hi,
The refresh rate when I do a simple drawing on the whole screen is unable to follow the mouse. I fear I'm missing something. Here is a simple program which demonstrate the issue:
TEMPLATE = app TARGET = qtperf INCLUDEPATH += . QT += core widgets gui HEADERS += qtperf.hpp SOURCES += qtperf.cpp
qtperf.hpp:
#include <QWidget> class TestPerf: public QWidget { Q_OBJECT public: TestPerf(QWidget* parent=0, Qt::WindowFlags flags=0); protected: void paintEvent(QPaintEvent*) override; void mousePressEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; private: QPoint mTop; QPointF mLastPosition; bool mShowGrid; };
qtperf.cpp:
#include <QApplication> #include <QPainter> #include <QMouseEvent> #include "qtperf.hpp" int const gGridSize = 100; TestPerf::TestPerf(QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), mTop(0.0, 0.0), mLastPosition(0.0, 0.0), mShowGrid(false) { setMouseTracking(true); } void TestPerf::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setPen(Qt::black); if (mShowGrid) { for (int i = mTop.x(); i < size().width(); i += gGridSize) { painter.drawLine(i, 0, i, size().height()); } for (int i = mTop.y(); i < size().height(); i += gGridSize) { painter.drawLine(0, i, size().width(), i); } } painter.drawRect(QRectF(mLastPosition - QPointF(3, 3), QSizeF(6, 6))); } void TestPerf::mousePressEvent(QMouseEvent* event) { auto oldShowGrid = mShowGrid; mShowGrid = event->buttons() == Qt::RightButton; if (mShowGrid != oldShowGrid) { update(); } } void TestPerf::mouseReleaseEvent(QMouseEvent* event) { auto oldShowGrid = mShowGrid; mShowGrid = event->buttons() == Qt::RightButton; if (mShowGrid != oldShowGrid) { update(); } } void TestPerf::mouseMoveEvent(QMouseEvent* event) { switch (event->buttons()) { case Qt::LeftButton: update(mLastPosition.x()-3, mLastPosition.y()-3, 7, 7); mLastPosition = event->localPos(); update(mLastPosition.x()-3, mLastPosition.y()-3, 7, 7); break; case Qt::RightButton: { auto top = mTop + event->localPos() - mLastPosition; mTop = QPoint(top.x(), top.y()); mTop.rx() = mTop.x() % gGridSize; mTop.ry() = mTop.y() % gGridSize; mLastPosition = event->localPos(); update(); } break; default: mLastPosition = event->localPos(); update(); } } int main(int argc, char** argv) { QApplication app(argc, argv); app.setAttribute(Qt::AA_CompressHighFrequencyEvents); TestPerf mainWin; mainWin.show(); return app.exec(); }
Run it, maximize it and move the mouse. The small square is unable to keep up with the mouse. If I limit the zone where redraw happen, there is no issue (press the left mouse button to demonstrate). But that's something which is not always possible. For instance if there is a grid which has to move in sync with the mouse (press the right mouse button to demonstrate).
I find this strange when the system is able to display video without problems.
At the very least, I'd like QT to compress the mouse move event when there is too much to do between two of them. But setting the attribute AA_CompressHighFrequencyEvents doesn't help.
What am I missing?
System info: Ubuntu 20.04, using the bundled Qt 5.12.8.
Display size: 1920x1200
24 planes
Available X Extensions:BIG-REQUESTS Composite DAMAGE DOUBLE-BUFFER DPMS DRI2 DRI3 GLX Generic Event Extension MIT-SCREEN-SAVER MIT-SHM Present RANDR RECORD RENDER SECURITY SHAPE SYNC X-Resource XC-MISC XFIXES XFree86-DGA XFree86-VidModeExtension XINERAMA XInputExtension XKEYBOARD XTEST XVideo
Edit: I've written a simple XLib program which has the same "drag a grid" and there is no lag between the mouse pointer and the grid.
Edit: switching from xorg to nVidia drivers solved the lag issue. I still wonder what explain the difference between Qt and the XLib program with xorg, but I don't have a usability problem with my main program. In case someone is interested in investigating further, I've a github repo with programs showing the issue : https://github.com/nhamblenne/qtperf.
Yours,
-
I've 20-30% cpu usage with right click + mouse move in my linux virtualbox vm. I would say this is ok.
-
Well when you call update() on EVERY mouse event it's going to bog down. You need to be more intelligent about when repaints are really necessary.
-
@Kent-Dorfman What you say makes sense at a high level but as someone who is pretty new to Qt and GUI development in general I am wondering what practical strategies one might employ to avoid too many repaints.
One thought I have is to set a timer after handling a mouse event and do not repaint on events received while the timer is active. When the timer times out, then repaint if the position has changed in the meantime. Is that a stupid approach? It might be tricky to get the timer interval right - otherwise a permanent lag is being built in. However, it seems to me that there has to be some concept of measuring events vs time somehow in order to decide not to act upon some of them.
-
@Nicolas-I-Hamblenne said in Performance issue:
default:
mLastPosition = event->localPos();
update();
}
}Here's one to start. Why call update if you are not holding a mouse button?
Also, yes. drawing the primitives on every move is expensive. Often times implementors will simply draw a liteweight primitive while the button is being held and only redraw the complete scene at the end of hte movement...so yes, think about strategies to more intelligently update only when necessary.
-
@Kent-Dorfman The windows manager is able to drag a big window without getting this lag between the mouse cursor and the window. Thus it seems to me that there is an issue in what I'm doing. And it isn't the time spend in paint, that's about 1/30th of the time spent outside when I'm dragging.
Being more intelligent is easiest said than done. I'm already minimizing the area which is updated, but at time I've to redraw a big portion of the screen, that's what I tried to show with the grid. I've tried to drag a pixmap, but that isn't better.
The true issue is really the lag. If I set the cursor blank and I draw myself a cursor so there is no apparent lag the effect is far better. But I've not yet found how to draw the standard cursor and I don't feel redesigning them.
-
Here's one to start. Why call update if you are not holding a mouse button?
To demonstrate the issue in the simplest way. The closest of the application behavior that this example shows is when the right mouse button is pressed. I already reduce the refreshed zone as much as I can, even experimenting fracturing the bounding box of the invalidated area it so that the area to be drawn is smaller.
But a window manager is able to drag a window and shows its content moving without lagging behind the mouse. A web browser is able to refresh a whole image when I drag the scrollbar without apparent lag. Is it so strange that I want to do the same?
But as I wrote in my other answer, the true issue is the lag between the cursor position and what is redraw. If I put a blank cursor and draw my own, (even if I put it at QCursor::pos and not at the event position as the two rarely differ) the effect is far better.