在存在许多非重叠项目的情况下更新项目时的QML性能问题

Isa*_*aac 6 performance qt qml qtquick2

在下面的QML中,唯一的动态部分是闪烁的矩形.虽然它与生成的项目无关,但闪烁的矩形会导致负载过重并降低系统速度(例如,我使用的i.MX6处理器上的CPU负载为100%),即使它与其他设备之间没有重叠/绑定项目.删除中继器解决了问题,矩形平滑闪烁.

import QtQuick 2.3

Rectangle  {
    id: root
    anchors.fill: parent

    Repeater {
        model: 10000
        delegate: Rectangle {
            width: 5
            height: 5
            x: (index % 200)*6
            y: 50 + Math.floor(index / 200)*6
            color: "blue"
            border.color: "black"
        }
    }

    Rectangle {
        property bool blinker: false
        width: 20
        height: 20
        color: blinker ? "green" : "red"

        Timer {
            running: true
            interval: 100
            repeat: true
            onTriggered: { parent.blinker = !parent.blinker }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是输出(红色矩形将在实际应用程序中闪烁): 在此输入图像描述

model: 10000如果您具有更好的规格并且没有经历减速,则可能需要将Repeater 的参数设置为更高的值.代码在Qt 5.3.2和Qt 5.5.0上进行了测试,两者都存在问题.

在我的实际应用程序中,我的模型数量较少(~100),但委托更复杂.因此,CPU(GPU?)的使用取决于代理的复杂性+ Repeater中的模型项的数量.

为什么由Repeater生成的大量项目(或复杂项目)会影响应用程序的性能,而它们与其他动态对象没有关系/重叠?

更新1

我已Repeater用以下javascript代码替换为生成具有相同属性的相同数量的对象:

Component.onCompleted: {
    var objstr = 'import QtQuick 2.0;Rectangle{id:sample;width:5; height:5;color:"blue";border.color: "black"}';
    for(var i=0;i<200;i++) {
        for(var j=0;j<50;j++) {
            var obj = Qt.createQmlObject(objstr,root);
            obj.x = i * 6
            obj.y = 50 + j*6
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是仍存在性能问题.

更新2

我已经根据这篇文章做了一些考试.

QSG_RENDERER_DEBUG =渲染

设置此标志会输出有关渲染和批处理的一些调试信息.测试应用程序的输出

isaac@ubuntu:~$ QSG_RENDERER_DEBUG=render ./qml-test 
QML debugging is enabled. Only use this in a safe environment.
Batch thresholds: nodes: 64  vertices: 1024
Using buffer strategy: static
Renderer::render() QSGAbstractRenderer(0x93b9570) "rebuild: full"
Rendering:
 -> Opaque: 14002 nodes in 2 batches...
 -> Alpha: 0 nodes in 0 batches...
 - 0x8f0a698 [  upload] [  clip] [opaque] [  merged]  Nodes: 14000  Vertices: 168000  Indices: 224000  root: 0xb3e2a90 sets: 3
 - 0x8f0b310 [  upload] [noclip] [opaque] [  merged]  Nodes:    2  Vertices:     8  Indices:    12  root: 0x0
Renderer::render() QSGAbstractRenderer(0x93b9570) "rebuild: none"
Rendering:
 -> Opaque: 14002 nodes in 2 batches...
 -> Alpha: 0 nodes in 0 batches...
 - 0x8f0a698 [retained] [  clip] [opaque] [  merged]  Nodes: 14000  Vertices: 168000  Indices: 224000  root: 0xb3e2a90 sets: 3
 - 0x8f0b310 [retained] [noclip] [opaque] [  merged]  Nodes:    2  Vertices:     8  Indices:    12  root: 0x0
Renderer::render() QSGAbstractRenderer(0x93b9570) "rebuild: none"
Run Code Online (Sandbox Code Playgroud)

这表示项目是在2组中进行批处理的; 一个有14000个节点,一个有2个节点.这似乎是我们所期望的.

QSG_VISUALIZE =批次标志

此开关可视化UI上的批次.运行它显示覆盖整个UI的纯色.这意味着闪烁的矩形和小矩形将在一批中呈现:

在此输入图像描述

设置clip: true无助于强制分离批次.通过设置opacity: 0.5闪烁矩形,我终于成功强制QML引擎将其放入另一批:

在此输入图像描述

有趣的是,眨眼仍然受到大量小矩形的影响和减速!

QSG_RENDER_TIMING = 1

我试过的最后一个标志是QSG_RENDER_TIMING报告渲染的一些定时信息.根据输出,实际花费的时间是render在渲染循环中.根据Qt文档,render时间是

渲染帧所花费的总时间,包括准备和上传所有必要的数据到GPU.这是总的渲染时间.不要将它与下面的净渲染渲染时间混淆.

但这对我没有帮助.到目前为止,我无法找到此问题的根本原因.

isaac@ubuntu:~$ QSG_RENDER_TIMING=1 ./qml-test 
QML debugging is enabled. Only use this in a safe environment.
qt.scenegraph.time.compilation: shader compiled in 3ms
qt.scenegraph.time.renderer: time in renderer: total=27ms, preprocess=0, updates=5, binding=0, rendering=21
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 107ms, polish=0, sync=65, render=27, swap=1, frameDelta=0
qt.scenegraph.time.renderer: time in renderer: total=1ms, preprocess=0, updates=0, binding=0, rendering=1
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 1ms, polish=0, sync=0, render=1, swap=0, frameDelta=2
qt.scenegraph.time.renderer: time in renderer: total=8ms, preprocess=0, updates=0, binding=0, rendering=8
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 255ms, polish=0, sync=0, render=8, swap=24, frameDelta=255
qt.scenegraph.time.renderer: time in renderer: total=1ms, preprocess=0, updates=0, binding=0, rendering=1
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 290ms, polish=0, sync=0, render=1, swap=28, frameDelta=297
qt.scenegraph.time.renderer: time in renderer: total=0ms, preprocess=0, updates=0, binding=0, rendering=0
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 296ms, polish=0, sync=0, render=0, swap=29, frameDelta=303
qt.scenegraph.time.renderer: time in renderer: total=298ms, preprocess=0, updates=0, binding=0, rendering=298
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 300ms, polish=0, sync=0, render=298, swap=0, frameDelta=306
qt.scenegraph.time.renderer: time in renderer: total=592ms, preprocess=0, updates=0, binding=0, rendering=592
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 593ms, polish=0, sync=0, render=592, swap=0, frameDelta=600
qt.scenegraph.time.renderer: time in renderer: total=292ms, preprocess=0, updates=0, binding=0, rendering=292
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 298ms, polish=0, sync=0, render=295, swap=0, frameDelta=305
qt.scenegraph.time.renderer: time in renderer: total=286ms, preprocess=0, updates=0, binding=0, rendering=286
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 291ms, polish=0, sync=0, render=286, swap=0, frameDelta=298
qt.scenegraph.time.renderer: time in renderer: total=291ms, preprocess=0, updates=0, binding=0, rendering=291
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 296ms, polish=0, sync=0, render=294, swap=0, frameDelta=305
qt.scenegraph.time.renderer: time in renderer: total=286ms, preprocess=0, updates=0, binding=0, rendering=286
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 292ms, polish=0, sync=0, render=286, swap=0, frameDelta=298
qt.scenegraph.time.renderer: time in renderer: total=290ms, preprocess=0, updates=0, binding=0, rendering=290
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 295ms, polish=0, sync=0, render=291, swap=0, frameDelta=301
qt.scenegraph.time.renderer: time in renderer: total=297ms, preprocess=0, updates=0, binding=0, rendering=297
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 302ms, polish=0, sync=0, render=298, swap=0, frameDelta=310
qt.scenegraph.time.renderer: time in renderer: total=290ms, preprocess=0, updates=0, binding=0, rendering=290
qt.scenegraph.time.renderloop: Frame rendered with 'basic' renderloop in 293ms, polish=0, sync=0, render=290, swap=0, frameDelta=316
Run Code Online (Sandbox Code Playgroud)

Rob*_*ell 2

这是一个老问题,但看起来这里没有真正的解决方案,所以我会尽力补充一些有用的点滴。

\n\n

所以,在考虑批处理方面,您肯定部分走在正确的轨道上,这是一个很好的开始。我想你没有看到设置任何效果的原因clip: true是您可能将其设置在错误的位置 - 您需要将其设置在底部矩形(包含计时器),或者您需要包含中继器可以剪辑在其他内容中,例如:

\n\n
Item {\n    anchors.fill: parent\n    clip: true\n    Repeater {\n        ...\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是因为,虽然 Repeater 继承了 Item 类型,但它有点特殊。它创建的子级的父级是中继器的父级,而不是中继器本身,因此中继器将具有剪辑集 \xe2\x80\x93,但在您的情况下没有可应用该剪辑的视觉子级。

\n\n

这里理想的解决方案是clip: true在包含中继器的东西上设置(如上所述),并在底部矩形上设置,以确保两个子树都不会影响另一个子树的性能。

\n\n

但是,您注意到这并没有直接解决您的问题,所以让我们从批处理转向其他事情。

\n\n

快速观察:我注意到您正在使用“基本”渲染循环而不是“线程”渲染循环。是否有一个原因?对于这里的示例,它不会给您带来太多好处(因为您没有很多绑定评估,也没有其他应用程序可言),但在现实世界的情况下,它应该好得多,所以如果可能的话,我建议尝试使用它。

\n\n

一旦你克服了这一点,你需要知道 QtQuick 场景图期望以阻塞垂直同步运行。动画和其他一切都与显示器的垂直同步相关。当您在这个级别上工作时,您需要知道图形设置是如何工作的,并特别小心以确保您能够实现这一点。

\n\n

那么,现在让我们来谈谈图片的硬件方面。我不知道你在imx6上的具体设置是什么,但我假设你在fbdev上使用Linux和Vivante驱动程序,以及Qt的eglfs QPA插件。首先,您应该使用FB_MULTI_BUFFER环境变量来确保您与显示器的垂直同步相关联(即您可能需要FB_MULTI_BUFFER=2FB_MULTI_BUFFER=3)。我不知道现在是否自动设置,但我上次在这样的系统上工作时并不是自动设置的。

\n\n

假设您使用 fbdev,等待显示的机制是 ioctl。您想要查看内核中的显示驱动程序,看看它是否尊重 ioctl FBIO_WAITFORVSYNC,然后编译 Qt 以使用它(grep qtbase for FBIO_WAITFORVSYNC\xe2\x80\x93 它应该位于eglfs 平台插件中的某个位置)。您还会注意到它“隐藏”在环境变量后面: ,因此一旦您确保它构建为发出该 ioctl,您QT_QPA_EGLFS_FORCEVSYNC就会想要这样做。export QT_QPA_EGLFS_FORCEVSYNC=1当您这样做时,您应该检查FBIOGET_VSCREENINFO返回有用且正确的信息,因为eglfs将使用从中返回的信息来确定显示器的刷新率(请参阅eglfs插件中的q_refreshRateFromFb)。

\n\n

毕竟,事情可能会有所改善。如果他们不这样做,我可以说,在类似的设置中,我之前遇到过无法强制限制渲染的情况(其中 FBIO_WAITFORVSYNC 实际上无法使用),这意味着您已经离开了自己做这件事。我不知道这个问题有多普遍,但它很可能适用于你,所以:

\n\n

如果您遇到这种情况,您可以调整QT_QPA_UPDATE_IDLE_TIME=x环境变量,告诉 Qt 在绘制另一帧之前等待至少x毫秒的最短持续时间,例如,export QT_QPA_UPDATE_IDLE_TIME=32在帧之间至少等待 32 毫秒,从而获得大约 30 FPS。不过,您应该谨慎对待这一点,因为它远非理想情况,而且它并不是我所谓的广泛“支持”的东西。

\n