交換畫素緩衝區

最初設計時,Linux 圖形子系統對程序、裝置和子系統之間共享畫素緩衝區分配的支援非常有限。現代系統需要這三類之間進行廣泛整合;本文件詳細介紹了應用程式和核心子系統應如何處理二維影像資料的這種共享。

本文件參考了用於 GPU 和顯示裝置的 DRM 子系統、用於媒體裝置的 V4L2 以及用於使用者空間支援的 Vulkan、EGL 和 Wayland,但任何其他子系統也應遵循此設計和建議。

術語表

影像:

概念上是畫素的二維陣列。畫素可以儲存在一個或多個記憶體緩衝區中。具有畫素的寬度和高度、畫素格式和修飾符(隱式或顯式)。

行:

沿單個 y 軸值的跨度,例如從座標 (0,100) 到 (200,100)。

掃描線:

行的同義詞。

列:

沿單個 x 軸值的跨度,例如從座標 (100,0) 到 (100,100)。

記憶體緩衝區:

用於儲存(部分)畫素資料的記憶體塊。具有步幅和大小(以位元組為單位),並且在某些 API 中至少有一個控制代碼。可能包含一個或多個平面。

平面:

一些或所有影像顏色和 alpha 通道值的二維陣列。

畫素:

圖片元素。具有由一個或多個顏色通道值定義的單個顏色值,例如 R、G 和 B,或 Y、Cb 和 Cr。也可能具有 alpha 值作為附加通道。

畫素資料:

表示畫素或影像的一些或所有顏色/alpha 通道值的位元組或位。一個畫素的資料可以分佈在多個平面或記憶體緩衝區中,具體取決於格式和修飾符。

顏色值:

表示顏色的數字元組。元組中的每個元素都是一個顏色通道值。

顏色通道:

顏色模型中的一個維度。例如,RGB 模型具有通道 R、G 和 B。Alpha 通道有時也被計為顏色通道。

畫素格式:

描述畫素資料如何表示畫素的顏色和 alpha 值的描述。

修飾符:

描述畫素資料在記憶體緩衝區中如何佈局的描述。

Alpha:

表示畫素中的顏色覆蓋率的值。有時也用於半透明。

步幅:

表示畫素位置座標和位元組偏移值之間關係的值。通常用作垂直連續平鋪塊起點的兩個畫素之間的位元組偏移量。對於線性佈局,是兩個垂直相鄰畫素之間的位元組偏移量。對於非線性格式,必須以一致的方式計算步幅,通常就像佈局是線性的一樣。

間距:

步幅的同義詞。

格式和修飾符

每個緩衝區都必須具有基礎格式。此格式描述了為每個畫素提供的顏色值。儘管每個子系統都有自己的格式描述(例如,V4L2 和 fbdev),但應儘可能重用 DRM_FORMAT_* 令牌,因為它們是用於交換的標準描述。這些令牌在 drm_fourcc.h 檔案中進行了描述,該檔案是 DRM uAPI 的一部分。

每個 DRM_FORMAT_* 令牌都描述了影像中畫素座標與該畫素的顏色值(包含在其記憶體緩衝區中)之間的轉換。描述了顏色通道的數量和型別:它們是 RGB 還是 YUV,整數還是浮點,每個通道的大小及其在畫素記憶體中的位置,以及顏色平面之間的關係。

例如,DRM_FORMAT_ARGB8888 描述了一種格式,其中每個畫素在記憶體中都具有單個 32 位值。Alpha、紅色、綠色和藍色顏色通道均可用於每個通道 8 位精度,按從最高有效位到最低有效位的順序排列在小端儲存中。DRM_FORMAT_* 不受 CPU 或裝置位元組順序的影響;記憶體中的位元組模式始終如格式定義中所述,通常為小端。

作為更復雜的示例,DRM_FORMAT_NV12 描述了一種格式,其中亮度(luma)和色度(chroma)YUV 樣本儲存在單獨的平面中,其中色度平面以兩個維度的一半解析度儲存(即,為每個 2x2 畫素分組儲存一個 U/V 色度樣本)。

格式修飾符描述了這些每個畫素記憶體樣本與緩衝區的實際記憶體儲存之間的轉換機制。最直接的修飾符是 DRM_FORMAT_MOD_LINEAR,它描述了一種方案,其中每個平面按行順序從左上角到右下角進行佈局。這被認為是基線交換格式,並且對於 CPU 訪問最方便。

現代硬體採用更復雜的訪問機制,通常利用平鋪訪問,也可能利用壓縮。例如,DRM_FORMAT_MOD_VIVANTE_TILED 修飾符描述了記憶體儲存,其中畫素儲存在按行主序排列的 4x4 塊中,即平面中的第一個平鋪儲存畫素 (0,0) 到 (3,3)(含),平面中的第二個平鋪儲存畫素 (4,0) 到 (7,3)(含)。

某些修飾符可能會修改影像所需的平面數量;例如,I915_FORMAT_MOD_Y_TILED_CCS 修飾符向 RGB 格式新增第二個平面,用於儲存有關每個平鋪狀態的資料,特別是包括該平鋪是否完全填充了畫素資料,或者是否可以從單個純色展開。

這些擴展布局是高度特定於供應商的,甚至特定於每個供應商的特定代或裝置配置。因此,必須由所有使用者顯式列舉和協商對修飾符的支援,以確保相容且最佳的流水線,如下所述。

尺寸和大小

每個畫素緩衝區都必須附帶邏輯畫素尺寸。這指的是可以從底層記憶體儲存中提取或儲存到其中的唯一樣本的數量。例如,即使 1920x1080 DRM_FORMAT_NV12 緩衝區具有包含 Y 分量的 1920x1080 個樣本的亮度平面,以及包含 U 和 V 分量的 960x540 個樣本的色度平面,但整個緩衝區仍被描述為具有 1920x1080 的尺寸。

不能保證緩衝區的記憶體中儲存立即從底層記憶體的基地址開始,也不能保證記憶體儲存緊密地裁剪到任一維度。

因此,每個平面都必須使用位元組為單位的 offset 來描述,該偏移量將在執行任何按畫素計算之前新增到記憶體儲存的基地址。這可用於將多個平面組合到單個記憶體緩衝區中;例如,DRM_FORMAT_NV12 可以儲存在單個記憶體緩衝區中,其中亮度平面的儲存從緩衝區的開頭立即開始,偏移量為 0,而色度平面的儲存從同一緩衝區中該平面的位元組偏移量開始。

每個平面還必須具有位元組為單位的 stride,表示兩個連續行之間的記憶體偏移量。例如,尺寸為 1000x1000 的 DRM_FORMAT_MOD_LINEAR 緩衝區可能已分配為好像它是 1024x1000,以便允許對齊的訪問模式。在這種情況下,緩衝區仍將被描述為具有 1000 的寬度,但是步幅將為 1024 * bpp,表明在 x 軸的正極端有 24 個畫素,其值並不重要。

還可以透過簡單地分配比通常需要的更大的區域來在 y 維度中進一步填充緩衝區。例如,許多媒體解碼器無法本地輸出高度為 1080 的緩衝區,而是需要 1088 畫素的有效高度。在這種情況下,緩衝區將繼續被描述為具有 1080 的高度,並且每個緩衝區的記憶體分配都會增加以考慮額外的填充。

列舉

畫素緩衝區的每個使用者都必須能夠列舉一組受支援的格式和修飾符,這些格式和修飾符一起描述。在 KMS 中,這是透過每個 DRM 平面上的 IN_FORMATS 屬性實現的,該屬性列出了受支援的 DRM 格式,以及每個格式支援的修飾符。在使用者空間中,EGL 透過 EGL_EXT_image_dma_buf_import_modifiers 擴充套件入口點支援,Vulkan 透過 VK_EXT_image_drm_format_modifier 擴充套件支援,Wayland 透過 zwp_linux_dmabuf_v1 擴充套件支援。

這些介面中的每一個都允許使用者查詢一組受支援的格式 + 修飾符組合。

協商

使用者空間有責任為其用法協商可接受的格式 + 修飾符組合。這是透過簡單的列表交集來執行的。例如,如果使用者想要使用 Vulkan 渲染要在 KMS 平面上顯示的影像,則必須

  • 查詢 KMS 以獲取給定平面的 IN_FORMATS 屬性

  • 查詢 Vulkan 以獲取其物理裝置支援的格式,確保傳遞與預期渲染用途相對應的 VkImageUsageFlagBitsVkImageCreateFlagBits

  • 對這些格式求交集以確定最合適的格式

  • 對於此格式,對 KMS 和 Vulkan 支援的修飾符列表求交集,以獲得該格式的可接受修飾符的最終列表

必須對所有用法執行此交集。例如,如果使用者還希望將影像編碼為影片流,則必須查詢其打算用於編碼的媒體 API,以獲取其支援的修飾符集,並另外與此列表求交集。

如果所有列表的交集是一個空列表,則無法以這種方式共享緩衝區,並且必須考慮替代策略(例如,使用 CPU 訪問例程在不同的用途之間複製資料,併產生相應的效能成本)。

生成的修飾符列表未排序;順序並不重要。

分配

一旦使用者空間確定了合適的格式,以及相應的可接受修飾符列表,它必須分配緩衝區。由於在核心或使用者空間級別沒有可用的通用緩衝區分配介面,因此客戶端可以任意選擇分配介面,例如 Vulkan、GBM 或媒體 API。

每個分配請求必須至少採用:畫素格式、可接受的修飾符列表以及緩衝區的寬度和高度。每個 API 都可以透過不同的方式擴充套件此屬性集,例如允許在兩個以上的維度中進行分配,預期的使用模式等。

分配緩衝區的元件將任意選擇它認為請求分配的可接受列表中“最佳”的修飾符、所需的任何填充以及底層記憶體緩衝區的其他屬性,例如它們是儲存在系統還是裝置特定的記憶體中,它們是否物理連續,以及它們的快取模式。這些記憶體緩衝區的屬性對於使用者空間是不可見的,但是 dma-heaps API 旨在解決此問題。

分配後,客戶端必須查詢分配器以確定為緩衝區選擇的實際修飾符,以及每個平面的偏移量和步幅。不允許分配器更改使用的格式,選擇未在可接受列表中提供的修飾符,也不允許更改畫素尺寸,但透過偏移量、步幅和大小表示的填充除外。

傳遞其他約束,例如步幅或偏移量的對齊、在特定記憶體區域內的放置等,超出了 dma-buf 的範圍,並且不能透過格式和修飾符令牌來解決。

匯入

要在不同的上下文、裝置或子系統中使用緩衝區,使用者會將這些引數(格式、修飾符、寬度、高度以及每個平面的偏移量和步幅)傳遞給匯入 API。

每個記憶體緩衝區都由緩衝區控制代碼引用,該控制代碼在影像中可以是唯一的或重複的。例如,DRM_FORMAT_NV12 緩衝區可以透過使用每個平面的偏移量引數將亮度緩衝區和色度緩衝區組合到單個記憶體緩衝區中,或者它們可能是記憶體中完全獨立的分配。因此,每個匯入和分配 API 都必須為每個平面提供單獨的控制代碼。

每個核心子系統都有自己的型別和介面用於緩衝區管理。DRM 使用 GEM 緩衝區物件 (BO),V4L2 有自己的引用等。這些型別在上下文、程序、裝置或子系統之間不可移植。

為了解決這個問題,dma-buf 控制代碼被用作緩衝區的通用交換。子系統特定的操作用於將本機緩衝區控制代碼匯出到 dma-buf 檔案描述符,並將這些檔案描述符匯入到本機緩衝區控制代碼。dma-buf 檔案描述符可以在上下文、程序、裝置和子系統之間傳輸。

例如,Wayland 媒體播放器可以使用 V4L2 將影片幀解碼為 DRM_FORMAT_NV12 緩衝區。這將導致使用者從 V4L2 中取消佇列兩個記憶體平面(亮度和平面)。然後,這些平面將匯出到每個平面的一個 dma-buf 檔案描述符,這些描述符將與元資料(格式、修飾符、寬度、高度、每個平面的偏移量和步幅)一起傳送到 Wayland 伺服器。然後,Wayland 伺服器會將這些檔案描述符作為 EGLImage 匯入,以透過 EGL/OpenGL (ES) 使用,作為 VkImage 匯入,以透過 Vulkan 使用,或作為 KMS 幀緩衝區物件匯入;每個匯入操作都將採用相同的元資料並將 dma-buf 檔案描述符轉換為其本機緩衝區控制代碼。

具有受支援修飾符的非空交集並不能保證匯入將成功匯入到所有消費者中;他們可能具有超出修飾符所暗示的必須滿足的約束。

隱式修飾符

修飾符的概念晚於上面提到的所有子系統。因此,它已被追溯到所有這些 API 中,並且為了確保向後相容性,需要支援不支援修飾符的驅動程式和使用者空間。

例如,GBM 用於分配要在 EGL(用於渲染)和 KMS(用於顯示)之間共享的緩衝區。它有兩個用於分配緩衝區的入口點:gbm_bo_create,它僅採用格式、寬度、高度和用法令牌,以及 gbm_bo_create_with_modifiers,它使用修飾符列表對其進行擴充套件。

在後一種情況下,分配如上所述,提供了一個可接受的修飾符列表,實現可以從中選擇(如果無法在這些約束內進行分配,則失敗)。在前一種情況下,未提供修飾符,GBM 實現必須自己選擇什麼可能是“最佳”佈局。這種選擇完全特定於實現:如果實現透過任何啟發式方法認為這是一個好主意,則某些實現將在內部使用 CPU 無法訪問的平鋪佈局。實現有責任確保此選擇是適當的。

為了支援這種由於不瞭解修飾符而導致佈局未知的情況,定義了一個特殊的 DRM_FORMAT_MOD_INVALID 令牌。這個偽修飾符宣告佈局未知,並且驅動程式應使用其自己的邏輯來確定底層佈局可能是什麼。

注意

DRM_FORMAT_MOD_INVALID 是一個非零值。修飾符值零是 DRM_FORMAT_MOD_LINEAR,這是一個明確的保證,即影像具有線性佈局。應注意確保將零作為預設值與沒有修飾符或線性修飾符混淆。另請注意,在某些 API 中,無效修飾符值使用帶外標誌指定,例如在 DRM_IOCTL_MODE_ADDFB2 中。

在以下四種情況下可以使用此令牌
  • 在列舉期間,介面可能會返回 DRM_FORMAT_MOD_INVALID,作為修飾符列表中唯一的成員來宣告不支援顯式修飾符,或者作為較大列表的一部分來宣告可以使用隱式修飾符

  • 在分配期間,使用者可能會提供 DRM_FORMAT_MOD_INVALID,作為修飾符列表中唯一的成員(等同於根本不提供修飾符列表)以宣告不支援顯式修飾符並且不得使用,或者作為較大列表的一部分來宣告可以使用隱式修飾符分配是可以接受的

  • 在分配後查詢中,實現可能會返回 DRM_FORMAT_MOD_INVALID 作為已分配緩衝區的修飾符,以宣告底層佈局是實現定義的,並且沒有顯式修飾符描述可用;根據上述規則,僅當用戶已將 DRM_FORMAT_MOD_INVALID 作為可接受的修飾符列表的一部分包含在內時,或者未提供列表時,才可以返回此值

  • 在匯入緩衝區時,使用者可以提供 DRM_FORMAT_MOD_INVALID 作為緩衝區修飾符(或不提供修飾符)以指示修飾符由於某種原因未知;僅當緩衝區不是使用顯式修飾符分配的時,才可以接受此操作

由此可見,對於任何單個緩衝區,由生產者和所有消費者形成的完整操作鏈必須是完全隱式或完全顯式的。例如,如果使用者希望分配一個緩衝區以在 GPU、顯示和媒體之間使用,但是媒體 API 不支援修飾符,那麼使用者不得使用顯式修飾符分配緩衝區,並嘗試在沒有修飾符的情況下將緩衝區匯入到媒體 API 中,而是使用隱式修飾符執行分配,或者單獨為媒體使用分配緩衝區並在兩個緩衝區之間進行復制。

作為上述情況的一個例外,分配可以從“隱式”升級為“顯式”修飾符。例如,如果使用 gbm_bo_create(不採用修飾符)分配緩衝區,則使用者可以使用 gbm_bo_get_modifier 查詢修飾符,然後如果返回有效的修飾符,則將此修飾符用作顯式修飾符令牌。

在為不同使用者之間的交換分配緩衝區且修飾符不可用時,強烈建議實現使用 DRM_FORMAT_MOD_LINEAR 進行分配,因為這是交換的通用基線。但是,不能保證這將導致對緩衝區內容的正確解釋,因為隱式修飾符操作可能仍然受到驅動程式特定的啟發式方法的影響。

任何新的使用者 - 使用者空間程式和協議、核心子系統等 - 希望交換緩衝區必須透過 dma-buf 檔案描述符為記憶體平面提供互操作性、DRM 格式令牌來描述格式、DRM 格式修飾符來描述記憶體中的佈局、至少寬度和高度用於尺寸,以及至少偏移量和步幅用於每個記憶體平面。