Drawing thousands of rects quickly

2019-06-14 07:55发布

What is the proper way to draw thousands of rects in QT (around 100,000 or more)?

I tried:

  • Simple with paintEvent() of QWidget.
  • Drawing objects to QImage then this image to QWidget.
  • Using QGraphicsScene (maybe I didn't use it properly, I just added rects to scene)

Every time drawing was really slow and I don't have more ideas on how to do this (maybe with opengl/directx but this doesn't sound like a good idea). I know that there exist applications that do that so there should be some way.

EDIT:

I wonder how drawRects() work? Is there a chance that filling some uchar* array and passing it to QImage will be better?

标签: qt drawing scene
2条回答
SAY GOODBYE
2楼-- · 2019-06-14 08:21

Solution which I found:

Create array of uint32_t which can contain all pixels of widget, fill it using memcpy(). Create QImage with this array and use drawImage() to show it. It can have some optimization like (for profiler) merging rects that are continues ( start time second is equal to end of first ). Don't draw rects that are out of time bounds. Maybe skip too small rects. For drawing things like text, tool tips you can still use Qt functions. For alpha blending in simplest case you can just take existing values, blend them in loop and write blended values or maybe use SIMD for this.

Of course for more complex shapes it will get harder to draw but still, I think, it will be faster than using Qt functions.

查看更多
淡お忘
3楼-- · 2019-06-14 08:26

The first trick is to do the drawing in a separate thread onto a QImage, then pass that into the main thread. This won't make it quicker, but it'll make it not block the GUI thread.

// https://github.com/KubaO/stackoverflown/tree/master/questions/threaded-paint-36748972
#include <QtWidgets>
#include <QtConcurrent>

class Widget : public QWidget {
   Q_OBJECT
   QImage m_image;
   bool m_pendingRender { false };
   Q_SIGNAL void hasNewRender(const QImage &);
   // Must be thread-safe, we can't access the widget directly!
   void paint() {
      QImage image{2048, 2048, QImage::Format_ARGB32_Premultiplied};
      image.fill(Qt::white);
      QPainter p(&image);
      for (int i = 0; i < 100000; ++i) {
         QColor c{rand() % 256, rand() % 256, rand() % 256};
         p.setBrush(c);
         p.setPen(c);
         p.drawRect(rand() % 2048, rand() % 2048, rand() % 100, rand() % 100);
      }
      emit hasNewRender(image);
   }
   void paintEvent(QPaintEvent *) {
      QPainter p(this);
      p.drawImage(0, 0, m_image);
   }
public:
   Widget(QWidget * parent = 0) : QWidget(parent) {
      this->setAttribute(Qt::WA_OpaquePaintEvent);
      setMinimumSize(200, 200);
      connect(this, &Widget::hasNewRender, this, [this](const QImage & img) {
         m_image = img;
         m_pendingRender = false;
         update();
      });
      refresh();
   }
   Q_SLOT void refresh() {
      if (!m_pendingRender) {
         m_pendingRender = true;
         QtConcurrent::run([this] { paint(); });
      }
   }
};

int main(int argc, char ** argv) {
   QApplication app{argc, argv};
   Widget w;
   QPushButton button{"Refresh", &w};
   button.connect(&button, &QPushButton::clicked, &w, [&w]{ w.refresh(); });
   w.show();
   return app.exec();
}

#include "main.moc"

As a separate concern, you can then split the drawing across multiple parallel jobs, by clipping each job's painter to a sub-area of the shared image, and noting that fully clipped rectangle draws are no-ops, and partially clipped ones will only fill the pixels they affect.

查看更多
登录 后发表回答