How to optimize QGraphicsView performance?
-
I want to make an application to simulate LED screen, which can display some text and scroll. So I came up with the idea of using
QGraphView
. I can draw a Ellipse to represent an LED lamp bead. I make theGrid
class, which is used to layout these Ellipses. First, I will call thegenerate_grid
method to generate each Ellipse. Then, call theupdate_pixel
method to display the text. It can work perfectly. But when i want to scroll the text, the CPU usage will be high, and the graphics will not be smooth. My Cpu isIntel(R) Core(TM) i7-10510U CPU @ 1.80GHz 2.30 GHz
.
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class QTimer; class QGraphicsScene; class QGraphicsView; class QGraphicsEllipseItem; class Grid final : public QObject { Q_OBJECT public: explicit Grid(QGraphicsScene * scene, QObject * parent = Q_NULLPTR) : QObject(parent), m_scene(scene) {} virtual ~Grid() {} inline uint get_gap() const { return this->m_gap; } inline uint get_pixel_width() const { return m_grid_size.width(); } inline uint get_pixel_height() const { return m_grid_size.height(); } void generate_grid(const QColor & color); void update_pixel(uint x, uint y, const QColor & color); private: static constexpr uint default_size = 10; static constexpr uint default_gap = default_size + 2; static constexpr uint default_start_x = 10; static constexpr uint default_start_y = 10; uint m_size = default_size; uint m_gap = default_gap; uint m_startX = default_start_x; uint m_startY = default_start_y; QSize m_grid_size = {32, 150}; QGraphicsScene * m_scene; QVector<QGraphicsEllipseItem *> m_items; }; /***********************************************************/ struct varSection final { uint offset = 0; uint width = 0; uint height = 0; uint real_width = 0; QString str_point; uint get_max_width() const { return std::max(this->real_width, this->width); } }; class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); virtual void resizeEvent(QResizeEvent *event) override; private slots: void handle_section_move(); private: void init_widget(); private: Grid * m_grid = Q_NULLPTR; QTimer * m_timer = Q_NULLPTR; QGraphicsView * m_view = Q_NULLPTR; varSection m_section; }; #endif // WIDGET_H
#include "widget.h" #include <QGraphicsEllipseItem> #include <QGraphicsScene> #include <QGraphicsView> #include <QDebug> #include <QTimer> void Grid::generate_grid(const QColor & color) { for (int cur = m_items.size(); cur <= m_grid_size.height() * m_grid_size.width(); ++cur) { QGraphicsEllipseItem * pItem = this->m_scene->addEllipse(QRect()); m_items.push_back(pItem); } for (int i = 0; i < m_grid_size.height(); i++) { for (int j = 0; j < m_grid_size.width(); j++) { int idx = i * m_grid_size.width() + j; if (idx >= m_items.size()) continue; QGraphicsEllipseItem * pItem = m_items[i * m_grid_size.width() + j]; if (pItem) { pItem->setRect(QRect(m_startX + m_gap * i, m_startY + m_gap * j, m_size, m_size)); pItem->setPen(color); pItem->setBrush(color); pItem->show(); } } } } void Grid::update_pixel(uint x, uint y, const QColor & color) { int idx = x * m_grid_size.width() + y; if (idx < 0 || idx >= m_grid_size.height() * m_grid_size.width()) return; m_items[idx]->setPen(color); m_items[idx]->setBrush(color); } /******************************************************************************/ Widget::Widget(QWidget *parent) : QWidget(parent) { this->init_widget(); } Widget::~Widget() { } void Widget::init_widget() { m_view = new QGraphicsView(this); m_view->setCacheMode(QGraphicsView::CacheBackground); m_view->setOptimizationFlag(QGraphicsView::DontSavePainterState, true); m_view->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true); // m_view->setViewportUpdateMode(QGraphicsView::NoViewportUpdate); m_view->resize(QSize(this->width(), this->height())); QGraphicsScene * scene = new QGraphicsScene(this); scene->setItemIndexMethod(QGraphicsScene::NoIndex); scene->addEllipse(QRect(0, 0, 1, 1)); m_view->setScene(scene); m_grid = new Grid(scene, this); m_grid->generate_grid(Qt::gray); m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &Widget::handle_section_move); m_timer->start(1000 / 30); m_section.str_point = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000011110001100001100011000000001100011000000000000011001100110000001111001100000000000" "00000000000000011000000000000110110000000000000000000000000000000011001100110001100001100000000110011000000000000001100110110000" "00110011011000000000000000000000000001100000000000011011000000000000000000000000000000001100000011000000000000000000000000000000" "00000000011001100000000011000001100000000000000000000000000110000000000000001100000000000000000000000000000000111000011110011110" "01100111100110110011011011000001100110111000001100000111110001111001101100011111011111000111100110110000111100000000000000000000" "00000111000011000000110110110011011011001101110110000110011001100000110000011001101100110111011011001101100110000011011011000110" "01100000000000000000000000001110001100000011011011100001101100110110011000011001100110000011000001100110110011011001101100110110" "01100000110110110001100110000000000000000000000000011100110001111101100111100110110011011001100001100110011000001100000110011011" "00110110011011001101100110011111011011000111111000000000000000000000000000110011001100110110000111011011001101100110000110011001" "10000011000001100110110011011001101100110110011011001101101100011000000000000000000000000000000011001100110011011000001101101100" "11011001100001100110011000001100000110011011001101100110110011011001101100110110110001100000000000000000000000000011001100110011" "00110110110011011011001101100110000110011001100000110011011001101100110110011011001101100110110011011011100110000000000000000000" "00000000011110000110011111011001111001100111110110011000001111001111000001111001100110011110011001100111110110011001111101100111" "00111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; m_section.width = 150; m_section.height = 32; m_section.real_width = 131; this->resize(1500, 500); } void Widget::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); m_view->resize(QSize(this->width(), this->height())); } void Widget::handle_section_move() { uint offset = m_section.offset % (m_section.real_width + m_section.width + 1); for (int i = 0; i < m_section.str_point.size(); ++i) { uint posx = (i % m_section.get_max_width()) + m_section.width - offset; uint posy = (i / m_section.get_max_width()); if (posx < 0 || posy < 0 || posx >= 150 || posy >= 32) continue; m_grid->update_pixel(posx, posy, m_section.str_point[i] == '1' ? Qt::blue : Qt::gray); } m_section.offset++; }
Upper codes can be copied to other projects for running. Please ignore the source of
m_section
, which comes from another project of mine. The code here is only used to demonstrate this problem.
If I revise theupdate_pixel
method, as follow.void Grid::update_pixel(uint x, uint y, const QColor & color) { int idx = x * m_grid_size.width() + y; if (idx < 0 || idx >= m_grid_size.height() * m_grid_size.width()) return; //m_items[idx]->setPen(color); //m_items[idx]->setBrush(color); }
the CPU usage will be lower. I know that
QPainter
will eventually be called here to render graphics. I need to scroll at 30 steps per second or faster. Maybe it will be scrolling more text, depending on the user. How can I optimize this program or any other solution? -
I don't have a "do this, then it works" answer. However, there are a few things you could try to improve performance:
- Set Qt::NoPen and just set the brush. Saves a lot on the painting, and also on painter state changes
- Try setting ItemIgnoresParentOpacity flag on the LED item.
- Do you need to scale your scene? If you can create the items in the correct size for screen display, you could also set ItemIgnoresTransformations, this should save some calculations
- Try whether rectangles are a lot faster than ellipses. If they are, you can maybe approximate the small ellipses you need using 3-5 rectangles each
- Alternatively, try drawing ALL your LEDs in a single custom QGraphicsItem. That way, you can first draw all gray LEDs, then all blue LEDs, which saves a lot of painter state changes.
From my perspective, the first and last suggestions are the most promising.
In any case, keep in mind that GraphicsView is a pure single-thread CPU thing. So it can't make proper use of modern computer architectures. For that, you'd need QML, ideally with custom SceneGraph classes that are more lightweight than normal QML items. But that's a tall order, given that documentation and examples in that area are thin.
-
@Asperamanca I really appreciate your suggestions. I will try it one by one.
-
I don't know as much about QGraphicsView, the above comments seem like good ideas. However, are you scrolling the text by clearing and painting pixels in each column at each time step? If so maybe instead generate an image bitmap wider than the view, use a set of update_pixel() calls to draw the text offscreen once, then to animate the scrolling effect, just translate the whole image bitmap at each step interval?
-
@reedhedges said in How to optimize QGraphicsView performance?:
I don't know as much about QGraphicsView, the above comments seem like good ideas. However, are you scrolling the text by clearing and painting pixels in each column at each time step? If so maybe instead generate an image bitmap wider than the view, use a set of update_pixel() calls to draw the text offscreen once, then to animate the scrolling effect, just translate the whole image bitmap at each step interval?
Good idea! Let me expand on it:
Why not paint the whole text into a large QPixmap at runtime (using QPainter), and then simply move it at the required speed? -
@reedhedges Yep, it's really a good idea. but it doesn't suit to my situation. It's my demo that didn't demonstrate clearly. We will divide a screen into multiple areas, and then perform different operations on multiple areas. Thanks for your reply. Maybe I will use this idea in the future.