本页内容

Qt Quick 场景图

场景图在Qt Quick

Qt Quick 2中采用了一个专用的场景图,随后通过OpenGL ES、OpenGL、Vulkan、Metal或Direct 3D等图形API对其进行遍历和渲染。 在图形渲染中使用场景图而非传统的命令式绘制系统(如QPainter 及其类似方法),意味着待渲染的场景可以在帧与帧之间保留,且在渲染开始前已知待渲染的完整基元集。这为多种优化提供了可能,例如通过批量渲染来最小化状态变化,以及丢弃被遮挡的基元。

例如,假设一个用户界面包含一个由十个项目组成的列表,每个项目都有背景色、图标和文本。如果使用传统的绘制技术,这将导致 30 次绘制调用和数量相当的状态变更。 而场景图则可以重新组织待渲染的基元,使所有背景在一次调用中完成绘制,随后一次性绘制所有图标,最后一次性绘制所有文本,从而将总渲染调用次数减少到仅 3 次。此类批处理和状态变更的减少,在某些硬件上可以显著提升性能。

场景图与Qt Quick 2.0紧密关联,无法独立使用。场景图由QQuickWindow 类进行管理和渲染,自定义Item类型可通过调用QQuickItem::updatePaintNode()将图形基元添加到场景图中。

场景图是 Item 场景的图形化表示,它是一个独立的结构,包含渲染所有项所需的全部信息。一旦建立,它就可以独立于项的状态进行操作和渲染。 在许多平台上,当 GUI 线程正在准备下一帧的状态时,场景图甚至会在一条专用的渲染线程上进行渲染。

注意: 本页列出的许多信息 专门针对Qt Quick 场景图的内置默认行为。当使用其他场景图适配方案(例如software 适配方案)时,并非所有概念都适用。有关不同场景图适配方案的更多信息,请参阅《场景图适配方案》。

Qt Quick 场景图结构

场景图由若干预定义的节点类型组成,每种类型都具有特定的用途。虽然我们将其称为“场景图”,但更精确的定义应为“节点树”。该树由 QML 场景中的QQuickItem 类型构建而成,随后场景会在内部由渲染器进行处理,并最终绘制出来。 节点本身不包含任何活跃的绘制代码,也不包含虚拟的paint() 函数。

尽管节点树主要由现有的Qt Quick QML类型在内部构建而成,但用户也可以添加包含自身内容的完整子树,包括代表3D模型的子树。

节点

对用户而言最重要的节点是QSGGeometryNode 。它通过定义几何形状和材质来定义自定义图形。 几何形状通过QSGGeometry 进行定义,用于描述图形基元的形状或网格。它可以是直线、矩形、多边形、多个不相连的矩形,或是复杂的3D网格。材质则定义了该形状中像素的填充方式。

一个节点可以拥有任意数量的子节点,且几何节点在渲染时将按子节点顺序显示,父节点位于其子节点之后。

注意:这 并不涉及渲染器中的实际渲染顺序。仅保证视觉输出效果。

可用的节点包括:

QSGClipNode

在场景图中实现裁剪功能

QSGGeometryNode

用于场景图中所有渲染内容

QSGNode

场景图中所有节点的基类

QSGOpacityNode

用于更改节点的不透明度

QSGTransformNode

在场景图中实现变换

通过继承 `QQuickItem::updatePaintNode()` 并设置 `QQuickItem::ItemHasContents ` 标志,可将自定义节点添加到场景图中。

警告: 原生图形(OpenGL、Vulkan、Metal 等)操作以及与场景图的交互必须 仅在渲染线程上进行,主要是在调用 updatePaintNode() 期间。经验法则是:仅在QQuickItem::updatePaintNode() 函数内部使用以“QSG”为前缀的类。

更多详情,请参阅“场景图 - 自定义几何体”。

预处理

节点具有一个虚拟函数QSGNode::preprocess(),该函数将在渲染场景图之前被调用。节点的子类可以设置标志QSGNode::UsePreprocess ,并重写QSGNode::preprocess()函数,以对节点进行最终准备。例如,将贝塞尔曲线划分为适合当前缩放因子的正确细节级别,或者更新纹理的某一部分。

节点所有权

节点的所有权要么由创建者显式指定,要么通过设置标志QSGNode::OwnedByParent 由场景图自动分配。将所有权分配给场景图通常更为可取,因为当场景图位于 GUI 线程之外时,这可以简化清理工作。

材质

材质描述了QSGGeometryNode 中几何体内部的填充方式。它封装了图形管道中顶点阶段和片段阶段的图形着色器,并在实现效果方面提供了极大的灵活性,尽管大多数Qt Quick 项目本身仅使用非常基础的材质,例如纯色填充和纹理填充。

对于仅希望对 QML 项类型应用自定义着色的用户,可以直接在 QML 中使用 `ShaderEffect ` 类型来实现。

以下是材质类的完整列表:

QSGFlatColorMaterial

在场景图中渲染纯色几何体的便捷方式

QSGMaterial

封装着着色器程序的渲染状态

QSGMaterialShader

表示一种与图形API无关的着色器程序

QSGMaterialType

与 QSGMaterial 结合使用时,作为唯一类型标识符

QSGOpaqueTextureMaterial

在场景图中渲染带纹理几何体的便捷方式

QSGTextureMaterial

在场景图中渲染带纹理几何体的便捷方式

QSGVertexColorMaterial

在场景图中渲染按顶点着色的几何体的便捷方式

便捷节点

场景图 API 属于低级 API,侧重于性能而非便捷性。即使是最基础的自定义几何体和材质,从零开始编写也需要相当数量的代码。因此,该 API 提供了一些便捷类,以便用户能够轻松使用最常见的自定义节点。

场景图与渲染

场景图的渲染在QQuickWindow 类内部进行,且没有公开的API可供访问。 不过,在渲染管道的某些位置,用户可以接入应用程序代码。这可用于添加自定义场景图内容,或通过直接调用场景图所使用的图形 API(如 OpenGL、Vulkan、Metal 等)来插入任意渲染命令。这些集成点由渲染循环定义。

有关场景图渲染器工作原理的详细说明,请参阅《Qt Quick 场景图默认渲染器》。

目前提供两种渲染循环变体:basic 和threaded 。basic 是单线程的,而threaded 则在专用线程上执行场景图渲染。 Qt会根据平台以及可能使用的图形驱动程序尝试选择合适的渲染循环。当选择结果不理想时,或者出于测试目的,可以使用环境变量QSG_RENDER_LOOP 来强制使用指定的渲染循环。要验证当前正在使用的渲染循环,请启用qt.scenegraph.general logging category 。

多线程渲染循环('threaded')

在许多配置中,场景图的渲染将在一个专用的渲染线程上进行。这样做是为了提高多核处理器的并行性,并更好地利用诸如等待阻塞式交换缓冲区调用等空闲时间。这虽然能显著提升性能,但会对与场景图交互的时间和位置施加一定的限制。

以下简要概述了使用多线程渲染循环和 OpenGL 渲染一帧画面的大致流程。除 OpenGL 上下文的具体细节外,这些步骤与其他图形 API 也是相同的。

显示GUI和渲染线程同步的流程图

  1. QML 场景中发生变化,导致调用 `QQuickItem::update() `。这可能是动画或用户输入等操作的结果。向渲染线程发布一个事件以启动新帧。
  2. 渲染线程准备绘制新帧,并开始阻塞 GUI 线程。
  3. 在渲染线程准备新帧期间,GUI 线程会调用 `QQuickItem::updatePolish()` 方法,在项目渲染前对其进行最终调整。
  4. GUI 线程被阻塞。
  5. QQuickWindow::beforeSynchronizing() 信号被发出。应用程序可以通过Qt::DirectConnection 直接订阅该信号,以便在调用QQuickItem::updatePaintNode() 之前进行必要的准备工作。
  6. 将 QML 状态同步到场景图中。这是通过针对自上一帧以来发生变化的所有项目调用QQuickItem::updatePaintNode() 函数来实现的。这是 QML 项目与场景图中的节点唯一交互的时刻。
  7. 释放 GUI 线程锁。
  8. 渲染场景图:
    1. 发出QQuickWindow::beforeRendering() 信号。应用程序可通过Qt::DirectConnection 直接连接到该信号,以调用自定义图形 API 方法,这些内容将视觉上叠加在 QML 场景下方。
    2. 已指定 `QSGNode::UsePreprocess` 的项,其 `QSGNode::preprocess()` 函数将被调用。
    3. 渲染器处理这些节点。
    4. 渲染器生成状态,并记录所用图形 API 的绘制调用。
    5. QQuickWindow::afterRendering() 信号被发出。应用程序可以(使用Qt::DirectConnection )直接连接到此信号,以发出自定义图形 API 调用,这些调用将在视觉上叠加在 QML 场景之上。
    6. 帧现已准备就绪。缓冲区被交换(OpenGL),或者记录一个呈现命令,并将命令缓冲区提交到图形队列(Vulkan、Metal)。发出QQuickWindow::frameSwapped() 信号。
  9. 在渲染线程进行渲染期间,GUI 可以自由地推进动画、处理事件等操作。

目前,多线程渲染器默认用于以下场景:Windows 系统上的 Direct3D 11,以及使用 opengl32.dll 时的 OpenGL;Linux 系统(不包括 Mesa llvmpipe);使用 Metal 的 macOS;移动平台;使用 EGLFS 的嵌入式 Linux;以及无论平台如何均支持 Vulkan 的情况。 未来版本中,上述情况可能会发生变化。您始终可以通过在环境中设置 `QSG_RENDER_LOOP=threaded ` 来强制使用多线程渲染器。

非多线程渲染循环(“basic”)

目前,在 Windows 系统上使用 OpenGL 且未采用系统标准 opengl32.dll 时、macOS 系统上使用 OpenGL 时、WebAssembly 以及部分 Linux 驱动程序环境下,默认使用非线程化渲染循环。 对于后者,这主要是一种预防措施,因为并非所有 OpenGL 驱动程序与窗口系统的组合都经过了测试。

在 macOS 和 OpenGL 环境下,若使用 Xcode 10(10.14 SDK)或更高版本进行构建,则不支持多线程渲染循环,因为这会在 macOS 10.14 上启用基于图层的视图。 您可以使用 Xcode 9(10.13 SDK)进行构建以禁用图层支持,在这种情况下,多线程渲染循环将默认可用并被采用。Metal 则不存在此类限制。

WebAssembly 不支持多线程渲染循环,因为 Web 平台对在主线程以外的线程上使用 WebGL 的支持有限,且对阻塞主线程的支持也有限。

即使使用非线程化渲染循环,您也应像使用线程化渲染器那样编写代码,否则会导致代码无法移植。

以下是对非多线程渲染器中帧渲染序列的简化说明。

显示单线程渲染循环序列的流程图

驱动动画

上图中的“Advance Animations ”指的是什么?

默认情况下,Qt Quick 动画(例如NumberAnimation )由默认动画驱动程序驱动。该驱动程序依赖于基本系统定时器,例如QObject::startTimer()。该定时器通常以 16 毫秒的间隔运行。 虽然这种方式永远无法完全精确,且还取决于底层平台中定时器的精度,但它的优点在于与渲染过程相互独立。无论显示器的刷新率如何,也无论是否启用了与显示器垂直同步的同步功能,它都能提供一致的效果。这就是动画在basic 渲染循环中工作的方式。

为了提供更精确的结果并减少屏幕卡顿,且不受渲染循环设计(无论是单线程还是多线程)的影响,渲染循环可能会选择安装自己的自定义动画驱动程序,并亲自接管advancing 的运行,而不依赖定时器。

这就是threaded 渲染循环所实现的功能。实际上,它安装的并非一个,而是两个动画驱动程序:一个位于 GUI 线程(用于驱动常规动画,例如NumberAnimation ),另一个位于渲染线程(用于驱动渲染线程动画,即Animator 类型的动画,例如OpacityAnimator 或XAnimator )。 这两类动画都会在帧准备阶段同步推进,即动画现已与渲染同步。这是合理的,因为底层图形栈会将呈现过程限制在显示器的垂直同步频率内。

因此,在上方的threaded 渲染循环图中,两个线程上都明确包含了一个Advance animations 步骤。 对于渲染线程而言,这很简单:由于该线程受垂直同步限制,在每帧中将动画(针对Animator 类型)推进16.67毫秒,比依赖系统计时器能获得更准确的结果。 (当受限于垂直同步(vsync)时——在60 Hz刷新率下,垂直同步周期为1000/60 毫秒——可以合理地假设,自上一帧执行相同操作以来,已大致经过了这么长的时间)

同样的方法也适用于GUI(主)线程上的动画: 由于GUI线程与渲染线程之间存在本质上的数据同步需求,GUI线程实际上被限制在与渲染线程相同的速率下运行;同时,由于大部分渲染准备工作现已卸载至渲染线程,GUI线程的工作量相应减少,从而为应用程序逻辑留出了更大的处理余地。

虽然上述示例使用了每秒 60 帧的速率,但 `Qt Quick ` 也支持其他刷新率:刷新率会从 `QScreen ` 和平台中查询获得。 例如,对于 144 Hz 的屏幕,间隔为 6.94 毫秒。与此同时,如果基于垂直同步(vsync)的限速机制未能按预期工作,这恰恰会引发问题,因为如果渲染循环对当前状况的判断与实际情况不符,就会导致动画节奏出现偏差。

注意: 从 Qt 6.5开始, 多线程渲染循环提供了仅基于已用时间(QElapsedTimer )选择其他动画驱动程序的选项。要启用此功能,请将QSG_USE_SIMPLE_ANIMATION_DRIVER 环境变量设置为非零值。 此方法的优势在于:当存在多个窗口时,无需任何用于回退到QTimer 的基础架构;无需通过启发式方法来判断基于垂直同步(vsync)的限速是否缺失或失效;能够兼容垂直同步限速中的任何时间漂移, 且不受主屏幕刷新率的限制,因此在多屏幕配置中可能表现更佳。此外,即使基于垂直同步的限速功能失效或被禁用,它也能正确驱动渲染线程的动画(如Animator 类型)。 另一方面,采用此方法时,动画可能会被感知为不够流畅。出于兼容性考虑,该功能目前作为可选功能提供。

总而言之,只要满足以下条件,threaded 渲染循环预计能提供更流畅、卡顿更少的动画:

  • 屏幕上仅有一个窗口(如QQuickWindow 所示)。
  • 基于 VSync 的限速机制能与底层图形和显示堆栈正常配合工作。

如果没有可见窗口,或者有多个可见窗口,情况又会如何?

当没有可渲染的窗口时(例如,因为我们的QQuickWindow 被最小化(Windows)或完全被遮挡(macOS)),我们无法呈现帧,因此无法依赖该线程与屏幕刷新率“同步”运行。 在这种情况下,threaded 渲染循环会自动切换到基于系统定时器的机制来驱动动画,即暂时切换到basic 循环所使用的机制。

当屏幕上存在多个QQuickWindow 实例时,情况也是如此。上文所述的通过与渲染线程同步来在GUI线程上推进动画的模型,现已不再适用,因为现在存在多个同步点以及多个渲染线程(每个窗口一个)。 此时,同样有必要回退到基于系统定时器的方法,因为GUI线程被阻塞的时间长短和频率现在取决于多个因素,包括窗口中的内容 (它们是否在进行动画?更新频率如何?)以及图形栈的行为(它究竟如何处理两个或多个线程在“等待垂直同步(wait-for-vsync)”状态下的呈现?)。 由于我们无法以稳定且跨平台的方式保证渲染速率被限制在窗口的呈现速率内(首先,这到底是指哪个窗口?),因此动画的推进不能基于渲染进程。

这种动画处理机制的切换对应用程序而言是透明的。

如果基于垂直同步的限速功能失效、被全局禁用,或者应用程序自己将其禁用了呢?

threaded 的渲染循环依赖于图形API实现和/或窗口系统来进行限速,例如:在OpenGL(GLX、EGL、WGL)中请求交换间隔为1;在Direct 3D中以间隔1调用Present();或在Vulkan中使用FIFO 呈现模式。

某些图形驱动程序允许用户覆盖此设置并将其关闭,从而忽略 Qt 的请求。例如,图形驱动程序的系统级控制面板允许用户覆盖应用程序关于垂直同步(vsync)的设置。 此外,图形栈也可能无法提供基于垂直同步的正确限速功能,某些虚拟机中就可能出现这种情况(主要是由于使用了基于软件光栅化的 OpenGL 或 Vulkan 实现)。

如果不在交换/呈现操作(或其他图形操作)中进行阻塞,此类渲染循环会导致动画推进过快。对于basic 渲染循环而言,这不会造成问题,因为它始终依赖于系统定时器。对于threaded ,其行为会因Qt版本而异:

  • 如果已知系统无法提供基于垂直同步(vsync)的限速功能,在 Qt 6.4 之前,唯一的解决方案是使用basic 渲染循环,方法是在运行应用程序前手动在环境中设置QSG_RENDER_LOOP=basic 。
  • 从 Qt 6.4 开始,将环境变量 `QSG_NO_VSYNC ` 设置为非零值,或者将窗口的 `QSurfaceFormat::swapInterval()` 设置为 `0 `,均可同样缓解该问题: 通过显式请求禁用基于垂直同步(vsync)的阻塞机制——无论该请求在实际中是否产生效果——threaded 渲染循环便能进而识别出依赖垂直同步来驱动动画是徒劳的,并会回退到使用系统定时器,就像处理多个窗口时那样。
  • 更妙的是,从 Qt 6.4 开始,场景图还会尝试通过一些简单的启发式算法来识别帧率是否“过快”,并在必要时自动切换到系统定时器。 这意味着在大多数情况下无需进行任何操作,即使默认渲染循环为threaded ,应用程序也能按预期运行动画。虽然这一过程对应用程序而言是透明的,但出于故障排除和开发目的,需要知道当启用QSG_INFO 或qt.scenegraph.general 时,系统会通过"Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..." 消息记录此操作。 该方法的缺点在于,由于它首先需要收集数据进行评估,因此通常需要处理一小批帧后才会生效,这意味着在打开QQuickWindow 时,应用程序在短时间内仍可能显示动画速度过快的情况。此外,它可能无法捕获所有可能的垂直同步(vsync)失效情况。

但请注意,按设计,上述方法均无法帮助渲染线程的动画(即Animator 类型)。在缺乏基于垂直同步的阻塞机制的情况下,animators 默认会以快于预期的速度错误地推进,即使已为常规的animations 启用了这些变通方案。如果这成为问题,请考虑通过设置QSG_USE_SIMPLE_ANIMATION_DRIVER 来使用替代的动画驱动程序。

注意:请 注意,即使禁用了对垂直同步的等待,GUI(主)线程上的渲染循环逻辑和事件处理也不一定不受限制:两个渲染循环都会通过QWindow::requestUpdate() 调度窗口的更新。在大多数平台上,这由一个 5 毫秒的 GUI 线程定时器支持,以便为事件处理留出时间。 在某些平台(例如 macOS)上,系统会使用平台专有的 API(如 CVDisplayLink)来获取准备新帧的合适时机通知,这很可能以某种形式与显示器的垂直同步(vsync)相关联。这在基准测试及类似场景中可能具有重要意义。 对于试图进行低级基准测试的应用程序和工具,将QT_QPA_UPDATE_IDLE_TIME 环境变量设置为0 可能会有所帮助,这有助于减少GUI线程的空闲时间。对于常规应用程序的使用,在大多数情况下,默认设置就足够了。

注意:如有疑问 ,请启用qt.scenegraph.general 和qt.scenegraph.time.renderloop 日志类别以进行故障排除,因为这些日志可能会揭示渲染和动画未按预期速度运行的原因线索。

使用 QQuickRenderControl 自定义控制渲染

使用QQuickRenderControl 时,驱动渲染循环的责任将转移给应用程序。在这种情况下,不会使用内置的渲染循环。取而代之的是,由应用程序在适当的时候调用润色、同步和渲染步骤。可以实现类似于上文所示的线程化或非线程化行为。

此外,应用程序可能希望结合QQuickRenderControl 来实现并安装自己的QAnimationDriver。这可以对Qt Quick 动画的驱动实现完全控制,对于未在屏幕上显示的内容而言,这一点尤为重要——由于这些内容并未进行帧渲染,因此与呈现速率毫无关联。此操作是可选的,默认情况下,动画将根据系统计时器进行推进。

使用基于 QRhi 和原生 3D 渲染扩展场景图

场景图提供了三种方法来集成应用程序提供的图形命令:

  • 在场景图自身的渲染之前或之后,直接发出基于QRhi 的命令,或OpenGL、Vulkan、Metal、Direct3D命令。这实际上是在主渲染阶段的前面或后面插入一组绘制调用。不会使用额外的渲染目标。
  • 渲染到纹理中,并在场景图中创建一个带纹理的节点。这涉及一个额外的渲染通道和渲染目标。
  • 通过在场景图中实例化QSGRenderNode 子类,在场景图自身的渲染过程中内联发出绘制调用。这与第一种方法类似,但自定义绘制调用实际上被注入到了场景图的命令流中。

底层/叠加模式

通过连接到QQuickWindow::beforeRendering()和QQuickWindow::afterRendering()信号,应用程序可以直接在与场景图渲染相同的上下文中调用QRhi 或原生3D API。借助Vulkan或Metal等API,应用程序可以通过QSGRendererInterface 查询原生对象(例如场景图的命令缓冲区),并根据需要向其中写入命令。 正如信号名称所示,用户可以选择在Qt Quick 场景之下或之上渲染内容。这种集成方式的优势在于,渲染过程无需额外的渲染目标,并省去了可能耗时的贴图步骤。其缺点是,自定义渲染只能在Qt Quick 自身渲染的开始或结束时进行。 使用QSGRenderNode 代替QQuickWindow 信号可以在一定程度上解除该限制,但在处理3D内容和深度缓冲区使用时,无论采用哪种方式都必须格外谨慎,因为依赖深度测试并在启用深度写入的情况下进行渲染,很容易导致自定义内容与Qt Quick 内容的深度缓冲区使用发生冲突。

从 Qt 6.6 开始,QRhi API 被视为半公开的,即向应用程序提供并有相关文档记录,尽管其兼容性保证有限。这使得开发者能够利用场景图本身所使用的相同图形和着色器抽象,来创建可移植的、跨平台的 2D/3D 渲染代码。

“场景图 - QML 下的 RHI”示例展示了如何使用 `QRhi` 实现底层/覆盖层(underlay/overlay)方法。

“场景图 - QML 下的 OpenGL”示例展示了如何在 OpenGL 环境中使用这些信号。

“场景图 - QML 下的 Direct3D11”示例演示了如何使用 Direct3D 调用这些信号。

“场景图 - QML 下的 Metal”示例展示了如何使用Metal调用这些信号。

“场景图 - QML 下的 Vulkan”示例演示了如何在 Vulkan 环境中使用这些信号。

从 Qt 6.0 开始,直接使用底层图形 API 必须通过调用QQuickWindow::beginExternalCommands() 和QQuickWindow::endExternalCommands() 来封装。 这个概念可能在QPainter::beginNativePainting()中已经很熟悉,其作用类似:它使Qt Quick 场景图能够识别到,当前记录的渲染通过(如果存在)中的任何缓存状态以及关于该状态的假设现在都已失效,因为应用程序代码可能通过直接操作底层图形API对其进行了修改。在使用QRhi 时,这既不适用也不必要。

在将自定义 OpenGL 渲染与场景图混合使用时,必须确保应用程序不会将 OpenGL 上下文保留在缓冲区已绑定、属性已启用、z 缓冲区或模板缓冲区中存在特殊值等状态下。否则可能会导致不可预测的行为。

自定义渲染代码必须具备线程意识,即不应假设其是在应用程序的GUI(主)线程上执行的。在连接到QQuickWindow 信号时,应用程序应使用Qt::DirectConnection ,并理解已连接的槽函数将在场景图的专用渲染线程(若存在)上被调用。

基于纹理的方法

当应用程序需要在Qt Quick 场景中获取某个自定义3D渲染的“扁平化”2D图像时,基于纹理的方法是最灵活的方案。这还允许使用独立于主渲染过程所用缓冲区的专用深度/模板缓冲区。

在使用 OpenGL 时,可借助传统便利类QQuickFramebufferObject 来实现此功能。基于QRhi 的自定义渲染器以及除 OpenGL 以外的其他图形 API 也可采用此方法,尽管QQuickFramebufferObject 目前尚不支持这些方案。 以下示例演示了如何使用底层 API 直接创建纹理并渲染到纹理中,随后在自定义的 `QQuickItem` 中将该资源封装并用于 `Qt Quick ` 场景:

场景图 - RHI 纹理项示例。

场景图 - Vulkan 纹理导入示例。

场景图 - Metal 纹理导入示例。

内联方法

使用QSGRenderNode 时,自定义绘制调用并非注入在场景图渲染阶段录制的开头或结尾,而是注入在场景图的渲染过程中。 这是通过基于QSGRenderNode 实例创建自定义QQuickItem 实现的,该场景图节点专门用于允许通过QRhi 或原生3D API(如OpenGL、Vulkan、Metal或Direct 3D)发出图形命令。

“场景图 - 自定义 QSGRenderNode”示例演示了这种方法。

使用 QPainter 的自定义项

QQuickItem 提供了一个子类QQuickPaintedItem ,它允许用户使用QPainter 渲染内容。

警告:使用 QQuickPaintedItem 会通过间接的 2D 表面来渲染其内容,无论是采用软件光栅化还是使用 OpenGL 帧缓冲区对象 (FBO),因此渲染过程需要两个步骤:首先对表面进行光栅化,然后绘制该表面。直接使用场景图 API 始终要快得多。

日志记录支持

场景图支持多种日志记录类别。这些不仅有助于追踪性能问题和错误,对 Qt 贡献者也大有裨益。

  • qt.scenegraph.time.texture - 记录纹理上传所花费的时间
  • qt.scenegraph.time.compilation - 记录着色器编译所花费的时间
  • qt.scenegraph.time.renderer - 记录渲染器各步骤所耗时间
  • qt.scenegraph.time.renderloop - 记录渲染循环各步骤所耗时间。在threaded 渲染循环中,这有助于了解GUI线程和渲染线程中各帧准备步骤之间所耗费的时间。 因此,它还可以作为有用的故障排除工具,例如,用于确认基于垂直同步(vsync)的限速以及其他低级 Qt 启用功能(如QWindow::requestUpdate())如何影响渲染和呈现管道。
  • qt.scenegraph.time.glyph - 记录准备距离场图标所花费的时间
  • qt.scenegraph.general - 记录关于场景图和图形栈各部分的一般信息
  • qt.scenegraph.renderloop - 生成有关渲染各阶段的详细日志。此日志模式主要适用于从事 Qt 开发的工程师。

旧版环境变量 `QSG_INFO ` 仍然可用。将其设置为非零值可启用 `qt.scenegraph.general ` 日志类别。

注意:当 遇到图形问题,或不确定正在使用哪个渲染循环或图形 API 时,请务必在启动应用程序时至少启用qt.scenegraph.general 和qt.rhi.* ,或设置QSG_INFO=1 。这样,在初始化过程中,一些关键信息将输出到调试日志中。

场景图后端

除了公共 API 之外,场景图还拥有一个适配层,该层允许实现针对特定硬件的适配。这是一个未记录的、内部且私有的插件 API,可让硬件适配团队充分利用其硬件性能。它包括:

  • 自定义纹理;具体包括对 `QQuickWindow::createTextureFromImage ` 的实现,以及 `Image ` 和 `BorderImage ` 类型所使用的纹理的内部表示形式。
  • 自定义渲染器;适配层允许插件决定如何遍历和渲染场景图,从而能够针对特定硬件优化渲染算法,或利用可提升性能的扩展功能。
  • 许多默认 QML 类型的自定义场景图实现,包括其文本和字体渲染。
  • 自定义动画驱动程序;允许动画系统挂接到低级显示垂直刷新率上,以实现流畅的渲染。
  • 自定义渲染循环;可更好地控制 QML 处理多个窗口的方式。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.