前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Qt源码笔记】深谈 Qt 绘制

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

作者头像
Harper
发布2021-07-27 10:11:33
1.9K0
发布2021-07-27 10:11:33
举报
文章被收录于专栏:Harper的碎碎念Harper的碎碎念

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

结论

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

探究过程

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

代码语言:javascript
复制
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 ,根据目录下代码文件名即可知道是哪种指令集,一目了然。

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

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

意外收获

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

代码语言:javascript
复制
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为例:

代码语言:javascript
复制
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 这个项目,对于客户端开发人员的价值。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结论
  • 探究过程
  • 意外收获
  • 附注
  • 后记
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档