VR/AR的显示流程以及MTP的影响因素
admin
2023-09-02 02:01:21
0

在试过quest 2之后,我发现VR存在的问题在于:

  1. 佩戴不舒服:头显非常重,而且设计不合理,头重脚轻。(HTC的新款也重,但也还舒服,说明还是设计问题,设计比减重重要),目前也有些减重方案,比如采用Pancake的透镜,但除此之外,也没有大的招数,材料重量不遵从摩尔定律。
  2. 较强的颗粒感或称纱窗效应:分辨率还是低,单眼不到2k;单眼4K很快就上了,很可能是后年产品,而8K大体是最后的目标,到时候像素将达到8096x8096x2,是目前主流旗舰手机1440x2400的38倍,好在性能还有摩尔定律,可惜电池并没有,散热更没有。而能耗比,看到骁龙888的表现,可能也指望不上;
  3. 眼睛疲劳:大概原因是VAC(辐辏冲突),目前离量产还有段距离,属于XR最后要攻克的难关;
  4. 头晕:可能是与外面物理世界隔离后的不适应,也可能是众所周知的“晕动症”,但这却并不是Meta显示负责人提出的十大障碍之一,说明目前VR设备的延时做的已经很不错了。这便是本文讨论的重点,本文主要分为两部分:首先是VR的显示流程做了那些调整,然后看看MTP的实际效果如何(对于其他基本概念的介绍,比如ASW、PTW、固定和动态注视点渲染、注视点传输、CAC,LDC,MulitView,STI、IPD、eyebox、VAC、Duty time (display persistence)等解释放在下一次)。

Meta显示系统负责人演讲:完美AR/VR头显的十大障碍 - 映维网资讯 (nweon.com)




Motion2Photon

我们知道MTP经典的定义就是:图左做出运动的时间,与图右在屏幕上看到的时间,二者的差值。如果差值过大(For VR systems, the M2P is typically below 20 milliseconds. For AR, the required latency is below 5 milliseconds. This is due to the fact that the user has the surrounding environment as a reference.),则会导致晕动症。但这样的定义存在迷惑,后面会解释。

既然VR的一个重要原则就是把这个延时做到最低,那我们看下,VR系统做了哪些努力。

1. 显示流程的调整

1.1 操作系统的调整

A、绕过Android系统

虽然Meta一直想换掉整个Android系统,有团队一直在做更加实时的操作系统,最近看网上消息是解散了。

下面这张图是手机上Android系统的CS架构,大部分硬件都需要经过Android的Service才能与应用通信。有个例外便是GPU,为了提高渲染效率,GPU是不经过Binder的直接渲染模式。




Android系统的Client-Server架构

但对于XR来说,则不一样了,一切以延时为第一要义,所有涉及可能导致延时的服务全部绕过android系统。像是IMU和SLAM则非常依赖于DSP的计算,也完全绕过了Android。




VR系统绕过各种耗时资源

B、绕过 SufaceFlinger

之前手机上,GPU虽然不经过Android的service,但是:

  • 普通UI应用还是要经过Android的hwui流程走一遭的;
  • 对于游戏来说虽不经过hwui,却还是要经过SurfaceFlinger合成。

对于VR应用,当然也不经过hwui,甚至SurfaceFlinger也会bypass,合成也不经过Android了。(对于Android的普通应用还是走原来的流程,即使在VR平台上)

C、绕过DRAM

直连:比如上图Sensors直接到DSP,需要全局时间戳。

D、绕过内存拷贝

共享内存:3Dof,6Dof, 以及Slam信息,GPU都是需要的,这就需要一个高效的共享内存机制;

1.2 显示流水线的调整

如前面的“Android系统上的操控延时”一文讲过了,Android显示流水线的设计以及与触控延时的关系,详见

1.2.1 手机上的显示流水线

A、三级流水线

“绘制+合成+显示”这是手机上经典的三级流水线,在此不多说,显示阶段响应时间为2.5帧(一般我们选择屏幕显示中心位置,算是约定俗成,在此忽略touch事件的sensor等时间,只算显示)。




触控延时2.5帧

其中绘制流水线虽然只有1帧时间,但其实留给绘制时间非常长,远超一帧的时间,如下图所示,原因在于合成通常只在最后一刻处理。




绘制时间的余量大于1.5帧

B、四级流水线

标准UI经过Android的hwui,与游戏的SurfaceView自己控制渲染有所不同,在此分成两部分讨论。

标准UI: 随着刷新率的上升,渲染时间越来越不够用了,所以120HZ上就有了四级流水线(针对系统UI,对于游戏系统管不了),就是将渲染切成两部分(渲染是3D,通常是瓶颈,而合成是2D操作,速度快),如下图。



这样的缺点就是增加单帧的延迟 (延迟量为 number_of_pipeline_stages x longest_pipeline_stage),但由于120HZ的原因,即使增加一级流水,也比60HZ的延时也好很多。




延时为3.5帧

游戏:可以针对渲染自行设计流水线(一般通过SurfaceView实现,不经过系统的默认DectorView),很多也是将渲染分为两部分,加上系统的两级,也是四级。

优点:渲染时间长,帧率稳定;

缺点:响应时间长,吞吐量和延时往往是相反的,总要有取舍,所以VR的设计方向与此相反。

C、如何设计多级流水线

其实非常简单,就是sleep,本来做完了,也要等待,强行拉长渲染时间,这也是响应时间长的原因。

就像是公交车一样,增加站点的等待时间(前提是上车的人很多,瓶颈在此处)吞吐量自然会上来。

1.2.2 PC上的两级流水线




延时只有1.5帧,但会撕裂

PC上的游戏跑起来帧率都能到几百帧,而屏幕一般120HZ,多出的这些帧有什么用呢?

就在于响应延时,如果只看显示效果,还不如打开Vsync + 3buffer

优点:响应快,电脑上鼠标要求显然是较高的,鼠标一滑整个画面可能就会有360度的转变,而触控是很难有这种效果的,而VR一扭头也会应有较大幅度的转变,这一点是符合的;

缺点:不考虑功耗,PC上对于耗电无感,所以浪费点就浪费点,但对于VR设备则不可能,续航和热都要考虑。

因为上屏显示这个时间是无法减小的(严格说也可以,就是FreeSync,GSync,QSync,参见Android上的FreeSync(下)实现方法 - 知乎 (zhihu.com)),所以只能将绘制+合成放在一个流水线里面,但这个流水线又不是严格的,它并不对齐,因为PC对于上屏这事完全不在意,撕裂就撕裂,默认就没有Vsync同步。

1.2.3 VR上的一级流水线




VR理想流水线延时0.75帧,实际上不会如此设计

为了减小响应延时,需要流水线短、单buffer最好、一个T周期内完成、刷新率越高越好。

单屏OLED(最开始广泛使用,目前很少有人使用了)为例,为避免撕裂,左右眼分开(strip 渲染)。

为何VR可以使用single buffer而屏幕不撕裂?



MIPI读与GPU写是错开的

因为左右眼内容不一样(与双buffer也差不多),写左眼与显示右眼并没有什么冲突。
这种也称为Strip rendering(Imagnation):
Strip rendering的想法就是,改变还没有显示出的那部分buffer,有2种不同的策略可供选择。
1. Beam Racing:改变下一步将显示的屏幕的那部分,因为我们想要跑在扫描线前更新还未被显示的那部分内存。对于优化延迟这个目的来说,beam racing策略会更好,但是同时也更难实现。GPU需要保证在很紧的时间窗口完成渲染。这非常难保证,尤其是在一个多线程的操作系统中,所有运作都发生在后台
2. Beam Chasing:在扫描线后更新画面。VR 应用取得显示的上一个Vsync 的时间,来调整自己以保证自己有渲染整个画面的时间。为了计算开始渲染的时间。我们需要条的尺寸。strip的尺寸,和使用多少strip,在执行时被定义。这取决于画面需要多久被显示,和我们需要多久去渲染这个画面。
在多数情况下,2条是最合适的。 在VR中,这还有个优点,就是一个眼睛一个strip。(我们可以从做渲染到右边)。这使得实现更容易一点。 (4个strip渲染当然也没问题,如下图4个GPU同时渲染4块,当然也可以使用1个GPU同时渲染4块,只要保持好同步)



4个strip渲染

响应延时:3/4 帧,(理想情况,不会达到,下一节会解释)

比如4个strip渲染,延时更低,但一般不这么做。

2. VR上的显示流水线

2.1 MTP概念澄清

2.1.1 MTP与响应时延的关系(为何VR的响应延时要比PC长)

重看“VR上的理想流水线”存在的问题:渲染时间太短(包含CPU处理,Pose计算),而渲染分辨率又大,以单眼2k 来算,也是手机像素的4倍了。合成时间也大大压缩了(合成的有些时间是可以留给绘制的),之前总和2T的时间压缩到0.5T,所以VR实际流水线如下,渲染流程明显拉长




VR实际流水线 延时1.75T

说明:“重绘L、R“就是ATW进程,后面会详细介绍这个单buffer渲染流程。

响应延时:1.75T 甚至比PC上 1.5T 还长

这说明提出MTP这个概念的主要目的并不是减小响应延时,而在于头部rotation导致的晕动症。

MTP不同于响应延时,按键事件的延时并不会产生晕动症,和大脑预期一致就不会,即使比较慢,比如手机上游戏触控延时在70ms了(算上服务器延时),但你大脑预期它就会慢,所以你也不会头晕。

ATW等减小延时的策略并不会减小响应延时,响应延时更多的和手柄测试链路相关。

既然MTP并不等同于响应时延,如果预计准确,预计位置就在显示L的中间位置,MTP的延时是不是0?

2.1.2 预测是否能将MTP降为0?

对于MTP,我们经常看到下面这张图的拆解,所以很多测评机构给出的都是20+ms。实际上根据下面这张图来计算,20ms是非常难以实现的(最好也就20ms),对于如今的fast LCD方案都要有30ms了。




整个链路很长,20ms非常困难

同时,我们也看到其他MTP的测试机构,结果是下面这样的,延时及其短。




MTP值

这个问题不是技术问题,而是语言问题。

  • 通常MTP是指未预测过的时间,这个值通常设定的就是20ms
  • 经过pose预测后的MTP就称为预测后MTP,这个平均值通常是0,因为过预测则为负数,比如-4ms,少预测则为正数,比如+4ms,平均下来就是0;
  • 平均值和最大值都很重要,随机移动和突然启动都会有影响;
  • 最大值≠未加预测值;
  • 对于MTP目前只有象征意义,其他人无法测试“关闭预测的值”,所以20ms只能算是debug的一个参考值。

重要的已经不是整个链路值,而是预测的准确性,预测时间要尽量短和尽量准确

【MTP的测试方法】




两个颜色的差值是MTP

1. MTP一般测试的是3dof(Yaw,Pitch,Roll),只应用于rotation(对于6dof用处不大,但最新也加入了运动向量和深度信息的预测,本文不涉及,具体测试方法,本文也不涉及,下次介绍)。

2. 任何MTP测试时,都会强调显示屏不同,则延时不同,到底差别在哪里?

2.1.3 显示屏的不同如何影响MTP?

对于VR来说,LCD首先排除,灰阶响应速度太慢,20ms的响应速度,而OLED的灰阶响应速度可以忽略,但是成本较高,不同于手机,VR是放大镜,对于坏点率要求非常高,另外做高PPI也比较难。所以目前VR的产品都是Fast LCD(左右分开的双屏,对于带宽也压力小一些),灰阶响应速度在4-5ms,如下图所示。




双 fastLCD

通过这个图可以看出,MTP的预测时间变长了很多,黑实线为OLED预测时长,而虚线为Fast LCD的预测时长。

  • 多了一个TFlip的灰阶响应时间4-5ms
  • 多了一个Tlight点亮时间1ms
  • Mipi数据全部传输完毕后才能翻转,这又多了半个Vsync周期
  • OLED的单屏方案,TW的时候是左眼与右眼;双屏方案则是 上半屏(左上右上) 和 下半屏(左下右下)分别做TW;单屏幕方案左右眼MTP是分开预测的,时间间隔是一样的,而双屏方案,上半屏和下半屏是同时预测的(防撕裂),下半屏则要多预测半个VSync周期;

(按理说,上半屏与下半屏同时预测,TW同时做也没问题,之所以TW还是分成两个时间段去处理,在于GPU资源的平均分配,也就是在于性能,而非功能上的原因)

所以未来还是Micro OLED更适合VR设备。

2.2 单Buffer的渲染方式

为了减小MTP时间,VR使用的SingleBuffer机制,是应用配置的吗?




OpenXR 应用渲染

OpenXR应用渲染方式分为MainThread(CPU)与RenderThread(GPU),这种方式一看上去似乎与Android上的典型渲染方式并没有太大差别,Android5.0之后就已是如此了。




Android上的应用渲染

再看下图,OpenXR应用也是3buffer(也有设置为2个的),对于应用渲染,看起来的确相差不大。




openxr的应用buffer

区别在于:Android上应用buffer则是ION分配的共享graphic buffer,XR上的应用buffer却是只有GPU认识的texture buffer,其他硬件都不认识,这3个buffer并不是single buffer(single buffer是ion分配的graphic buffer),如上图 ViewL + ViewR 加起来是一个single buffer,对应了上图6个texture buffer(eye buffer)

这说明:SingleBuffer的使用是在TW(上面的Runtime)上的,而非应用渲染上的,应用有自己的buffers,buffer多了会引入延时,这种延时更多的是操控延时,而不是MTP,对于帧率反而是好事情,再次说明MTP与响应延时不同

另外非VR的普通应用不使用single buffer,默认双buffer

下面开始介绍渲染流程。

2.3 渲染流程

2.3.1 TW 和 ATW是一个意思(在当前语境下)

TW对于VR的渲染流程来说太重要了,必须要将仔细说下。




不幸的是,只支持 3dof 旋转

有个表述比较形象,在3DOF的VR体验中,一切观察的基点都来源于头部的视角,用户就像一个被装在电线杆上可以任意旋转的摄像头。TW就是根据预测位置值(3dof信息)重新调整当前画面,但是这样也有明显缺点,见下图,暂不讨论。




幸运的是,3dof 最重要

只单看如何实现这个TW功能,需要2点:

  • 2个线程,优先级不同;TW优先级更高;
  • 支持GPU抢占,从而执行TW操作(TW在GPU完成)


因此一个正常的TW流程如下:




理想中的TW

为了解决上面提到的TW问题,比如translation则需要运动向量和深度信息估计,比如ASW,暂不在本文讨论。

在这里首先考虑如何解决丢帧问题,就是ATW(网上信息很多,最大的问题就是夸大ATW,似乎和TW差别很大一样,给人造成迷惑)。




实际上的TW(ATW)

ATW使用最后一个有效的帧加上时间扭曲来处理这种情况。如果渲染引擎没有及时完成(图像渲染的工作),ATW使用前一帧,并用时间扭曲处理它。

ATW实现方法:相比TW,改一下变量就好了,需要改动的代码不超过两行,叫它TW也没啥问题,(目前TW都是ATW了),下面就来介绍详细实现方法。

2.3.2 eyebuffer render thread 和 ATW thread

1)eyebuffer render thread:那就是VR的render出来的是texture(分配的eye buffer是GPU认识的texture),而不是graphic buffer(ion的共享内存),并不需要和其他硬件共享buffer,主要是3D操作,耗费时间。

此时作为GPU的输入端除了vectices,texture外,还有6dof送过来的Predicted pose。输出则是L和R的2个texture buffers (eye buffer)。

  1. GetPredictedHeadPose
  2. Left Eye Render
  3. Right Eye Render
  4. SumitFrame

2)ATW thread: 既然VR的render出来的是texture,那么无法显示到屏幕上,所以还要有个将这个texture转换为graphic buffer的这么一个操作,这里便负责TW和distortion correction和CAC,主要是2D操作,时间短暂。

此时GPU的输入则是L和R的2个texture buffer,同时也有6dof送过来的Predicted pose,输出则是graphic buffer(这个进程的GPU优先级更高,可以抢占render线程)

  1. Wait until vsync + 8.3 ms.
  2. Get the last completed frame. (ATW的关键,如果当前帧没有绘制完成,则自然去找上一帧了)
  3. GetPredictedHeadPose
  4. (TW) the left eye. (左眼并不是Vsync的起始位置)
  5. Wait until vsync.
  6. GetPredictedHeadPose
  7. (TW)the right eye.

可见Pose预计两次,第一次是绘制时,第二次是ATW时,误差更小,但对于按键事件没有影响;

2.3.3 系统完整流程图




VR 流水线

细节拆分如下(以SXR为例,对于单oled屏幕):

  • Render线程并不必然对应vsync,这样效率更高;
  • Render + SubmitFrame是一个Vsync周期;
  • Wrap thread被submitFrame唤醒;
  • TW buf left与right间隔半个Vsync周期;
  • TW 起始位置并不对应于vsync起始位置,可以调节;
  • SurfaceFlinger还在,主要用于graphic buffer管理,并不合成,还是在vsync的周期到来提交;
  • MIPI left读取的是TW left buffer,这是同步的关键;
  • hwcomposer往外送的时候还是整个一帧的起始地址,它并不清楚buffer被切成了2块。
  • 左右眼虽然是双buffer循环(对应于渲染线程),提交则是4帧一循环(TW使用),防止丢帧,从而使用上一帧的数据。
  • overlay的texture是为了非VR应用的,比如menu等,不会经过TW,不需要pose更正。
  • App(上图长剪头):预测时长(> 1.5T)
  • ATW(上图短箭头):预测时长 0.75T (左眼),0,75T(右眼)

2.3.4 SXR与OpenXR的区别

1)SXR是高通的SDK,已经开源了,可以去看,是用opengles实现的,理解起来比较容易。但是未来高通也支持OpenXR了,所以SXR将不会再支持了,但是整体思路是不变的。

OpenXR有开源的mondo,是用Vulkan实现的,未来XR应用都推荐使用Vulkan,这样性能会更好些(当然如前所述,XR runtime和 app是两个组成部分,所以应用使用vulkan,并不影响runtime使用OpenGL)。

2)SXR的runtime,WarpThread是在同一个进程里面的高优先级进程,对用户是黑盒,也可以看出与屏幕做同步的是WarpThread,这与Openxr是不同的。




SXR



OpenXR

OpenXR runtime是围绕Out of Process Composition(OOPC)这一概念设计,从而有一个独立的进程:VR Compositor。它在后台运行,同时从所有客户端收集帧提交信息,然后进行合成和显示(附录有个更完整的图)。




2.3.5 附录:OpenXR的App示例与runtime介绍

启动帧:使用OpenXR时,PhaseSync始终处于启用状态,xrWaitFrame将负责帧同步和延迟优化,以便API可以阻塞调用线程。另外,开发者不需要调用特殊的API来获得predictedDisplayTime。这个值是从xrWaitFrame通过XrFrameState::predictedDisplayTime返回。
获取姿态:要获取追踪姿态,开发者可以调用xrLocateViews,它类似于vrapi_GetPredictedTracking2。
渲染:需要注意的是,OpenXR有专门的API来管理交换链;在将内容渲染到交换链之前,应调用xrAcquireSwapchainImage/xrWaitSwapchainImage。如果合成器尚未释放交换链图像,xrWaitSwapchainImage可以阻塞渲染线程。
提交帧:xrEndFrame负责帧提交,但与vrapi_SubmitFrame2不同,它不需要进行帧同步和交换链可用性检查,所以这个函数不会阻塞渲染线程。
以下是常见多线程OpenXR应用的示例:(runtime未介绍)



对于MTP与晕动症的关系,放在下一篇了,本文已经很长了。

对于mondo的源码分析,可见 dogee.tech/2022/03/21/2022-03-21_Monado Android Sensor Data/




openxr的mondo实现

(对于其他基本概念的介绍,比如ASW、PTW、固定和动态注视点渲染、注视点传输、CAC,LDC,MulitView,STI、IPD、VAC、Duty time (display persistence)等解释放在下一次)

参考:

whitepaper-makingimmersivevirtualrealitypossibleinmobile.pdf (qualcomm.com)

Keeping the virtual world stable in VR | Qualcomm

Augmented Reality & Virtual Reality Head Mounted Display Testing

刘卫华 映维网:一张VR图像帧的完整生命周期:从帧生成到帧显示

相关内容