翻譯:Mainline Explicit Fencing

譯者注

dma-fence 作爲 kernel 中 buffer 共享同步機制,已經成爲 DRM 驅動框架必不可少的基礎組件。瞭解 dma-fence 的背景知識,有助於後期學習 DRM 中 fence 相關的驅動開發。

本文翻譯自 Gustavo Padovan 於 2016 年 9 月發表在 Collabora 官網的三篇文章,Gustavo 本人也是內核中 dma-fence 的提交作者。通過本文,我們可以瞭解到 dma-fence 最初是如何演變而來的,以及它是如何在 graphics pipeline 中起作用的。

Mainline Explicit Fencing - Part 1

當我們談到 Linux kernel 中的 buffer 共享同步機制時,通常有兩種:Implicit(隱式) FenceExplicit(顯式) Fence。它們的主要區別在於:kernel 是否會將同步信息共享給用戶空間。因此它要麼是隱式的(不提供任何 fence 信息給用戶空間);要麼是顯式的(提供所有 fence 信息給用戶空間)。

fence 同步機制可以確保 buffer 在共享過程中,驅動程序或用戶空間不會對一個正在被寫入的 buffer 進行讀操作,或對一個仍在被其他模塊使用的 buffer 進行寫操作。fence 確保了這些操作能夠有序進行,即只會在 buffer 不被使用時才進行讀寫操作。例如,當一個 GPU 的 job 被塞進隊列時,該 job 中的 buffer 會被關聯上一個 fence,其他驅動程序可以藉助該 fence 來進行同步。在收到該 fence 的信號(signal)之前,這些驅動程序不會對該 buffer 進行任何操作,fence signal 表明該 buffer 現在可以被正常使用了。同樣地,爲了讓 GPU 驅動能等待 buffer 從顯示屏中切換出來,我們可以給顯示驅動採用相同的 fence 機制,以便 GPU 能再次使用這些 buffer 進行渲染。

fence 是該機制的核心元素,每當向 kernel 發送 buffer 相關的請求時,該 buffer 都會附帶一個 fence。用戶空間或其他驅動程序可以使用 fence 來等待硬件工作完成。因此,一旦工作完成,該 fence 就會被 signal,等待該 fence 的模塊也就可以繼續對該 buffer 進行它們想要的操作了。

雖然 Implicit Fence 在 buffer 同步方面起了很大作用,但在某些情況下,整個桌面的合成可能會被卡住。想象一下下面的合成過程:有 A、B、C 三個 buffer 需要處理,A 和 B 被 GPU 拿去同時做渲染,而 C 將作爲 A 與 B 合成的結果拿去顯示。但只有在 A、B 這兩個 buffer 都渲染完成時纔會通知 compositor 做合成,因此如果 B 渲染的時間太長,將導致整個桌面的合成因爲需要等待 B 而被 block 住,於是 C 就無法及時的顯示出來。

在這裏插入圖片描述

圖 1:Compositor 採用 Implicit Fence 機制同時處理兩塊 buffer,如果 buffer B 渲染耗時太長,則會導致桌面被凍結

而如果採用 Explicit Fence 機制,compositor 會爲每個 buffer 關聯上一個 fence,這樣就可以在每個 buffer 渲染完成時收到通知。因此,如果 A 渲染的很快而 B 需要很長時間才能渲染完成,那麼 compositor 可以做出決定不等待 B,而是繼續使用舊的 buffer B 與 A 一起做合成,接着顯示 C。因此 compositor 可以根據 fence 信息來做出更加明智的決定,以此來避免屏幕凍結的發生。

到目前爲止,Linux 內核只有 Implicit Fence 的通用 API,雖然有些驅動程序已經實現了 Explicit Fence,但它們的 API 都是與設備強相關的。Android 目前已經有了一套自己的同步實現機制,即 Android Sync Framework —— 我會在下一篇文章中對它進行介紹。

Explicit Fence 是一種 消費者-生產者 模型,在一條 GPU 渲染 + 掃描上屏顯示的 pipeline 中,它將在 kernel 驅動程序之間進行同步,因此當向 GPU(生產者)提交一個新的渲染 job 時,用戶空間將獲得一個與本次提交 buffer 相關聯的 fence。也就是說用戶空間不需要一直等待 job 完成而被阻塞在那裏,當 job 完成時會自動向該 fence 發送一個信號。因爲用戶空間不再需要一直等在那裏,且有了 fence 的加持,它就可以立即進行系統調用,告訴 Display 硬件(消費者)去掃描還未渲染完的 buffer。採用 Explicit Fence,內核空間會被告需要等到 fence signal 之後才能開始 buffer 掃描上屏的處理。

當用戶空間向 kernel 空間提交一個 buffer 用於 Display 顯示時,用戶空間同時會收到一個新創建的 fence。當該 buffer 不再需要被顯示時,它所對應的 fence 就會被 signal,於是這塊 buffer 就可以被另一個渲染 job 拿去重新使用了。一旦戶空間拿到該 fence,它可以無需等待就向 GPU 提交一個新的渲染 job。GPU 驅動則在內核空間等待該 buffer 顯示完成,一旦 fence 發出信號,buffer 的渲染工作就可以立即啓動。

在這裏插入圖片描述
圖 2:fence 總是穿梭於 userspace 和 pipeline 中下一個模塊之間,黃色箭頭表示用戶空間裏的 fence

最後不得不提的是,Explicit Fence 大大改善了圖形 pipeline 的 debug 能力。在用戶空間訪問 fence 可以更加清楚的知道底層 pipeline 當前正在發生的事情。以前,由於 Implicit Fence 沒有可訪問的信息,因此很難弄清楚 pipeline 上到底發生了什麼,而且每個 vendor 廠商都試圖實現他們自己的 Implicit Fence 機制,調試起來難道很大。現在,有了標準的 Expicit Fence,我們就可以更容易的建立起 debug 和 trace 框架,以便能夠跟蹤任何系統上的顯示問題。

下一篇文章將描述 Android Sync Framework,之後還將描述在 mainline 上添加 Explicit Fence 支持所作的工作。

Mainline Explicit Fencing - Part 2

在第一篇文章中,我們討論了 Linux 內核 Explicit Fence 的基本概念。作爲本系列的第二篇文章,我們將介紹 Android Sync Framework,這是 Linux 內核中首個(out-of-tree)Explicit Fence 的具體實現。

Sync Framework 是 Android 在 AOSP 中的 Explicit Fence 實現方案,它使用文件描述符 fd 在 userspace 和 kernel 之間、以及進程之間傳遞 fence 信息。

在 Sync Framework 中,一切都從創建一個 Sync Timeline 開始,Sync Timeline 是爲每個驅動程序上下文創建的一個結構體,用來表示一個單調遞增的計數器。Sync Timeline 將保證同一時間軸上不同 fence 之間的執行順序,驅動程序上下文可以是不同的 GPU ring,或是硬件上不同的 Display。
在這裏插入圖片描述
圖 1:Sync Timeline

然後我們需要有 Sync Point(sync_pt),Android 給 fence 起的名字,它們代表 Sync Timeline 上某個特定的值。Sync Point 剛被創建的時候會被初始化爲 Active 狀態,當它發出信號時(比如與它關聯的 job 執行結束了),它將轉換成 Signaled 狀態,並通知 Sync Timeline 的計數器更新爲最後一次被 signal 的 Sync Point 的值。

在這裏插入圖片描述
圖 2:Sync Point

要想將 Sync Point 導出給用戶空間,或從用戶空間導入到內核,需要藉助 Sync Fence 結構體。Sync Fence 本質上是一個 linux file,我們使用 Sync Fence 來存儲 Sync Point 的信息。要想將它導出給用戶空間,需要給 Sync Fence file 關聯上一個 unused 的文件描述符(fd),然後驅動程序就可以通過該 fd 來傳遞 Sync Point 的信息了。

在這裏插入圖片描述
圖 3:Sync Fence

Sync Fence 通常是在 Sync Point 被創建之後才創建的,然後經過用戶空間最終在 pipeline 模塊之間傳遞,直到驅動程序等待 Sync Fence 發出信號爲止。只有當 Sync Fence 內部所有的 Sync Point 都被 signal 時,Sync Fence 自己纔會被 signal。

Android Sync Framework 最重要的一個功能就是能夠將兩個 Sync Fence merge(合併)到一個新的 Sync Fence 中,它包含來自兩個 Sync Fence 的所有 Sync Point。只要你的資源允許,它可以包含無數多個 Sync Point。merge 後的 Sync Fence 只會在其內部所有的 Sync Point 都發出信號後纔會被 signal。

在這裏插入圖片描述
圖 4:Merge 後的 Sync Fence

說起用戶空間 API,Sync Framework 實現了三個 ioctl 調用。第一個是等待 sync_fence 被 signal,第二個是將兩個 sync_fence merge 爲第三個新的 sync_fence,最後一個 API 是用來獲取 sync_fence 及其所有 sync_point 信息的。

當我們通過系統調用,請求 kernel 對一塊 buffer 進行渲染或顯示時,Sync Fence 的 fd 將通過該系統調用傳遞給 kernel,或從 kernel 返回給用戶空間。

本篇的主要目的爲了簡要概括一下 Sync Framework,因爲我們將在下一篇文章中看到這些概念。同時我將會在下一篇講述爲了讓 mainline kernel 支持 Explicit Fence,我們都做了哪些工作。如果你想了解更多關於 Sync Framework 的詳細信息,請點擊 這裏這裏 查看。

Mainline Explicit Fencing - Part 3

在前兩篇文章中,我們討論了 Explicit Fence 對於 graphics pipeline 大體上都起了什麼作用,以及爲了將 Android Sync Framework upstream 我們都做了哪些事情。現在,作爲在本系列的最後一篇文章,我將介紹在 DRM 和圖形棧的其他方面應該如何來實現 Explicit Fence。

DRM 實現 Explicit Fence 需要建立在兩個 kernel 基礎框架之上:struct dma_fence —— 該結構體表示 fence,和 struct sync_file —— 用來提供與用戶空間共享的文件描述符(如前幾篇文章中所討論的)。在使用 fence 時,Display 底層驅動需要等待該 fence 發出信號後,才能在屏幕上顯示該 buffer。在 Explicit Fence 的實現中,fence 從用戶空間發送給內核空間,Display 底層驅動同樣會給用戶空間返回一個新的 fence,該 fence 被封裝在 sync_file 結構體中。當該 buffer 被顯示到屏幕上時,它所對應的 fence 就會被 signal。同樣的,在渲染側也採用相同的等待流程。

必須使用 Atomic Modesetting,我們不打算支持 legacy API。DRM 要等待的 fence 需要通過每個 DRM Plane 的 IN_FENCE_FD property 來傳遞,也就是說每個 Plane 將收到一個 sync_file fd,每個 fd 將包含一個或多個 dma_fence。請記住,在 DRM 中一個 Plane 直接對應一個 framebuffer,因此也可以說一個 framebuffer 對應一個 sync_file。

另一方面,對於 kernel 創建的 fence,需要藉助於 OUT_FENCE PTR 這個 property 來返回給用戶空間。這是一個 DRM CRTC 的 property,因爲 CRTC 上的所有 buffer 都是在同一時刻被掃描出去的,所以我們只爲每個 CRTC 創建一個 dma_fence。kernel 通過將 fd 的值寫入 OUT_FENCE PTR property 所指向的用戶空間內存裏,這樣就能將這個 fence 返回給用戶空間了。請注意,與 Android 不同的是,Mainline 中的 fence 在被 signal 時,意味着上一個 buffer(即已經從屏幕上移除的 buffer)可以被再次使用了。而在 Android 上,當信號被觸發時,意味着當前正在顯示的 buffer 被釋放了。不過 Android 的開發人員已經重新修改了 SurfaceFlinger,以支持 Mainline 上 Explicit Fence 的使用規則!

不過,以上僅僅只是一方面,要想讓 Explicit Fence 機制在整個 graphics pipeline 中運行起來,我們還需要在渲染側對它添加支持。由於每個渲染驅動程序都有自己的 userspace API,因此我們需要爲每個渲染驅動添加 Explicit Fence 支持。freedreno 驅動程序已經可以支持 mainline 上的 Explicit Fence,而 i915 和 virtio_gpu 驅動也正在添加對該 fence 的支持。

在用戶空間這一端,Mesa 已經添加了對 EGL_ANDROID_native_fence_sync 擴展接口的支持,這樣就可以在 Android 平臺上使用 Explicit Fence 了。libdrm 的頭文件也已經合入了對 sync_file 的 ioctl 封裝。在 Android 平臺上,libsync 現在既可以支持舊的 Android Sync API,又可以支持新的 Mainline Sync File API。最後,在 drm_hwcomposer 上,使用 Atomic Modesetting 和 Explicit Fence 的 patch 也都已經有了,只不過它們暫時還沒有被 upstream。

IGT(intel-gpu-tools) 中也已經添加對 Sync File 和 fence 的 Atomic API 測試 case。

全文完。

原文連接

  1. Mainline Explicit Fencing - Part 1
  2. Mainline Explicit Fencing - Part 2
  3. Mainline Explicit Fencing - Part 3

擴展閱讀:

  1. Mainline Explicit Fencing - X.Org
  2. Bringing Android explicit fencing to the mainline
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章