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

时间:2014-04-30 00:21:46

标签: android performance systrace

我正在研究我的应用程序的性能,因为我注意到它在滚动时丢弃了一些帧。我运行了systrace(在运行4.3的Nexus 4上)并在输出中注意到interesting section

一开始一切都很好。 Zooming in on the left section,我们可以看到绘图从每个vsync开始,以及备用时间结束,并等到下一个vsync。由于它是三重缓冲的,因此它应该绘制到一个缓冲区中,该缓冲区将在完成后发布在以下vsync上。

在放大屏幕截图中的第4个vsync上,应用程序完成了一些工作,并且绘制操作没有及时完成下一个vsync。但是,我们不会丢弃任何帧,因为之前的抽奖正在提前一帧。

在此之后,绘制操作不会弥补错过的vsync。相反,每个vsync只开始一次绘制操作,现在他们不再向前绘制一帧了。

Zooming in on the right section,该应用程序会做更多工作并错过另一个vsync。由于我们没有提前画框,所以框架实际上会被丢弃。在此之后,它会回到前方画一帧。

这是预期的行为吗?我的理解是,如果你错过了一个vsync,三重缓冲允许你恢复,但是这种行为看起来像每两个你错过的vsyncs一次丢帧。


跟进问题

  1. this screenshot的右侧,应用程序实际上渲染缓冲区的速度比显示器消耗它们的速度快。在performTraversals#1(屏幕截图中标记)期间,假设正在显示缓冲区A并且正在渲染缓冲区B. #1在vsync之前完成,并将缓冲区B放入队列中。此时,应用程序是否应该能够立即开始渲染缓冲区C?相反,performTraversals#2直到下一个vsync才开始,浪费了宝贵的时间。

  2. 与此类似,我对waitForever on the left side here的需求感到有些困惑。假设正在显示缓冲区A,缓冲区B在队列中,并且正在呈现缓冲区C.当缓冲区C完成渲染时,为什么它不会立即添加到队列中?相反,它会执行waitForever,直到从队列中删除缓冲区B,此时它会添加缓冲区C,这就是为什么无论应用程序渲染缓冲区有多快,队列似乎始终保持大小为1。

1 个答案:

答案 0 :(得分:9)

只有在保持缓冲区满的情况下才提供缓冲量。这意味着渲染速度比显示器消耗速度快。

标签不会出现在您的图片中,但我猜测绿色vsync行上方的紫色行是BufferQueue状态。您可以看到它通常在任何时候都有0或1个完整缓冲区。在左边放大"放大"图片你可以看到它有两个缓冲区,但之后它只有一个,并且屏幕上有3/4的方向你看到一个非常短的紫色条表示它只是勉强及时渲染帧。

请参阅this postthis post了解背景信息。

更新以了解添加的问题......

the other post中的细节几乎没有触及表面。我们必须更深入。

systrace中显示的BufferQueue计数是排队缓冲区的数量,即包含内容的缓冲区数。当SurfaceFlinger抓取缓冲区进行显示时,它立即释放缓冲区,将其状态更改为" free"。当缓冲区显示在叠加层上时,这尤其令人兴奋,因为显示器直接从缓冲区渲染(而不是合成到暂存缓冲区并显示)。

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

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

请注意,waitForever()的来电只会在您不落后时(跟踪的左侧和右侧)显示。当队列只有一个完整的缓冲区时,我不确定它为什么会发生这种情况 - 它应该使最旧的缓冲区出列,该缓冲区应该已经发出信号。

最重要的是,你永远不会看到BufferQueue超过两个进行三重缓冲。

并非所有设备都如上所述工作。 Nexus 7(2012)并未使用"显式同步"机制和前ICS设备根本没有BufferQueues。

回到您的编号屏幕截图,是的,' 1'之间有充足的时间。和' 2'您的应用可以在哪里运行performTraversals()。在不知道你的应用程序在做什么的情况下确定很难说,但我猜你已经有了一个Choreographer驱动的动画循环,唤醒每个VSYNC并且确实有效。它的运行频率不高于此。

如果你systrace Android Breakout,你可以看到你尽可能快地渲染时的样子("队列填充")并依靠BufferQueue背压来调节游戏速度。

将运行4.3的N4与运行4.4的N4进行比较会特别有趣。在4.3上,跟踪与你的类似,队列主要在1处徘徊,常规下降到0,偶尔下降到2.在4.4,队列几乎总是在2,偶尔下降到1.在两种情况下它都是#39;睡在eglSwapBuffers();在4.3中,跟踪通常显示waitForever()以下,而在4.4中显示dequeueBuffer()。 (我不知道这个副手的原因。)

更新2: 4.3和4.4之间差异的原因似乎是Nexus 4驱动程序更改。 4.3驱动程序使用旧的dequeueBuffer调用,该调用变为dequeueBuffer_DEPRECATED()Surface.cpp line 112)。旧的界面并没有把围栏当作一个" out"参数,所以调用必须自己调用waitForever()。较新的界面只是将围栅返回到GL驱动程序,它在需要时进行等待(可能不会立即进行)。

更新3:现在可以使用更长的解释here