Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc

写在前面

在 Android 显示框架中,GraphicBuffer 是一个重要的基础概念,它代表了一块内存,可以被 GPU/CPU 读写。无论是 App UI 的绘制,还是 SurfaceFlinger 的合成,本质上都是对 GraphicBuffer 的读写

GraphicBuffer 的使用场景

App UI 绘制

当 App 需要进行 UI 绘制的时候,需要通过 SurfaceIGraphicBufferProducer (IGBP)BLASTBufferQueue 中申请一块 GraphicBuffer,如果 BLASTBufferQueue 中没有可用的内存,那么就会通过 Gralloc 分配一块 Buffer, 然后把 App 把 UI 绘制到这一块 GraphicBuffer 中,再提交给 SurfaceFlinger 去做进一步合成。

SurfaceFlinger 合成

SurfaceFlinger 会从每一个 Layer(图层) 获取到最新的 GraphicBuffer,然后通过硬件或者 GPU 进行合成,如果 SurfaceFlinger 是使用 GPU 合成,那么合成的结果也是一块新的 GraphicBuffer

所以,GraphicBuffer 在 Android 显示框架中是图像帧的通用载体,无论是渲染还是合成都离不开 GraphicBuffer 这个最基础的载体。

GraphicBuffer 定义

kotlin 复制代码
// frameworks/native/libs/ui/include/ui/GraphicBuffer.h
class GraphicBuffer
    : public ANativeObjectBase<ANativeWindowBuffer, GraphicBuffer, RefBase>,
      public Flattenable<GraphicBuffer>
{
    ...
}

GraphicBuffer 继承于 ANativeObjectBaseFlattenable

Flattenable :继承于 Flattenable 支持 GraphicBuffer 序列化,所以可以使用 Binder 跨进程传递 GraphicBuffer

ANativeObjectBaseANativeObjectBase 是一个模版类,ANativeObjectBase 又会继承于 ANativeWindowBuffer, 在 ANativeWindowBuffer 中有一个成员变量 native_handle_t

ini 复制代码
// external/swiftshader/include/Android/nativebase/nativebase.h
struct ANativeWindowBuffer {
    ANativeWindowBuffer() {
        common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
        common.version = sizeof(ANativeWindowBuffer);
        memset(common.reserved, 0, sizeof(common.reserved));
    }
    ...
    const native_handle_t* handle;
};

native_handle_t 是 Android 图形系统底层 Buffer 内存的真实"句柄", 而这个一块 Buffer 是由 Gralloc 分配。

所以,从 GraphicBuffer 源码的定义来看,有两点需要了解:

  1. 继承了 Flattenable,所以 GraphicBuffer 支持跨进程
  2. 持有 native_handle_t,所以 GraphicBuffer 间接持有一块底层真实的 Buffer

GraphicBuffer 的创建和初始化

最典型,GraphicBuffer 的创建时机发生在:当一个 App 需要绘制 UI 的时候,会通过 SurfaceBLASTBufferQueue 中申请一块内存

arduino 复制代码
// 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) {
                                            
   ...  
   sp<GraphicBuffer> graphicBuffer =
        sp<GraphicBuffer>::make(width, height, format, BQ_LAYER_COUNT, usage, std::string{mConsumerName.c_str(), mConsumerName.size()});
   ...
}

接下来调用 GraphicBuffer 的构造函数

c 复制代码
GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
                             uint32_t inLayerCount, uint64_t inUsage, std::string requestorName)
      : GraphicBuffer() { 
    mInitCheck = initWithSize(inWidth, inHeight, inFormat, inLayerCount, inUsage,
                              std::move(requestorName));
}
  1. 先执行 GraphicBuffer 无参构造函数
  2. 执行 initWithSize,从函数名字来看,可以猜出应该是通过 HAL 来真正分配一块 Buffer

GraphicBuffer 创建

ini 复制代码
GraphicBuffer::GraphicBuffer()
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
      mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0)
{
    width  =
    height =
    stride =
    format =
    usage_deprecated = 0;
    usage  = 0;
    layerCount = 0;
    handle = nullptr;
    mDependencyMonitor.setToken(std::to_string(mId));
}

在无参构造函数中,主要是初始化变量,其中包括 mBufferMapper = GraphicBufferMapper::get(), GraphicBufferMapper::get() 会创建一个 GraphicBufferMapper,构造函数如下

ini 复制代码
// frameworks/native/libs/ui/GraphicBufferMapper.cpp
GraphicBufferMapper::GraphicBufferMapper() {
    mMapper = std::make_unique<const Gralloc5Mapper>();
    if (mMapper->isLoaded()) {
        mMapperVersion = Version::GRALLOC_5;
        return;
    }
    mMapper = std::make_unique<const Gralloc4Mapper>();
    if (mMapper->isLoaded()) {
        mMapperVersion = Version::GRALLOC_4;
        return;
    }
    mMapper = std::make_unique<const Gralloc3Mapper>();
    if (mMapper->isLoaded()) {
        mMapperVersion = Version::GRALLOC_3;
        return;
    }
    mMapper = std::make_unique<const Gralloc2Mapper>();
    if (mMapper->isLoaded()) {
        mMapperVersion = Version::GRALLOC_2;
        return;
    }

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

这里会优先创建 Gralloc5Mapper, 构造函数如下

scss 复制代码
// frameworks/native/libs/ui/Gralloc5.cpp
Gralloc5Mapper::Gralloc5Mapper() {
    mMapper = getInstance().mapper;
}


static const Gralloc5 &getInstance() {
    static Gralloc5 instance = []() {
        // 拿到 IAllocator 的 Binder 句柄
        auto allocator = waitForAllocator();
        if (!allocator) {
            return Gralloc5{};
        }
        // 通过 dlopen 加载 Gralloc 相关的动态库
        void *so = loadIMapperLibrary();
        if (!so) {
            return Gralloc5{};
        }
        // 拿到 AIMapper_loadIMapperFn 这个函数指针
        auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
        AIMapper *mapper = nullptr;
        AIMapper_Error error = loadIMapper(&mapper);
        if (error != AIMAPPER_ERROR_NONE) {
            ALOGE("AIMapper_loadIMapper failed %d", error);
            return Gralloc5{};
        }
        return Gralloc5{std::move(allocator), mapper};
    }();
    return instance;
}

Gralloc5Mapper 在创建过程中,会创建两个非常重要的对象,allocatormapper

allocator

allocate 的创建在 waitForAllocator()

css 复制代码
static std::shared_ptr<IAllocator> waitForAllocator() {

        auto allocator = IAllocator::fromBinder(
                ndk::SpAIBinder(AServiceManager_waitForService(kIAllocatorServiceName.c_str())));
        ...
        return allocator;
   
    }
}

这里就很明显了,通过 AServiceManager_waitForService 拿到了名为 kIAllocatorServiceName 这个 Binder Service 的句柄,也就意味着 allocator 能通过这个句柄调用这个 Binder Service 的方法,具体有哪些方法,我们可以查看对应的 aidl 文件

java 复制代码
interface IAllocator {
 AllocationResult allocate(in byte[] descriptor, in int count);
 AllocationResult allocate2(in BufferDescriptorInfo descriptor, in int count);
 boolean isSupported(in BufferDescriptorInfo descriptor);
}

可以看到这个 aidl 提供了 allocateallocate2 等方法, 这些方法运行在 Binder Service 所在进程,并且具体的实现由手机厂商来提供。

mapper

首先通过 loadIMapperLibrary() 加载 .so 库,

ini 复制代码
static void *loadIMapperLibrary() {
    static void *imapperLibrary = []() -> void * {
        ...
        void* so = nullptr;
        if (__builtin_available(android __ANDROID_API_V__, *)) {
            so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
                                                            RTLD_LOCAL | RTLD_NOW);
        } else {
            std::string lib_name = "mapper." + mapperSuffix + ".so";
            so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
        }
        return so;
    }();
    return imapperLibrary;
}

这里设计非常巧妙,相比于 allocate 通过 Binder 实现跨进程通信,mapper 使用的是 SPHal(Same-Process HAL), 通过 dlopen 直接把 .so 加载到本进程来调用相关的方法,相比于通 Binder,SPHAL 的效率会更高。

加载了 mapper 相关的 so 之后,通过 (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper") 拿到 AIMapper_loadIMapperFn 这个函数指针,通过这个函数指针就可以拿到真正的"主角" AIMapper 接口,AIMapper 接口提供的方法如下:

arduino 复制代码
...
AIMapper_Error (*_Nonnull importBuffer)(const native_handle_t* _Nonnull handle,
                                            buffer_handle_t _Nullable* _Nonnull outBufferHandle);
   
AIMapper_Error (*_Nonnull freeBuffer)(buffer_handle_t _Nonnull buffer);
AIMapper_Error (*_Nonnull flushLockedBuffer)(buffer_handle_t _Nonnull buffer);
...

AIMapper 接口对外暴露了 importBuffer freeBuffer 等多个操作内存的方法,而这些方法的实现也是由厂商提供。

到这里为止,GraphicBuffer 的构造函数就执行完了,在构造函数中

  1. 初始化了 allocator 用于后续的内存分配 (通过 Binder方式)
  2. 初始化了 Mapper,用于后续对分配内存的操作 (通过 SP-HAL方式)

接下来继续分析 GraphicBuffer 的初始化

GraphicBuffer 初始化

在前面的源码中,GraphicBuffer 的无参构造函数执行之后,会调用 initWithSize执行初始化

ini 复制代码
status_t GraphicBuffer::initWithSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage,
        std::string requestorName)
{
    // 这个 allocator 就是上面一步创建的 allocator
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    uint32_t outStride = 0;
    // 通过 Binder Server 分配一块内存,并且返回这块内存的地址由 handle 持有
    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);
        mDependencyMonitor.setToken(requestorName.append(":").append(std::to_string(mId)));
    }
    return err;
}

了解了 GraphicBuffer 的创建之后,初始化的理解就很简单了,无非就是通过 Binder IPC 调用 Binder Server 来分配一块内存,并且这块内存的 fdGraphicBuffer 持有。

相关推荐
千里马学框架14 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台14 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone14 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc15 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo17 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
我爱cope17 小时前
【操作系统 | 计算机硬件:CPU、内存与 I/O 如何支撑操作系统?】
学习·操作系统
sun00770017 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼18 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone19 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen19 小时前
属性动画原理与高级动画实现
android·kotlin·canvas