小谈一下Qt的绘制引擎 qt 绘制
bigegpt 2024-12-23 08:48 4 浏览
小谈一下Qt的绘制引擎 - QWidget
#Qt
序
先谈一个疑问?如何设计一个优秀的绘制引擎。
注意下这里,我说的是绘制引擎,而不是光栅化引擎。
这有本质的区别。光栅化引擎只是绘制渲染引擎的一部分。
绘制引擎是我们开发者用的一些常见的接口。光栅化引擎我认为是绘制引擎一部分的实现,所以这里只讲外层的东西。逃)
个人认为,Qt是把C++ OOP的特性用到滚瓜烂熟的框架-封装,继承,多态。
那么目前Qt6的图形架构是这个样子的。
你会发现,Qt Widget这套,基本上都已经跟qml这套脱离了,所以Qt6最新的RHI架构对3D做了很好的支持。
今天主要讲一下QWidget这套的绘制流程,后面会简单说下优缺点。
废话不多说,先举个栗子吧。
举个
假如要画一条线,需要哪几步
要把画一条线总共需要几个角色(要把大象装冰箱总共分几步)
第一步,需要一个人(画线的方法)。(废话)
第二步,需要一个笔。
第三步,需要一张纸。
换成Qt来画线的话那就是
第一步,需要一个光栅化引擎(QPaintEngine)
第二步,需要一个笔(QPainter)
第三步,需要一个设备(QPaintDevice)
所以Qt给我们暴露的接口就是这三个
- QPaintEngine
- QPainter
- QPaintDevice
Qt的绘制引擎简介
Qt官方简介
QPaintEngine,QPainter,QPaintDevice组成了Qt绘制界面的基础。
直接贴上三个类的官方说明介绍
顺便打个广告,如果有兴趣参与Qt文档的翻译,欢迎参与项目 QtDocumentCN/QtDocumentCN: Qt中文文档翻译 (github.com)
一下说明来自项目QtDocumentCN
QPaintEngine
QPaintEngine类为QPainter提供了如何在指定绘图设备上(译者注:一般为QPaintDevice的派生)绘制的一些抽象的方法。
Qt为不同的painter后端提供了一些预设实现的QPaintEngine
译者注:提供一个更加好理解的说法。QPainter的Qt实现一般默认调用的是QPaintEngine的方法。
现在QPaintEngine主要提供的是Qt自带的光栅化引擎(raster engine),Qt在他所有支持的平台上,提供了一个功能完备的光栅化引擎。
在Windows, X11 和 macOS平台上,Qt自带的光栅化引擎都是QWidget这个基础类的默认的绘制方法的提供者,亦或是QImage的绘制方法的提供者。当然有一些特殊的绘制设备的绘制引擎不提供对应的绘制方法,这时候就会调用默认的光栅化引擎。
当然,我们也为OpenGL(可通过QOpenGLWidget访问)跟打印(允许QPainter在QPrinter对象上绘制,用于生成pdf之类的)也提供了对应的QPaintEngine的实现。
译者注: QPainter,QPainterEngine,QPaintDevice三个是相辅相成的。
QPainter为开发者提供外部接口方法用于绘制
QPaintEngine为QPainter提供一些绘制的具体实现
QPaintDevice为QPainter提供一个绘图设备,用于显示亦或储存。
如果你想使用QPainter绘制自定义的后端(译者注:这里可以理解为QPaintDevice)。你可以继承QPaintEngine,并实现其所有的虚函数。然后子类化QPaintDevice并且实现它的纯虚成员函数(QPaintDevice::paintEngine())。
由QPaintDevice创建QPaintEngine,并维护其生命周期。
另请参见QPainter,QPaintDevice::paintEngine()和Paint System
QPaintDevice
翻译TODO
QPainter
翻译TODO
举个Qt里实现QPaintEngine相关的例子
首先在Qt的源码里打开终端执行命令
find . -name qpaintengine*.cpp
或者你在windows上用everything搜一下
./5.15.2/Src/qtbase/src/gui/image/qpaintengine_pic.cpp
./5.15.2/Src/qtbase/src/gui/painting/qpaintengine.cpp
./5.15.2/Src/qtbase/src/gui/painting/qpaintengineex.cpp
./5.15.2/Src/qtbase/src/gui/painting/qpaintengine_blitter.cpp
./5.15.2/Src/qtbase/src/gui/painting/qpaintengine_raster.cpp
./5.15.2/Src/qtbase/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
./5.15.2/Src/qtbase/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
./5.15.2/Src/qtbase/src/printsupport/kernel/qpaintengine_alpha.cpp
./5.15.2/Src/qtbase/src/printsupport/kernel/qpaintengine_preview.cpp
这些都是QPaintEngine在各个不同端的派生,有兴趣可以搜下qpaintdevice相关的,也差不多都是这样。
比如qpaintengine_raster.cpp 就是Qt自己的光栅化引擎实现,qpaintengine_x11.cpp就是在Linux下默认跟x11交互的光栅化实现。。。
「当然并不是所有的派生都会有自己独立的cpp文件,或者叫相关的cpp」 。可以对比Qt的官方API来对照下
枚举类型 枚举值 描述
枚举类型 | 枚举值 | 描述 |
Constant | Value | Description |
QPaintEngine::X11 | 0 | |
QPaintEngine::Windows | 1 | |
QPaintEngine::MacPrinter | 4 | |
QPaintEngine::CoreGraphics | 3 | macOS的Quartz2D(CoreGraphics) |
QPaintEngine::QuickDraw | 2 | macOS的QuickDraw |
QPaintEngine::QWindowSystem | 5 | 嵌入式Linux的Qt |
QPaintEngine::PostScript | 6 | (不再支持) |
QPaintEngine::OpenGL | 7 | |
QPaintEngine::Picture | 8 | QPicture 格式 |
QPaintEngine::SVG | 9 | 可伸缩矢量图形XML格式 |
QPaintEngine::Raster | 10 | |
QPaintEngine::Direct3D | 11 | 仅Windows,基于Direct3D的引擎 |
QPaintEngine::Pdf | 12 | PDF格式 |
QPaintEngine::OpenVG | 13 | |
QPaintEngine::User | 50 | 用戶自定义的最小美剧 |
QPaintEngine::MaxUser | 100 | 用戶自定义的最大美剧 |
QPaintEngine::OpenGL2 | 14 | |
QPaintEngine::PaintBuffer | 15 |
说下Qt绘制一条线的流程
现在有这样的代码,我们来在Qt中绘制一条线
QLineF line(10.0, 80.0, 90.0, 20.0);
QPainter painter(this);
painter.drawLine(line);
如果我们的Qt把渲染引擎设置成了raster引擎,那么qpainter的实现本质上是调用的QPaintEngine的相关代码。
void QPainter::drawLines(const QLineF *lines, int lineCount)
{
//此处精简代码
xxxxxxxx
if (lineEmulation) {
if (lineEmulation == QPaintEngine::PrimitiveTransform
&& d->state->matrix.type() == QTransform::TxTranslate) {
for (int i = 0; i < lineCount; ++i) {
QLineF line = lines[i];
line.translate(d->state->matrix.dx(), d->state->matrix.dy());
d->engine->drawLines(&line, 1); //这里调用qpaintengine
}
} else {
QPainterPath linePath;
for (int i = 0; i < lineCount; ++i) {
linePath.moveTo(lines[i].p1());
linePath.lineTo(lines[i].p2());
}
d->draw_helper(linePath, QPainterPrivate::StrokeDraw); //这里会走模拟绘制本质上也会走一个engine
}
return;
}
d->engine->drawLines(lines, lineCount); //或者这里调用qpaintengine
}
那么就调用到了QPaintEngineRaster的相关实现。
QRasterPaintEngine继承自QPaintEngineEx,QPaintEngineEx继承自QPaintEngine
void QRasterPaintEngine::drawLines(const QLine *lines, int lineCount)
{
#ifdef QT_DEBUG_DRAW
qDebug() << " - QRasterPaintEngine::drawLines(QLine*)" << lineCount;
#endif
Q_D(QRasterPaintEngine);
QRasterPaintEngineState *s = state();
ensurePen();
if (!s->penData.blend)
return;
if (s->flags.fast_pen) {
QCosmeticStroker stroker(s, d->deviceRect, d->deviceRectUnclipped);
stroker.setLegacyRoundingEnabled(s->flags.legacy_rounding);
for (int i=0; i<lineCount; ++i) {
const QLine &l = lines[i];
stroker.drawLine(l.p1(), l.p2());
}
} else {
QPaintEngineEx::drawLines(lines, lineCount);
}
}
所以QPainter在画画的时候本质上是QPaintEngine提供的方法。
关于QPaintDevice
由QPaintDevice创建QPaintEngine,并维护其生命周期。by官方文档
上面的代码中,是这样初始化QPainter的。
我们一般重写一个QWidget的paintevent的时候才会这样。
//这里的this实际上就是一个QWidget,QWidget继承自QPaintDevice
QPainter painter(this);
QWidget继承自QPaintDevice,看下源码实现
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
: QObject(dd, nullptr), QPaintDevice()
QPaintDevice本质上就是一个绘制设备,供我们使用,由于QPaintDevice创建QPaintEngine ,所以QPaintDevice跟QPaintEngine一样,也会有很多种类型的派生。
- QGLFramebufferObject
- QGLPixelBuffer
- QImage,
- QOpenGLPaintDevice,
- QPagedPaintDevice
- QPaintDeviceWindow,
- QPicture
- QPixmap,
- QSvgGenerator
- QWidget
简单说一下绘制的一些流程
本质上,绘制引擎绘制的流程是一个状态机。
QPainter在绘制的时候是有很多讲究的,就拿标脏来说吧。
假如有这么一段代码
QPen pen;
QPainter painter(this);
painter.setPen(pen);
这时候setPen的时候,QPainter里的QPaintEngine会直接设置这个flag
QPaintEngine::DirtyPen
然后内部再走标脏之后需要走的逻辑,QPaintEngine使用函数QPaintEngine::updateState()来通知QPainter的延迟刷新。所以基本上,Qt的绘制效率跟效果都是有保证的。
如果你想继承QPaintEngine来实现自己的光栅化引擎的话,不一定是光栅化。比如像Qt那样支持保存成pdf也可以。
必须更新下面所有的标脏状态(译者注:比如你自定义一个QPaintEngine,就需要处理上面的所有的状态的刷新)
比如你setFont,那么状态就QPaintEngine::DirtyFont
setBrush,那么状态就QPaintEngine::DirtyBrush
枚举类型 枚举值 描述
枚举类型 | 枚举值 | 描述 |
QPaintEngine::DirtyPen | 0x0001 | 画笔已经标脏,应刷新 |
QPaintEngine::DirtyBrush | 0x0002 | 画刷已经标脏,应刷新 |
QPaintEngine::DirtyBrushOrigin | 0x0004 | 画刷原始数据已经变化,应刷新 |
QPaintEngine::DirtyFont | 0x0008 | 字体发生变化,应刷新 |
枚举类型 | 枚举值 | 描述 |
QPaintEngine::DirtyBackground | 0x0010 | 背景标脏,应刷新 |
QPaintEngine::DirtyBackgroundMode | 0x0020 | 背景状态标脏,应刷新 |
QPaintEngine::DirtyTransform | 0x0040 | 当前矩阵标脏,应刷新 |
QPaintEngine::DirtyClipRegion | 0x0080 | 当前裁剪区域标脏,应刷新 |
QPaintEngine::DirtyClipPath | 0x0100 | 裁剪路径标脏,应刷新 |
QPaintEngine::DirtyHints | 0x0200 | 当前绘制精度标志变化,应刷新 |
QPaintEngine::DirtyCompositionMode | 0x0400 | 绘制组合模式变化,应刷新 |
QPaintEngine::DirtyClipEnabled | 0x0800 | 无论是否当前可裁剪,都应刷新 |
QPaintEngine::DirtyOpacity | 0x1000 | 当前透明度已经更改,应当使用 QPaintEngine::updateState()来进行刷新 |
QPaintEngine::AllDirty | 0xffff | 内部枚举使用变量。 |
扩展用法 - 自定义类型
举个很简单的栗子
比如有一个图片类型,Qt是不支持的, 如果我还是想用Qt那种形式,那么我该如何接入。
首先我要继承QPaintDevice来叫一个QXXXXImage。(类似QImage那样,当然你也可以直接绘制到QWidget上)
也要继承一个QPaintEngine来叫一个QXXXXXEngine。(类似QPaintEngine::SVG那样)
QPainter不变。
在绘制图片的时候,QPainter会直接调用到自己实现的QXXXXXEngine,这里接到设备上,来自己实现绘制这个新格式的流程。
这样,我们就什么都不用变,就可以支持一种新格式了,你QPainter原来该怎么drawImage,还怎么draw。
「Qt源码里这里当然有一个参考的栗子」
可以参考源码
./5.15.2/Src/qtbase/src/printsupport/kernel/qprintengine_pdf.cpp
Qt的PDF引擎很好的实现了自己的QPaintEngine,跟QPaintDevice,要不然怎么能够支持导出pdf呢!
个人的一些看法
说句心里话,Qt Widget这套渲染引擎的设计已经是比较过时了。(当然只是相对来说,毕竟Qt诞生的太早了。
比如最诟病的QWidget的渲染机制。我比较诟病的大概有这么几点。
QPaintDevice & QPaintEngine没有分开
由QPaintDevice创建QPaintEngine
在Qt源码里,QPaintEngine的构造是在QPaintDevice创建的。 简单的说,绘制设备必须要跟光栅化引擎必须是同一个类型的后端。
比如OpenGL - QOpenGLWidget
这样就很尴尬,QPaintEngine很难换一个后端渲染引擎,必须得跟着QPaintDevice一起变。 这算是一个痛点吧。
多线程支持
现在QWidget对GPU的支持 & 动画效果并不是太好。
基于上面QWidget的这套设计,实际上,痛点就已经很明显了,即多线程支持。
看下RHI的一些简单的架构设计,对于多线程的支持
这才是现代的绘制引擎的设计,但是目前我也没有好好研究过qml,毕竟没写过。先给自己一个Flag吧,后面准备好好研究下这套引擎。
就大概讲了下这套渲染引擎吧。后面估计会好好研究下这套渲染引擎,逃)
相关推荐
- 了解Linux目录,那你就了解了一半的Linux系统
-
大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...
- Linux命令,这些操作要注意!(linux命令?)
-
刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...
- Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!
-
在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...
- linux必学的60个命令(linux必学的20个命令)
-
以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....
- 提高工作效率的--Linux常用命令,能够决解95%以上的问题
-
点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...
- 15 个实用 Linux 命令(linux命令用法及举例)
-
Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...
- Linux 常用命令集合(linux常用命令全集)
-
系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...
- Linux的常用命令就是记不住,怎么办?
-
1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...
- Linux常用文件操作命令(linux常用文件操作命令有哪些)
-
ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...
- Linux 常用命令(linux常用命令)
-
日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E...
- 简单粗暴收藏版:Linux常用命令大汇总
-
号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...
- 「Linux」linux常用基本命令(linux常用基本命令和用法)
-
Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...
- Linux的常用命令就是记不住,怎么办?于是推出了这套教程
-
1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...
- Linux的30个常用命令汇总,运维大神必掌握技能!
-
以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...
- Linux/Unix 系统中非常常用的命令
-
Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)