快速绘制数千个矩形
Drawing thousands of rects quickly
在QT中画几千rects
的正确方法是什么(大约100,000或更多)?
我试过了:
- 简单
paintEvent()
个 QWidget
。
- 正在将对象绘制到
QImage
,然后将此图像绘制到 QWidget
。
- 使用
QGraphicsScene
(可能是我没用好,只是在场景中添加了rects
)
每次绘图都非常慢,我没有更多关于如何做到这一点的想法(也许 opengl/directx 但这听起来不是个好主意)。我知道存在这样做的应用程序,所以应该有一些方法。
编辑:
我想知道 drawRects()
是如何工作的?是否有可能填充一些 uchar*
数组并将其传递给 QImage
会更好?
第一个技巧是在单独的线程中将绘图绘制到 QImage
上,然后将其传递到主线程中。这不会使其更快,但不会阻塞 GUI 线程。
// https://github.com/KubaO/Whosebugn/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"
作为一个单独的问题,您可以将绘图拆分到多个并行作业中,方法是将每个作业的画家剪裁到共享图像的一个子区域,并注意完全剪裁的矩形绘图是空操作,部分剪裁的只会填充它们影响的像素。
我找到的解决方案:
创建 uint32_t
的数组,其中可以包含 widget
的所有像素,使用 memcpy()
填充它。使用此数组创建 QImage
并使用 drawImage()
显示它。
它可以有一些优化,比如(对于探查器)合并 rects
继续(开始时间第二等于第一结束)。不要绘制超出时间范围的 rects
。可能跳过太小 rects
。
对于绘制文本、工具提示等内容,您仍然可以使用 Qt
函数。
对于最简单情况下的 alpha 混合,您可以只获取现有值,将它们混合在循环中并写入混合值,或者为此使用 SIMD
。
当然,对于更复杂的形状,它会变得更难绘制,但我认为,它仍然比使用 Qt
函数更快。
在QT中画几千rects
的正确方法是什么(大约100,000或更多)?
我试过了:
- 简单
paintEvent()
个QWidget
。 - 正在将对象绘制到
QImage
,然后将此图像绘制到QWidget
。 - 使用
QGraphicsScene
(可能是我没用好,只是在场景中添加了rects
)
每次绘图都非常慢,我没有更多关于如何做到这一点的想法(也许 opengl/directx 但这听起来不是个好主意)。我知道存在这样做的应用程序,所以应该有一些方法。
编辑:
我想知道 drawRects()
是如何工作的?是否有可能填充一些 uchar*
数组并将其传递给 QImage
会更好?
第一个技巧是在单独的线程中将绘图绘制到 QImage
上,然后将其传递到主线程中。这不会使其更快,但不会阻塞 GUI 线程。
// https://github.com/KubaO/Whosebugn/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"
作为一个单独的问题,您可以将绘图拆分到多个并行作业中,方法是将每个作业的画家剪裁到共享图像的一个子区域,并注意完全剪裁的矩形绘图是空操作,部分剪裁的只会填充它们影响的像素。
我找到的解决方案:
创建 uint32_t
的数组,其中可以包含 widget
的所有像素,使用 memcpy()
填充它。使用此数组创建 QImage
并使用 drawImage()
显示它。
它可以有一些优化,比如(对于探查器)合并 rects
继续(开始时间第二等于第一结束)。不要绘制超出时间范围的 rects
。可能跳过太小 rects
。
对于绘制文本、工具提示等内容,您仍然可以使用 Qt
函数。
对于最简单情况下的 alpha 混合,您可以只获取现有值,将它们混合在循环中并写入混合值,或者为此使用 SIMD
。
当然,对于更复杂的形状,它会变得更难绘制,但我认为,它仍然比使用 Qt
函数更快。