QT性能优化之QT6框架高性能图形视图框架快速展示百万图元大规模场景
简介:
本文展示了使用QT图形视图框架在一个场景中绘制出百万个图元的程序的效果以及源代码;本文还介绍了QT图形视图框架的一些实用功能和QT图形视图框架的整体结构;本文还介绍了QT大场景应用有哪些可能的优化改进空间。本文还展示了如何通过线程池初始化百万个图元而不影响用户界面及时响应用户操作。
QT能否快速展示千万级图元的大规模场景? 看完补天云的这个实测视频就有结果了! QT性能优化之QT6框架高性能图形视图框架快速展示千万图元大规模场景。本文稍后会提供实际效果演示视频链接。
正文:
QT绘制百万图元大场景
备注:
(a)1000行1000列,总共100万个图元。注意存在水平滚动条和垂直滚动条。
(b)由于创建了100个图元对象,使得这个初始化过程比较费时间。在这个实例中使用QT线程池避免了漫长的初始化过程影响到了界面操作的用户体验。
©一旦初始化过程完成之后,可以通过滚动条或者鼠标控制随意滚动,几乎感受不到延迟。
QT绘制百万图元大场景
备注:
(a)100行10000列,总共100万个图元。注意存在水平滚动条和垂直滚动条。
QT能否快速展示千万级图元的大规模场景? 看完补天云的这个实测视频就有结果了! QT性能优化之QT6框架高性能图形视图框架快速展示千万图元大规模场景
本文描述之运行效果对应视频如下:
QT性能优化之QT6框架高性能图形视图框架快速展示千万图元大规模场景
主窗口ButianyunWidget.h文件:
class QGraphicsScene;
class QGraphicsView;
class QGraphicsItem;
class QLabel;
class QThreadPool;
class ButianyunWidget : public QWidget
{
Q_OBJECT
public:
ButianyunWidget(QWidget *parent = nullptr);
~ButianyunWidget();
signals:
//场景初始化部分完成的信号
void sig_scene_items_ready(QVector<QGraphicsItem*>* items, int count);
private slots:
//场景初始化部分完成的槽函数
void slot_scene_items_ready(QVector<QGraphicsItem*>* items, int count);
private:
//场景
QGraphicsScene* scene;
//视图
QGraphicsView* view;
//线程池
QThreadPool* pool;
//状态标签
QLabel* label;
//记录现在已经完成了多少个图元的创建
int ready_count;
};
主窗口ButianyunWidget.cpp文件:
void butianyun_init_scene(ButianyunWidget* widget, QThreadPool* pool);
ButianyunWidget::ButianyunWidget(QWidget *parent)
: QWidget(parent)
, ready_count(0)
{
scene = new QGraphicsScene(this);
QVBoxLayout* main_layout = new QVBoxLayout();
setLayout(main_layout);
label = new QLabel("Initializing...");
main_layout->addWidget(label);
view = new QGraphicsView(scene);
main_layout->addWidget(view);
//创建好空的界面之后立即把界面显示出来
showMaximized();
//链接信号和槽。
connect(this, &ButianyunWidget::sig_scene_items_ready,
this, &ButianyunWidget::slot_scene_items_ready);
//创建线程池来在后台执行初始化操作
//避免漫长的初始化过程影响到对用户界面操作的及时响应
pool = new QThreadPool(this);
butianyun_init_scene(this, pool);
}
ButianyunWidget::~ButianyunWidget()
{
}
void ButianyunWidget::slot_scene_items_ready(QVector<QGraphicsItem*>* items, int count)
{
ready_count += items->length();
view->setUpdatesEnabled(false);
for (int i = 0; i < items->length(); i++)
{
scene->addItem(items->at(i));
}
view->setUpdatesEnabled(true);
delete items;
if (ready_count < count)
{
label->setText(QString("Initializing %%1 ...").arg(ready_count * 100 / count));
}
else
{
label->setText(QString("total %1 items ready").arg(count));
}
}
在线程池中初始化场景中的百万个图元对象:
//场景中行的数量和列的数量。1000行x1000列,总共100万个图元。
#define BUTIANYUN_CELL_ROW_COUNT 1000
#define BUTIANYUN_CELL_COLUMN_COUNT 1000
//每个网格的宽度和高度
#define BUTIANYUN_CELL_WIDTH 10.0
#define BUTIANYUN_CELL_HEIGHT 10.0
//线程池中的线程使用的堆栈字节数
#define BUTIANYUN_STACK_SIZE_BYTES (1024 * 1024 * 64)
//线程池中最大的线程数量
#define BUTIANYUN_THREADPOOL_MAX_THREAD_COUNT 16
//在线程池中初始化场景中的所有图元
void butianyun_init_scene(ButianyunWidget* 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(BUTIANYUN_STACK_SIZE_BYTES);
pool->setMaxThreadCount(BUTIANYUN_THREADPOOL_MAX_THREAD_COUNT);
//每列中的所有网格使用一个线程进行初始化。
for (int j = 0; j < BUTIANYUN_CELL_COLUMN_COUNT; j++)
{
//具体初始化操作的LAMBDA表达式
auto f = [=] {
//创建具体的图元对象之后保存到这个数组中。
QVector<QGraphicsItem*>* items = new QVector<QGraphicsItem*>();
items->resize(BUTIANYUN_CELL_ROW_COUNT);
//每一个网格中包含一个图元。
for (int i = 0; i < BUTIANYUN_CELL_ROW_COUNT; i++)
{
//计算图元的坐标和大小。
qreal x = j * BUTIANYUN_CELL_WIDTH + BUTIANYUN_CELL_WIDTH * 0.1;
qreal y = i * BUTIANYUN_CELL_HEIGHT + BUTIANYUN_CELL_HEIGHT * 0.1;
qreal w = BUTIANYUN_CELL_WIDTH * 0.8;
qreal h = BUTIANYUN_CELL_HEIGHT * 0.8;
//创建椭圆图元或者矩形图元。
if ((i + j) % 2)
{
QGraphicsEllipseItem* item = new QGraphicsEllipseItem();
item->setRect(x, y, w, h);
item->setBrush(QBrush(COLORS[(i * BUTIANYUN_CELL_COLUMN_COUNT + j) % COLOR_COUNT]));
(*items)[i] = item;
}
else
{
QGraphicsRectItem* item = new QGraphicsRectItem();
item->setRect(x, y, w, h);
item->setBrush(QBrush(COLORS[(i * BUTIANYUN_CELL_COLUMN_COUNT + j) % COLOR_COUNT]));
(*items)[i] = item;
}
}
//这一列的所有图元创建完成之后给主窗口发送信号通知。
emit widget->sig_scene_items_ready(items, BUTIANYUN_CELL_COLUMN_COUNT * BUTIANYUN_CELL_ROW_COUNT);
};
//在线程池中启动这个LAMBDA表达式所表示的工作任务。
pool->start(f);
}
}
图形视图提供了一个表面用于管理大量自定义的二维图形条目并与之进行交互,还提供了一个视图窗口用于可视化这些图形条目,并提供缩放和旋转支持。
这个框架包含了事件传播体系,这个体系允许对场景中的条目的双精度的交互能力。条目能够处理键盘事件、鼠标按下、移动、释放和双击事件,条目还能跟踪鼠标运动。
图形视图使用BSP(二叉空间剖分)树来提供很快速的条目发现能力,作为这种能力的一个结果,它还能实时的可视化拥有数以百万计的宏大场景。
这里介绍一下QT图形视图框架中的图形条目(QGraphicsItem)的能力。
图形条目可以包含另外的图形条目。也就是若干图形条目可以组装成一个整体,既能够对整体进行各种常用操作,也能对部分进行各种常用操作。这个特点使得QT图形视图框架能够表达一些包含多个部件和多层次部件的复杂图形对象。图形条目组合管理能力使得一个复杂图形对象整体上可以看做是一个通过图形条目对象之间的父子关系建立起来的树状结构,而且每一个子节点都可以进行管控。
QT图元条目组合管理
QT图元条目组合管理
图形视图框架提出了图形条目分组的概念,并设计了QGraphicsItemGroup这个类型来支持分组管理的概念。在QT图形视图框架中,QGraphicsItemGroup是QGraphicsItem的派生类,因此图形条目分组是一种特殊的图形条目。一个分组中可以包含多个具体图形条目。图形条目分组管理能力使得使用树状结构来描述复杂场景中的层次结构关系变得非常便利。
QT图元条目分组管理
QT图元条目分组管理
支持各种几何变换操作,包括平移、缩放和旋转等操作,还支持拖放操作(Drog & Drop),动画操作。
QT图形条目几何变换
QGraphicsItem类型提供了设置几何变换的函数接口。
QT图形条目几何变换
支持事件传播和处理,包括各种鼠标事件、键盘事件等。 QGraphicsItem类型提供了较多的事件处理有关的虚函数,派生类型可以通过重写某个虚函数来响应图形条目上的鼠标键盘事件等QT事件。
QT图形条目事件传播和处理
QGraphicsItem并不是QObject的派生类型,因此普通的图形条目类型并不支持QT对象的信号与槽的连接能力。但是QT图形视图框架还提供了QGraphicsItem的一个特殊的派生类型QGraphicsObject类型,这个类型同时也是QObject类型的派生类型。因此使用QGraphicsObject及其派生类型创建的对象,既能够放到图形视图场景中使用,也能够当做一个QT对象来发挥QT信号与槽的价值。
QT图形视图框架中的图形场景QGraphicsScene已经能够完整的管理场景中的图形条目QGraphicsItem,包括将绘制出整个场景的展示结果。但是这一部分是图形视图框架相对比较独立的核心结构和功能。
QT图形视图与QWidget窗口组合
QT图形视图框架中的图形视图QGraphicsView类型的祖先类型是QWidget类型,因此QGraphicsView类型使得QT图形场景和图形条目的渲染结果能够直接在视图中进行展示。而这个视图也就是QGraphicsView对象,其类型本身就是一个QT窗口类型,可以直接嵌入到使用QWidget的任何普通窗口应用程序中作为界面的一部分,或者就将这个视图作为主窗口。
QT图形视作为独立窗口
另外QGraphicsView类型是QAbstractScrollArea类型的直接派生类型,可以支持水平滚动条和垂直滚动条的相关操作。
QGraphicsView类型本身提供了场景整体的几何变换能力。这使得QT图形视图框架不仅支持图形条目图元级别的几何变换,还支持图形场景整体的几何变换。这是普通QWidget窗口没有直接的功能。
QT图形视图整体几何变换
QT图形视图框架尽管可以轻松支持百万数量级的图元,如果计算机内存足够大则可以支持更多数量的图元。 比如本文介绍的使用QT图形视图框架绘制百万个图元的场景的实例程序运行时,消耗掉了将近2GB的内存。
QT图形视图框架100万大场景内存占用
这个应用程序直接在初始化时创建了100万个图形条目,占用了较多内存。整个场景中的图形条目数量巨大,无法在完整展现在窗口可见区域,必须拖动滚动条才能看到各个区域的图形条目。如果能够进一步优化程序,使得初始化时只创建出可见区域中的图形条目,在场景滚动过程中动态的创建所需的可见区域的图形条目,那么程序的初始化时间成本和内存消耗成本可能能够得到大幅度的降低。
QT图形视图框架默认情况下没有启用OpenGL支持,因此无法使用GPU硬件加速能力。下图是本文介绍的QT绘制百万个图元的场景的实例运行时的CPU和GPU占用情况,可以看到这个进程没有使用GPU硬件加速。
QT图形视图框架默认不启用GPU硬件加速
QT图形视图框架可以通过OpenGL支持来使用GPU硬件加速能力。
通过如下代码启用OpenGL支持,从而使得一个使用QT图形视图框架的QT应用程序能够充分借助GPU硬件加速能力来提升应用程序的性能。
QT图形视图框架启用GPU硬件加速
在本文介绍的QT绘制百万个图元的场景的实例代码中加上上述代码之后再次运行后快速滚动图形场景则可以看到已经使用到GPU硬件加速能力。
QT图形视图框架启用GPU硬件加速
但是美中不足的是,从这个实例运行情况来看,似乎使用GPU硬件加速之后,CPU占用率也增加了不少。有待进一步优化大场景应用程序。
下面这个视频展示了如何使用QT框架实现快速展示千万级别的图元的大规模场景。
QT性能优化之QT6框架高性能图形视图框架快速展示千万图元大规模场景
从整体上来讲,QT图形视图框架包含了三个组成部分:
图形场景(QGraphicsScene)
负责图形场景的整体控制,以及场景中的图形条目的管理工作。
ü 图形条目(QGraphicsItem)
负责具体图元的绘制和几何变换控制以及事件处理。
ü 图形视图(QGraphicsView)
负责图形场景的用户界面交互操作。
下图是<<项目实战原理源码界面美化视频课程QT/QSS/QML/C++/STL >>中的一个截图,可能对理解QT图形视图框架的整体结构有一些帮助。
如果您想系统学习QT框架常规技术,可以看一下这个课程。
QT5/QSS/QML/C++界面美化原理源码项目实战视频课程
如果您C++基础比较好,可以看一下这个课程。
QT5 QSS QML 原理源码 界面美化 项目实战 视频课程
如果您想系统学习QT6性能优化技术,可以看一下这个课程。
QT性能优化实战 QML优化 QT高性能 QT6系列视频课程
如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。