一張VR圖像幀的生命周期

来源:https://www.cnblogs.com/mikaelzero/archive/2022/11/27/16929708.html
-Advertisement-
Play Games

華為開發者大會2022(HDC)上,HMS Core手語數字人以全新形象亮相,併在直播中完成了長達3個多小時的實時手語翻譯,向線上線下超過一千萬的觀眾提供了專業、實時、準確的手語翻譯服務,為聽障人士提供了無障礙參會體驗。面對專業性強且辭彙量大的科技大會,HMS Core手語數字人是如何準確且流暢地打 ...


“VR 應用程式每幀渲染兩張圖像,一張用於左眼,一張用於右眼。”人們通常這樣來解釋 VR 渲染,雖然沒有錯,但可能過於簡單化了。對於 Quest 開發人員來說,瞭解全貌是有益的,這樣你就可以使你的應用程式性能更高、視覺效果更吸引人,並輕鬆排除故障和解決問題。

這篇博文將帶你瞭解 VR 幀的生命周期,解釋從幀生成到最終顯示的端到端過程。這段旅程可以分為三個階段:

  • 從幀生成到提交: 應用程式如何呈現幀,包括應用程式 API 和幀 timing 模型
  • 從幀提交給合成器: 幀數據如何在 app 和合成器之間共用
  • 從合成到顯示: Compositor 的責任以及最終圖像如何顯示在 HMD(頭顯示) 顯示器上

第一階段:從幀生成到提交

對於 Quest 應用程式,我們使用 VrApi / OpenXR 與 HMD 進行通信。具體到渲染部分,這些 API 負責以下工作:

  • 姿態預測:與傳統的 3D 應用不同,大多數 VR 概念的設計都是為了減少延遲。要為 VR 中的特定幀設置渲染 camera,僅知道當前的頭顯姿態並不足夠,我們同時需要知道幀何時顯示在 HMD 屏幕上,這稱為PredictedDisplayTime。然後,我們可以利用所述時間來預測頭顯姿態,並用預測的姿態對幀進行渲染,從而大大減少渲染誤差。
  • 幀同步:VR Runtime 負責幀同步。我們(Quest)的 SDK 提供了 API 來控制幀何時啟動,並且不允許應用以高於所需幀速率的速度運行,而是通常以與顯示器相同的幀速率運行。app 不需要(也不應該)插入手動等待或幀同步。

對於特定的應用程式,根據它是使用 VrApi 還是 OpenXR,行為可能會有所不同,因此我們將分別解決。

VrApi Application

下麵是一個典型的多線程 VrApi 應用程式的框架:

  • Start the Frame:主線程調用 vrapi_WaitFrame 來啟動主線程幀,並調用 vrapi_BeginFrame 來啟動渲染線程幀。
  • Get the Poses:應用通常需要知道頭顯和控制器在模擬線程(主線程)中的姿態,以便正確執行游戲邏輯或物理計算。要獲取所述信息,我們需要調用 vrapi_GetPredictedDisplayTime,並使用返回的時間調用 vrapi_GetPredictedTracking2
  • Rendering:在渲染線程中,我們可以使用從主線程獲得的頭顯/控制器姿態來完成渲染。但是,大多數應用(如 UE4)選擇在渲染幀開始時再次調用 vrapi_GetPredictedDisplayTime / vrapi_GetPredictedTracking2。這是一個減少延遲的優化。我們正在預測頭顯在預測的顯示時間中的姿態,我們越晚調用感測器採樣 API,我們需要執行的預測就越少,從而能夠獲得更準確的預測。
  • Submit Frame:在渲染線程完成所有調用提交之後,應用程式應該調用vrapi_SubmitFrame2 來告訴 VR 運行時應用已完成幀的 CPU 工作.它將向 VR 運行時提交有用的信息(註意:由於同步的性質,GPU 的工作可能依然在進行中,我們將在後面討論)。然後,提交幀的 API 將執行以下操作:
    • Frame Synchronization:如果幀完成得太快,在這裡阻塞以避免下一幀過早開始,保證應用不會以高於系統所需的 FPS 運行(例如,Quest 預設情況下是 72 FPS)。
    • Check Texture Swap Chain Availability(檢查 texture 交換鏈的可用性):從 Swap Chain 阻塞下一個 eye texture 如果這個 texture 依然在運行時使用的話 . 阻塞通常由過時幀觸發,因為運行時必須將舊幀再重用一幀。
    • Advance Frame:增加幀的 index 並決定下一幀的預測顯示時間,下一幀的 vrapi_GetPredictedDisplayTime 調用將依賴於 vrapi_SubmitFrame2。

這就是大多數 VrApi 應用的工作方式。不過,有兩條評論值得一提:

  • 由於歷史原因,vrapi_BeginFrame / vrapi_WaitFrame 是後來添加的,部分早期的應用程式只能訪問 vrapi_SubmitFrame2
  • 我們發佈了PhaseSync作為 VrApi 的一個 opt-in 功能,它將幀同步移到了vrapi_WaitFrame 以更好地管理延遲。所以,幀行為更類似於 OpenXR 應用,我們將在下麵討論。

OpenXR Application

與 VrApi 應用相比,OpenXR 應用存在關鍵的區別:

  • Start the Frame:使用 OpenXR 時,PhaseSync 始終處於啟用狀態,xrWaitFrame 將負責幀同步和延遲優化,以便 API 可以阻塞調用線程。另外,開發者不需要調用特殊的 API 來獲得 predictedDisplayTime。這個值是從 xrWaitFrame 通過 XrFrameState::predictedDisplayTime 返回。
  • Get the Poses:要獲取追蹤姿態,開發者可以調用 xrLocateViews,它類似於 vrapi_GetPredictedTracking2。
  • Rendering:需要註意的是,OpenXR 有專門的 API 來管理交換鏈;在將內容渲染到交換鏈之前,應調用 xrAcquireSwapchainImage/xrWaitSwapchainImage。如果合成器尚未釋放交換鏈圖像,xrWaitSwapchainImage 可以阻塞渲染線程。
  • Submit Frame:xrEndFrame 負責幀提交,但與 vrapi_SubmitFrame2 不同,它不需要進行幀同步和交換鏈可用性檢查,所以這個函數不會阻塞渲染線程。

一個典型的多線程 Open XR 應用程式的框架如下圖所示:

總的來說,無論你是在開發 VrApi 應用還是 OpenXR 應用,有兩個主要的阻塞源;一個來自幀同步,一個來自交換鏈可用性檢查。如果你事先執行了 Systrace 抓取,你將看到一個熟悉的結果。當應用以滿 FPS 運行時,這種 sleep 是可以預期的,因為除了優化延遲之外,它們(像 eglSwapBuffer 這樣的傳統 vsync 函數)同時阻塞應用程式以超出顯示器允許的速度呈現。當應用程式無法達到目標 FPS 時,情況就會變得更為複雜。例如,由於新幀延遲,合成器可能仍在使用以前提交的圖像。這導致“交換鏈可用性檢查”阻塞變長,並且可能導致幀同步阻塞。這就是為什麼當應用程式已經很慢的時候,應用程式仍然在阻塞上花費時間。出於這些原因,我們不建議使用 FPS 作為性能剖析指標,因為它通常不能準確反映應用工作負載。gpusystrace 和 Perfetto 是在 CPU 和 GPU 端測量應用性能的更好工具。

第二階段:從幀提交到合成器

我們的 VR 運行時是圍繞 Out of Process Composition(OOPC)這一概念設計。我們有一個獨立的進程:VR Compositor。它在後臺運行,同時從所有客戶端收集幀提交信息,然後進行合成和顯示。

VR 應用是從中收集幀信息的客戶端之一。提交的幀數據將通過進程間通信(IPC)發送到 VR 合成器。我們不需要將 eye buffer 的副本發送到合成器進程,因為這意味著大量的數據。相反,eye buffer 的記憶體所有權從交換鏈分配開始就屬於合成器進程。所以,只需要交換鏈句柄和交換索引。但是,我們確實需要保證數據的訪問是安全的,這意味著合成器應該只在應用完成渲染後讀取數據,並且應用程式不應該在合成器使用數據時修改數據。這是通過 FenceChecker 和 FrameRetirement 系統完成。

FenceChecker

Quest GPU(高通 Adreno 540/650)是 Tile-Based 架構,其只在提交所有調用後才開始工作(直到顯式或隱式 flushing)。當應用程式調用SubmitFrame 時,通常 GPU 才剛剛開始渲染相應的 eye texture(因為大多數引擎在調用 SubmitFrame 之前都會顯式 flush GPU)。如果這個時候合成器立即讀取提交的圖像,它將會接收未完成的數據,從而導致圖形損壞和撕裂。

為瞭解決這個問題,我們在幀尾向 GPU 命令流(vrapi_SubmitFrame / xrEndFrame)發出一個 fence 對象,然後啟動一個非同步線程(FenceChecker)來等待。fence 是一個 GPU->CPU sync 原語,它可以在 GPU 處理到達 fence 時告訴 CPU。因為我們在幀尾插入了 fence,當 fence 返回時,我們就能知道 GPU 幀已經完成,然後我們可以通知合成器現在可以使用所述幀。

systrace 抓取的流程圖:

提示:對於大多數應用程式,FenceChecker 標記的長度與應用程式 GPU 成本大致相同。

Frame Retirement

FenceChecker 有助於將眼睛紋理的所有權從應用程式轉移到合成器,但這隻是周期的一半。在幀完成顯示後,合成器需要將數據的所有權交還給應用程式,以便它可以再次使用 eye texture,這稱為“Frame Retirement”

VR 合成器設計用於處理延遲(暫停)幀,如果預期幀未按時交付,則重用幀並將其再次投影到顯示器。因為我們不知道下一幀是否能在下一個合成周期準時到達(TW),所以我們必須等到合成器拾取下一幀後才能釋放當前幀。一旦合成器確認不再需要該幀,它就會將該幀標記為“retired”,以便客戶端知道該幀已被合成器釋放。

你可以通過 Systrace 查看,當 TimeWarp 讀取新幀時,需要返回相應幀的客戶端 FenceChecker,以確認 GPU 渲染完成。

第三階段:從合成到顯示

這時,幀(eye textures)已到達合成器,需要在 VR 顯示屏顯示。根據硬體的不同,這大致會發生涉及以下組件的一系列步驟:

  • Layer Composition:負責混合不同的合成器層。層可以來自一個或多個客戶端
  • TimeWarp:我們用以減少頭顯旋轉延遲的重投影技術
  • Distortion Correction:VR 透鏡造成畸變以增加感知視場。為了幫助用戶看到一個非畸變的世界,反畸變非常必要。
  • 其他後處理:存在其他後處理,如色差校正(CAC)。

從開發者的角度來看,以上大都是作為顯示管道的一部分自動完成,並可以將它們視為黑盒。在所有這些艱苦的工作完成後,屏幕會在 PredictedDisplayTime 點亮,而用戶會看到你的應用程式顯示出來。

考慮到合成器工作的重要性(如果沒有合成器,屏幕將被凍結),它在 GPU 上的更高優先順序上下文中運行,併在需要執行時中斷任何其他工作負載,例如渲染。你可以在 GPU systrace 上看到它對 Preempt blocks 的影響。對於 Quest1 和 Quest2,它的每幀工作分成兩部分以優化延遲,通常每幀搶占兩次,因為它每 7 毫秒運行一次。

總結

我們希望這篇概述有助於 Quest 開發者進一步理解系統,並幫助你構建更好的 VR 應用程式。從應用渲染開始到顯示結束,我們介紹了一個典型的 VR 幀生命周期。我們解釋了客戶端應用和合成器伺服器之間的數據流。如果你有問題或反饋,請通過 Oculus 開發者論壇告訴我們。

原文鏈接:https://developer.oculus.com/blog/a-vr-frames-life/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們都知道在Java編程中多線程的同步使用synchronized關鍵字來標識,那麼這個關鍵字在JVM底層到底是如何實現的呢。 我們先來思考一下如果我們自己實現的一個鎖該怎麼做呢: 首先肯定要有個標記記錄對象是否已經上鎖,執行同步代碼之前判斷這個標誌,如果對象已經上鎖線程就阻塞等待鎖的釋放。 其次要 ...
  • JZ7重建二叉樹 描述 給定節點數為 n 的二叉樹的前序遍歷和中序遍歷結果,請重建出該二叉樹並返回它的頭結點。 例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6} 提示: 1.vin.length == pre.length 2.pre 和 vin ...
  • 本文是對Datawhale的動手學數據分析課程的學習總結,記錄了整體的學習過程、答案以及個人感想,代碼較為詳細。 ...
  • 簡介 本文的初衷是希望幫助那些有其它平臺視覺演算法開發經驗的人能快速轉入Halcon平臺下,通過文中的示例開發者能快速瞭解一個Halcon項目開發的基本步驟,讓開發者能把精力完全集中到演算法的開發上面。 首先,你需要安裝Halcon,HALCON 18.11.0.1的安裝包會放在文章末尾。安裝包分開發和 ...
  • 一、併發與競爭簡介 併發:多個“用戶”同時訪問一個共用的記憶體。 競爭:多個“用戶”同時訪問一段共用的記憶體並對其修改,就會造成數據混亂,甚至程式崩潰,這就是競爭。 二、造成併發與競爭的原因 1、多線程併發訪問, Linux 是多任務(線程)的系統,所以多線程訪問是最基本的原因。 2、搶占式併發訪問, ...
  • Systemd為Linux中的初始化init系統,用於啟動與停止服務進程,設計目標為:儘可能啟動更少進程、更多進程並行啟動;Systemd使用Linux的CGroup特性用來跟蹤與管理進程的生命周期,在服務啟動時會併發創建依賴的服務進程,子進程繼承父進程CGroup相關服務進程歸屬與同一個CGrou ...
  • 大數據時代,無人不知Google的“三駕馬車”。“三駕馬車”指的是Google發佈的三篇論文,介紹了Google在大規模數據存儲與計算方向的工程實踐,奠定了業界大規模分散式存儲系統的理論基礎,如今市場上流行的幾款國產資料庫都有參考這三篇論文。 《The Google File System》,200 ...
  • 騰訊雲資料庫在助力金融核心系統分散式替換上,已經輻射到了東南亞市場。 東南亞最大的銀行之一印尼BNC銀行(Bank Neo Commerce)已正式完成新核心分散式遷移,使用騰訊雲資料庫TDSQL後,系統運行平穩順暢。這標志著騰訊雲資料庫在經過國內多家大型核心系統的落地實踐後,開始走向海外,“技術出 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...