tan*_*avo 25 android lag surfaceview
关于Stack Overflow的其他一些问题,我已经从这里阅读了Android Surfaces,SurfaceViews等内部指南:
https://source.android.com/devices/graphics/architecture.html
该指南让我对Android上所有不同部分的组合方式有了更好的理解.它介绍了eglSwapBuffers如何将渲染帧推送到队列中,当它准备下一帧显示时,SurfaceFlinger将使用该队列.如果队列已满,那么它将等到缓冲区在返回之前可用于下一帧.上面的文档将其描述为"填充队列"并依赖交换缓冲区的"反压"来限制渲染到显示的vsync.这是使用GLSurfaceView的默认连续渲染模式发生的情况.
如果你的渲染是简单的并且在比帧周期少得多的情况下完成,那么这是由BufferQueue引起的额外延迟,因为SwapBuffers上的等待直到队列满了才发生,因此我们'重新渲染始终位于队列的后面,因此不会立即显示在下一个vsync上,因为队列中可能存在缓冲区.
相比之下,按需渲染的发生频率通常比显示更新速率低得多,因此通常这些视图的BufferQueues为空,因此推送到这些队列的任何更新都将被SurfaceFlinger在下一个vsync上抓取.
所以问题在于:如何设置连续渲染器,但延迟最小?目标是每个vsync开始时缓冲区队列为空,我在16ms内渲染我的内容,将其推送到队列(缓冲区计数= 1),然后由SurfaceFlinger在下一个vsync(缓冲区计数)上使用它= 0),重复一遍.队列中的缓冲区数量可以在systrace中看到,因此目标是在0和1之间交替使用.
我上面提到的文档介绍了Choreographer作为在每个vsync上获得回调的方法.但是我不相信这足以让我能够实现我追求的最小滞后行为.我已经测试了在vsync回调上使用非常小的onDrawFrame()执行requestRender(),它确实展示了0/1缓冲区计数行为.但是,如果SurfaceFlinger无法在一个帧周期内完成所有工作(可能是通知弹出或其他),该怎么办?在这种情况下,我希望我的渲染器很乐意为每个vsync生成1帧,但该BufferQueue的消费者端已经丢弃了一帧.结果:我们现在在队列中交替使用1到2个缓冲区,并且我们在渲染和查看帧之间获得了一段滞后.
该文档似乎建议查看报告的vsync时间与回调运行时间之间的时间偏差.我可以看到,如果你的回调由于你的主线程由于布局传递或其他东西而延迟传递,那会有什么帮助.但是我认为这不会允许检测到SurfaceFlinger跳过节拍并且不能消耗帧.应用程序是否有任何方法可以解决SurfaceFlinger丢帧的问题?似乎无法告诉队列的长度打破了使用vsync时间进行游戏状态更新的想法,因为在您实际渲染的渲染之前,队列中存在未知数量的帧.
减少队列的最大长度并依赖背压将是实现此目的的一种方法,但我认为没有API来设置GLSurfaceView BufferQueue中的最大缓冲区数量?
fad*_*den 24
好问题.
阅读此内容的其他人的快速背景知识:
这里的目标是最小化显示延迟,即应用渲染帧和显示面板点亮像素之间的时间.如果你只是在屏幕上投放内容,那没关系,因为用户无法区分.但是,如果您正在响应触摸输入,那么延迟的每一帧都会让您的应用感觉响应性稍差.
问题类似于A/V同步,当视频帧显示在屏幕上时,您需要与帧相关联的音频从扬声器中出来.在这种情况下,只要在音频和视频输出上始终相等,整体延迟就无关紧要了.但是,这会面临非常类似的问题,因为如果SurfaceFlinger停止并且您的视频在一帧之后始终显示,您将失去同步.
SurfaceFlinger以更高的优先级运行,并且相对较少的工作,所以不可能错过自己的节拍......但它可能发生.此外,它是从多个源合成帧,其中一些使用栅栏来发出异步完成信号.如果使用OpenGL输出组成准时视频帧,并且在截止日期到达时GLES渲染尚未完成,则整个组合将被推迟到下一个VSYNC.
最小化延迟的愿望足够强大,以至于Android KitKat(4.4)版本在SurfaceFlinger中引入了"DispSync"功能,该功能可以减少通常的两帧延迟的半帧延迟.(这在图形架构文档中有简要提及,但它并未得到广泛使用.)
这就是情况. 在过去,这对视频来说不是一个问题,因为30fps视频每隔一帧更新一次.打嗝会自然地解决问题,因为我们并没有试图让队列保持满员.我们开始看到48Hz和60Hz的视频,所以这更重要.
问题是,我们如何检测我们发送给SurfaceFlinger的帧是否正在尽快显示,或者是否在我们先前发送的缓冲区后面等待额外的帧?
答案的第一部分是:你做不到.SurfaceFlinger上没有状态查询或回调,它会告诉您它的状态.从理论上讲,您可以查询BufferQueue本身,但这并不一定能告诉您需要知道的内容.
与查询和回调的问题是,他们不能告诉你什么状态是,只有什么状态了.当应用程序收到信息并对其采取行动时,情况可能完全不同.该应用程序将以正常优先级运行,因此可能会出现延迟.
对于A/V同步,它稍微复杂一些,因为应用程序无法知道显示特性.例如,某些显示器具有内置存储器的"智能面板".(如果屏幕上的内容不经常更新,则可以通过不让面板以每秒60x的速度扫描内存总线上的像素来节省大量电量.)这些可能会增加必须考虑的额外延迟帧.
Android正在向A/V同步迈进的解决方案是让应用程序在想要显示帧时告诉SurfaceFlinger.如果SurfaceFlinger错过截止日期,则会丢弃框架.这是在4.4中通过实验添加的,虽然它并不打算在下一个版本之前使用(它应该在"L预览"中运行得很好,但我不知道是否包含完全使用它所需的所有部分) .
应用程序使用此方法的方法是先调用eglPresentationTimeANDROID()扩展程序eglSwapBuffers().该函数的参数是所需的呈现时间,以纳秒为单位,使用与Choreographer相同的时基(特别是Linux CLOCK_MONOTONIC).因此,对于每个帧,您获取从Choreographer获得的时间戳,添加所需的帧数乘以近似刷新率(您可以通过查询Display对象获得 - 请参阅MiscUtils#getDisplayRefreshNsec()),并传递它到EGL.交换缓冲区时,所需的显示时间与缓冲区一起传递.
回想一下,SurfaceFlinger每个VSYNC唤醒一次,查看待处理缓冲区的集合,并通过Hardware Composer向显示硬件提供一组.如果您在时间T请求显示,并且SurfaceFlinger认为传递给显示硬件的帧将在时间T-1或更早时间显示,则将保持该帧(并重新显示前一帧).如果框架将出现在时间T,它将被发送到显示器.如果帧将出现在时间T + 1或更晚(即它将错过其截止日期),并且在队列中有另一个帧,其被安排在稍后的时间(例如,用于时间T + 1的帧),那么用于时间T的帧将被丢弃.
解决方案并不完全适合您的问题.对于A/V同步,您需要恒定的延迟,而不是最小延迟.如果您查看Grafika的" 预定交换 "活动,您可以找到一些代码,其使用eglPresentationTimeANDROID()方式类似于视频播放器的功能.(在目前的状态下,它只不过是用于创建systrace输出的"音调发生器",但基本部分就在那里.)策略是提前几帧,所以SurfaceFlinger永远不会干,但这对你来说是完全错误的应用程序.
但是,表示时间机制确实提供了一种删除帧而不是让它们备份的方法.如果你碰巧知道Choreographer报告的时间和你的帧可以显示的时间之间有两个延迟帧,你可以使用这个功能来确保帧被丢弃而不是排队,如果它们太远了过去.Grafika活动允许您设置帧速率和请求的延迟,然后在systrace中查看结果.
应用程序知道SurfaceFlinger实际上有多少帧延迟会有所帮助,但是没有查询.(无论如何,这有点尴尬,因为"智能面板"可以改变模式,从而改变显示延迟;但除非你正在进行A/V同步,否则你真正关心的是最小化SurfaceFlinger延迟.)它是在4.3+上假设两帧是合理安全的.如果它不是两帧,那么你的表现可能不是很理想,但如果没有设置演示时间,那么净效果将不会比你想要的更差.
您可以尝试将所需的演示时间设置为等于Choreographer时间戳; 最近的时间戳意味着"尽快显示".这确保了最小的延迟,但可能适得其反.SurfaceFlinger具有两帧延迟,因为它为系统中的所有内容提供了足够的时间来完成工作.如果您的工作负载不均匀,您将在单帧延迟和双帧延迟之间摆动,并且输出在转换时看起来很笨拙.(这是DispSync的一个问题,它将总时间减少到1.5帧.)
我不记得何时eglPresentationTimeANDROID()添加了该功能,但在旧版本中它应该是无操作.
底线:对于'L',在某种程度上4.4,您应该能够使用具有两帧延迟的EGL扩展来获得您想要的行为.在早期版本中,系统没有任何帮助.如果你想确保你的方式没有缓冲区,你可以经常故意丢弃一个帧,让缓冲区队列耗尽.
更新:避免排队帧的一种方法是调用eglSwapInterval(0).如果您将输出直接发送到显示器,则呼叫将禁用与VSYNC的同步,从而取消应用程序的帧速率.当通过SurfaceFlinger渲染时,这会将BufferQueue置于"异步模式",如果帧提交的速度超过系统可以显示的速度,则会导致帧丢弃.
请注意,您仍然是三重缓冲的:正在显示一个缓冲区,一个由SurfaceFlinger保存,以便在下一次翻转时显示,另一个正由应用程序绘制.
| 归档时间: |
|
| 查看次数: |
5155 次 |
| 最近记录: |