Back to Blog home

天鹅绒般的QML Scene Graph

Published on 星期一 二月 28, 2011 by 殷允桥 in OpenGL Performance Painting qt-labs-chinese | Comments

原文链接:gunnar - Velvet and the QML Scene Graph

首先,让我从一点点澄清开始。今年我在参加Qt开发者日的时候遇到了很多人,然后我意识到我们在给scene graph项目命名方面做了件糟糕的工作。因为名字的相似性,这显得这一项目和[Open Scene Graph](http://www.openscenegraph.org/projects/osg)似乎有某种关联,不过这里的Scene Graph显然不是,也从来没有这个意愿。我们这里的Scene Graph是为了渲染QML文件的一个压缩和小巧的2D场景图。所以,从现在开始,我们将会称之为QML Scene Graph。她存在的唯一的目的是使QML变得更好。

让我们继续...

动画效果应该给人天鹅绒般的感觉 - 丝绸般光滑和舒适。这从技术角度来看需要以下元素:

* 确保显示器可以绘制的每一帧得到绘制。现代显示器,比如你正在用来阅读本文的LCD或者LED屏幕,大部分都是60HZ的时钟。这当然依赖于显示分辨率(dpi),但是在60HZ左右会发生一些奇妙的事情。如果你以30HZ来更新2D图形,你可以真正看到每个帧都是单独的一帧。而当你逼近60HZ,这些帧会混合在一起并且眼睛开始感觉到平滑的运动而不是一个个的帧图像,而这就是天鹅绒和砂纸的关键区别。

* 以时间来计算,为了达到60HZ,你需要在16.66毫秒内完成一帧的绘制。如果你超出了这一点,就会失分。这意味着当实现动画时,除了更新属性和绘制以外无法做任何其他事情。如果需要更多时间,将需要事先准备好或者按照我[前一次博客提到的](http://labs.qt.nokia.com/2010/01/21/qt-graphics-and-performance-generating-content-in-threads/)在一个后台线程中做这一工作。直到最近,我还是被说服不时地丢掉一些帧并不是一件灾难性的事故,但是这之间真的是有如天鹅绒和砂纸般的区别。

* 在垂直刷新的中间不要绘制。否则将会导致[裂屏现象](http://en.wikipedia.org/wiki/Screen_tearing),从而让你完美的一帧分为2截并且毁掉视觉上那美妙的一刻。解决方案是使用某种形式的同步来绑定到你屏幕的垂直刷新率上。在这里Qt并不能总是帮到你。在Mac OS X和Symbian N8上,如果你绘制得更加频繁,我们将会把更新频率绑定到垂直刷新率上,图形窗口系统会阻塞你得渲染线程。在Linux/X11,Maemo和Windows上,不会阻塞,所以你很有可能看到裂屏出现。幸运的是,有一个很简单的解决办法。QGLWidget结合[QGLFormat::setSwapInterval](http://doc.qt.nokia.com/latest/qglformat.html#setSwapInterval)设定为1,将能够使QGLWidget同步到垂直刷新率上。在QML Scene Graph中,我们使用OpenGL并且设定这一转换间隔的默认值为1。

* 在帧之间按照相对的时间来运行动画。如果你得对象每毫秒移动1个像素,那么它就必须每帧移动16.66个像素。如果你丢失一帧,则它就应该在下一次移动33.33像素。当然如果你总是能够严格按照16.66毫秒的时间间隔更新,这一问题就不会存在。

但即使这些条件都已经有了,Qt还是无法保证给出天鹅绒般的效果... ...

缺失的一环是这个动画的"滴答"声是从哪里来的。 Qt的动画框架使用的是一个时钟来展现动画并且更新所有对象的属性。这一时钟发送几种更新请求并最终使得动画帧得到重绘。这一设定有2个缺陷:首先,时钟是从1000/60(译者注:毫秒)开始的,这个值大约是16,而不是应该的16.66。而且,时钟是不精确的。它可以在值15或者17触发,而如果在17触发,那么动画就会丢失一帧。

这困扰了我好久,所以当我在旧金山参加Qt开发者日的时候,我找到了一些空闲时间开始研究这件事。最终就是在QML Scene Graph中我们开始以一种不同的方式驱动动画。我们顺着下面这些代码做了一些事情:

while (animationIsRunning) {
processEvents();
advanceAnimations();
paintQMLScene();
swapAndBlockForNextVSync();
}

结果就是, 我们总是可以每16.66毫秒,并且每次一帧保持动画和垂直刷新的同步。我说过我一开始不认为偶尔丢掉一帧是很糟糕的事情, 但是这让我又看了一眼这个结果, 我想我们终于得到了天鹅绒般的感觉!

一般来说, 我们如果只使用Qt无法做到这一点,因为我们只能通过OpenGL的swapBuffers()函数来同步垂直刷新, 所以我们只能将其绑定到一个窗口上。通过使用Wayland或者定制的OpenGL扩展,我们也可能在不通过缓冲区交换而得到垂直同步效果, 这意味这,理论上我们可以跨多个窗口增强动画效果, 但是这个话题超出了我们现在的讨论范围,暂且不表。 目前只是修正了一个窗口运行QML Scene Graph的问题。

这里是QML Scene Graph的代码库: http://qt.gitorious.org/qt-labs/scene-graph

Subscribe to Our Blog

Stay up to date with the latest marketing, sales and service tips and news.