如何优化QPainter::drawPolyline()?超10万点折线绘制性能优化
嘿,看你在QQuickPaintedItem子类里绘制十万+点的折线,目前的实现肯定会有性能瓶颈,咱们一步步来优化:
1. 先解决数据结构的遍历开销
你现在用QLinkedList<QPointF>存储数据,然后通过foreach循环手动把点赋值到QPolygonF里——这可是个大坑!QLinkedList是链表结构,每次访问元素都是O(n)的时间复杂度,而且内存不连续,CPU缓存命中率极低,遍历十万个点会慢得离谱。
优化方案:
把数据换成QVector<QPointF>存储(如果没办法改数据源,就直接转成QVector再处理),QVector是连续内存,访问和遍历效率高得多。而且QPolygonF可以直接用QVector构造,省去手动循环的步骤:
// 替换原来的手动赋值代码 QVector<QPointF> dataVec = data.toVector(); // 如果data是QLinkedList的话 QPolygonF polyline; polyline.reserve(dataVec.size()); // 提前预分配内存,避免多次扩容 for (const auto& point : dataVec) { polyline.append(transformToCoord(point)); } // 或者更简洁的方式,用std::transform std::transform(dataVec.begin(), dataVec.end(), std::back_inserter(polyline), [this](const QPointF& p) { return transformToCoord(p); });
2. 把坐标转换移出paint函数
paint()函数会被频繁调用(比如窗口刷新、元素移动时),每次都对十万个点做transformToCoord转换,纯粹是在浪费CPU资源!
优化方案:
在数据更新的时候(比如收到新数据、视图变换时)提前完成坐标转换,把转换后的QPolygonF缓存成类的成员变量,paint的时候直接拿缓存好的结果来绘制:
// 类成员变量 QPolygonF m_cachedPolyline; // 数据更新时调用 void Plotter::updateCurveData(const QLinkedList<QPointF>& rawData) { QVector<QPointF> dataVec = rawData.toVector(); m_cachedPolyline.clear(); m_cachedPolyline.reserve(dataVec.size()); for (const auto& point : dataVec) { m_cachedPolyline.append(transformToCoord(point)); } } // paint里的drawCurves简化成 void Plotter::drawCurves(QPainter *painter) { // ... QTime timer; timer.start(); painter->drawPolyline(m_cachedPolyline.constData(), m_cachedPolyline.size()); qDebug() << "绘制耗时:" << timer.elapsed() << "ms"; }
3. 终极优化:抛弃QQuickPaintedItem,用场景图硬件加速
QQuickPaintedItem本质是软件渲染——CPU绘制好图像再上传到GPU显示,十万个点的折线会让CPU直接拉满。而Qt Quick的场景图(QSG)是硬件加速的,直接让GPU处理绘制任务,性能提升不是一点点。
实现思路:
让你的Plotter继承QQuickItem,重写updatePaintNode()方法,用QSGGeometryNode来绘制折线:
// 自定义场景图节点 class PlotCurveNode : public QSGGeometryNode { public: PlotCurveNode() { // 使用Point2D属性,设置绘制模式为折线 m_geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0); m_geometry->setLineWidth(1); m_geometry->setDrawingMode(GL_LINE_STRIP); setGeometry(m_geometry); // 设置颜色材质 m_material = new QSGFlatColorMaterial; setMaterial(m_material); setFlag(OwnsGeometry); setFlag(OwnsMaterial); } // 更新几何数据 void updatePoints(const QVector<QPointF>& points) { m_geometry->allocate(points.size()); QSGGeometry::Point2D* vertices = m_geometry->vertexDataAsPoint2D(); for (int i = 0; i < points.size(); ++i) { vertices[i].x = points[i].x(); vertices[i].y = points[i].y(); } markDirty(QSGNode::DirtyGeometry); } // 设置折线颜色 void setColor(const QColor& color) { m_material->setColor(color); markDirty(QSGNode::DirtyMaterial); } private: QSGGeometry* m_geometry; QSGFlatColorMaterial* m_material; }; // 你的Plotter类 class Plotter : public QQuickItem { Q_OBJECT // ... 其他成员 private: QVector<QPointF> m_cachedPoints; // 提前转换好的坐标点 protected: QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override { PlotCurveNode* node = static_cast<PlotCurveNode*>(oldNode); if (!node) { node = new PlotCurveNode; node->setColor(Qt::darkBlue); } // 更新节点的几何数据 node->updatePoints(m_cachedPoints); return node; } };
这种方式下,GPU会直接处理顶点数据的绘制,十万个点的折线几乎能瞬间完成,CPU占用也会降到很低。
最后再提个小细节
如果你的折线不需要抗锯齿,可以在QPainter里关闭它:painter->setRenderHint(QPainter::Antialiasing, false);,能再省一点性能。
内容的提问来源于stack exchange,提问作者Nikita

