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位字节上。

posted @ 2024-11-25 19:01  上山砍大树  阅读(22)  评论(0编辑  收藏  举报