高性能2D绘图

时间:2016-05-29 13:25:57

标签: qt plot 2d heap visualization

我正在寻找绘图库(目前使用Qt作为UI),但我的问题如下:我需要在画布上绘制数百万的非常小的矩形,并且能够缩放画布和出。要求:它必须非常快,最好是跨平台。随着"快速"我的意思是在不到一秒的时间内绘制几百万(或更多)矩形。

到目前为止我尝试过的所有东西都太慢了。我一直在使用Qt + OpenGL,但它有点太低级了,反正很慢(可能是由于缺乏OpenGL经验)。还有qcustomplot,不幸的是太迟钝了。甚至尝试过HTML + Canvas作为实验。

对于那些想知道为什么我有这样的要求并且更好地了解我需要的人,请参阅此演示文稿:Heap Visualisation Tools(参见幻灯片10到17和28-29)。

你有什么建议?什么工具/库/框架能够绘制数百万个矩形而没有明显的性能问题?如果有足够快的JavaScript库,我甚至可以使用web。也许在引擎盖下使用LOD的东西。

1 个答案:

答案 0 :(得分:0)

TL; DR:你应该能够在4核系统上一秒钟内绘制大约1000万个小矩形,只使用QPainter::drawRect上的QImage

绘制一百万个矩形并不是什么大问题,典型的1920x1080屏幕有大约200万像素,所以绘制"小"矩形就像是单独写每对像素,或者平均每个矩形写8个字节。

在我的特定系统上,运行Qt 5.6的i5 iMac,需要约1/3秒来绘制100万个小矩形:

#include <QtWidgets>
int main(int argc, char ** argv) {
  QApplication app{argc, argv};
  QImage image(1920, 1080, QImage::Format_ARGB32_Premultiplied);
  QPainter p(&image);
  QElapsedTimer timer;
  timer.start();
  int n = 0;
  for (int y = 0; y < image.height(); ++y)
    for (int x = 0; x < image.width(); x+=2) {
      ++ n;
      p.drawRect(x, y, 2, 1);
    }
  p.end();
  qDebug() << n << timer.elapsed();
}

如果您愿意,可以跨多个线程并行化绘图。

// https://github.com/KubaO/stackoverflown/tree/master/questions/qimage-rectangles-37510435
#include <QtWidgets>
#include <QtConcurrent>

QVector<QRect> rects(const QSize & size) {
  QVector<QRect> rs;
  for (int y = 0; y < size.height(); ++y)
    for (int x = 0; x < size.width(); x+=2)
      rs.append(QRect(x, y, 2, 1));
  return rs;
}

QImage render(const QVector<QRect> & rects, const QSize & size, const QPair<int,int> range)
{
  QImage image(size, QImage::Format_ARGB32_Premultiplied);
  image.fill(Qt::transparent);
  QPainter p(&image);
  QElapsedTimer timer;
  timer.start();
  const int n = range.second-range.first;
  for (int i = range.first; i < range.second; ++i)
    p.drawRect(rects[i]);
  p.end();
  qDebug() << n << timer.elapsed();
  return image;
}

struct Render {
  const QVector<QRect> & rects;
  const QSize & size;
  typedef QImage result_type;
  QImage operator()(const QPair<int,int> range) { return render(rects, size, range); }
  Render(QVector<QRect>& rects, const QSize& size) : rects(rects), size(size) {}
};

template <typename Seq>
QVector<QPair<int,int>> partition(const Seq & s, int n)
{
  QVector<QPair<int,int>> ps;
  ps.reserve(n);
  int begin = 0;
  for (int i = 0; i < n; ++i) {
    int end = (s.count() * (i+1))/n;
    ps.append(qMakePair(begin, end));
    begin = end;
  }
  return ps;
}

void combine(QImage & result, const QImage & source)
{
  if (result.isNull()) {
    result = source;
    return;
  }
  QPainter p(&result);
  p.drawImage(0, 0, source);
}

int main(int argc, char ** argv) {
  QApplication app{argc, argv};
  QSize size{1920, 1080};
  auto rs = rects(size);
  auto ranges = partition(rs, QThread::idealThreadCount());
  QElapsedTimer t;
  t.start();
  QtConcurrent::blockingMappedReduced(ranges, Render(rs, size), combine);
  qDebug() << "parallel time" << t.elapsed() << "ms";
  t.restart();
  render(rs, size, qMakePair(0, rs.count()));
  qDebug() << "serial time" << t.elapsed() << "ms";
}

输出:

259200 94
259200 97
259200 102
259200 102
parallel time 112 ms
1036800 360
serial time 362 ms

如果你可以将矩形分组到具有相同笔/笔的组中,那么请利用drawRects比重复调用drawRect更快的事实(在我的机器上快约20%)。

您也可以自己实现一个未经转化的drawRect,并使其更快。