【问题标题】:Performant 2D plotting高性能二维绘图
【发布时间】:2016-09-27 09:13:46
【问题描述】:

我正在寻找绘图库(目前使用 Qt 作为 UI),但我的问题如下:我需要在画布上绘制 数百万 个非常小的矩形,并且能够放大画布并出去。要求:它必须非常快,最好是跨平台的。 “快速”是指在一秒钟内绘制数百万(或更多)矩形。

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

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

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

【问题讨论】:

  • 返回并花时间了解更多 OpenGL。你的 OpenGL 代码有多慢?您的矩形列表是否每秒都在变化(?!?),还是您只需要在一秒钟内创建 3D 图形,然后允许用户根据需要滚动和放大和缩小? (如果您正在创建一个用户可以放大到数百万上下文中的任何一个矩形的缩放用户界面,那么您将遇到一些解决起来很有趣的 UI/UX 问题。)一起,“快速" 和 "graphics" 和 "zoom" 应该会引导您在独立显卡上以保留模式运行 OpenGL。
  • 在现代 GPU 上,您应该能够以 60FPS 的速度绘制这些矩形,而且代码应该适合什么 - 150 行?也许我应该证明这一点,它根本不应该是困难的,无论如何你应该抽象出任何“低”级别。

标签: qt plot 2d heap-memory visualization


【解决方案1】:

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

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

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

#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 并使其更快。

【讨论】:

  • 我在实际应用中测试过,180万个矩形在我的硬件(专用GPU,6核CPU)上用drawRects在7秒内串行渲染。
  • 抱歉,忘记说Debug模式下7秒,Release模式下2秒左右。但还是不够,想一些优化。
  • @unsigned 跨 6 个核心或仅在一个核心上需要 2 秒? QImage 的格式是什么(它可以产生 巨大的 差异)。另请注意,这些矩形太小而无法看到,因此当用户放大时,即使您让QPainter 为您完成所有剪辑,它也会快得多。对于QImage 上的光栅绘制引擎,GPU 根本不重要。它实际上什么也没做。
  • 一个核心需要2秒,QImage Format_ARGB32_Premultiplied,(我也试过QPixmap,但速度不快) .
  • 默认栅格后端上的 QPixmap 实际上在内部只是某种格式的 QImage,因此在这里没有帮助也没有区别。
猜你喜欢
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 2011-01-05
  • 2016-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多