Android三重缓冲 – 预期的行为?

我正在调查我的应用程序的性能,因为我注意到在滚动时会丢失一些帧。 我运行systrace(在运行4.3的Nexus 4上),并注意到输出中有一个有趣的部分 。

一切都很好。 放大左侧部分 ,我们可以看到每个vsync都开始绘制,随着时间的推移结束,并等待下一个vsync。 由于它是三重缓冲的,因此应该将其绘制到一个缓冲区中,该缓冲区在完成后会发布在以下的vsync中。

在放大的屏幕截图中的第4个vsync中,应用程序会执行一些操作,并且绘制操作无法及时完成下一个vsync。 但是,我们不会因为之前的抽签正在进行一个框架而放弃任何框架。

在这之后,绘制操作不能弥补错过的vsync。 相反,每个vsync只能开始一个绘制操作,现在他们不再画出一帧了。

放大正确的部分 ,应用程序做了一些工作,并错过了另一个vsync。 既然我们没有画出一个框架,一个框架实际上被放在这里。 在此之后,它回到画一个框架。

这是预期的行为? 我的理解是,三倍缓冲可以让你恢复,如果你错过了一个vsync,但是这个行为看起来像是每两个vsyncs丢失一个帧。


跟进问题

  1. 在这个屏幕截图的右侧,应用实际上是渲染缓冲区比显示器消耗更快。 在performTraversals#1(在屏幕截图中标记)期间,让我们说缓冲区A正在显示和缓冲区B正在呈现。 #1在vsync之前完成并将缓冲区B放入队列中。 在这一点上,应该不应该能够立即开始渲染缓冲区C? 相反,执行traversals#2不会启动,直到下一个vsync,浪费宝贵的时间。

  2. 同样的道理,我对这里左边的 waitForever有点困惑。 假设正在显示缓冲区A,缓冲区B在队列中,并且正在渲染缓冲区C. 当缓冲区C完成渲染时,为什么不立即添加到队列中? 相反,它会执行一个waitForever,直到缓冲区B从队列中删除,此时它将添加缓冲区C,这就是为什么无论应用程序呈现缓冲区有多快,队列似乎总是保持在1的大小。

Solutions Collecting From Web of "Android三重缓冲 – 预期的行为?"

提供的缓冲量只在保持缓冲区满时才起作用。 这意味着渲染速度比显示器消耗更快。

标签不会出现在您的图片中,但我猜测绿色vsync行上方的紫色行是BufferQueue状态。 你可以看到,它一般有0或1个完整的缓冲区在任何时候。 在左侧放大图像的左边,你可以看到它有两个缓冲区,但是之后只有一个,而在屏幕的3/4处,你会看到一个很短的紫色条,表明它只是几乎没有及时渲染帧。

看到这个职位和这个职位的背景。

更新增加的问题…

在另一篇文章中的细节几乎没有划伤表面。 我们必须更深入。

systrace中显示的BufferQueue计数是排队缓冲区的数量,即具有内容的缓冲区的数量。 当SurfaceFlinger抓取缓冲区进行显示时,立即释放缓冲区,将其状态更改为“空闲”。 当缓冲区显示在覆盖层上时,这是特别令人兴奋的,因为显示是直接从缓冲区渲染的(而不是合成到暂存缓冲区并显示)。

让我再说一遍:在BufferQueue中,显示器主动读取数据以显示在屏幕上的缓冲区被标记为“空闲”。 该缓冲区具有最初“活动”的相关围栏。 当它处于活动状态时,不允许任何人修改缓冲区内容。 当显示不再需要缓冲区时,它发出信号。

所以跟踪左边的代码在waitForever()中的原因是因为它正在等待围栏发信号。 当VSYNC命中时,显示切换到不同的缓冲区,发出信号,你的应用程序可以立即开始使用缓冲区。 这消除了如果您必须等待SurfaceFlinger唤醒,查看缓冲区不再被使用,通过BufferQueue发送IPC释放缓冲区等情况,才会发生延迟。

请注意, waitForever()调用仅在不落后(跟踪的左侧和右侧)时显示。 我并不确定为什么当队列只有一个完整的缓冲区时,它会发生什么 – 它应该将最早的缓冲区出队,这个缓冲区应该已经有了信号。

底线是你永远不会看到BufferQueue超过两个三重缓冲。

并非所有设备都如上所述工作。 Nexus 7(2012)不使用“显式同步”机制,ICS之前的设备根本没有BufferQueue。

回到你的编号截图,是的,“1”和“2”之间有足够的时间可以运行performTraversals()。 很难说如果不知道你的应用程序在做什么,但是我想你已经有了一个Choreographer驱动的animation循环,唤醒了每一个VSYNC并且工作。 它不会比这更频繁地运行。

如果你使用Android Breakout,你可以看到当你尽可能快地渲染时(“队列填充”),依靠BufferQueue背压来调节游戏速度。

将运行4.3的N4与运行4.4的N4进行比较是特别有趣的。 在4.3上,跟踪与你的类似,队列大部分停留在1,常规下降到0,偶尔的上升到2.在4.4上,队列几乎总是2,偶尔下降到1.在这两种情况下,睡在eglSwapBuffers() ; 在4.3中trace通常显示waitForever() ,而在4.4中则显示dequeueBuffer() 。 (我不知道这个原因。)

更新2: 4.3和4.4之间的差异的原因似乎是一个Nexus 4驱动程序的变化。 4.3驱动程序使用旧的dequeueBuffer调用,该调用变为dequeueBuffer_DEPRECATED() ( Surface.cpp第112行 )。 旧的接口不把栅栏当作“out”参数,所以调用必须调用waitForever()本身。 较新的接口只是将栅栏返回给GL驱动程序,在需要的时候可以等待(可能不是马上)。

更新3:现在有更详细的解释。