【Qt源码笔记】深谈 Qt 绘制
之前写了一篇 浅谈Qt控件绘制 。之所以叫浅谈是因为调用都是比较表层的调用。其实 Qt 的绘制,可以说用 Qt 的人都有用到,但是对于绘制底层,了解的人并不见得很多。我其实之前也是云山雾罩,从来没有深究过。所以想着知其然还是要知其所以然。
结论
在 Windows 平台 默认的 Qt 绘制,最终到底层,是直接调用指令集指令的,这有别于我最初的猜测,我以为是用 Windows API 。这着实让我吃了一惊。这让我对 Qt 的性能又放心了一些。
探究过程
其实研究这个,比其他的更好溯源。附上三段堆栈信息。
1 | Qt5Guid.dll!BLEND_SOURCE_OVER_ARGB32_AVX2(unsigned int * dst, const unsigned int * src, const int length) 行 184 C++ |
只要从绘制代码,单步调试即可找到指定地点。不过最终使用的指令集却并不一样。有的用 avx2 、有的则是用 sse2 。如果想探究指令集部分的使用,需要到源码目录 qtbase\src\gui\painting ,根据目录下代码文件名即可知道是哪种指令集,一目了然。
回过头来再看上边的那些函数调用。其实不难发现,所有的绘制在中间都必然要经过QPaintEngine
。QRasterPaintEngine
只不过是它的一个派生,这个后边再说。而 QPaintEngine
根据所要绘制的内容,来区分绘制逻辑,比方说涂色采用填充 buffer 、统一刷新的方式;字体绘制要调用字体图元相关绘制逻辑等等。
所有的表层绘制都要经过绘制引擎来向下传递绘制信息。这是 Qt 作为一个高级框架的闪光点,在其他的 Qt 模块也有类似发现,比如控件的绘制上。这样看来 Qt 这个框架能给我们的,除了代码逻辑本身,还有设计。
意外收获
在整个代码探究的过程,我发现了这样一段代码,可以说是非常惊喜了。
1 | if (pd->devType() == QInternal::Pixmap) |
这段代码是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
为例:
1 | QPaintEngine *QImage::paintEngine() const |
简而言之就是取决于QGuiApplicationPrivate::platformIntegration()
的返回值。至于这个调用相关的,那是有关 QPA 的范畴了,就不再这篇赘述了。有时间再整理 QPA 相关的内容出来。
后记
对于 Qt 绘制的深入探究,可以说是受益匪浅,这篇文章只是描述了冰山一角,其实整个流程比这个简要概括要高级的多。从研究 Qt 源码至今,对整个 Qt 项目的感受与评价,已和往日截然不同。而网上大部分人对 Qt 的评价,其实在我看来,无异于盲人摸象。只有对源码稍有了解的人,才知道 Qt 这个项目,对于客户端开发人员的价值。