display test
我们将在三个层面来分析:am-tests
中的display test
是如何输出相应的动画效果的
应用程序
首先在文件am-kernels/tests/am-tests/src/tests/video.c
中,定义了画布canvas的大小为32*32
。
static uint32_t canvas[N][N];
调用函数update()
来更新画布的内容,这样就会产生一张新的图片。
更新完画布内容后,就需要将画布的内容,加载到屏幕上。
这里函数redraw()
的作用就是将画布的像素数据存储到显存中,并且刷新屏幕展示画布内容。
/**
* @brief 将画布 canvas 中的像素数据按行优先的顺序存储到显存中,并通过 io_write 将其绘制到屏幕上
* 参数:
* - canvas[y][x]:表示画布canvas中(y , x)坐标的像素块。y 是行索引,x 是列索引
* - color_buf:一个缓冲区,它的大小是固定的 32 * 32,用来存储单个块的像素数据
*
* 步骤:
* 1. 获取屏幕的宽度和高度,并计算每个块的尺寸
* 2. 遍历 canvas 中的每个像素点,将像素数据填充到 color_buf 中
* 3. 通过 io_write 将 color_buf 中的像素数据传输到显存,绘制每个块
* 4. 最后调用同步操作,刷新屏幕。
*
* @date 2024-11-25
*/
void redraw() {
// 将屏幕分割为 32x32 个小块
int w = io_read(AM_GPU_CONFIG).width / N; // 每个小块的宽度为 w
int h = io_read(AM_GPU_CONFIG).height / N; // 每个小块的高度为 h
int block_size = w * h; // 小块的面积
assert((uint32_t)block_size <= LENGTH(color_buf));
int x, y, k;
// 将画布canvas[32][32] 的像素点,映射到屏幕分割的每个块
for (y = 0; y < N; y ++) {
for (x = 0; x < N; x ++) {
for (k = 0; k < block_size; k ++) {
color_buf[k] = canvas[y][x]; // 将 canvas[y][x] 的颜色值重复填充到 color_buf 中
}
// color_buf保存的是每个屏幕块的像素集合
io_write(AM_GPU_FBDRAW, x * w, y * h, color_buf, w, h, false);
}
}
// 同步操作,通常用来通知显存操作完成并刷新屏幕
io_write(AM_GPU_FBDRAW, 0, 0, NULL, 0, 0, true);
}
运行库IOE
我们可以看到,程序用到了IOE提供的相关接口来实现了以下操作:
-
读取屏幕宽和高的数据
io_read(AM_GPU_CONFIG)
-
将像素块填充到显存的操作
io_write(AM_GPU_FBDRAW, x * w, y * h, color_buf, w, h, false)
-
通知显存操作刷新屏幕的操作
io_write(AM_GPU_FBDRAW, 0, 0, NULL, 0, 0, true);
这里将用到的定义放在这:
#define io_read(reg) ({ reg ## _T __io_param; ioe_read(reg, &__io_param); __io_param; })
enum { AM_GPU_FBDRAW = (11) };
typedef struct { int x, y; void *pixels; int w, h; bool sync; } AM_GPU_FBDRAW_T;
enum { AM_GPU_CONFIG = (9) };
typedef struct { bool present, has_accel; int width, height, vmemsz; } AM_GPU_CONFIG_T;
#define SYNC_ADDR (VGACTL_ADDR + 4)
首先读取屏幕信息的操作展开为:
// io_read
(
{
AM_GPU_CONFIG_T __io_param;
ioe_read(AM_GPU_CONFIG, &__io_param);
__io_param;
}
)
而ioe_read(AM_GPU_CONFIG, &__io_param)
则是
void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
*cfg = (AM_GPU_CONFIG_T) {
.present = true, .has_accel = false,
.width = BITS(inl(VGACTL_ADDR), 31, 16),
.height = BITS(inl(VGACTL_ADDR), 15, 0),
.vmemsz = inl(FB_ADDR)
};
}
此代码从vga控制寄存器所在的内存中,读取对应的高2字节作为宽度,低2字节作为高度。
然后就是将像素块填充到显存的操作展开为:
// io_write(AM_GPU_FBDRAW, x * w, y * h, color_buf, w, h, false);
(
{
AM_GPU_FBDRAW_T __io_param = (AM_GPU_FBDRAW_T) { x * w, y * h, color_buf, w, h, false };
ioe_write(AM_GPU_FBDRAW, &__io_param);
}
)
即首先声明一个AM_GPU_FBDRAW_T
类型的变量__io_param
,其内部成员为:
int x = x * w;
int y = y * h;
void *pixels = color_buf;
int w = w;
int h = h;
bool sync = false;
而向显存写操作ioe_write(AM_GPU_FBDRAW, &__io_param)
对应调用
/**
* @brief 从AM帧缓冲控制器中读取图像信息,写入到显存中
* AM_GPU_FBDRAW_T参数:int x, y; void *pixels; int w, h; bool sync;
* @param AM_GPU_FBDRAW_T *ctl
* @date 2024-11-25
*/
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
// 获取图像在屏幕的位置信息(x, y) 和图像大小信息(w, h)
int x = ctl->x, y = ctl->y, w = ctl->w, h = ctl->h;
// 如果同步寄存器为false 并且 要没有要输出的图像(宽高任意为0),则不做任何操作
if (!ctl->sync && (w == 0 || h == 0)) return;
uint32_t *pixels = ctl->pixels;
// 将图像的像素信息 pixels,写到显存中
uint32_t *fb = (uint32_t *)(uintptr_t)FB_ADDR;
uint32_t screen_w = BITS(inl(VGACTL_ADDR), 31, 16);
for (int j = y; j < y+h; j++) { // 固定行的高度为j
for (int i = x; i < x+w; i++) { // 按列遍历此行的每一个像素点
fb[i + j*screen_w] = pixels[(i-x) + (j - y)*w];
}
}
// 若`sync`为`true`, 则马上将帧缓冲中的内容同步到屏幕上
if (ctl->sync) {
outl(SYNC_ADDR, 1);
}
}
这个函数还蕴含着同步寄存器就在控制寄存器的高4位字节上。