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

时间:2015-12-21 16:16:34

标签: performance qt qml qtquick2

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

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 }
        }
    }
}

这是输出(红色矩形在实际应用中会闪烁): enter image description here

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

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

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

更新1

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

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
        }
    }
}

但是性能问题仍然存在。

更新2

我已根据this article完成了一些考试。

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"

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

QSG_VISUALIZE =批次标志

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

enter image description here

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

enter image description here

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

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

1 个答案:

答案 0 :(得分:1)

这是一个老问题,但看起来这里没有真正的解决方案,所以我会尽力使用一些有用的零碎件。

所以,你肯定会部分地走上正确的轨道,看看批量,开始很好。我想你没有看到设置clip: true的任何影响的原因是你可能已将它设置在错误的位置 - 你需要将它设置在底部矩形(包含Timer),或者你需要将Repeater包含在您可以剪辑的其他内容中,例如:

Item {
    anchors.fill: parent
    clip: true
    Repeater {
        ...
    }
}

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

这里理想的解决方案是在包含Repeater的内容(如上所述)和底部Rectangle上设置clip: true,以确保两个子树中的任何一个都不会影响另一个子树的性能。

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

快速观察:我注意到您使用的是“basic”renderloop而不是“threaded”。是否有一个原因?用你在这里的例子不会给你带来太多的好处(因为你没有很多绑定评估,也没有其他应用可以说),但在现实世界的情况下,它应该会好一点,所以我会建议尽可能使用它。

一旦你完成了这个,你需要知道QtQuick场景图预计会运行阻塞vsync。动画和其他所有内容都与显示器的vsync相关联。当你在这个级别工作时,你需要知道你的图形设置是如何工作的,并特别注意确保你能够实现这一点。

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

假设您正在使用fbdev,等待显示的机制是ioctl。你想在内核中查看你的显示驱动程序,看看它是否尊重FBIO_WAITFORVSYNC ioctl,并编译Qt使用它(grep qtbase for FBIO_WAITFORVSYNC - 它应该在eglfs平台插件中的某个地方)。您还会注意到它隐藏在环境变量QT_QPA_EGLFS_FORCEVSYNC后面,所以一旦确保它构建为发出ioctl,您就会想要export QT_QPA_EGLFS_FORCEVSYNC=1。当你在它的时候,你应该检查FBIOGET_VSCREENINFO ioctl是否正在返回有用和正确的信息,因为eglfs将使用返回的信息来确定显示的刷新率(参见eglfs插件中的q_refreshRateFromFb)

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

如果 在这种情况下,您可以调整QT_QPA_UPDATE_IDLE_TIME=x环境变量,告诉Qt等待至少x ms的最短持续时间,然后再绘制另一个例如,export QT_QPA_UPDATE_IDLE_TIME=32帧至少在帧之间等待32ms,大约30 FPS。你应该谨慎对待这一点,因为它远非一个理想的场景,而且它并不是我称之为广泛“支持”的东西。