您的位置:首页 > 产品设计 > UI/UE

Qt:使用ShaderEffectSource对Item拍摄"UI快照",提升渲染效率

2017-03-19 16:03 483 查看
在上一篇中,提到了如何使用QML Profiler对程序性能问题进行分析

http://blog.csdn.net/wsj18808050/article/details/62226574

而这片文章,就开始我们第一个实战。

这个实战,对应一个很简单并且很常见的需求,就是我们有一个静态(固定内容)的图像,但是这个图像需要一直显示在那里,也许我们还需要对这个图像进行动画。这时候,根据这个图像本身的复杂程度,会给我们整体渲染带来不同程度的渲染开销,当图像足够复杂时,带来的开销可能会大到导致界面卡顿。而既然这是一个静态的图像,照道理就不应该带来过多的开销。所以本次文章的思路就是利用ShaderEffectSource对复杂的Item拍摄一个”UI快照”,代替其本体显示,提升整体渲染效率。

在我写的Demo里,我有一个GridView并且里面创建了240个Item(就是下图中的小方块),每个Item都有一个子Rectangle并且做了旋转等各种操作,同时Item本身也开启了clip(这个clip会给渲染带来很高的负担,因为每个clip都会开启一个渲染批次)。

代码如下:

GridView {
id:gridView
width: parent.width
height: parent.height
cellWidth: 50
cellHeight: 50

delegate: Item {
width: 50
height: 50
clip: true

Rectangle {
anchors.centerIn: parent
width: 32
height: 32
color: Qt.rgba( Math.random(), Math.random(), Math.random(), 1 ).toString()
rotation: Math.random() * 360

Text {
anchors.centerIn: parent
font.pixelSize: 8 + Math.random() * 5
color: Qt.rgba( Math.random(), Math.random(), Math.random(), 1 ).toString()
text: "Text"
}

transform: [

Rotation {
id: rotation
origin.x: Math.random() * 32
origin.y: Math.random() * 32
axis { x: 0; y: 1; z: 0 }
angle: Math.random() * 15
}
]
}
}

model: ListModel
{
Component.onCompleted: {
for ( var count = 0; count < 240; ++count )
{
append( { } );
}
}
}
}


效果如下:



这时候,我对GridView进行了中心旋转动画,类似于这样的效果:



恩,也许在我电脑上肉眼看不出旋转时卡顿,但是这不代表性能很高。我们继续分析,打开QML Profiler,可以看到如下数据:



根据QML Profiler给出的数据,我们可以得知渲染一帧,大约需要5ms到6ms。虽然这符合60FPS的要求,但是这开销也太大了吧,毕竟就这么点静态图像,不能忍。

这时候,我们可以拿出ShaderEffectSource来解决这个问题。首先介绍一下ShaderEffectSource,按照官方的描述:

The ShaderEffectSource type renders sourceItem into a texture and displays it in the scene. sourceItem is drawn into the texture as though it was a fully opaque root item. Thus sourceItem itself can be invisible, but still appear in the texture.


更多信息请前往文档查看:http://doc.qt.io/qt-5/qml-qtquick-shadereffectsource.html

也就是说,ShaderEffectSource可以直接将设置进去的Item,渲染成一个纹理,并且用于显示。

这正是我们需要的,因为对于静态图像,我们不需要每次显示的时候,都把里面的数据渲染一遍,毕竟他们没有任何的变化,我们只需要渲染一次,然后记录下来并用于显示即可。

注意,ShaderEffectSource默认情况下会随着设置的Item变化而变化的,在目前我们场景中不需要这个特性,因为我们希望的就是只渲染一次。所以有一个live的参数,要设置为false,这样渲染完一次后,就不会发生变化了。

根据我们的需求和ShaderEffectSource用法,我封装了一个Snapshots.qml,代码如下:

import QtQuick 2.7

Loader {
id: snapshots
width: sourceItem.width
height: sourceItem.height
active: false

property var sourceItem
property int shaderEffectFormat: ShaderEffectSource.RGBA

sourceComponent: Component {

ShaderEffectSource {
id: shaderEffectSource
width: snapshots.width
height: snapshots.height
live: false
format: snapshots.shaderEffectFormat
sourceItem: snapshots.sourceItem

Component.onCompleted: {
sourceItem.visible = false;
}

Component.onDestruction: {
sourceItem.visible = true;
}
}
}
}


使用的话,直接把sourceItem设置一下,并且把active设置为true即可

那么使用效果如何呢?

我先让这个GridView按照正常方式旋转,然后在鼠标点击后切换为这个Snapshots,效果如下:

切换前:



切换中:



切换后:



可以看出来,对于我们这个测试工程,每帧的渲染时间从5毫秒,直接下降到了90微秒不到,渲染效率直接提高几十倍。性能优化目标达成。

而至于切换中,也就是快照拍摄的那一帧渲染,虽然比正常渲染一帧慢一些,因为多了一次渲染,就是sourceItem渲染成材质那一步,但是对于我们要实现的功能而言,已经是可以接受的了。

其实Qt也有提供一个grabToImage接口,可以提取出图像,但是这个需要把显存中的数据复制到内存中,非常耗时,而这个ShaderEffectSource是完全GPU内实现,不存在拷贝到内存的开销,速度飞起。

示例工程我传百度云了,链接如下:

https://pan.baidu.com/s/1i4XoehB
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: