乐趣区

Android图形系统系统篇之HWC

HWC 概述

HWC(hwcomposer)是 Android 中进行窗口(Layer)合成和显示的 HAL 层模块,其实现是特定于设备的,而且通常由显示设备制造商 (OEM)完成,为 SurfaceFlinger 服务提供硬件支持。

SurfaceFlinger可以使用 OpenGL ES 合成 Layer,这需要占用并消耗 GPU 资源。大多数 GPU 都没有针对图层合成进行优化,当SurfaceFlinger 通过 GPU 合成图层时,应用程序无法使用 GPU 进行自己的渲染。而 HWC 通过硬件设备进行图层合成,可以减轻 GPU 的合成压力。

显示设备的能力千差万别,很难直接用 API 表示硬件设备支持合成的 Layer 数量,Layer是否可以进行旋转和混合模式操作,以及对图层定位和硬件合成的限制等。因此 HWC 描述上述信息的流程是这样的:

  1. SurfaceFlingerHWC 提供所有 Layer 的完整列表,让 HWC 根据其硬件能力,决定如何处理这些Layer
  2. HWC会为每个 Layer 标注合成方式,是通过 GPU 还是通过 HWC 合成。
  3. SurfaceFlinger负责先把所有注明 GPU 合成的 Layer 合成到一个输出 Buffer,然后把这个输出 Buffer 和其他 Layer(注明 HWC 合成的 Layer)一起交给HWC,让HWC 完成剩余 Layer 的合成和显示。

虽然每个显示设备的能力不同,但是官方要求每个 HWC 硬件模块都应该支持以下能力:

  1. 至少支持 4 个叠加层:状态栏、系统栏、应用本身和壁纸或者背景。
  2. 叠加层可以大于显示屏,例如:壁纸
  3. 同时支持预乘每像素(per-pixel)Alpha 混合和每平面(per-plane)Alpha 混合。
  4. 为了支持受保护的内容,必须提供受保护视频播放的硬件路径。
  5. RGBA packing order, YUV formats, and tiling, swizzling, and stride properties

Tiling:可以把 Image 切割成 MxN 个小块,最后渲染时,再将这些小块拼接起来,就像铺瓷砖一样。
Swizzling:一种拌和技术,表示向量单元可以被任意地重排或重复。

但是并非所有情况下 HWC 都比 GPU 更高效,例如:当屏幕上没有任何变化时,尤其是叠加层有透明像素并且需要进行图层透明像素混合时。在这种情况下,HWC可以要求部分或者全部叠加层都进行 GPU 合成,然后 HWC 持有合成的结果 Buffer,如果 SurfaceFlinger 要求合成相同的叠加图层列表,HWC可以直接显示之前合成的结果 Buffer,这有助于提高待机设备的电池寿命。

HWC也提供了 VSync 事件,用于管理渲染和图层合成时机,后续文章会进行介绍。

HWC2 实现

Android7.0 提供了 HWC 和 HWC2 两个版本,默认使用 HWC,但是手机厂商也可以选择 HWC2,如下所示:

// frameworks\native\services\surfaceflinger\Android.mk
USE_HWC2 := false
ifeq ($(USE_HWC2),true)
    LOCAL_CFLAGS += -DUSE_HWC2
    LOCAL_SRC_FILES += \
        SurfaceFlinger.cpp \
        DisplayHardware/HWComposer.cpp
else
    LOCAL_SRC_FILES += \
        SurfaceFlinger_hwc1.cpp \
        DisplayHardware/HWComposer_hwc1.cpp
endif

SurfaceFlinger通过 HWComposer 使用 HWC 硬件能力,HWComposer构造函数通过 loadHwcModule 方法加载 HWC 模块,并封装成 HWC2::Device 结构,如下所示:

// Load and prepare the hardware composer module,HWComposer 构造函数中通过此方法加载 HWC 模块
void HWComposer::loadHwcModule()
{
    // 定义在 hardware.h 中,表示一个硬件模块,是 HAL 层的灵魂
    hw_module_t const* module;
    // 加载硬件厂商提供的 hwcomposer 模块,HWC_HARDWARE_MODULE_ID 定义在 hwcomposer_defs.h 中,表示 "hwcomposer"
    if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {ALOGE("%s module not found, aborting", HWC_HARDWARE_MODULE_ID);
        abort();}

    hw_device_t* device = nullptr;
    // 通过硬件厂商提供的 open 函数打开一个 "composer" 硬件设备,HWC_HARDWARE_COMPOSER 也定义在 hwcomposer_defs.h 中,表示 "composer"
    int error = module->methods->open(module, HWC_HARDWARE_COMPOSER, &device); 
    if (error != 0) {ALOGE("Failed to open HWC device (%s), aborting", strerror(-error));
        abort();}

    uint32_t majorVersion = (device->version >> 24) & 0xF;
    // mHwcDevice 是 HWC2.h 中定义的 HWC2::Device,所有与 HWC 的交互都通过 mHwcDevice
    if (majorVersion == 2) { // HWC2,hwc2_device_t 是 hwcomposer2.h 中的结构体
        mHwcDevice = std::make_unique<HWC2::Device>(reinterpret_cast<hwc2_device_t*>(device));
    } else { // 设备是基于 HWC1,这里用 HWC2 去适配,Android7.0 及以前默认都是 HWC1,hwc_composer_device_1_t 是 hwcomposer.h 中的结构体
        mAdapter = std::make_unique<HWC2On1Adapter>(reinterpret_cast<hwc_composer_device_1_t*>(device));
        mHwcDevice = std::make_unique<HWC2::Device>(static_cast<hwc2_device_t*>(mAdapter.get()));
    }
    // 获取硬件支持的最大虚拟屏幕数量,VirtualDisplay 可用于录屏
    mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount();}

先加载 hwcomposer 模块得到 hw_module_t,再打开composer 设备得到 hw_device_thw_module_thw_device_t定义在 hardwarelibhardwareincludehardwarehardware.h,表示一个 HAL 层模块和属于该模块的一个实现设备。注意这里是先有 HAL 模块,再有实现此模块的硬件设备。

上述通过 hw_get_module 方法(hardwarelibhardwarehardware.c)加载 hwcomposer 模块,此模块由硬件厂商提供实现,例如:hardwarelibhardwaremoduleshwcomposerhwcomposer.cpp 是 hwcomposer 模块基于 HWC1 的 default 实现,对应的共享库是 hwcomposer.default.so;hardwareqcomdisplaymsm8994libhwcomposerhwc.cpp 是高通 MSM8994 基于 HWC1 的实现,对应的共享库是hwcomposer.msm8994.so
如果是基于 HWC2 协议实现,则需要实现 hwcomposer2.h 中定义的 hwc2_device_t 接口,例如:class VendorComposer : public hwc2_device_t。Android7.0 的 hwcomposer 模块默认都是基于 HWC1 协议实现的。
每个 HAL 层模块实现都要定义一个 HAL_MODULE_INFO_SYM 数据结构,并且该结构的第一个字段必须是 hw_module_t,下面是高通 MSM8994hwcomposer 模块的定义:

// 高通 MSM8994
hwc_module_t HAL_MODULE_INFO_SYM = {
    // common 表示 hw_module_t 模块
    common: {
        tag: HARDWARE_MODULE_TAG,
        version_major: 2,
        version_minor: 0,
        id: HWC_HARDWARE_MODULE_ID, // hwcomposer
        name: "Qualcomm Hardware Composer Module",
        author: "CodeAurora Forum",
        methods: &hwc_module_methods,
        dso: 0,
        reserved: {0},
    }
};

最重要的一点:HWComposer::loadHwcModule方法最终把 HWC 模块封装成了HWC2::Device

frameworksnativeservicessurfaceflingerDisplayHardwareHWC2.h 主要定义了以下三个结构体:

  • HWC2::Device:表示硬件合成显示设备
  • HWC2::Display:表示一个显示屏幕,可以是物理显示屏(可以热插拔接入或者移除),也可以是虚拟显示屏,现在的游戏录屏一般都是基于虚拟屏幕实现的。
  • HWC2::Layer:表示一个叠加图层,对应与应用侧的 Surface。

它们是对 HWC 硬件模块的进一步封装,方便进行调用。HWC2::Device持有一个 hwc2_device_t,用于连接硬件设备,它包含了很多 HWC2_PFN 开头的函数指针变量,这些函数指针定义在hwcomposer2.h
HWC2::Device的构造函数中,会通过 Device::loadFunctionPointers -> loadFunctionPointer(FunctionDescriptor desc, PFN& outPFN) -> hwc2_device_t::getFunction 的调用链从硬件设备中获取具体的函数指针实现。关键模板函数如下所示:

// 模板函数,用于向硬件设备查询具体的函数指针实现
template <typename PFN>
[[clang::warn_unused_result]] bool loadFunctionPointer(FunctionDescriptor desc, PFN& outPFN) {
    // desc 表示一个枚举类型值
    auto intDesc = static_cast<int32_t>(desc);
    // mHwcDevice 表示 hwc2_device_t,是硬件驱动提供的实现
    auto pfn = mHwcDevice->getFunction(mHwcDevice, intDesc);
    if (pfn != nullptr) {
        // 强转函数指针
        outPFN = reinterpret_cast<PFN>(pfn); 
        return true;
    } else {ALOGE("Failed to load function %s", to_string(desc).c_str());
        return false;
    }
}

这些函数指针主要分为三类:

  1. 硬件设备(Device)相关的函数指针
  2. 显示屏幕(Display)相关的函数指针
  3. 叠加图层(Layer)相关的函数指针

通过上述函数指针可以与 hwc2_device_t 表示的硬件合成模块进行交互。三类指针分别选取了一个示例:

// Device 方法:获得设备支持的最大虚拟屏幕个数
uint32_t Device::getMaxVirtualDisplayCount() const
{return mGetMaxVirtualDisplayCount(mHwcDevice);
}
// Display 方法:为指定 Device 的指定 Display 创建一个 Layer
Error Display::createLayer(std::shared_ptr<Layer>* outLayer)
{
    // 表示创建的 layer 的唯一标识符
    hwc2_layer_t layerId = 0;
    // mDevice.mHwcDevice 表示 hwc2_device_t,即真正的硬件设备,mId 表示当前 Display 的唯一标识符,即为指定 Device 的指定 Display 创建一个 Layer
    int32_t intError = mDevice.mCreateLayer(mDevice.mHwcDevice, mId, &layerId);
    auto error = static_cast<Error>(intError);
    if (error != Error::None) {return error;}
    // 基于 layerId 创建 HWC2::Layer
    auto layer = std::make_shared<Layer>(shared_from_this(), layerId);
    // 保存当前 Display 所有的 Layer
    mLayers.emplace(layerId, layer);
    // 返回创建的 HWC2::Layer
    *outLayer = std::move(layer);
    return Error::None;
}
// Layer 方法:为指定 Device 的指定 Display 的指定 Layer 指定合成方式
Error Layer::setCompositionType(Composition type)
{auto intType = static_cast<int32_t>(type);
    // 为指定 Device 的指定 Display 的指定 Layer 指定合成方式
    int32_t intError = mDevice.mSetLayerCompositionType(mDevice.mHwcDevice,
            mDisplayId, mId, intType);
    return static_cast<Error>(intError);
}

可以通过类图,直观感受下引用关系。

HWC2::Device构造函数除了完成获取函数指针实现以外,还会通过 Device::registerCallbacks 向硬件设备注册三个 Display 的回调:热插拔,刷新和 VSync 信号,如下所示:

// HWC 硬件的几种回调描述符
// hardware\libhardware\include\hardware\hwcomposer2.h
enum class Callback : int32_t {
    Invalid = HWC2_CALLBACK_INVALID,
    // 显示屏幕(Display)的热插拔
    Hotplug = HWC2_CALLBACK_HOTPLUG,
    // 显示屏幕(Display)的刷新
    Refresh = HWC2_CALLBACK_REFRESH,
    // 显示屏幕(Display)的 VSync 信号
    Vsync = HWC2_CALLBACK_VSYNC,
};
// 注册热插拔 / 刷新 /VSync 回调
void Device::registerCallbacks()
{   // Callback 枚举类型如上所示
    registerCallback<HWC2_PFN_HOTPLUG>(Callback::Hotplug, hotplug_hook);
    registerCallback<HWC2_PFN_REFRESH>(Callback::Refresh, refresh_hook);
    registerCallback<HWC2_PFN_VSYNC>(Callback::Vsync, vsync_hook);
}

// 模板函数,用于向硬件设备(hwc2_device_t)注册函数回调
template <typename PFN, typename HOOK>
void registerCallback(Callback callback, HOOK hook) {
    // Callback 枚举类型如上所示
    auto intCallback = static_cast<int32_t>(callback);
    // this 表示 HWC2::Device, hwc2_callback_data_t 表示 void 指针类型
    auto callbackData = static_cast<hwc2_callback_data_t>(this);
    // 把函数指针强转成统一的 void (*)() 函数指针类型
    auto pfn = reinterpret_cast<hwc2_function_pointer_t>(hook);
    // 向 hwc2_device_t 注册函数回调,callbackData 表示透传的资源
    mRegisterCallback(mHwcDevice, intCallback, callbackData, pfn);
}
    
// 以 VSync 的静态函数为例
static void vsync_hook(hwc2_callback_data_t callbackData,
            hwc2_display_t displayId, int64_t timestamp) {
    // callbackData 表示透传的 void 指针,实际指 HWC2::Device        
    auto device = static_cast<HWC2::Device*>(callbackData);
    // 通过 displayId 获取对应的 HWC2::Display
    auto display = device->getDisplayById(displayId);
    if (display) {
        // 向外回调
        device->callVsync(std::move(display), timestamp);
    } else {ALOGE("Vsync callback called with unknown display %" PRIu64, displayId);
    }
}

void Device::callVsync(std::shared_ptr<Display> display, nsecs_t timestamp)
{
    // 通过 std::function 可调用对象 mVsync 向外回调,该可调用对象实际是 HWComposer 通过 Device::registerVsyncCallback 方法注册的
    if (mVsync) {mVsync(std::move(display), timestamp);
    } else {mPendingVsyncs.emplace_back(std::move(display), timestamp);
    }
}   

总结一下,HWC2::Device构造函数向硬件设备注册三个 Display 回调:热插拔,刷新和 VSync 信号。当 HWC2::Device 收到这些回调时,会通过监听器向外回调到对应的 HWComposer 函数:HWComposer::hotplug/HWComposer::invalidate/HWComposer::vsync。HWComposer 再通过这些信息驱动对应工作,后续文章进行介绍。

HWC Layer 合成方式

上文提到 HWC2::Device 中的函数指针是 hardwarelibhardwareincludehardwarehwcomposer2.h 中定义的,除此之外,该头文件还定义了一些重要的结构体,这里介绍两个比较重要的:

// 显示屏类型
enum class DisplayType : int32_t {
    Invalid = HWC2_DISPLAY_TYPE_INVALID,
    // 物理显示屏,显示设备有一个主屏幕,然后可以通过热插拔添加或者删除外接显示屏
    Physical = HWC2_DISPLAY_TYPE_PHYSICAL,
    // 虚拟显示屏,内容会渲染到离屏缓冲区,Android 录屏功能就是基于虚拟屏实现的
    Virtual = HWC2_DISPLAY_TYPE_VIRTUAL,
};

// Layer 合成类型,HWC2_COMPOSITION_XX 取自 hwc2_composition_t 枚举
enum class Composition : int32_t {
    Invalid = HWC2_COMPOSITION_INVALID,
    Client = HWC2_COMPOSITION_CLIENT,
    Device = HWC2_COMPOSITION_DEVICE,
    SolidColor = HWC2_COMPOSITION_SOLID_COLOR,
    Cursor = HWC2_COMPOSITION_CURSOR,
    Sideband = HWC2_COMPOSITION_SIDEBAND,
};

DisplayType表示显示屏类型,上面注释已经介绍,重点看下 Layer 合成类型:

  1. Client:这里的 Client 是相对于 HWC 硬件设备来说的,即不通过 HWC 来合成图层,而是通过 GPU 先把所有的这类图层合成到 client target buffer(一个离屏的图形缓冲区,buffer_handle_t 表示指向这块显存的指针,显存由 Gralloc 模块分配),然后再通过 Display::setClientTarget 把这块图形 Buffer 的地址传递给 HWC 设备,最后由 HWC 设备把其他 Layer 和这个图形 Buffer 进一步合成,并最终展示在 Display 上。
  2. Device:通过 HWC 硬件来合成图层,默认情况下,SurfaceFlinger会配置每个 Layer 都通过 Device 方式合成,但是 HWC 设备会根据硬件设备的性能改变某些图层的合成方式。
  3. SolidColor:HWC 设备将通过 Layer::setColor 设置的颜色渲染这个图层,如果 HWC 设备不支持这种合成方式,那么将会请求 SurfaceFlinger 改变合成方式为 Client。
  4. Cursor:与 Device 类似,但是这个图层的位置可以通过 setCursorPosition 异步设置。如果 HWC 设备不支持这种合成方式,那么将会请求 SurfaceFlinger 改变合成方式为 Client 或者 Device。
  5. Sideband:HWC 硬件会处理该类图层的合成,以及它的缓冲区更新和内容同步,但是只有拥有 HWC2_CAPABILITY_SIDEBAND_STREAM 能力的设备才支持这种图层,若设备不支持,那么将会请求 SurfaceFlinger 改变合成方式为 Client 或者 Device。

那么一个 Layer 的合成方式是怎么确定的那?大致流程如下所示:

  1. 当 VSync 信号到来时,SurfaceFlinger 被唤醒,处理 Layer 的新建,销毁和更新,并且为相应 Layer 设置期望的合成方式。
  2. 所有 Layer 更新后,SurfaceFlinger 调用validateDisplay,让 HWC 决定每个 Layer 的合成方式。
  3. SurfaceFlinger 调用 getChangedCompositionTypes 检查 HWC 是否对任何 Layer 的合成方式做出了改变,若是,那么 SurfaceFlinger 则调整对应 Layer 的合成方式,并且调用 acceptDisplayChanges 通知 HWC。
  4. SurfaceFlinger 把所有 Client 类型的 Layer 合成到 Target 图形缓冲区,然后调用 setClientTarget 把 Target Buffer 设置给 HWC。(如果没有 Client 类型的 Layer,则可以跳过该方法)
  5. 最后,SurfaceFlinger 调用presentDisplay,让 HWC 完成剩余 Layer 的合成,并且在显示屏上展示出最终的合成结果。

总结

本篇文章只是简单介绍了 HWC 模块的相关类:HWComposerHWC2::DeviceHWC2::DisplayHWC2::Layer,以及它们的关系。此外,还着重介绍了 Layer 的合成方式和合成流程。后续文章会更加全面的介绍SurfaceFlinger 是如何通过 HWC 模块完成 Layer 合成和上屏的(虚拟屏幕是到离屏缓冲区)。

相关源码

  1. hardwarelibhardwareincludehardwarehardware.h
  2. hardwarelibhardwarehardware.c
  3. hardwarelibhardwareincludehardwarehwcomposer_defs.h
  4. hardwarelibhardwareincludehardwarehwcomposer.h
  5. hardwarelibhardwareincludehardwarehwcomposer2.h
  6. frameworksnativeservicessurfaceflingerDisplayHardwareHWC2.h
  7. frameworksnativeservicessurfaceflingerDisplayHardwareHWC2.cpp
  8. frameworksnativeservicessurfaceflingerDisplayHardwareHWComposer.h
  9. frameworksnativeservicessurfaceflingerDisplayHardwareHWComposer.cpp
  10. frameworksnativeservicessurfaceflingerSurfaceFlinger.h
  11. frameworksnativeservicessurfaceflingerSurfaceFlinger.cpp
退出移动版