【Qt源码笔记】深谈 Qt 绘制

news/2024/10/25 11:32:21/

之前写了一篇 浅谈Qt控件绘制 。之所以叫浅谈是因为调用都是比较表层的调用。其实 Qt 的绘制,可以说用 Qt 的人都有用到,但是对于绘制底层,了解的人并不见得很多。我其实之前也是云山雾罩,从来没有深究过。所以想着知其然还是要知其所以然。

结论

在 Windows 平台 默认的 Qt 绘制,最终到底层,是直接调用指令集指令的,这有别于我最初的猜测,我以为是用 Windows API 。这着实让我吃了一惊。这让我对 Qt 的性能又放心了一些。

探究过程

其实研究这个,比其他的更好溯源。附上三段堆栈信息。

Qt5Guid.dll!BLEND_SOURCE_OVER_ARGB32_AVX2(unsigned int * dst, const unsigned int * src, const int length) 行 184	C++Qt5Guid.dll!qt_blend_argb32_on_argb32_avx2(unsigned char * destPixels, int dbpl, const unsigned char * srcPixels, int sbpl, int w, int h, int const_alpha) 行 253	C++Qt5Guid.dll!QRasterPaintEnginePrivate::drawImage(const QPointF & pt, const QImage & img, void(*)(unsigned char *, int, const unsigned char *, int, int, int, int) func, const QRect & clip, int alpha, const QRect & sr) 行 1057	C++Qt5Guid.dll!QRasterPaintEngine::drawImage(const QPointF & p, const QImage & img) 行 2250	C++Qt5Guid.dll!QRasterPaintEngine::drawPixmap(const QPointF & pos, const QPixmap & pixmap) 行 2128	C++Qt5Guid.dll!QPainter::drawPixmap(const QPointF & p, const QPixmap & pm) 行 5079	C++Qt5Guid.dll!QPainter::drawPixmap(const QPoint & p, const QPixmap & pm) 行 796	C++
------------------
Qt5Guid.dll!alphargbblend_argb32(unsigned int * dst, unsigned int coverage, const QRgba64 & srcLinear, unsigned int src, const QColorProfile * colorProfile) 行 5771	C++Qt5Guid.dll!qt_alphargbblit_argb32(QRasterBuffer * rasterBuffer, int x, int y, const QRgba64 & color, const unsigned int * src, int mapWidth, int mapHeight, int srcStride, const QClipData * clip, bool useGammaCorrection) 行 5878	C++Qt5Guid.dll!QRasterPaintEngine::alphaPenBlt(const void * src, int bpl, int depth, int rx, int ry, int w, int h, bool useGammaCorrection) 行 2723	C++Qt5Guid.dll!QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const unsigned int * glyphs, const QFixedPoint * positions, QFontEngine * fontEngine) 行 2976	C++Qt5Guid.dll!QRasterPaintEngine::drawTextItem(const QPointF & p, const QTextItem & textItem) 行 3183	C++Qt5Guid.dll!QPainterPrivate::drawTextItem(const QPointF & p, const QTextItem & _ti, QTextEngine * textEngine) 行 6531	C++Qt5Guid.dll!QTextLine::draw(QPainter * p, const QPointF & pos, const QTextLayout::FormatRange * selection) 行 2615	C++Qt5Guid.dll!qt_format_text(const QFont & fnt, const QRectF & _r, int tf, const QTextOption * option, const QString & str, QRectF * brect, int tabstops, int * ta, int tabarraylen, QPainter * painter) 行 7702	C++Qt5Guid.dll!QPainter::drawText(const QRect & r, int flags, const QString & str, QRect * br) 行 5955	C++-------------------
Qt5Guid.dll!qt_memfill32(unsigned int * dest, unsigned int value, int count) 行 262	C++Qt5Guid.dll!qt_memfill<unsigned int>(unsigned int * dest, unsigned int color, int count) 行 901	C++Qt5Guid.dll!blend_color_argb(int count, const QT_FT_Span_ * spans, void * userData) 行 4347	C++Qt5Guid.dll!qt_span_fill_clipRect(int count, const QT_FT_Span_ * spans, void * userData) 行 4229	C++Qt5Guid.dll!QSpanBuffer::flushSpans() 行 112	C++Qt5Guid.dll!QSpanBuffer::~QSpanBuffer() 行 87	C++Qt5Guid.dll!QRasterizer::rasterizeLine(const QPointF & a, const QPointF & b, double width, bool squareCap) 行 1191	C++Qt5Guid.dll!QRasterPaintEngine::fillRect(const QRectF & r, QSpanData * data) 行 1858	C++Qt5Guid.dll!QRasterPaintEngine::fillRect(const QRectF & r, const QBrush & brush) 行 1882	C++Qt5Guid.dll!QPainter::fillRect(const QRect & r, const QBrush & brush) 行 6971	C++

只要从绘制代码,单步调试即可找到指定地点。不过最终使用的指令集却并不一样。有的用 avx2 、有的则是用 sse2 。如果想探究指令集部分的使用,需要到源码目录 qtbase\src\gui\painting ,根据目录下代码文件名即可知道是哪种指令集,一目了然。

回过头来再看上边的那些函数调用。其实不难发现,所有的绘制在中间都必然要经过QPaintEngine。QRasterPaintEngine只不过是它的一个派生,这个后边再说。而 QPaintEngine 根据所要绘制的内容,来区分绘制逻辑,比方说涂色采用填充 buffer 、统一刷新的方式;字体绘制要调用字体图元相关绘制逻辑等等。

所有的表层绘制都要经过绘制引擎来向下传递绘制信息。这是 Qt 作为一个高级框架的闪光点,在其他的 Qt 模块也有类似发现,比如控件的绘制上。这样看来 Qt 这个框架能给我们的,除了代码逻辑本身,还有设计。

意外收获

在整个代码探究的过程,我发现了这样一段代码,可以说是非常惊喜了。

if (pd->devType() == QInternal::Pixmap)static_cast<QPixmap *>(pd)->detach();
else if (pd->devType() == QInternal::Image)static_cast<QImage *>(pd)->detach();
d->engine = pd->paintEngine();

这段代码是QPainter::begin()中的代码。当时是在研究QWidget的绘制过程中,走到了这里。只看代码很难体验它的神奇之处。

pd 在前边是 QWidget 的一个指针,当经过这个 if 语句之后,pd 就变成了一个 QImage 指针。这不可谓之不神奇。对于稍微对 Qt 源码有一些理解的同学对 detach() 并不陌生,它本是 Qt 中最常用的 Copy-on-Write 的实现。不过经常用于在类的成员方法中调用,今天看到它这种用法着实惊艳到了。至于为什么这种用法可行,这也是一个可研究的点,有时间,将其整理出来。这段代码算是研究绘制过程中的一个小礼物,这也解开了QWidget绘制的本质。至于QWidget的绘制,也是一个很有意思的东西了,以后有机会详细整理一下。

附注

之前我说QRasterPaintEngine只是QPaintEngine的派生类。我也说 Windows 平台下默认的 Qt 绘制是使用指令集的。原因就在于默认条件下,绝大部分的QPaintDevice是选择用QRasterPaintEngine的,这里我说绝大部分是因为,我没有完整的看过所有派生自QPaintDevice的类的代码。而选择用何种QPaintEngine具体逻辑可以以QImage为例:

QPaintEngine *QImage::paintEngine() const
{if (!d)return 0;if (!d->paintEngine) {QPaintDevice *paintDevice = const_cast<QImage *>(this);QPaintEngine *paintEngine = 0;QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();if (platformIntegration)paintEngine = platformIntegration->createImagePaintEngine(paintDevice);d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice);}return d->paintEngine;
}

简而言之就是取决于QGuiApplicationPrivate::platformIntegration()的返回值。至于这个调用相关的,那是有关 QPA 的范畴了,就不再这篇赘述了。有时间再整理 QPA 相关的内容出来。

后记

对于 Qt 绘制的深入探究,可以说是受益匪浅,这篇文章只是描述了冰山一角,其实整个流程比这个简要概括要高级的多。从研究 Qt 源码至今,对整个 Qt 项目的感受与评价,已和往日截然不同。而网上大部分人对 Qt 的评价,其实在我看来,无异于盲人摸象。只有对源码稍有了解的人,才知道 Qt 这个项目,对于客户端开发人员的价值


http://www.ppmy.cn/news/5781.html

相关文章

Typora配合PicGo阿里云图床配置

写博客的时候&#xff0c;刚开始直接在各大平台上直接写&#xff0c;后来还是觉得不太方便&#xff0c;需要在各大平台之间来回切换。于是就改用Typora&#xff0c;但是有个问题就是图片的处理&#xff0c;只能放在本地。想要发布到各大平台&#xff0c;就需要图床。本文结合阿…

m基于kmeans和SVM的网络入侵数据分类算法matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 首先计算整个数据集合的平均值点&#xff0c;作为第一个初始聚类中心C1&#xff1b; 然后分别计算所有对象到C1的欧式距离d&#xff0c;并且计算每个对象在半径R的范围内包含的对象个数W。 此时计…

用简单伪随机数发生器实现随机中点位移分形(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 随机分形(random fractal)采用随机生成机制而得到的分形集.分形体不具有特征尺度(亦即大小尺度跨好几个量级)&#xff0c;却有…

Biotin-PEG-AC,Biotin-PEG-Acrylate,生物素PEG丙烯酸酯线性杂双功能PEG试剂

英文名称&#xff1a;Biotin-PEG-AC&#xff0c;Biotin-PEG-Acrylate 中文名称&#xff1a;生物素-聚乙二醇-丙烯酸酯 生物素-聚乙二醇-丙烯酸酯是一种含有生物素和丙烯酸酯的线性杂双功能聚乙二醇试剂。它是一种有用的带有PEG间隔基的交联或生物结合试剂。生物素能与亲和素和…

进程间通信

进程间通信 进程间通信是不同进程之间的信息传输或交换。在不同的过程中&#xff0c;双方可以访问哪些媒体&#xff1f;进程的用户空间相互独立。一般来说&#xff0c;他们不能互相接触。唯一的例外是共享内存区域。此外&#xff0c;系统空间是一个“公共场所”&#xff0c;所…

力扣(15.18)补9.19

15.三数之和 我以为不会太难&#xff0c;md不会。 其实很让我惊讶的是&#xff0c;双指针用了2层循环但复杂度确是O&#xff08;n&#xff09;。牛&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&am…

【Python基础】内置函数

Python内置函数 1 abs(x) 返回数字x的绝对值或复数的模 2 all(iterable) 如果对于可迭代对象中所有元素x都等价于True&#xff0c;即对所有元素x都有bool(x)等于True&#xff0c;则返回True&#xff1b;对于空的可迭代对象也返回True 3 any(iterable) 只要可迭代对象iter…

Python中转义字符是个啥

文章目录前言一、转义字符是什么&#xff1f;二、常见的转义字符有哪些&#xff1f;总结前言 昨天有粉丝问了我这个代码问题&#xff0c;如下图&#xff1a; 他很好奇代码都没有错误&#xff0c;怎么运行就报错&#xff0c;不知道有咩有小伙伴能看出问题在哪呢&#xff1f; 其…