Qt|绘制100万个图元大规模场景
参考:QT性能优化之QT6框架高性能图形视图框架快速展示百万图元大规模场景
文章目录
- 前因
- 关键点
- 代码放最后
前因
前天参加一个面试问我使用qt100万个数据怎么优化内存。
上篇写的很好,参考着完成了一下,视频指路B站,效果如下图:
关键点
- QGraphicsView类型本身提供了场景整体的几何变换能力。这使得QT图形视图框架不仅支持图形条目图元级别的几何变换,还支持图形场景整体的几何变换。这是普通QWidget窗口没有直接的功能。
- 这个应用程序直接在初始化时创建了100万个图形条目,占用了较多内存。整个场景中的图形条目数量巨大,无法在完整展现在窗口可见区域,必须拖动滚动条才能看到各个区域的图形条目。如果能够进一步优化程序,使得初始化时只创建出可见区域中的图形条目,在场景滚动过程中动态的创建所需的可见区域的图形条目,那么程序的初始化时间成本和内存消耗成本可能能够得到大幅度的降低。(当然这个看需求,显示窗口有多大,如果需要全部显示还是初始化创建,避免过多的创建和销毁的操作。)
- 线程池的使用 每列中的所有网格使用一个线程进行初始化
从整体上来讲,QT图形视图框架包含了三个组成部分:
图形场景(QGraphicsScene):负责图形场景的整体控制,以及场景中的图形条目的管理工作。
图形条目(QGraphicsItem):负责具体图元的绘制和几何变换控制以及事件处理。
图形视图(QGraphicsView):负责图形场景的用户界面交互操作。
代码放最后
工程总览:
主函数:
#include "BaiWanYuanWidget.h"
#include <QtWidgets/QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);BaiWanYuanWidget w;w.show();return a.exec();
}
创建工程自带的QMainWindow,里面只创建了一个创建百万圆类:
.h
#pragma once#include <QtWidgets/QMainWindow>
#include "ui_BaiWanYuanWidget.h"
#include "CreatRoundWidget.h"class BaiWanYuanWidget : public QMainWindow
{Q_OBJECTpublic:BaiWanYuanWidget(QWidget *parent = nullptr);~BaiWanYuanWidget();private:Ui::BaiWanYuanWidgetClass ui;CreatRoundWidget* round_widget;
};
.cpp
#include "BaiWanYuanWidget.h"BaiWanYuanWidget::BaiWanYuanWidget(QWidget *parent): QMainWindow(parent)
{ui.setupUi(this);round_widget = new CreatRoundWidget();setCentralWidget(round_widget);
}BaiWanYuanWidget::~BaiWanYuanWidget()
{}
最关键的创建百万圆类:
.h
#pragma once
#include <qwidget.h>class QGraphicsScene;
class QGraphicsView;
class QGraphicsItem;
class QLabel;
class QThreadPool;class CreatRoundWidget :public QWidget
{Q_OBJECTpublic:CreatRoundWidget(QWidget* parent = nullptr);~CreatRoundWidget();signals:// 场景初始化部分完成的信号void SignalSceneItemsReady(QVector<QGraphicsItem*>* items, int count);private slots:// 场景初始化部分完成的槽函数void SlotSceneItemsReady(QVector<QGraphicsItem*>* items, int count);private:// 场景QGraphicsScene* scence;// 视图QGraphicsView* view;// 线程池QThreadPool* pool;// 状态标签QLabel* label;// 记录现在已经完成了多少个图元的创建int readyCount;
};
.cpp
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QVBoxLayout>
#include <QThreadPool>
#include <QGraphicsEllipseItem>
#include <QLabel>
#include "CreatRoundWidget.h"// 添加线程池有关操作
#define CREATROUND_ROW_COUNT 1000
#define CREATROUND_COLUMN_COUNT 1000
// 每个网格的宽度和高度
#define CREATROUND_WIDTH 10
#define CREATROUND_HEIGHT 10
// 线程池中的线程使用的堆栈字节数
#define CREATROUND_STACK_SIZE_BYTES (1024*1024*64)
// 线程池中最大的线程数量
#define CREATROUND_THREAD_POOL_MAX_THREAD_COUNT 16
// 在线程池中初始化场景中的所有图元
void CreatRoundInitScene(CreatRoundWidget* widget, QThreadPool* pool) {// 这些图元使用的颜色const QColor COLORS[] = {QColor(255,0,0),QColor(0,255,0),QColor(0,0,255),QColor(255,255,0),QColor(255,0,255),QColor(0,255,255),QColor(128,0,0),QColor(0,128,0),QColor(0,0,128),QColor(128,128,0),QColor(128,0,128),QColor(0,128,128)};const int COLOR_COUNT = sizeof(COLORS) / sizeof(QColor);pool->setStackSize(CREATROUND_STACK_SIZE_BYTES);pool->setMaxThreadCount(CREATROUND_THREAD_POOL_MAX_THREAD_COUNT);// 每列中的所有网格使用一个线程进行初始化for (int j = 0; j < CREATROUND_COLUMN_COUNT; ++j) {// 具体初始化操作的Lambda表达式auto f = [=] {// 创建具体的图元对象之后保存到这个数组中QVector<QGraphicsItem*>* items = new QVector<QGraphicsItem*>();items->resize(CREATROUND_ROW_COUNT);// 每个网格中包含一个图元for (int i = 0; i < CREATROUND_ROW_COUNT; ++i) {// 计算图元的坐标和大小qreal x = j * CREATROUND_WIDTH + CREATROUND_WIDTH * 0.1;qreal y = i * CREATROUND_HEIGHT + CREATROUND_HEIGHT * 0.1;qreal w = CREATROUND_WIDTH * 0.8;qreal h = CREATROUND_HEIGHT * 0.8;// 创建椭圆图元或者矩形图元if ((i + j) % 2) {QGraphicsEllipseItem* item = new QGraphicsEllipseItem();item->setRect(x, y, w, h);item->setBrush(QBrush(COLORS[(i * CREATROUND_COLUMN_COUNT + j) % COLOR_COUNT]));(*items)[i] = item;}}// 这一列的所有图元创建完成之后给主窗口发送信号通知emit widget->SignalSceneItemsReady(items, CREATROUND_COLUMN_COUNT * CREATROUND_ROW_COUNT);};// 在线程池中启动这个Lambda表达式所表示的工作任务pool->start(f);}
}CreatRoundWidget::CreatRoundWidget(QWidget* parent):QWidget(parent), readyCount(0) {scence = new QGraphicsScene(this);QVBoxLayout* main_layout = new QVBoxLayout();setLayout(main_layout);label = new QLabel("Initializing...");main_layout->addWidget(label);view = new QGraphicsView(scence);main_layout->addWidget(view);// 创建好空的界面后立即把界面显示出来showMaximized();// 链接信号和槽connect(this, &CreatRoundWidget::SignalSceneItemsReady, this, &CreatRoundWidget::SlotSceneItemsReady);// 创建线程池来在后台执行初始化操作// 避免漫长的初始化过程影响到对用户界面操作的及时响应pool = new QThreadPool(this);CreatRoundInitScene(this, pool);
}CreatRoundWidget::~CreatRoundWidget(){}void CreatRoundWidget::SlotSceneItemsReady(QVector<QGraphicsItem*>* items, int count) {readyCount += items->length();view->setUpdatesEnabled(false);for (int i = 0; i < items->length(); ++i) {scence->addItem(items->at(i));}view->setUpdatesEnabled(true);delete items;if (readyCount < count) {label->setText(QString("Initializing%%1 ...").arg(readyCount * 100 / count));}else {label->setText(QString("total %1 items ready").arg(count));}
}