Android 12(S) 圖形顯示系統 - 解讀Gralloc架構及GraphicBuffer建立/傳遞/釋放(十四)

二的次方發表於2022-03-27

必讀:

Android 12(S) 圖形顯示系統 - 開篇


 

一、前言

在前面的文章中,已經出現過 GraphicBuffer 的身影,GraphicBuffer 是Android圖形顯示系統中的一個重要概念和元件,顧名思義,它就是用來儲存和傳遞需要繪製的影像資料的。GraphicBuffer 可以在應用程式和 BufferQueue 或 SurfaceFlinger 間傳遞。


本文及接下來的幾篇文章,將聚焦分析 GraphicBuffer 建立的流程,相關元件、服務的基本架構和基本實現原理。

 

二、GraphicBuffer 相關元件/層級結構預覽

Android 圖形顯示系統中管理、分配、使用 GraphicBuffer 涉及眾多元件,我的理解:

1. 生產者、消費者一般是作為 GraphicBuffer 的使用者,或寫資料,或讀資料;

2. BufferQueue 可以視作 GraphicBuffer 的管理者,它統一處理來自使用者的請求,從而統一管理 GraphicBuffer 的分配、釋放及流轉;

3. Gralloc HAL 是實際快取memory的分配模組,它負責分配可以在程式間共享的圖形buffer;

不同的功能邏輯被封裝為不同的元件/子系統, 從系統層級來看,gralloc屬於最底層的HAL層模組,為上層的libuilibgui 庫提供服務,整個層級結構如下所示:

如上圖所示,簡單概述

最底層是 Gralloc HAL 模組,這部分按照功能劃分為兩部分:allocator 和 mapper。Gralloc 是Android中負責為GraphicBuffer申請和釋放記憶體的HAL層模組,由硬體驅動提供實現,為BufferQueue機制提供了基礎。Gralloc 分配的圖形Buffer是程式間共享的,且根據其Flag支援不同硬體裝置的讀寫。同其它HAL模組一樣,Gralloc 也是實現為 HIDL service的方式,我們暫且稱為 gralloc-allocator hidl service 和 gralloc-mapper hidl service;

上面一層 libui 庫,主要功能是封裝對 Gralloc HAL 層的呼叫。程式碼目錄是 frameworks/native/include/ui 和 frameworks/native/libs/ui。其中的 GrallocAllocator看作 gralloc-allocator hal 代理,GrallocMapper 看作 gralloc-mapper hal 的代理;

再向上一層是 libgui 庫,主要功能是封裝連線生產者和消費者的BufferQueue,向下依賴於於libui。程式碼目錄是 frameworks/native/include/gui 和 frameworks/native/libs/gui;

最上面是使用者,他們對 GraphicBuffer 進行讀寫處理,Skia、Hwui 和  OpenGL ES 是BufferQueue的生產方,SurfaceFlinger 是BufferQueue的消費方;

 

三、Gralloc HAL介紹

Gralloc HAL 分為了兩部分:一個是 allocator ,一個是 mapper。Android系統定義了標準的 Gralloc HAL interface,具體實現有OEM/晶片廠商完成。

3.1 allocator HAL interface 的定義

[ /hardware/interfaces/graphics/allocator/4.0/IAllocator.hal]

interface IAllocator {
    /**
     * Allocates buffers with the properties specified by the descriptor.
     *
     * Allocations should be optimized for usage bits provided in the
     * descriptor.
     *
     * @param descriptor Properties of the buffers to allocate. This must be
     *     obtained from IMapper::createDescriptor().
     * @param count The number of buffers to allocate.
     * @return error Error status of the call, which may be
     *     - `NONE` upon success.
     *     - `BAD_DESCRIPTOR` if the descriptor is invalid.
     *     - `NO_RESOURCES` if the allocation cannot be fulfilled at this time.
     *     - `UNSUPPORTED` if any of the properties encoded in the descriptor
     *       are not supported.
     * @return stride The number of pixels between two consecutive rows of
     *     an allocated buffer, when the concept of consecutive rows is defined.
     *     Otherwise, it has no meaning.
     * @return buffers Array of raw handles to the allocated buffers.
     */
    allocate(BufferDescriptor descriptor, uint32_t count)
        generates (Error error,
                   uint32_t stride,
                   vec<handle> buffers);
};

IAllocator介面的定義很簡單,只有一個方法allocate,其中引數 descriptor 描述了需要分配的buffer的屬性,它是通過 IMapper::createDescriptor()獲取的,count 標識需要分配的buffer數量。

呼叫成功後返回 raw handle 的陣列buffers

Allocator 實現為一個 Binderized HAL Service(繫結式HAL),執行在獨立的程式中,使用者通過 HwBinder 與之建立連線,類似與AIDL獲取服務的方式。

Android 框架層libui庫中的Gralloc4Allocator 就是作為一個代理並對其功能的封裝:

[/frameworks/native/libs/ui/Gralloc4.cpp]

Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(mapper) {
    mAllocator = IAllocator::getService();
    ...
}

其中成員sp<hardware::graphics::allocator::V4_0::IAllocator> mAllocator;通過呼叫 IAllocator::getService()便建立了和 allocator hal 的遠端連線。

 

3.2 mapper HAL interface 的定義

mapper HAL 的定義稍稍複雜一些,原始碼位於/hardware/interfaces/graphics/mapper/4.0/

types.hal 中定義了 Error values 和 BufferDescriptor,比較簡單

IMapper.hal 中定義了介面 IMapper還有一些struct等型別,具體的內容建議直接閱讀原始碼的英文註釋,很詳細。在此我們只列舉幾個主要的相關方法。

[ /hardware/interfaces/graphics/mapper/4.0/IMapper.hal]

interface IMapper {
    // BufferDescriptorInfo用於描述圖形buffer的屬性(寬、高、格式...)
    struct BufferDescriptorInfo {
        string name; // buffer的名字,用於debugging/tracing
        uint32_t width; // width說明了分配的buffer中有多少個畫素列,但它並不表示相鄰行的同一列元素的偏移量,區別stride。
        uint32_t height; // height說明了分配的buffer中有多少畫素行
        uint32_t layerCount; // 分配的緩衝區中的影像層數
        PixelFormat format; // 畫素格式 (參見/frameworks/native/libs/ui/include/ui/PixelFormat.h中的定義)
        bitfield<BufferUsage> usage;buffer使用方式的標誌位(參見/frameworks/native/libs/ui/include/ui/GraphicBuffer.h的定義)。
        uint64_t reservedSize; // 與緩衝區關聯的保留區域的大小(位元組)。
    };
    /**
     * 建立一個 buffer descriptor,這個descriptor可以用於IAllocator分配buffer
     * 主要完成兩個工作:
     * 1. 檢查引數的合法性(裝置是否支援);
     * 2. 把BufferDescriptorInfo這個結構體變數進行重新的包裝,本質就是轉化為byte stream,這樣可以傳遞給IAllocator
     */
    createDescriptor(BufferDescriptorInfo description)  
            generates (Error error,
                       BufferDescriptor descriptor);
                       
    /**
     * 把raw buffer handle轉為imported buffer handle,這樣就可以在呼叫程式中使用了
     * 當其他程式分配的GraphicBuffer傳遞到當前程式後,需要通過該方法對映到當前程式,為後續的lock做好準備
     */
    importBuffer(handle rawHandle) generates (Error error, pointer buffer);
    
    /**
     * importBuffer()返回的buffer handle不再使用後必須呼叫freeBuffer()釋放
     */
    freeBuffer(pointer buffer) generates (Error error);

    /**
     * 已指定的CPU usage 鎖定緩衝區的指定區域accessRegion。lock之後就可以對buffer進行讀寫了
     */
    lock(pointer buffer,
         uint64_t cpuUsage,
         Rect accessRegion,
         handle acquireFence)
            generates (Error error,
                       pointer data);
    /**
     * 解鎖緩衝區以指示對緩衝區的所有CPU訪問都已完成
     */
    unlock(pointer buffer) generates (Error error, handle releaseFence);
    
    /**
     * 根據給定的MetadataType獲取對應的buffer metadata 
     */
    get(pointer buffer, MetadataType metadataType)
            generates (Error error,
                       vec<uint8_t> metadata);
 
    /**
     * 設定給定的MetadataType對應的buffer metadata 
     */
    set(pointer buffer, MetadataType metadataType, vec<uint8_t> metadata)
            generates (Error error);
};

IMapper 我們可以理解為完成了 buffer handle 所指向的圖形快取到執行程式的對映。訪問 buffer 資料一般遵循這樣的流程:

importBuffer -> lock -> 讀寫GraphicBuffer-> unlock -> freeBuffer

Mapper 實現為一個 Passthrough HAL Service(直通式HAL), 執行在呼叫它的程式中。本質上 Mode of HIDL in which the server is a shared library, dlopened by the client. In passthrough mode, client and server are the same process but separate codebases. 

Android 框架層libui庫中的Gralloc4Mapper 就是作為一個代理並對其功能的封裝:

Gralloc4Mapper::Gralloc4Mapper() {
    mMapper = IMapper::getService();
    ...
}

其中成員sp<hardware::graphics::mapper::V4_0::IMapper> mMapper;通過呼叫 IMapper::getService()便建立了和 mapper hal 的連線。

 


對於 Passthrough mode下,IMapper::getService()服務建立的過程,下篇文中有分析,可以參考。為什麼 passthrough mode 下,client and server are the same process 執行在同一個程式中?

下一篇文章中我有做一點簡單的探索,感興趣的可以參考:Android 12(S) 圖形顯示系統 - 簡述Allocator/Mapper HAL服務的獲取過程(十五)


 

3.3 Gralloc Allocator & Mapper HAL 的實現

Gralloc HAL 一般是由硬體廠商完成的,Android 原始碼中有高通的一些參考,不過貌似是一些舊的適配模式。比如  /hardware/qcom/display/msm8998/libgralloc1/

如果是使用的是 ARM Mali GPUs 可以參考官網的開原始碼 :Open Source Mali GPUs Android Gralloc Module

我學習中就參考了最新的一個版本 :BX304L01B-SW-99005-r36p0-01eac0.tar在它的原始碼目錄下 driver\product\android\gralloc\src\4.x就有 IAllocator 和 IMapper 的具體實現。這部分在此就不做分析了。

 

四、libui庫中的基本元件

libui 庫主要封裝了對 gralloc allocator /mapper HAL模組的呼叫,管理GraphicBuffer的分配釋放以及在不同程式間的對映,主要包含3個核心類,類圖如下所示: 

1. GraphicBuffer:對應gralloc分配的圖形Buffer(也可能是普通記憶體,具體要看gralloc實現),它繼承ANativeWindowBuffer結構體,核心成員是指向圖形快取的控制程式碼(native_handle_t * handle),並且圖形Buffer本身是多程式共享的,跨程式傳輸的是GraphicBuffer的關鍵屬性,這樣在使用程式可以重建GraphicBuffer,同時指向同一塊圖形Buffer。

2. GraphicBufferAllocator:向下對接gralloc allocator HAL服務,是程式內單例,負責分配程式間共享的圖形Buffer,對外即GraphicBuffer

3. GraphicBufferMapper:向下對接gralloc mapper HAL服務,是程式內單例,負責把GraphicBufferAllocator分配的GraphicBuffer對映到當前程式空間。

 

五、建立 CraphicBuffer 的基本流程

5.1 生產者請求去建立 GraphicBuffer

在之前講解 BufferQueue 的工作流程 Android 12(S) 圖形顯示系統 - BufferQueue的工作流程(九)這篇文章中,應用程式作為生產者,在準備繪製影像時,呼叫 dequeuBuffer 函式向 BufferQueue 請求一塊可用的圖形快取 GraphicBuffer。

在 dequeuBuffer 函式中,如果找到的 BufferSlot 沒有繫結 GraphicBuffer 或者已繫結的 GraphicBuffer 的屬性與我們需要的不一致,設定標誌 BUFFER_NEEDS_REALLOCATION, 這個時候就要去分配圖形快取了,即需要去建立 GraphicBuffer 物件了。

可以先看看 dequeueBuffer 函式中程式碼:

[/frameworks/native/libs/gui/BufferQueueProducer.cpp]

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                            uint32_t width, uint32_t height, PixelFormat format,
                                            uint64_t usage, uint64_t* outBufferAge,
                                            FrameEventHistoryDelta* outTimestamps) {
    ...
    // 判斷需要去分配圖形快取,建立 GraphicBuffer 物件
    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
        BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                width, height, format, BQ_LAYER_COUNT, usage,
                {mConsumerName.string(), mConsumerName.size()});
        
    }
    ...
}

 

5.2 GraphicBuffer 的定義

[/frameworks/native/libs/ui/include/ui/GraphicBuffer.h]

class GraphicBuffer
    : public ANativeObjectBase<ANativeWindowBuffer, GraphicBuffer, RefBase>,
      public Flattenable<GraphicBuffer>
{
    ...
}

ANativeObjectBase 繼承了 ANativeWindowBuffer 和 RefBase,GraphicBuffer 繼承了 ANativeObjectBase和Flattenable,這樣可以達到:

1. 繼承了 RefBase,使 GraphicBuffer 支援引用計數;

2. 繼承了 Flattenable,使 GraphicBuffer 支援序列化,可以透過 Binder IPC 程式間傳遞;

還有重要的一點,其父類 ANativeWindowBuffer 中儲存有各種屬性資訊,摘取基本資訊如下:

[/frameworks/native/libs/nativebase/include/nativebase/nativebase.h]

// 圖形Buffer的Size = stride * height * 每畫素位元組數
typedef struct ANativeWindowBuffer
{   
    ...
    int width;      // 圖形Buffer的寬度
    int height;     // 圖形Buffer的高度
    int stride;     // 圖形Buffer的步長,為了處理對齊問題,與width可能不同
    int format;     // 圖形Buffer的畫素格式
    const native_handle_t* handle; // 指向一塊圖形Buffer
    uint64_t usage; // 圖形Buffer的使用規則(gralloc會分配不同屬性的圖形Buffer)
    ...

} ANativeWindowBuffer_t;

 

5.3 GraphicBuffer 的建構函式

 GraphicBuffer有好幾個,我們僅列出上述過程中涉及到的,原始碼如下:

[/frameworks/native/libs/ui/GraphicBuffer.cpp]

GraphicBuffer::GraphicBuffer()
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), // 注意這裡去初始化了 mBufferMapper
      mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0)
{
    width  =
    height =
    stride =
    format =
    usage_deprecated = 0;
    usage  = 0;
    layerCount = 0;
    handle = nullptr;
}

GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
                             uint32_t inLayerCount, uint64_t inUsage, std::string requestorName)
      : GraphicBuffer() {
    // 呼叫 initWithSize 去完成記憶體分配
    mInitCheck = initWithSize(inWidth, inHeight, inFormat, inLayerCount, inUsage,
                              std::move(requestorName));
}

GraphicBuffer 的建構函式非常簡單, 它只是呼叫了一個初始化函式 initWithSize,還有一點要注意就是去呼叫 GraphicBufferMapper::get() 初始化了 mBufferMapper。

GraphicBufferMapper 是一個單例模式的類,每個程式中只有一個例項,它向下對接 gralloc-mapper hal 的功能,負責把GraphicBufferAllocator分配的GraphicBuffer對映到當前程式空間。先簡單看一下 GraphicBufferMapper 建構函式:

[/frameworks/native/libs/ui/GraphicBufferMapper.cpp]

GraphicBufferMapper::GraphicBufferMapper() {
    // 按照版本由高到低的順序載入 gralloc mapper, 成功則記錄版本,然後退出
    mMapper = std::make_unique<const Gralloc4Mapper>(); // 建立 Gralloc4Mapper 物件
    if (mMapper->isLoaded()) {
        mMapperVersion = Version::GRALLOC_4; // 載入成功 設定 mapper 的版本
        return;
    }
    ...

    LOG_ALWAYS_FATAL("gralloc-mapper is missing");
}

建立 GraphicBufferMapper 物件時,其建構函式中會去建立GrallocMapper物件,系統中會有不同版本的 grolloc-mapper,優先使用高版本,所以建立 Gralloc4Mapper 物件,再看 Gralloc4Mapper 的建構函式:

[/frameworks/native/libs/ui/Gralloc4.cpp]

Gralloc4Mapper::Gralloc4Mapper() {
    mMapper = IMapper::getService(); // 去獲取 gralloc mapper 服務,
    ...
}

Gralloc4Mapper 的建構函式中去獲取 gralloc-mapper hal service,這是一個 passthrough hal service,具體的資訊我們等之後在講解。

這裡我們先暫時理解為:透過 GraphicBufferMapper  & Gralloc4Mapper 我們就可以使用 gralloc-mapper hal 的功能了。

 

5.4 GraphicBuffer::initWithSize

這個函式是核心功能的實現的地方,

[ /frameworks/native/libs/ui/GraphicBuffer.cpp]

status_t GraphicBuffer::initWithSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage,
        std::string requestorName)
{
    // 獲取 GraphicBufferAllocator 物件,程式中單例
    // GraphicBufferAllocator負責分配程式間共享的圖形Buffer
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get(); 
    uint32_t outStride = 0;
    // 分配一塊指定寬高的 GraphicBuffer
    status_t err = allocator.allocate(inWidth, inHeight, inFormat, inLayerCount,
            inUsage, &handle, &outStride, mId,
            std::move(requestorName));
    if (err == NO_ERROR) {
        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);

        width = static_cast<int>(inWidth);
        height = static_cast<int>(inHeight);
        format = inFormat;
        layerCount = inLayerCount;
        usage = inUsage;
        usage_deprecated = int(usage);
        stride = static_cast<int>(outStride);
    }
    return err;
}

申請成功的圖形Buffer的屬性會儲存在GraphicBuffer父類ANativeWindowBuffer對應欄位中:width/height/stride/format/usage/handle 。

圖形buffer的分配,最終是通過 GraphicBufferAllocator 完成的,GraphicBufferAllocator 向下對接 gralloc-allocator hal 的功能。它是程式內單例,負責分配程式間共享的圖形Buffer,對外即GraphicBuffer。簡單看看其建構函式:

[/frameworks/native/libs/ui/GraphicBufferAllocator.cpp]

GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) {
    // 按照版本由高到低的順序載入 gralloc allocator, 成功則退出
    mAllocator = std::make_unique<const Gralloc4Allocator>(
            reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper())); // 建立 Gralloc4Allocator 
    ...
}

建構函式中會去建立一個 Gralloc4Allocator 物件,並且傳遞一個 Gralloc4Mapper 物件作為引數:

[/frameworks/native/libs/ui/Gralloc4.cpp]

Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(mapper) {
    mAllocator = IAllocator::getService(); // 獲取 gralloc allocator 服務
    if (mAllocator == nullptr) {
        ALOGW("allocator 3.x is not supported");
        return;
    }
}

Gralloc4Allocator 的建構函式中去獲取 gralloc-allocator hal service,這是一個 binderized hal service,具體的資訊我們等之後在講解。

這裡我們先暫時理解為:透過 GraphicBufferAllocator & Gralloc4Allocator 我們就可以使用 gralloc-alloctor hal 的功能了。

 

申請圖形Buffer時,除了寬、高、畫素格式,還有一個usage引數,它表示申請方使用GraphicBuffer的行為,gralloc可以根據usage做對應優化,如下部分的 usage 列舉值:

[/frameworks/native/libs/ui/include/ui/GraphicBuffer.h]
[/hardware/libhardware/include/hardware/gralloc.h]

enum {
    USAGE_SW_READ_NEVER     = GRALLOC_USAGE_SW_READ_NEVER, // CPU不會讀GraphicBuffer
    USAGE_SW_READ_RARELY    = GRALLOC_USAGE_SW_READ_RARELY, // CPU很少讀GraphicBuffer
    USAGE_SW_READ_OFTEN     = GRALLOC_USAGE_SW_READ_OFTEN, // CPU經常讀GraphicBuffer
    USAGE_SW_READ_MASK      = GRALLOC_USAGE_SW_READ_MASK,

    USAGE_SW_WRITE_NEVER    = GRALLOC_USAGE_SW_WRITE_NEVER, // CPU不會寫GraphicBuffer
    USAGE_SW_WRITE_RARELY   = GRALLOC_USAGE_SW_WRITE_RARELY, // CPU很少寫GraphicBuffer
    USAGE_SW_WRITE_OFTEN    = GRALLOC_USAGE_SW_WRITE_OFTEN, // CPU經常寫GraphicBuffer
    USAGE_SW_WRITE_MASK     = GRALLOC_USAGE_SW_WRITE_MASK,

    USAGE_SOFTWARE_MASK     = USAGE_SW_READ_MASK|USAGE_SW_WRITE_MASK,

    USAGE_PROTECTED         = GRALLOC_USAGE_PROTECTED,

    USAGE_HW_TEXTURE        = GRALLOC_USAGE_HW_TEXTURE, // GraphicBuffer可以被上傳為OpenGL ES texture,相當於GPU讀GraphicBuffer
    USAGE_HW_RENDER         = GRALLOC_USAGE_HW_RENDER, // GraphicBuffer可以被當做OpenGL ES的渲染目標,相當於GPU寫GraphicBuffer
    USAGE_HW_2D             = GRALLOC_USAGE_HW_2D, // GraphicBuffer will be used by the 2D hardware blitter
    USAGE_HW_COMPOSER       = GRALLOC_USAGE_HW_COMPOSER, // HWC可以直接使用GraphicBuffer進行合成
    USAGE_HW_VIDEO_ENCODER  = GRALLOC_USAGE_HW_VIDEO_ENCODER, // GraphicBuffer可以作為Video硬編碼器的輸入物件
    USAGE_HW_MASK           = GRALLOC_USAGE_HW_MASK,

    USAGE_CURSOR            = GRALLOC_USAGE_CURSOR, // buffer may be used as a cursor
};

圖形Buffer的申請方可以根據場景,使用不同的usage組合。

 

5.5 GraphicBufferAllocator::allocate

GraphicBufferAllocator 它是一個單例,外部使用時可以通過它來為 GraphicBuffer 來分配記憶體,Android 系統是為了遮蔽不同硬體平臺的差異性,所以在framework 層使用它來為外部提供一個統一的介面。allocate 方法就是對外提供的分配記憶體的介面。

[/frameworks/native/libs/ui/GraphicBufferAllocator.cpp]

status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
                                          uint32_t layerCount, uint64_t usage,
                                          buffer_handle_t* handle, uint32_t* stride,
                                          uint64_t /*graphicBufferId*/, std::string requestorName) {
    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
                          true);
}

繼續呼叫到 allocateHelper

status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
                                                uint32_t layerCount, uint64_t usage,
                                                buffer_handle_t* handle, uint32_t* stride,
                                                std::string requestorName, bool importBuffer) {

    // 前面一些程式碼都是在判斷 width/height/format/layerCount 等資訊是否合法及預處理

    // 分配記憶體,呼叫 Gralloc4Allocator::allocate 去分配指定 width/height/format..的圖形buffer
    // 完成後handle就指向了這塊圖形快取
    status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
                                          1, stride, handle, importBuffer);
    ...

    if (!importBuffer) { // 如果不需要 註冊buffer則返回,我們講的流程 importBuffer = true
        return NO_ERROR;
    }
    size_t bufSize;

    // 計算 buffer size
    if ((*stride) != 0 &&
        std::numeric_limits<size_t>::max() / height / (*stride) < static_cast<size_t>(bpp)) {
        bufSize = static_cast<size_t>(width) * height * bpp;
    } else {
        bufSize = static_cast<size_t>((*stride)) * height * bpp;
    }

    Mutex::Autolock _l(sLock);
    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
    alloc_rec_t rec;
    rec.width = width;
    rec.height = height;
    rec.stride = *stride;
    rec.format = format;
    rec.layerCount = layerCount;
    rec.usage = usage;
    rec.size = bufSize;
    rec.requestorName = std::move(requestorName);
    list.add(*handle, rec); // 把這塊buffer的資訊記錄下來,放到sAllocList中,dumpsys SurfaceFlinger可以看到所有已分配的buffer的資訊

    return NO_ERROR;
}

GraphicBufferAllocator::allocateHelper 的還是要繼續呼叫 Gralloc4Allocator::allocate 去完成圖形儲存空間的分配,另一個就是它會把分配好的buffer的資訊記錄下來,放到 sAllocList 中,sAllocList 並不是儲存具體的Buffer,而是Buffer的屬性資訊 alloc_rec_t。這樣 dumpsys SurfaceFlinger可以看到所有已分配的buffer的資訊。

比如下面我執行 dumpsys SurfaceFlinger 擷取的部分資訊,可以看到每一個 GraphicBuffer 的資訊,包括 width/height/stride/usage/format/size

GraphicBufferAllocator buffers:
    Handle |        Size |     W (Stride) x H | Layers |   Format |      Usage | Requestor
0xf2fc1060 | 8100.00 KiB | 1920 (1920) x 1080 |      1 |        1 | 0x    1b00 | FramebufferSurface
0xf2fc29c0 | 8100.00 KiB | 1920 (1920) x 1080 |      1 |        1 | 0x    1b00 | FramebufferSurface
0xf2fc32d0 | 8100.00 KiB | 1920 (1920) x 1080 |      1 |        1 | 0x    1b00 | FramebufferSurface
Total allocated by GraphicBufferAllocator (estimate): 24300.00 KB

 

 

5.6 Gralloc4Allocator::allocate

GrallocAllocator 有多個實現版本,優先使用高版本 Gralloc4Allocator

[/frameworks/native/libs/ui/Gralloc4.cpp]


status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                     android::PixelFormat format, uint32_t layerCount,
                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
    // 構建descriptorInfo物件,用於儲存待建立的圖形buffer的屬性
    IMapper::BufferDescriptorInfo descriptorInfo;
    sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, &descriptorInfo);
    // 建立 BufferDescriptor 物件
    // createDescriptor主要完成兩個工作:1. 檢查引數的合法性(裝置是否支援);
    //                                   2. 把BufferDescriptorInfo這個結構體變數進行重新的包裝,
    //                                      本質就是轉化為byte stream,IPC時傳遞給IAllocator
    BufferDescriptor descriptor;
    status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
                                              static_cast<void*>(&descriptor));
    if (error != NO_ERROR) {
        return error;
    }
    // 呼叫 gralloc allocator hal service 去分配圖形快取
    auto ret = mAllocator->allocate(descriptor, bufferCount,
                                    [&](const auto& tmpError, const auto& tmpStride,
                                        const auto& tmpBuffers) {
                                        error = static_cast<status_t>(tmpError);
                                        if (tmpError != Error::NONE) {
                                            return;
                                        }

                                        if (importBuffers) {
                                            for (uint32_t i = 0; i < bufferCount; i++) {
                                                // importBuffer
                                                error = mMapper.importBuffer(tmpBuffers[i],
                                                                             &outBufferHandles[i]);
                                                if (error != NO_ERROR) {
                                                    for (uint32_t j = 0; j < i; j++) {
                                                        mMapper.freeBuffer(outBufferHandles[j]);
                                                        outBufferHandles[j] = nullptr;
                                                    }
                                                    return;
                                                }
                                            }
                                        } else {
                                            for (uint32_t i = 0; i < bufferCount; i++) {
                                                outBufferHandles[i] = native_handle_clone(
                                                        tmpBuffers[i].getNativeHandle());
                                                if (!outBufferHandles[i]) {
                                                    for (uint32_t j = 0; j < i; j++) {
                                                        auto buffer = const_cast<native_handle_t*>(
                                                                outBufferHandles[j]);
                                                        native_handle_close(buffer);
                                                        native_handle_delete(buffer);
                                                        outBufferHandles[j] = nullptr;
                                                    }
                                                }
                                            }
                                        }
                                        *outStride = tmpStride;
                                    });

    // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now
    hardware::IPCThreadState::self()->flushCommands();

    return (ret.isOk()) ? error : static_cast<status_t>(kTransactionError);
}

Gralloc4Allocator::allocate 中呼叫 gralloc-allocator hal service 完成最終的圖形快取分配,HAL 層呼叫使用的通訊方式就是 HIDL,其實和我們使用的 AIDL 是類似的原理,最終得到指向圖形buffer的  buffer_handle_t

本文作者@二的次方  2022-03-28 釋出於部落格園

ANativeWindowBuffer.handle是GraphicBuffer的核心資料成員,其型別native_handle_t的定義如下,其中還有定義 create, close, init, delete, clone等操作方法。
[ /system/core/libcutils/include/cutils/native_handle.h]

typedef struct native_handle
{
    int version;        /* sizeof(native_handle_t) */
    int numFds;         /* number of file-descriptors at &data[0] */
    int numInts;        /* number of ints at &data[numFds] */
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wzero-length-array"
#endif
    int data[0];        /* numFds + numInts ints */
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} native_handle_t;

typedef const native_handle_t* buffer_handle_t;

int native_handle_close(const native_handle_t* h);
native_handle_t* native_handle_init(char* storage, int numFds, int numInts);
native_handle_t* native_handle_create(int numFds, int numInts);
native_handle_t* native_handle_clone(const native_handle_t* handle);
int native_handle_delete(native_handle_t* h);

 

5.7 基本流程圖

我們把上面程式碼描述的過程,簡單總結為下面的圖示。

六、GraphicBuffer的跨程式共享

在圖形系統中,生產者和最終的消費者往往不在同一個程式中,所以 GraphicBuffer 需要跨程式傳遞,以實現資料共享。我們先用一張流程圖來概況:

 

1. 首先,生產程式通過GraphicBuffer::flattenANativeWindowBuffer關鍵屬性儲存在兩個陣列中:bufferfds,其實就是 Binder 資料傳輸前的序列化處理;

2. 其次,跨程式傳輸bufferfds,這裡一般就是 Binder IPC 跨程式通訊;

3. 然後,消費程式通過GraphicBuffer::unflatten在自己的程式中重建ANativeWindowBuffer,關鍵是重建ANativeWindowBuffer.handle這個結構體成員,相當於把生產程式的GraphicBuffer對映到了消費程式;

4. 最後,遵循 importBuffer->lock->讀寫GraphicBuffer->unlock->freeBuffer 的基本流程操作GraphicBuffer


 
Binder只是傳輸ANativeWindowBuffer屬性(width,height,stride...),真正的底層圖形視訊記憶體(記憶體)是程式間共享的。 從上下文可以看出,GraphicBufferAllocator負責在生產程式申請和釋放GraphicBufferGraphicBufferMapper負責在消費程式操作GraphicBuffer
 

 
GraphicBufferMapperGraphicBuffer的所有操作最後都是通過 gralloc mapper HAL 模組實現的。感興趣的的可以去參考原始碼中 IMapper的定義:/hardware/interfaces/graphics/mapper/4.0/IMapper.hal,還有一些廠商的實現  Open Source Mali GPUs Android Gralloc Module

根據上面的流程,我們看看一些關鍵的程式碼:

[/frameworks/native/libs/ui/GraphicBuffer.cpp]

// 計算傳輸GraphicBuffer需要的Size,位元組數
size_t GraphicBuffer::getFlattenedSize() const {
    return static_cast<size_t>(13 + (handle ? mTransportNumInts : 0)) * sizeof(int);
}
// 獲取native_handle_t中需要傳輸的檔案描述符數量
size_t GraphicBuffer::getFdCount() const {
    return static_cast<size_t>(handle ? mTransportNumFds : 0);
}
// 把GraphicBuffer關鍵屬性儲存在buffer和fds中,以進行Binder傳輸
// size表示buffer陣列可用長度,count表示fds陣列可用長度
status_t GraphicBuffer::flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const {
    size_t sizeNeeded = GraphicBuffer::getFlattenedSize();
    if (size < sizeNeeded) return NO_MEMORY; // 判斷buffer可用長度是否足夠

    size_t fdCountNeeded = GraphicBuffer::getFdCount();
    if (count < fdCountNeeded) return NO_MEMORY;  //判斷fds陣列長度是否足夠

    // 把當前GraphicBuffer的關鍵屬性儲存在buffer中
    int32_t* buf = static_cast<int32_t*>(buffer);
    buf[0] = 'GB01';
    buf[1] = width;
    buf[2] = height;
    buf[3] = stride;
    buf[4] = format;
    buf[5] = static_cast<int32_t>(layerCount);
    buf[6] = int(usage); // low 32-bits
    buf[7] = static_cast<int32_t>(mId >> 32);
    buf[8] = static_cast<int32_t>(mId & 0xFFFFFFFFull);
    buf[9] = static_cast<int32_t>(mGenerationNumber);
    buf[10] = 0;
    buf[11] = 0;
    buf[12] = int(usage >> 32); // high 32-bits

    if (handle) {
        buf[10] = int32_t(mTransportNumFds); // 儲存傳遞的檔案描述符數量
        buf[11] = int32_t(mTransportNumInts); // 儲存傳遞的int資料的數量
        // copy檔案描述符陣列到fds
        memcpy(fds, handle->data, static_cast<size_t>(mTransportNumFds) * sizeof(int));
        // copy int陣列到buffer
        memcpy(buf + 13, handle->data + handle->numFds,
               static_cast<size_t>(mTransportNumInts) * sizeof(int));
    }
    // 修改buffer地址和可用長度
    buffer = static_cast<void*>(static_cast<uint8_t*>(buffer) + sizeNeeded);
    size -= sizeNeeded;
    if (handle) {
        fds += mTransportNumFds; // 修改fds地址和可用長度
        count -= static_cast<size_t>(mTransportNumFds);
    }
    return NO_ERROR;
}
// 根據Binder傳輸的buffer和fds中,把建立程式的GraphicBuffer對映到使用程式
status_t GraphicBuffer::unflatten(void const*& buffer, size_t& size, int const*& fds,
                                  size_t& count) {
    // Check if size is not smaller than buf[0] is supposed to take.
    if (size < sizeof(int)) {
        return NO_MEMORY; 
    }

    int const* buf = static_cast<int const*>(buffer);

    // NOTE: it turns out that some media code generates a flattened GraphicBuffer manually!!!!!
    // see H2BGraphicBufferProducer.cpp
    uint32_t flattenWordCount = 0;
    if (buf[0] == 'GB01') {
        // new version with 64-bits usage bits
        flattenWordCount = 13;
    } else if (buf[0] == 'GBFR') {
        // old version, when usage bits were 32-bits
        flattenWordCount = 12;
    } else {
        return BAD_TYPE;
    }
    // 判斷buffer的正確性
    if (size < 12 * sizeof(int)) {
        android_errorWriteLog(0x534e4554, "114223584");
        return NO_MEMORY;
    }
    // 取出檔案描述符和int陣列的數量
    const size_t numFds  = static_cast<size_t>(buf[10]);
    const size_t numInts = static_cast<size_t>(buf[11]);

    // Limit the maxNumber to be relatively small. The number of fds or ints
    // should not come close to this number, and the number itself was simply
    // chosen to be high enough to not cause issues and low enough to prevent
    // overflow problems.
    const size_t maxNumber = 4096;
    if (numFds >= maxNumber || numInts >= (maxNumber - flattenWordCount)) {
        width = height = stride = format = usage_deprecated = 0;
        layerCount = 0;
        usage = 0;
        handle = nullptr;
        ALOGE("unflatten: numFds or numInts is too large: %zd, %zd", numFds, numInts);
        return BAD_VALUE;
    }

    const size_t sizeNeeded = (flattenWordCount + numInts) * sizeof(int);
    if (size < sizeNeeded) return NO_MEMORY; // 判斷buffer長度是否正確

    size_t fdCountNeeded = numFds;
    if (count < fdCountNeeded) return NO_MEMORY; // 判斷fds長度是否正確

    if (handle) {
        // free previous handle if any
        free_handle(); // 如果有,先釋放之前的ANativeWindowBuffer.handle
    }

    if (numFds || numInts) {
        width  = buf[1];
        height = buf[2];
        stride = buf[3];
        format = buf[4];
        layerCount = static_cast<uintptr_t>(buf[5]);
        usage_deprecated = buf[6];
        if (flattenWordCount == 13) {
            usage = (uint64_t(buf[12]) << 32) | uint32_t(buf[6]);
        } else {
            usage = uint64_t(usage_deprecated);
        }
        native_handle* h = // 建立ANativeWindowBuffer.handle,native_handle_create定義在native_handle.c
                native_handle_create(static_cast<int>(numFds), static_cast<int>(numInts));
        if (!h) {
            width = height = stride = format = usage_deprecated = 0;
            layerCount = 0;
            usage = 0;
            handle = nullptr;
            ALOGE("unflatten: native_handle_create failed");
            return NO_MEMORY;
        }
        // 從fds和buffer中copy檔案描述符和int陣列到ANativeWindowBuffer.handle結構體
        memcpy(h->data, fds, numFds * sizeof(int));
        memcpy(h->data + numFds, buf + flattenWordCount, numInts * sizeof(int));
        handle = h;
    } else {
        width = height = stride = format = usage_deprecated = 0;
        layerCount = 0;
        usage = 0;
        handle = nullptr;
    }
    // 從buffer中恢復其他欄位
    mId = static_cast<uint64_t>(buf[7]) << 32;
    mId |= static_cast<uint32_t>(buf[8]);

    mGenerationNumber = static_cast<uint32_t>(buf[9]);
    // 表示GraphicBuffer是從其他程式對映過來的,決定了釋放GraphicBuffer的邏輯
    mOwner = ownHandle;

    if (handle != nullptr) {
        // 呼叫importBuffer,把GraphicBuffer對映到當前程式
        buffer_handle_t importedHandle;
        status_t err = mBufferMapper.importBuffer(handle, uint32_t(width), uint32_t(height),
                uint32_t(layerCount), format, usage, uint32_t(stride), &importedHandle);
        if (err != NO_ERROR) {
            width = height = stride = format = usage_deprecated = 0;
            layerCount = 0;
            usage = 0;
            handle = nullptr;
            ALOGE("unflatten: registerBuffer failed: %s (%d)", strerror(-err), err);
            return err;
        }
        // 關閉釋放對映前的handle
        native_handle_close(handle);
        native_handle_delete(const_cast<native_handle_t*>(handle));
        // 賦值為importedHandle,這個handle即可以在當前程式使用了
        handle = importedHandle;
        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
    }
    // 調整buffer和fds陣列的地址和可用長度
    buffer = static_cast<void const*>(static_cast<uint8_t const*>(buffer) + sizeNeeded);
    size -= sizeNeeded;
    fds += numFds;
    count -= numFds;
    return NO_ERROR;
}

 

七、GraphicBuffer的釋放

前面講了 GraphicBuffer 的建立,那它是怎樣被釋放的呢?先看看解構函式的定義:

[/frameworks/native/libs/ui/GraphicBuffer.cpp]

GraphicBuffer::~GraphicBuffer()
{
    ATRACE_CALL();
    if (handle) {
        free_handle(); // handle不是null,需要free
    }
    for (auto& [callback, context] : mDeathCallbacks) {
        callback(context, mId);
    }
}

void GraphicBuffer::free_handle()
{
    if (mOwner == ownHandle) {
        // 僅僅持有handle控制程式碼, 表示圖形Buffer不是自己建立的,
        // 而是從生產程式對映過來的,即當前處於消費程式
        mBufferMapper.freeBuffer(handle);
    } else if (mOwner == ownData) {
        // 擁有資料,表示圖形Buffer是自己建立的,需要自己釋放,即處於生產程式
        GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
        allocator.free(handle);
    }
    handle = nullptr;
}

指向同一塊圖形Buffer的GraphicBuffer可以存在多個例項,但是底層的圖形Buffer是同一個。講到這裡突然有個疑問:生產程式和消費程式是如何做到同步的?即會不會出現生產程式把GraphicBuffer釋放掉了,而消費程式還在訪問這個GraphicBuffer的狀況?

 

八、小結

本篇文章分析了GraphicBuffer相關的元件、概念及建立釋放的流程。包括 Gralloc HAL模組,以及libui庫中的主要元件的邏輯。文中觀點也是本人邊學習,邊輸出的,難免有錯誤之處。部分內容的細節也沒有深入刨析,後續在學習中會再陸續補充。

下一篇文中中會講講 Binderized HAL 和 Passthrough HAL 的一點知識,加深對 IMapper 和 IAllocator 模組的理解。

 

相關文章