厂家应该根据自己的硬件去写HWC的代码,一般位于hardware/厂家名称/hwcomposer目录中。
1 HAL_MODULE_INFO_SYM
在 Android 中,HAL_MODULE_INFO_SYM
结构体用于描述硬件抽象层(HAL)模块的信息,它会在系统启动过程中被注册到系统中,具体过程如下:
1.1 模块加载
- Android 系统启动时,会通过
load
函数(位于system/core/init/init.cpp
中)加载HAL
模块。这个函数会根据模块的名称和路径,在系统中查找并加载对应的动态链接库(.so
文件)。 - 例如,对于
HWComposer
模块,系统会在hardware/libhardware/modules/
目录下查找对应的.so
文件。
1.2 符号查找
- 一旦动态链接库被加载,系统会使用
dlsym
函数在库中查找HAL_MODULE_INFO_SYM
符号。dlsym
函数会在指定的动态链接库中查找指定名称的符号,并返回其地址。 - 如果找到了
HAL_MODULE_INFO_SYM
符号,系统就可以通过这个符号获取到模块的信息,包括模块的版本、名称、方法等。
1.3 模块注册
- 找到
HAL_MODULE_INFO_SYM
后,系统会将模块的信息注册到hw_module_t
结构体中。这个结构体是 Android 系统中用于管理硬件模块的核心数据结构。 - 系统会将
HAL_MODULE_INFO_SYM
中的信息复制到hw_module_t
结构体中,并将hw_module_t
结构体添加到系统的模块列表中。这样,系统就可以通过模块列表来访问和管理所有已注册的硬件模块。 - 通过以上步骤,
HAL_MODULE_INFO_SYM
就被成功注册到 Android 系统中,使得系统能够识别和使用相应的硬件模块。这样,当上层应用或系统服务需要使用硬件功能时,就可以通过查询模块列表,找到对应的硬件模块,并调用其提供的方法来实现对硬件的访问和控制。
static struct hw_module_methods_t hwc_module_methods = {
.open = android::hwc_device_open
};
hwc_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = HWC_HARDWARE_MODULE_ID,
.name = "DRM hwcomposer module",
.author = "The Android Open Source Project",
.methods = &hwc_module_methods,
.dso = NULL,
.reserved = {0},
}
};
2 调用过程
从上面看狸猫主要实现了hwc_device_open函数,重点分析下这个函数的功能:
static int hwc_device_open(const struct hw_module_t *module, const char *name, struct hw_device_t **dev)
{
if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
ALOGE("Invalid module name- %s", name);
return -EINVAL;
}
init_rk_debug();
property_set("vendor.gralloc.no_afbc_for_fb_target_layer","1");
std::unique_ptr<hwc_context_t> ctx(new hwc_context_t());
if (!ctx) {
ALOGE("Failed to allocate hwc context");
return -ENOMEM;
}
int ret = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, (const hw_module_t **)&ctx->gralloc);
if (ret) {
ALOGE("Failed to open gralloc module %d", ret);
return ret;
}
ret = hwc_enumerate_displays(ctx.get());
if (ret) {
ALOGE("Failed to enumerate displays: %s", strerror(ret));
return ret;
}
ctx->device.common.tag = HARDWARE_DEVICE_TAG;
ctx->device.common.version = HWC_DEVICE_API_VERSION_1_4;
ctx->device.common.module = const_cast<hw_module_t *>(module);
ctx->device.common.close = hwc_device_close;
ctx->device.dump = hwc_dump;
ctx->device.prepare = hwc_prepare;
ctx->device.set = hwc_set;
ctx->device.eventControl = hwc_event_control;
ctx->device.setPowerMode = hwc_set_power_mode;
ctx->device.query = hwc_query;
ctx->device.registerProcs = hwc_register_procs;
ctx->device.getDisplayConfigs = hwc_get_display_configs;
ctx->device.getDisplayAttributes = hwc_get_display_attributes;
ctx->device.getActiveConfig = hwc_get_active_config;
ctx->device.setActiveConfig = hwc_set_active_config;
ctx->device.setCursorPositionAsync = NULL; /* TODO: Add cursor */
g_ctx = ctx.get();
ebc_fd = open("/dev/ebc", O_RDWR, 0);
if (ebc_fd < 0) {
ALOGE("open /dev/ebc failed\n");
return -1;
}
if (ioctl(ebc_fd, EBC_GET_BUFFER_INFO, &ebc_buf_info) != 0) {
ALOGE("EBC_GET_BUFFER_INFO failed\n");
close(ebc_fd);
return -1;
}
hwc_init_version();
*dev = &ctx->device.common;
ctx.release();
return 0;
}
2.1 hwc_enumerate_displays
从函数名称来看意思是枚举显示器,下面是其主要代码,
int ret, num_connectors = 0;
ret = ctx->eink_compositor_worker.Init(ctx);
if (ret) {
ALOGE("Failed to initialize virtual compositor worker");
return ret;
}
ret = hwc_initialize_display(ctx, 0);
if (ret) {
ALOGE("Failed to initialize display %d", 0);
return ret;
}
ret = ctx->primary_vsync_worker.Init(HWC_DISPLAY_PRIMARY);
if (ret) {
ALOGE("Failed to create event worker for primary display %d\n", ret);
return ret;
}
return 0;
Init里面做了一些硬件相关的初始化:
1 打开/dev/ebc,获取/dev/ebc的内存操作位置,
2 设置MMAP方便图像数据搬移,
3 设置CFA的输入图像参数
4 初始化波形文件
5 调用InitWorker
2.1.1 InitWorker的分析
主要创建了一个线程,名称是InternalRoutine,其作用是创建一个线程执行:
worker->Routine();
int wait_ret = 0;
if (composite_queue_.empty()) {
wait_ret = WaitForSignalOrExitLocked();
}
ret = pthread_mutex_lock(&eink_lock_);
if (ret) {
ALOGE("Failed to acquire compositor lock %d", ret);
}
std::unique_ptr<EinkComposition> composition;
if (!composite_queue_.empty()) {
composition = std::move(composite_queue_.front());
composite_queue_.pop();
pthread_cond_signal(&eink_queue_cond_);
}
这个里面,如果检查composite_queue不是空的,那么就会调用compose进行合成和刷新。
Compose(std::move(composition));
1. Compose
这是一个函数名,从函数名推测,它可能是用于执行某种合成操作的函数,比如图像合成、图层合成等。在 Android 硬件合成器(Hardware Composer)的上下文中,这个函数或许会将多个图形图层合成为最终要显示的图像。
2. std::move
std::move
是 C++ 标准库中的一个函数模板,定义在 <utility>
头文件中。它的作用是将一个左值强制转换为右值引用,从而可以调用移动语义。移动语义是 C++11 引入的一个重要特性,其主要目的是避免不必要的对象复制,提升性能。
左值和右值:左值是指有名称、可以取地址的对象;右值则是临时对象,没有名称,也不能取地址。
移动语义:当使用
std::move
将一个左值转换为右值引用后,对象的所有权可以被转移,而不是进行复制。这对于那些管理资源(如动态分配的内存、文件句柄等)的对象尤为有用,因为资源的转移比复制要高效得多。
3. composition
这是一个对象,它可能是一个自定义的类或者结构体,代表了要进行合成操作的内容。在调用 std::move(composition)
之后,composition
对象的资源所有权会被转移给 Compose
函数。
2.1.2 Compose的分析
void EinkCompositorWorker::Compose(std::unique_ptr<EinkComposition> composition)
在此函数内会转换成灰度图片并写入到驱动中。
2.2 hw_set函数
{
hwc_display_contents_1_t *dc = sf_display_contents[i];
if (!sf_display_contents[i])
continue;
size_t num_dc_layers = dc->numHwLayers;
for (size_t j = 0; j < num_dc_layers; ++j) {
hwc_layer_1_t *sf_layer = &dc->hwLayers[j];
if (sf_layer != NULL && sf_layer->handle != NULL) {
char layername[100];
hwc_get_handle_layername(ctx->gralloc, sf_layer->handle, layername, 100);
if (strstr(layername, "EBOOK_STANDBY")) {
ALOGD("EBOOK STANDBY\n");
gCurrentEpdMode = EPD_FORCE_FULL;
is_suspend = 1;
} else if (strstr(layername, "EBOOK_POWEROFF")) {
ALOGD("EBOOK POWEROFF\n");
gCurrentEpdMode = EPD_FORCE_FULL;
isPoweroff = 1;
}
if (sf_layer->compositionType == HWC_FRAMEBUFFER_TARGET)
ctx->eink_compositor_worker.QueueComposite(dc, gCurrentEpdMode);
}
}
}
这个函数也是在hwc_device_open的时候赋值,用于把各层的数据分别入队。入队后就可以在2.1.1的worker->Routine()里面检测到,然后进行格式转换和显示。
在 Android 系统的硬件合成器(Hardware Composer,HWC)中,hwc_set
函数是一个关键的接口,它主要用于提交显示内容到硬件合成器进行处理和显示。下面详细介绍 hwc_set
函数的调用时机和相关场景。
调用时机概述
hwc_set
函数通常在图形系统完成图层的准备(hwc_prepare
)之后被调用。在整个图形渲染和显示流程中,系统会先对各个图层的属性和内容进行准备和分析,确定哪些图层可以由硬件合成器直接处理,哪些需要 GPU 辅助处理,然后再调用 hwc_set
函数将最终的显示内容提交给硬件合成器。
具体调用场景
1. 屏幕刷新周期
在每个垂直同步(VSync)信号到来时,图形系统会触发一次新的渲染和显示周期。在这个周期内,系统首先会调用
hwc_prepare
函数对当前需要显示的图层进行准备工作,分析每个图层的属性(如位置、大小、透明度等),并确定最佳的合成策略。当
hwc_prepare
完成后,系统会调用hwc_set
函数将准备好的图层数据和合成信息提交给硬件合成器。硬件合成器会根据这些信息对图层进行合成,并将合成后的图像数据发送到显示设备进行显示。
2. 图层内容更新
当应用程序更新其界面内容时,会导致图形系统中的图层内容发生变化。例如,当用户滑动屏幕、点击按钮或者切换界面时,相关的应用图层会更新其内容。
图形系统会检测到这些图层的变化,并在下次 VSync 信号到来时,重新调用
hwc_prepare
和hwc_set
函数,将更新后的图层数据提交给硬件合成器进行处理和显示。
3. 显示设备状态变化
当显示设备的状态发生变化时,如分辨率改变、刷新率调整等,图形系统需要重新调整图层的合成和显示方式。
在这种情况下,系统会先调用
hwc_prepare
函数对图层进行重新准备,然后调用hwc_set
函数将新的显示内容提交给硬件合成器,以适应显示设备的新状态。2.3 primary_vsync_worker.Init
void VSyncWorker::Routine() {
ALOGD_IF(log_level(DBG_INFO),"----------------------------VSyncWorker Routine start----------------------------");
int ret = Lock();
if (ret) {
ALOGE("Failed to lock worker %d", ret);
return;
}
if (!enabled_) {
ret = WaitForSignalOrExitLocked();
if (ret == -EINTR) {
return;
}
}
bool enabled = enabled_;
int display = display_;
hwc_procs_t const *procs = procs_;
ret = Unlock();
if (ret) {
ALOGE("Failed to unlock worker %d", ret);
}
if (!enabled)
return;
int64_t timestamp;
ret = SyntheticWaitVBlank(×tamp);
if (ret)
return;
/*
* There's a race here where a change in procs_ will not take effect until
* the next subsequent requested vsync. This is unavoidable since we can't
* call the vsync hook while holding the thread lock.
*
* We could shorten the race window by caching procs_ right before calling
* the hook. However, in practice, procs_ is only updated once, so it's not
* worth the overhead.
*/
//zxl:In VtsHalGraphicsComposerV2_1TargetTest, sometimes procs->vsync will invalid.
if (procs && ((unsigned long)procs->vsync > 0x10))
procs->vsync(procs, display, timestamp);
last_timestamp_ = timestamp;
ALOGD_IF(log_level(DBG_INFO),"----------------------------VSyncWorker Routine end----------------------------");
}
}
VSyncWorker::Routine
是 VSyncWorker
类中的一个方法,通常作为工作线程的执行体,其主要功能是处理垂直同步(VSync)信号相关的逻辑,确保图形渲染和显示操作能够与显示设备的刷新率同步。
详细逻辑
日志输出与线程加锁:函数开始时会根据日志级别输出开始信息,然后尝试对线程加锁,如果加锁失败则输出错误日志并返回。
检查启用状态并等待信号:检查
enabled_
标志,如果未启用则调用WaitForSignalOrExitLocked
方法等待信号或退出条件。若该方法返回-EINTR
表示被中断,函数直接返回。保存关键变量并解锁线程:将
enabled_
、display_
和procs_
成员变量的值保存到局部变量中,然后解锁线程。若解锁失败,输出错误日志。再次检查启用状态:根据局部变量
enabled
的值判断是否继续执行,如果未启用则返回。等待垂直同步信号:调用
SyntheticWaitVBlank
方法等待垂直同步信号,并将获取到的时间戳存储在timestamp
中。若等待失败则返回。调用垂直同步回调:检查
procs
指针和procs->vsync
地址的有效性,若有效则调用procs->vsync
回调函数,传递procs
、display
和timestamp
作为参数。更新时间戳与日志输出:将当前获取到的时间戳更新到
last_timestamp_
成员变量中,最后根据日志级别输出结束信息。
SyntheticWaitVBlank
的实现如下,是软件模拟的信号:
int VSyncWorker::SyntheticWaitVBlank(int64_t *timestamp) {
struct timespec vsync;
int ret = clock_gettime(CLOCK_MONOTONIC, &vsync);
float refresh = 60.0f; // Default to 60Hz refresh rate
int64_t phased_timestamp = GetPhasedVSync(
kOneSecondNs / refresh, vsync.tv_sec * kOneSecondNs + vsync.tv_nsec);
vsync.tv_sec = phased_timestamp / kOneSecondNs;
vsync.tv_nsec = phased_timestamp - (vsync.tv_sec * kOneSecondNs);
do {
ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &vsync, NULL);
} while (ret == -1 && errno == EINTR);
if (ret)
return ret;
*timestamp = (int64_t)vsync.tv_sec * kOneSecondNs + (int64_t)vsync.tv_nsec;
return 0;
}
procs->vsync
回调函数是在open的时候由系统传递下来的:
ctx->device.registerProcs = hwc_register_procs;
3 总结
1 系统加载.so后,会通过hwc_device_open打开设备
2 打开设备的时候会建立2个线程:
一个用ComposerWorker于查询composite的队列是否为空,如果不为空则读取文件进行格式转换然后发送到显示设备。
一个用于模拟VSyncWorker用于产生垂直信号,这个信号会回调到系统注册的函数
hwc_register_procs
hwc_register_procs此函数会触发系统调用hw_set函数把图层入队到composite