Files
2025-12-03 11:12:34 +08:00

445 lines
15 KiB
C

/* Copyright(C) 2010- , JIELI TECHNOLOGY, Inc.
* All right reserved.
*/
/* ------------------------------------------------------------------------------------*/
/**
* @file gpu_demo_task.c
*
* @brief GPU 系统任务,GPU demo调度与推屏逻辑
*
* @author zhuhaifang@zh-jieli.com
*
* @version V1.0.0
*
* @date 2024-09-13
*/
/* ------------------------------------------------------------------------------------*/
#include "typedef.h"
#include "rect.h"
#include "res/resfile.h"
#include "dbi.h"
#include "jlgpu_math.h" // matrix
#include "jlgpu_driver.h" // gpu driver
#include "ui_resource.h" // JL UI resource
#include "ui_expand/ui_expand.h" // macro defined
#include "gpu_port.h" // module head file
#include "gpu_draw.h" // custom draw head file
#include "ui_expand/buffer_manager.h"
#include "ui_core.h"
#include "ui_measure.h"
#include "ui/lcd/lcd_drive.h"
#include "res/mem_var.h"
#include "football.h"
#include "gpu_demo.h"
#if defined GPU_DEMO_TASK_MAIN && GPU_DEMO_TASK_MAIN
/* GPU DEMO 背景颜色 */
#define GPU_DEMO_BACKGROUND 0xff404040
struct gpu_port_demo_priv {
struct lcd_interface *lcd; // LCD 设备控制API
struct lcd_info info; // LCD 设备信息
pJLGPUTaskHead_t gpu_task_head; // GPU任务链头指针
void *buffer_hdl; // 显存buffer管理句柄
void *buffer_adr; // 当前使用的显存buffer
OS_SEM buffer_sem; // buffer使用信号量
};
static struct gpu_port_demo_priv priv = {0};
#define __this (&priv)
static void *gpu_port_demo_malloc(int size, u32 ram_type, u32 module_type)
{
void *buf = (void *)malloc(size);
return buf;
}
static void gpu_port_demo_free(void *buf, u32 ram_type, u32 module_type)
{
free(buf);
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief gpu_port_demo_create_task 创建GPU DEMO任务
*
* @Params head GPU任务链头
*/
/* ------------------------------------------------------------------------------------*/
void gpu_port_demo_create_task(pJLGPUTaskHead_t head)
{
#if defined GPU_DEMO_TASK_FILL && GPU_DEMO_TASK_FILL
extern void gpu_demo_create_fill_task(pJLGPUTaskHead_t head);
gpu_demo_create_fill_task(head);
#elif defined GPU_DEMO_TASK_FILL_MASK && GPU_DEMO_TASK_FILL_MASK
extern void gpu_demo_create_fill_mask(pJLGPUTaskHead_t head);
gpu_demo_create_fill_mask(head);
#elif defined GPU_DEMO_TASK_FILL_AFFINE && GPU_DEMO_TASK_FILL_AFFINE
extern void gpu_demo_create_fill_affine(pJLGPUTaskHead_t head);
gpu_demo_create_fill_affine(head);
#elif defined GPU_DEMO_TASK_LINEGRAD && GPU_DEMO_TASK_LINEGRAD
extern void gpu_demo_create_linegrad_task(pJLGPUTaskHead_t head);
gpu_demo_create_linegrad_task(head);
#endif
#if defined GPU_DEMO_COLOR_SWAP_ENABLE && GPU_DEMO_COLOR_SWAP_ENABLE
extern void gpu_demo_color_swap(pJLGPUTaskHead_t head);
gpu_demo_color_swap(head);
#endif
#if defined GPU_DEMO_BLEND_ENABLE && GPU_DEMO_BLEND_ENABLE
extern void gpu_demo_blend_mode(pJLGPUTaskHead_t head);
gpu_demo_blend_mode(head);
#endif
#if defined GPU_DEMO_ALPHA_PREMULT_ENABLE && GPU_DEMO_ALPHA_PREMULT_ENABLE
extern void gpu_demo_alpha_premult_test(pJLGPUTaskHead_t head);
gpu_demo_alpha_premult_test(head);
#endif
#if defined GPU_DEMO_TASK_TEXTURE && GPU_DEMO_TASK_TEXTURE
void gpu_demo_texture_test(pJLGPUTaskHead_t head);
gpu_demo_texture_test(head);
#endif
#if defined GPU_DEMO_OUTPUT_FORMAT && GPU_DEMO_OUTPUT_FORMAT
void gpu_demo_output_mode(pJLGPUTaskHead_t head);
gpu_demo_output_mode(head);
#endif
#if defined GPU_DEMO_MASK_SWAP&& GPU_DEMO_MASK_SWAP
void gpu_demo_mask_swap_test(pJLGPUTaskHead_t head);
gpu_demo_mask_swap_test(head);
#endif
#if defined GPU_DEMO_CUSTOM_DRAW && GPU_DEMO_CUSTOM_DRAW
void gpu_demo_custom_draw_demo(pJLGPUTaskHead_t head);
gpu_demo_custom_draw_demo(head);
#endif
#if defined JPEG_DEMO_ENABLE && JPEG_DEMO_ENABLE
void jpeg_demo_test(pJLGPUTaskHead_t head);
jpeg_demo_test(head);
#endif
#if (defined GPU_DEMO_WITH_PSRAM && GPU_DEMO_WITH_PSRAM)
extern void gpu_demo_with_psram(pJLGPUTaskHead_t head);
gpu_demo_with_psram(head);
#endif
}
/* LCD 屏幕初始化 */
static int lcd_flush_finish_cb(void *priv);
static void gpu_port_demo_lcd_init(void)
{
/* 获取LCD设备句柄 */
__this->lcd = lcd_get_hdl();
/* 判断操作LCD必须的API是否存在 */
ASSERT(__this->lcd);
ASSERT(__this->lcd->init);
ASSERT(__this->lcd->get_screen_info);
ASSERT(__this->lcd->buffer_malloc);
ASSERT(__this->lcd->buffer_free);
ASSERT(__this->lcd->draw);
ASSERT(__this->lcd->set_draw_area);
ASSERT(__this->lcd->backlight_ctrl);
/* LCD屏幕初始化 */
if (__this->lcd->init) {
extern const struct ui_devices_cfg ui_cfg_data;
__this->lcd->init((void *)&ui_cfg_data);
}
/* 获取LCD屏幕信息,如屏幕宽、高等 */
if (__this->lcd->get_screen_info) {
__this->lcd->get_screen_info(&__this->info);
}
/* 清空屏幕显示,这里是用DBI模块的硬件纯色推屏功能,没有GPU参与,与LCD debug功能一致。 */
if (__this->lcd->clear_screen) {
__this->lcd->clear_screen(0x000000, 0, __this->info.width - 1, 0, __this->info.height - 1);
}
/* 设置LCD背光亮度等级为 100% */
if (__this->lcd->backlight_ctrl) {
__this->lcd->backlight_ctrl(100);
}
/* 创建buffer信号量 */
os_sem_create(&__this->buffer_sem, 1);
/* 设置DBI模块中断回调 */
lcd_draw_set_callback(lcd_flush_finish_cb);
/* mem 加速缓存,用于减少读flash次数 */
mem_var_init(3 * 1024, false);
ui_resfile_info_init(gpu_port_demo_malloc, gpu_port_demo_free); // UI 资源管理
}
static void gpu_port_demo_port_init(void)
{
/* GPU模块初始化 */
jlgpu_module_init(gpu_port_demo_malloc, gpu_port_demo_free, __this->info.width, __this->info.height);
}
static void gpu_port_demo_task_init(int gpu_out_format)
{
/* 创建一条GPU任务链 */
__this->gpu_task_head = jlgpu_create_task_list_head();
if (!__this->gpu_task_head) {
printf("<gpu_port_demo> gpu create task list head fail!!! ");
return;
}
/* 设置任务链输出的格式 */
jlgpu_set_task_list_out_format(__this->gpu_task_head, gpu_out_format);
}
static void gpu_port_demo_task_free(void)
{
if (__this->gpu_task_head) {
/* 释放GPU任务链,会将任务链中的GPU任务也一并释放 */
jlgpu_delete_task_list_head(__this->gpu_task_head);
__this->gpu_task_head = NULL;
}
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief gpu_port_demo_create_bg_task 创建GPU任务链背景任务
*
* @Params draw_rect GPU背景任务绘制区域
* @Params color GPU背景任务颜色
*
* @notes GPU背景任务本质为一个填充任务,非必须存在,但若无背景任务,LCD无数据区可能显示
* 为乱码内容,因此一般需有一个背景任务将无数据区域填充为纯色。
*/
/* ------------------------------------------------------------------------------------*/
static void gpu_port_demo_create_bg_task(struct rect draw_rect, u32 color)
{
/* 填充颜色拆分为ARGB8888分量 */
u8 a = (color >> 24) & 0xff;
u8 r = (color >> 16) & 0xff;
u8 g = (color >> 8) & 0xff;
u8 b = (color) & 0xff;
/* GPU 填充任务配置参数初始化 */
jlgpu_task_fill_param_init(0, 0, a, r, g, b);
/* 设置GPU填充区域,默认为整个屏幕区域 */
memcpy(&task_param.draw, &draw_rect, sizeof(struct rect));
/* 设置GPU填充任务绘制限制区域,GPU实际绘制为task_param.draw 与 task_param.area 重叠区域,这里也设置为全屏区域 */
jlgpu_get_win_rect(&task_param.area);
/* 创建GPU任务,并添加到gpu_task_head任务链中 */
jlgpu_update_task_by_id(__this->gpu_task_head, 0, 0, &task_param);
}
static void lcd_wait_te()
{
/*
TE同步,等待TE需使用TE中断回调,使用信号量进行同步。为避免函数定义冲突,这里不设置TE等待具体函数
可参考 gpu_task.c 文件中:lcd_wait_te(); lcd_te_sync_int_handler()和lcd_te_sem信号量相关逻辑。
*/
}
/* DBI模块中断回调函数,当DBI模块推完一帧数据,会起来一次中断 */
/* static void lcd_flush_finish_cb(int err) */
static int lcd_flush_finish_cb(void *p)
{
/* 设置推完的buf为空闲状态 */
set_buffer_idle(__this->buffer_hdl, __this->buffer_adr);
/* 发送信号量,允许下一个推屏,lcd_flush_buffer_start会等这个post */
os_sem_post(&__this->buffer_sem);
return 0;
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief lcd_flush_buffer_start 启动DBI模块推屏
*
* @Params buf_hdl buffer管理句柄
* @Params buf_adr buffer地址/待推屏的buf地址
* @Params rect 推屏的区域,与buf_adr指向的地址大小相同
* @Params is_continue 是否续传,每次推屏第一个分块为0,其它分块为1,直到本次推屏结束
*/
/* ------------------------------------------------------------------------------------*/
static void lcd_flush_buffer_start(void *buf_hdl, void *buf_adr, struct rect *rect, int is_continue)
{
/* 将推屏的rect区域转为start和end区域格式 */
int x_start = rect->left;
int x_end = x_start + rect->width - 1;
int y_start = rect->top;
int y_end = y_start + rect->height - 1;
/* 等待上一次DBI模块推完,并起中断。在DBI中断会post这个信号量 */
if (os_sem_pend(&__this->buffer_sem, 100)) {
/* 如果等待DBI模块推完数据超时,这里抛出Error打印 */
printf("Error: wait DBI interrupt time out!\n");
}
/* 把准备要推的buffer设置为使用状态 */
set_buffer_used(__this->buffer_hdl, buf_adr);
/* 把准备要推的buffer放到全局变量记录,等DBI模块推完后再中断中要用 */
__this->buffer_adr = buf_adr;
if (!is_continue) {
/* 仅第一个分块需考虑TE同步 */
lcd_wait_te();
/* 第一个分块用kistart接口进行推屏 */
lcd_draw_kistart(buf_adr, x_start, x_end, y_start, y_end); // DBI kistart
} else {
/* 其它分块用continue接口进行续传即可 */
lcd_draw_continue(buf_adr, x_start, x_end, y_start, y_end); // DBI 续传
}
}
void gpu_port_demo_start_draw(struct rect draw, pJLGPUTaskHead_t head)
{
u8 *buf;
u32 len;
/* 通过LCD的显存申请接口,申请显存buf,用于GPU硬件的输出缓存buf。这个接口会通过LCD屏驱配置的显存buf分块和数量进行申请。 */
__this->lcd->buffer_malloc(&buf, &len);
printf("@@@ disp buf init, buf: 0x%p, len: %d\n", buf, len);
/* 初始化显存buf管理模块,把刚刚申请的显存buf,和LCD屏驱配置的buf数量配置给buffer管理模块,创建buffer管理句柄。 */
buffer_manager_init(__this->buffer_hdl, buf, len, __this->info.buf_num);
/* 获取显存buf的分块行数,这里通过LCD驱动配置的显存buf长度反算,因为显存分块高度并没有被记录,但会反应在显存buf长度上。 */
int lines = len / __this->info.buf_num / __this->info.stride;
printf("@@@ lines: %d\n", lines);
/* 将要绘制的宽、高、分块高度用其它变量缓存,非必须,只是为方便使用。 */
int width = draw.width;
int height = draw.height;
int block_height = lines;
/* 计算分块数量,如果分块高度不能被全屏高度整除,最后一块不足一个分块高度,也要算做一个分块。 */
int block_number = (height % block_height) ? ((height / block_height) + 1) : (height / block_height);
printf("@@@ block_number: %d\n", block_number);
/* 设置LCD推屏区域。注意,这里是start和end的参数,而且刷新屏幕前设置一次要刷新的整个区域即可。 */
lcd_set_draw_area(draw.left, (draw.width - 1), draw.top, (draw.height - 1));
int draw_height;
struct rect draw_rect = {0};
for (int i = 0; i < block_number; i++) {
/* 计算当前需要合成的块高度,考虑最后一个块可能不足一个分块的高度,因此每次合成前需计算本次要合成的块高度。 */
if ((i + 1) * block_height < height) {
draw_height = block_height;
} else {
/* 最后一个分块时,一般不满足预设高度,需计算实际高度才能推屏 */
draw_height = height - (i * block_height);
}
/* 本次循环需要GPU合成的区域 */
draw_rect.left = draw.left + 0;
draw_rect.top = draw.top + i * block_height;
draw_rect.width = width;
draw_rect.height = draw_height;
printf("@@@ cur rect[%d, %d, %d, %d]\n", draw_rect.left, draw_rect.top, draw_rect.width, draw_rect.height);
/* 获取空闲的显存buf */
u8 *idle_buf = NULL;
u32 time_msec1 = jiffies_msec();
do {
/* 尝试获取空闲的显存buf */
idle_buf = get_idle_buffer_to_lock(__this->buffer_hdl);
u32 time_msec2 = jiffies_msec();
if (time_msec2 - time_msec1 > 500) {
/* 如果超过500ms没获取到空闲buf,则打印buffer管理句柄管理的所有buf状态 */
buffer_manager_status_dump(__this->buffer_hdl);
}
} while (!idle_buf);
memset(idle_buf, 0x00, draw_rect.width * draw_rect.height * 2);
/* 设置GPU任务链输出buf和绘制区域 */
jlgpu_set_task_list_out_buf(head, idle_buf, &draw_rect, 0, 0);
/* jlgpu_dump_all_task(head, __func__, __LINE__); */
/* 启动GPU合成 */
jlgpu_task_list_run(head);
/* 设置GPU输出的buf为等待状态,等待DBI模块推屏,到这里buf已经是GPU合成完毕的输出了。 */
set_buffer_pend(__this->buffer_hdl, idle_buf);
/* 启动LCD推屏 */
lcd_flush_buffer_start(__this->buffer_hdl, idle_buf, &draw_rect, i);
}
}
void gpu_demo_task(void *p)
{
/* 初始化LCD设备 */
gpu_port_demo_lcd_init();
/* 初始化GPU任务管理模块 */
gpu_port_demo_port_init();
/* 创建GPU任务链,并设置GPU任务链输出格式为RGB565 */
gpu_port_demo_task_init(GPU_FORMAT_RGB565);
/* 创建GPU背景任务 */
struct rect bg_rect;
bg_rect.left = 0;
bg_rect.top = 0;
bg_rect.width = __this->info.width; // 背景任务绘制区域为全屏
bg_rect.height = __this->info.height;
gpu_port_demo_create_bg_task(bg_rect, GPU_DEMO_BACKGROUND);
/* 创建GPU demo任务 */
gpu_port_demo_create_task(__this->gpu_task_head);
/* 启动GPU合成并输出到LCD屏幕,绘制范围也为全屏 */
gpu_port_demo_start_draw(bg_rect, __this->gpu_task_head);
/* 释放GPU任务链和任务链中的GPU任务 */
gpu_port_demo_task_free();
/* GPU demo 系统任务空转 */
os_time_dly(100);
while (1) {
os_time_dly(100);
}
}
/* 创建GPU demo系统任务 */
int gpu_demo_task_main(void)
{
printf(">>>>>>>>> %s()", __func__);
int err;
err = os_task_create(gpu_demo_task, NULL, 10, 1024 * 5, 512, "gpu_demo_task");
if (err != OS_NO_ERR) {
printf("gpu task creat fail %x\n", err);
}
return 0;
}
static u8 gpu_idle_query(void)
{
return 0;
}
REGISTER_LP_TARGET(gpu_demo_lp_target) = {
.name = "gpu_demo",
.is_idle = gpu_idle_query,
};
#endif