同步框架明確描述了Android圖形系統中不同異步操作之間的依賴關係。該框架提供了一個 API,使組件能夠指示何時釋放緩衝區。該框架還允許在驅動程序之間從內核到用戶空間以及用戶空間進程本身之間傳遞同步原語。
例如,應用程序可能會排隊等待在 GPU 中執行的工作。 GPU 開始繪製該圖像。雖然圖像尚未被繪製到內存中,但緩衝區指針與指示 GPU 工作何時完成的柵欄一起傳遞給窗口合成器。窗口合成器提前開始處理並將工作傳遞給顯示控制器。以類似的方式,CPU 工作提前完成。 GPU 完成後,顯示控制器會立即顯示圖像。
同步框架還允許實施者利用他們自己的硬件組件中的同步資源。最後,該框架提供了對圖形管道的可見性以幫助調試。
顯式同步
顯式同步使圖形緩衝區的生產者和消費者能夠在他們完成使用緩衝區時發出信號。顯式同步在內核空間中實現。
顯式同步的好處包括:
- 設備之間的行為差異較小
- 更好的調試支持
- 改進的測試指標
同步框架具有三種對像類型:
-
sync_timeline
-
sync_pt
-
sync_fence
同步時間線
sync_timeline
是供應商應為每個驅動程序實例實現的單調遞增時間線,例如 GL 上下文、顯示控制器或 2D blitter。 sync_timeline
計算提交給內核的特定硬件的作業。 sync_timeline
提供有關操作順序的保證並啟用特定於硬件的實現。
實施sync_timeline
時請遵循以下準則:
- 為所有驅動程序、時間線和柵欄提供有用的名稱以簡化調試。
- 在時間軸中實現
timeline_value_str
和pt_value_str
運算符,使調試輸出更具可讀性。 - 如果需要,實現填充
driver_data
以使用戶空間庫(例如 GL 庫)訪問私有時間線數據。data_driver
允許供應商傳遞有關不可變的sync_fence
和sync_pts
的信息,以基於它們構建命令行。 - 不允許用戶空間顯式地創建或發出柵欄信號。顯式創建信號/柵欄會導致拒絕服務攻擊,從而停止管道功能。
- 不要顯式訪問
sync_timeline
、sync_pt
或sync_fence
元素。 API 提供了所有必需的功能。
同步點
sync_pt
是sync_timeline
上的單個值或點。一個點具有三種狀態:活動、信號和錯誤。點從活動狀態開始並轉換到信號或錯誤狀態。例如,當圖像消費者不再需要緩衝區時,會發出一個sync_pt
信號,以便圖像生產者知道可以再次寫入緩衝區。
同步圍欄
sync_fence
是sync_pt
值的集合,這些值通常具有不同的sync_timeline
父級(例如用於顯示控制器和 GPU)。 sync_fence
、 sync_pt
和sync_timeline
是驅動程序和用戶空間用來傳達其依賴關係的主要原語。當圍欄發出信號時,保證在圍欄之前發出的所有命令都是完整的,因為內核驅動程序或硬件塊按順序執行命令。
同步框架允許多個消費者或生產者在他們完成使用緩衝區時發出信號,用一個函數參數傳達依賴信息。柵欄由文件描述符支持,並從內核空間傳遞到用戶空間。例如,柵欄可以包含兩個sync_pt
值,這表示兩個單獨的圖像消費者何時完成了對緩衝區的讀取。當圍欄發出信號時,圖像生產者知道兩個消費者都完成了消費。
柵欄,如sync_pt
值,開始活動並根據其點的狀態更改狀態。如果所有sync_pt
值都變為信號狀態,則sync_fence
變為信號狀態。如果一個sync_pt
陷入錯誤狀態,則整個sync_fence
都處於錯誤狀態。
創建柵欄後, sync_fence
中的成員資格是不可變的。要在柵欄中獲得多個點,需要進行合併,將來自兩個不同柵欄的點添加到第三個柵欄。如果其中一個點在原始圍欄中發出信號而另一個沒有發出信號,則第三個圍欄也不會處於信號狀態。
要實現顯式同步,請提供以下內容:
- 為特定硬件驅動程序實現同步框架的內核空間子系統。需要具有防護意識的驅動程序通常是訪問或與 Hardware Composer 通信的任何東西。關鍵文件包括:
- 核心實現:
-
kernel/common/include/linux/sync.h
-
kernel/common/drivers/base/sync.c
-
-
kernel/common/Documentation/sync.txt
中的文檔 - 與
platform/system/core/libsync
中的內核空間通信的庫
- 核心實現:
- 供應商必須提供適當的同步柵欄作為 HAL 中
validateDisplay()
和presentDisplay()
函數的參數。 - 兩個與柵欄相關的 GL 擴展(
EGL_ANDROID_native_fence_sync
和EGL_ANDROID_wait_sync
)和圖形驅動程序中的柵欄支持。
案例研究:實現顯示驅動程序
要使用支持同步功能的 API,請開發具有顯示緩衝區功能的顯示驅動程序。在同步框架存在之前,此函數將接收dma-buf
對象,將這些緩衝區放在顯示器上,並在緩衝區可見時阻塞。例如:
/* * assumes buffer is ready to be displayed. returns when buffer is no longer on * screen. */ void display_buffer(struct dma_buf *buffer);
有了同步框架, display_buffer
函數就更複雜了。在顯示緩衝區時,緩衝區與指示緩衝區何時準備就緒的柵欄相關聯。圍欄清除後,您可以排隊並開始工作。
清除圍欄後排隊和啟動工作不會阻止任何事情。您立即返回您自己的柵欄,這保證了緩衝區何時關閉。當您將緩衝區排隊時,內核會列出與同步框架的依賴關係:
/* * displays buffer when fence is signaled. returns immediately with a fence * that signals when buffer is no longer displayed. */ struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence *fence);
同步集成
本節說明如何將內核空間同步框架與 Android 框架的用戶空間部分以及必須相互通信的驅動程序集成。內核空間對像在用戶空間中表示為文件描述符。
集成約定
遵循 Android HAL 接口約定:
- 如果 API 提供引用
sync_pt
的文件描述符,則供應商的驅動程序或使用 API 的 HAL 必須關閉文件描述符。 - 如果供應商驅動程序或 HAL 將包含
sync_pt
的文件描述符傳遞給 API 函數,則供應商驅動程序或 HAL 不得關閉文件描述符。 - 要繼續使用柵欄文件描述符,供應商驅動程序或 HAL 必須複製該描述符。
每次通過 BufferQueue 時,柵欄對像都會被重命名。內核柵欄支持允許柵欄具有名稱字符串,因此同步框架使用正在排隊的窗口名稱和緩衝區索引來命名柵欄,例如SurfaceView:0
。這有助於調試以識別死鎖的來源,因為名稱出現在/d/sync
和錯誤報告的輸出中。
ANativeWindow 集成
ANativeWindow 是圍欄感知的。 dequeueBuffer
、 queueBuffer
和cancelBuffer
具有柵欄參數。
OpenGL ES 集成
OpenGL ES 同步集成依賴於兩個 EGL 擴展:
-
EGL_ANDROID_native_fence_sync
提供了一種在EGLSyncKHR
對像中包裝或創建原生 Android 圍欄文件描述符的方法。 -
EGL_ANDROID_wait_sync
允許 GPU 端停止而不是 CPU 端,使 GPU 等待EGLSyncKHR
。EGL_ANDROID_wait_sync
擴展與EGL_KHR_wait_sync
擴展相同。
要獨立使用這些擴展,請實現EGL_ANDROID_native_fence_sync
擴展以及相關的內核支持。接下來,在您的驅動程序中啟用EGL_ANDROID_wait_sync
擴展。 EGL_ANDROID_native_fence_sync
擴展由不同的本機柵欄EGLSyncKHR
像類型組成。因此,適用於現有EGLSyncKHR
像類型的擴展不一定適用於EGL_ANDROID_native_fence
對象,從而避免了不必要的交互。
EGL_ANDROID_native_fence_sync
擴展使用了相應的本機柵欄文件描述符屬性,該屬性只能在創建時設置,並且不能從現有同步對象直接查詢。此屬性可以設置為以下兩種模式之一:
- 有效的柵欄文件描述符將現有的原生 Android 柵欄文件描述符包裝在
EGLSyncKHR
像中。 - -1從
EGLSyncKHR
對象創建本機 Android 圍欄文件描述符。
使用DupNativeFenceFD()
函數調用從原生 Android 圍欄文件描述符中提取EGLSyncKHR
對象。這與查詢 set 屬性的結果相同,但遵守接收者關閉圍欄的約定(因此重複操作)。最後,銷毀EGLSyncKHR
對象會關閉內部圍欄屬性。
硬件作曲家集成
Hardware Composer 處理三種類型的同步柵欄:
- 獲取柵欄與輸入緩衝區一起傳遞給
setLayerBuffer
和setClientTarget
調用。這些表示對緩衝區的掛起寫入,並且必須在 SurfaceFlinger 或 HWC 嘗試從關聯的緩衝區讀取以執行組合之前發出信號。 - 在調用
presentDisplay
之後使用getReleaseFences
調用檢索釋放柵欄。這些表示從同一層上的前一個緩衝區進行的未決讀取。當 HWC 不再使用前一個緩衝區時,釋放柵欄會發出信號,因為當前緩衝區已替換了顯示器上的前一個緩衝區。釋放柵欄與將在當前合成期間替換的先前緩衝區一起傳遞回應用程序。應用程序必須等到釋放圍欄發出信號,然後才能將新內容寫入返回給它們的緩衝區。 - 作為調用
presentDisplay
的一部分,每幀返回一個當前柵欄。當前的柵欄表示此幀的合成何時完成,或者,當不再需要前一幀的合成結果時。對於物理顯示,噹噹前幀出現在屏幕上時,presentDisplay
返回當前柵欄。返回當前的柵欄後,如果適用,再次寫入 SurfaceFlinger 目標緩衝區是安全的。對於虛擬顯示器,當可以安全地從輸出緩衝區讀取時,會返回當前的柵欄。