# 7. 硬件加速

# 7.1 G2D 硬件加速适配

# 7.1.1 AWTK 绘图流程

G2D 硬件加速是指将计算量较大的图像处理工作分配给专门的硬件外设来处理,减轻 CPU 的计算量,以此提高图像绘制的性能。

不同硬件平台的硬件加速外设不一样,其实现方法也有区别,在 AWTK 中,用户需要实现 awtk/src/base/g2d.h 文件中的相关接口,详见下表,然后开启宏 WITH_G2D 即可支持硬件加速,更多关于硬件加速模块的说明详见本文附录二。

函数名称 说明 备注
g2d_fill_rect 用颜色填充指定的区域
g2d_copy_image 把图片指定区域拷贝到 Framebuffer 中
g2d_rotate_image 把图片指定区域进行旋转并拷贝到 Framebuffer 的相应区域 本函数主要用于辅助实现横屏和竖屏的切换,一般支持90度、180度和270度旋转即可
g2d_blend_image 把图片指定区域渲染到 Framebuffer 的指定区域,若两个区域大小不同则进行缩放 如果硬件设备不支持缩放或全局 Alpha 融合算法(绘制半透明图像),请返回 RET_NOT_IMPL 使用软件渲染

为方便读者理解,此处以绘制一张半透明的背景图为例,介绍 AWTK 是如何支持硬件加速的,绘制流程图如下:

图7.1 半透明背景图绘制流程
图7.1 半透明背景图绘制流程

特别备注:

  1. 上图中的 canvas_draw_image 函数是 AWTK 提供的绘图功能,其接口详见:awtk/src/base/canvas.h。
  2. 上图中的 lcd_draw_image 函数是 AWTK 提供的 LCD 相关功能,其接口详见:awtk/src/base/lcd.h。
  3. AWTK 提供了基于 Framebuffer 的 LCD 缺省实现,代码详见:awtk/src/lcd/lcd_mem.inc。
  4. 基于 Framebuffer 实现的 LCD 通常会调用 awtk/src/blend/image_g2d.h 中的接口进行绘制。

# 7.1.2 STM32 平台硬件加速案例

目前,AWTK 内置了 STM32 系列平台 G2D 硬件加速的实现,代码详见:awtk/src/blend/stm32_g2d.c,只需定义下面的宏即可启用该功能:

/* 如果支持 STM32 硬件加速,请定义本宏(AWTK 会定义真正起作用的宏 WITH_G2D) */
#define WITH_STM32_G2D 1

STM32 平台通过外设 DMA2D(Direct Memory Access 2D)支持硬件加速,此处以实现 g2d.h 文件中的 g2d_fill_rect 函数为例,介绍如何通过 DMA2D 实现硬件加速,其他接口的实现方法类似,详情可参考:awtk/src/blend/stm32_g2d.c。

/* 用颜色填充指定的区域 */
ret_t g2d_fill_rect(bitmap_t* fb, const rect_t* dst, color_t c) {
  ...
  /* 计算像素占用字节、颜色格式以及填充的颜色值 */
  get_output_info(fb->format, &o_pixsize, &o_format, &color);

  /* 为修改数据锁定位图缓冲区 */
  uint8_t* fb_data = bitmap_lock_buffer_for_write(fb);

  /* 计算行偏移以及填充区域的起始地址 */
  uint16_t o_offline = fb->w - dst->w;                 
  uint32_t o_addr = ((uint32_t)fb_data + o_pixsize * (fb->w * dst->y + dst->x));

/************************************************************
       以下为 DMA2D 的相关用法,具体请根据实际平台实现
*************************************************************/
  /* 使能 DMA2D,请根据实际情况实现 */
  __HAL_RCC_DMA2D_CLK_ENABLE();

  /* 先停止 DMA2D,并设置硬件加速模式为:从寄存器到内存 */
  DMA2D->CR &= ~(DMA2D_CR_START);
  DMA2D->CR = DMA2D_R2M;

  /* 配置 DMA2D,例如此处需进行以下配置,请根据实际情况实现 */
  DMA2D->OPFCCR = o_format;   /* 设置颜色格式 */
  DMA2D->OOR = o_offline;     /* 设置行偏移 */
  DMA2D->OMAR = o_addr;       /* 设置目标缓冲区地址 */
  DMA2D->OCOLR = color;       /* 设置填充颜色 */
  DMA2D->NLR = h | (w << 16); /* 设置行数 */

  /* 启动 DMA2D 进行填充,并等待它工作结束 */
  DMA2D->CR |= DMA2D_CR_START;
  DMA2D_WAIT
/************************************************************/

  /* 绘制完成,解锁位图缓冲区 */
  bitmap_unlock_buffer(fb);

  return RET_OK;
}

# 7.2 硬件图像解码适配

# 7.2.1 AWTK 解码图片的流程

AWTK 默认采用 stb 库进行软件图像解码,全靠 CPU 计算。硬件解码是指将图像解码的工作分配给专门的硬件来处理,减轻 CPU 的计算量,从而提高图像绘制的性能。

在 AWTK 中,用户需要自己实现硬件解码的接口,然后调用 image_loader_register 函数注册该接口,后续 AWTK 程序在进行解码时会自动调用该接口解码图片。

/**
 * @method image_loader_register
 * 注册图片加载器。
 *
 * @annotation ["static"]
 * @param {image_loader_t*} loader loader对象。
 *
 * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
 */
ret_t image_loader_register(image_loader_t* loader);

此处以获取 JPG 图片为例展示 AWTK 是如何获取图片的:

图7.2 获取图片流程
图7.2 获取图片流程

注:

  1. 上图中的 image_manager_get_bitimp 函数是 AWTK 提供的获取图片功能,接口详见:awtk/src/base/image_manager.c。
  2. 上图中的 image_loader_load_image 函数是 AWTK 提供的加载图片接口,接口详见:awtk/src/base/image_loader.h。

# 7.2.2 STM32 平台硬件图像解码案例

本节以 AWTK 针对 STM32F767igtx 为例,大致讲解如何将硬件解码接口集成到 AWTK 中,移植层源码可前往 GitHub (opens new window) 下载。

步骤一:用户需根据实际使用的硬件平台实现 JPG 图像硬件解码的接口,然后定义一个 image_loader_t 的对象,重载对象中的 load 函数,代码如下:

/* awtk-stm32f767igtx-raw/awtk-port/stm32_jpg_image_loader.c */
/* JPG硬件解码函数 */
static ret_t image_loader_stm32_jpg_load(image_loader_t *l,
                                         const asset_info_t *asset,
                                         bitmap_t *image) {
/* 实现JPG硬件解码,具体实现由硬件决定 */
}

static const image_loader_t stm32_jpg_image_loader = {
    .load = image_loader_stm32_jpg_load};

image_loader_t *image_loader_stm32_jpg() {
  return (image_loader_t *)&stm32_jpg_image_loader;
}

步骤二:在 LCD 初始化时顺便注册上一步骤中创建的 image_loader_t 对象,代码如下:

/* awtk-stm32f767igtx-raw/awtk-port/main_loop_stm32_raw.c */
/* 在LCD初始化时调用注册函数 */
lcd_t* platform_create_lcd(wh_t w, wh_t h) {
  image_loader_register(image_loader_stm32_jpg());
  return stm32f767_create_lcd(w, h);
}

# 7.3 图像专用内存

某些平台的 G2D 硬件加速需要特定的内存,不能直接使用 malloc 开辟出来的内存。例如:在 M1107 嵌入式 Linux 下的 G2D 硬件加速使用的内存需要真实的物理地址,不能是 malloc 出来的虚拟内存地址。AWTK 为了解决这个问题,提供了一种专用图像内存的解决方案。

# 7.3.1 工作原理

AWTK 内部加载图片(如 png、jpg 等)时,会把图片解码后的位图内容存放到 graphic_buffer_t 所管理的内存中,后续要显示图片时 AWTK 则会通过 graphic_buffer_t 获取对应位图数据并拷贝到目标 Framebuffer 中。

默认 graphic_buffer_t 对象所管理的内存是使用系统 malloc 分配出来的,如果要让位图数据存放到专用内存,则需要替换掉默认 graphic_buffer_t 的行为,改为通过专用 API 分配出来的内存。因此,需要重载 graphic_buffer_t 类成员函数实现多态,让 AWTK 在分配位图内存时使用我们自己内存分配函数。

基本步骤:

  1. 创建一个 graphic_buffer_physical.c 文件,并且实现 graphic_buffer_create_for_bitmap、graphic_buffer_create_with_data 函数,重载 graphic_buffer_t 的多态函数指针。
  2. 移除 awtk/src/graphic_buffer/graphic_buffer_default.c 文件的编译。
  3. 把 graphic_buffer_physical.c 文件放入项目中编译。

下面将详细解释位图缓存结构,及给出一个实际移植例子。

# 7.3.2 位图缓存 graphic_buffer_t

如果希望 AWTK 使用专用内存的话,就必须了解 AWTK 的位图缓存(graphic_buffer_t 类型)。该类型是用于分配和存放 AWTK 位图数据的,所以开辟专用内存的话,需要重新实现该类型的成员函数,并且要让 AWTK 创建和获取位图缓存时对接到这个新的 graphic_buffer_t 对象。

/* awtk/base/graphic_buffer.h */
struct _graphic_buffer_t;
typedef struct _graphic_buffer_t graphic_buffer_t;

typedef uint8_t* (*graphic_buffer_lock_for_read_t)(graphic_buffer_t* buffer);
typedef uint8_t* (*graphic_buffer_lock_for_write_t)(graphic_buffer_t* buffer);
typedef uint32_t (*graphic_buffer_get_physical_width_t)(graphic_buffer_t* buffer);
typedef uint32_t (*graphic_buffer_get_physical_height_t)(graphic_buffer_t* buffer);
typedef uint32_t (*graphic_buffer_get_physical_line_length_t)(graphic_buffer_t* buffer);
typedef ret_t (*graphic_buffer_unlock_t)(graphic_buffer_t* buffer);
typedef ret_t (*graphic_buffer_attach_t)(graphic_buffer_t* buffer, void* data, uint32_t w, uint32_t h);
typedef bool_t (*graphic_buffer_is_valid_for_t)(graphic_buffer_t* buffer, bitmap_t* bitmap);
typedef ret_t (*graphic_buffer_destroy_t)(graphic_buffer_t* buffer);

/* 省略无关代码.... */
typedef struct _graphic_buffer_vtable_t {
  graphic_buffer_lock_for_read_t lock_for_read;
  graphic_buffer_lock_for_write_t lock_for_write;
  graphic_buffer_unlock_t unlock;
  graphic_buffer_attach_t attach;
  graphic_buffer_is_valid_for_t is_valid_for;
  graphic_buffer_destroy_t destroy;
  graphic_buffer_get_physical_width_t get_width;
  graphic_buffer_get_physical_height_t get_height;
  graphic_buffer_get_physical_line_length_t get_line_length;
} graphic_buffer_vtable_t;

/**
 * @class graphic_buffer_t
 * graphic_buffer。
 */
struct _graphic_buffer_t {
  const graphic_buffer_vtable_t* vt;
};

从 graphic_buffer_t 类型来看,该类型只有一个虚表(graphic_buffer_vtable_t 对象),就是为了给用户多态使用的,我们可以通过下表了解虚表对象中的各个函数指针具体的作用是什么:

函数指针名字 作用
lock_for_read 用于读取数据的时候加锁使用的,在某些情况下位图数据需要加锁处理。
lock_for_write 用于写入数据的时候加锁使用的,在某些情况下位图数据需要加锁处理。
unlock 用于解锁上面的 lock_for_read 和 lock_for_write。
attach 给 graphic_buffer_t 对象直接赋予外部的内存地址。
is_valid_for 判断该 graphic_buffer_t 对象是否有效。
destroy 释放 graphic_buffer_t 对象
get_width 获取 graphic_buffer_t 对象物理宽
get_height 获取 graphic_buffer_t 对象物理高
get_line_length 获取 graphic_buffer_t 对象物理行长

上面讲的都是 graphic_buffer_t 对象创建之后的成员函数指针,而 graphic_buffer_t 对象是通过下面两个函数来创建的:

函数名字 作用
graphic_buffer_create_for_bitmap 创建一个 graphic_buffer_t 对象,给该对象分配一块内存地址。
graphic_buffer_create_with_data 创建一个 graphic_buffer_t 对象,并且绑定外部内存地址。

AWTK 的位图对象(bitmap_t 对象)默认是会使用 graphic_buffer_create_for_bitmap 函数来创建出来的。

而在有固定内存的情况(例如:FrameBuffer),又想使用 AWTK 的位图对象的时候,就使用 graphic_buffer_create_with_data 把外部固定内存地址绑定到 AWTK 的位图对象中。

# 7.3.3 awtk-linux-fb 的移植案例

本节以 awtk-linux-fb 移植层为例,大致讲解如何将实现位图使用专用内存的解决方案。

步骤一:在 awtk-linux-fb/awtk-port 目录下,创建一个 graphic_buffer_physical.c 文件,并且在该文件下,实现了 graphic_buffer 类型的多态。

/* graphic_buffer_physical.c */

/**
 * @class graphic_buffer_physical_t
 * graphic_buffer default
 */
typedef struct _graphic_buffer_physical_t {
  graphic_buffer_t graphic_buffer;

  uint8_t* data;
  uint32_t w;
  uint32_t h;
  uint32_t line_length;
  bitmap_format_t format;
} graphic_buffer_physical_t;

/* 此处简单实现 graphic_buffer_physical_t 对象中的虚表函数 */
static bool_t graphic_buffer_physical_is_valid_for(graphic_buffer_t* buffer, bitmap_t* bitmap) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return_value_if_fail(b != NULL && bitmap != NULL, FALSE);
  if (bitmap->orientation == LCD_ORIENTATION_0 || bitmap->orientation == LCD_ORIENTATION_180) {
    return b->w == bitmap->w && b->h == bitmap->h;
  } else {
    return b->w == bitmap->h && b->h == bitmap->w;
  }
}

static uint8_t* graphic_buffer_physical_lock_for_read(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return b->data;
}

static uint8_t* graphic_buffer_physical_lock_for_write(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return b->data;
}

static ret_t graphic_buffer_physical_unlock(graphic_buffer_t* buffer) {
  return RET_OK;
}

static ret_t graphic_buffer_physical_attach(graphic_buffer_t* buffer, void* data, uint32_t w, uint32_t h) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  b->w = w;
  b->h = h;
  b->data = data;
  return RET_OK;
}

static ret_t graphic_buffer_physical_destroy(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  /* 判断 data_head 来释放内存的 */
  if(b->data_head != NULL) {
    mem_physical_free(b->data_head);
  }
  TKMEM_FREE(b);
  return RET_OK;
}

static uint32_t graphic_buffer_physical_get_physical_width(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return b->w;
}

static uint32_t graphic_buffer_physical_get_physical_height(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return b->h;
}

static uint32_t graphic_buffer_physical_get_physical_line_length(graphic_buffer_t* buffer) {
  graphic_buffer_physical_t* b = (graphic_buffer_physical_t*)(buffer);
  return b->line_length;
}

static const graphic_buffer_vtable_t s_graphic_buffer_physical_vtable = {
    .lock_for_read = graphic_buffer_physical_lock_for_read,
    .lock_for_write = graphic_buffer_physical_lock_for_write,
    .unlock = graphic_buffer_physical_unlock,
    .attach = graphic_buffer_physical_attach,
    .is_valid_for = graphic_buffer_physical_is_valid_for,
    .get_width = graphic_buffer_physical_get_physical_width,
    .get_height = graphic_buffer_physical_get_physical_height,
    .get_line_length = graphic_buffer_physical_get_physical_line_length,
    .destroy = graphic_buffer_physical_destroy};

graphic_buffer_t* graphic_buffer_create_with_data(const uint8_t* data, uint32_t w, uint32_t h, bitmap_format_t format) {
  /* 创建 graphic_buffer 对象 */
  graphic_buffer_physical_t* buffer = TKMEM_ZALLOC(graphic_buffer_physical_t);
  /* 把外部的内存地址配置给 graphic_buffer 对象 */
  buffer->data = (uint8_t*)data;
  /* 配置 graphic_buffer 对象的物理宽高等数据 */
  buffer->w = w;
  buffer->h = h;
  buffer->line_length = bitmap_get_bpp_of_format(format) * w;
  buffer->graphic_buffer.vt = &s_graphic_buffer_physical_vtable;

  return (graphic_buffer_t*)(buffer);
}

ret_t graphic_buffer_create_for_bitmap(bitmap_t* bitmap) {  
  uint8_t* data = NULL;
  /* 创建 graphic_buffer 对象 */
  graphic_buffer_physical_t* buffer = TKMEM_ZALLOC(graphic_buffer_physical_t);
  /* 配置 graphic_buffer 对象的物理宽高等数据 */
  buffer->w = bitmap->w;
  buffer->h = bitmap->h;
  buffer->line_length = bitmap_get_line_length(bitmap);
  buffer->graphic_buffer.vt = &s_graphic_buffer_physical_vtable;
  /* 根据具体平台来分配位图专用内存,这里是分配物理内存地址,这里特别要给 data_head 赋值,用于释放的 */
  buffer->data = buffer->data_head = mem_physical_alloc(bitmap->h * buffer->line_length);
  
  bitmap->buffer = buffer;
  return buffer != NULL ? RET_OK : RET_OOM;
}

步骤二:修改 awtk-linux-fb/awtk-port/SConscript 脚本,让上面创建的 graphic_buffer_physical.c 文件加入到项目中编译,在 SOURCES 对象中增加 graphic_buffer_physical.c 文件。

# awtk-linux-fb/awtk-port/SConscript

# 省略其他无关代码
SOURCES = [
  'input_thread/mouse_thread.c', 
  'input_thread/input_thread.c', 
  'input_thread/input_dispatcher.c', 
  'lcd_linux/lcd_linux_fb.c', 
  'lcd_linux/lcd_linux_drm.c', 
  'lcd_linux/lcd_linux_egl.c', 
  'lcd_linux/lcd_mem_others.c' ,
  'main_loop_linux.c'# 增加编译 graphic_buffer_physical.c 文件
  'graphic_buffer_physical.c']

步骤三:修改 awtk-linux-fb/awtk_config.py 脚本,禁用 AWTK 默认的 graphic_buffer 类型,找到 GRAPHIC_BUFFER='default',修改为 GRAPHIC_BUFFER='custom'

# awtk-linux-fb/awtk_config.py

#GRAPHIC_BUFFER='default'
GRAPHIC_BUFFER='custom'