Android HWComposer代码分析

发布于:2025-04-12 ⋅ 阅读:(35) ⋅ 点赞:(0)

 厂家应该根据自己的硬件去写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(&timestamp);
    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)信号相关的逻辑,确保图形渲染和显示操作能够与显示设备的刷新率同步。

详细逻辑

  1. 日志输出与线程加锁:函数开始时会根据日志级别输出开始信息,然后尝试对线程加锁,如果加锁失败则输出错误日志并返回。

  2. 检查启用状态并等待信号:检查 enabled_ 标志,如果未启用则调用 WaitForSignalOrExitLocked 方法等待信号或退出条件。若该方法返回 -EINTR 表示被中断,函数直接返回。

  3. 保存关键变量并解锁线程:将 enabled_display_ 和 procs_ 成员变量的值保存到局部变量中,然后解锁线程。若解锁失败,输出错误日志。

  4. 再次检查启用状态:根据局部变量 enabled 的值判断是否继续执行,如果未启用则返回。

  5. 等待垂直同步信号:调用 SyntheticWaitVBlank 方法等待垂直同步信号,并将获取到的时间戳存储在 timestamp 中。若等待失败则返回。

  6. 调用垂直同步回调:检查 procs 指针和 procs->vsync 地址的有效性,若有效则调用 procs->vsync 回调函数,传递 procsdisplay 和 timestamp 作为参数。

  7. 更新时间戳与日志输出:将当前获取到的时间戳更新到 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


网站公告

今日签到

点亮在社区的每一天
去签到