云实验室案例- DRM用户层代码篇 (3)
Pingpong buffer乒乓缓冲
在显示数据处理中,pingpong buffer(乒乓缓冲)是一个非常重要的概念,它也被称作双缓存(double buffer)。
Pingpong buffer的基本概念
Pingpong buffer由两个或多个缓存单元组成。以两个缓冲单元为例,在数据处理的某个阶段,一个缓存单元(buffer A)用于写入数据,而另一个缓存单元(buffer B)则用于显示。当第一个缓存单元(buffer A)渲染完毕后,系统会切换到第二个缓存单元(buffer B)进行写入,同时第一个缓存单元则用于显示。这种交替进行的方式不仅仅局限于显示,他也被应用于多种场景中,可以极大提高了数据传输和处理的效率。
对于显示而言,如果只有一个缓存单元,那么就不可避免的会发生画面撕裂现象。画面撕裂是指屏幕上的一部分显示旧帧的内容,而另一部分显示新帧的内容。这通常发生在画面的更新不是在垂直同步的边界进行时。在单缓冲系统中,由于只有一个缓冲区用于存储图像数据,因此当新图像数据正在被写入时,旧图像数据可能还在被显示,从而导致画面撕裂现象。
显示相关的时序参数
如果用户希望能够显示动态的内容,那就不肯避免的要为显示测试程序创建至少两个缓冲区域。
为保证扩展性,可以为buffer建立数据结构
struct drm_buffer {
__u32 *fb_base;
__u32 buf_id;
};
最简结构中包含两部分内容,一个是映射到用户空间的虚拟地址,方便在用户层对buffer进行绘制;另一个是buffer在DRM中注册的id号,该参数会被传递给送显函数drmModeSetCrtc,告诉DRM当前要显示哪个buffer中的内容。
ret = drmModeSetCrtc(drm_fd, g_crtc_id, pingpong_buffers[current_buffer].buf_id, 0, 0, conn_id, 1, &mode);
此外,由于需要申请两个buffer,因此内存申请部分的代码将要被执行两次。为简化程序,这里直接将两块儿buffer的内容写为固定的单色块。完整程序如下所示:
// SPDX-License-Identifier: GPL-2.0+
/* *
* Copyright 2024 NXP
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#define WIDTH 1280
#define HEIGHT 800
struct drm_buffer {
__u32 *fb_base;
//__u32 handle;
__u32 buf_id;
};
int main()
{
int ret;
int drm_fd;
drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
ret = drmSetMaster(drm_fd);
drmModeRes *res;
drmModeConnector *conn;
res = drmModeGetResources(drm_fd);
conn = drmModeGetConnector(drm_fd, res->connectors[0]);
drmModeEncoder *encoder;
encoder = drmModeGetEncoder(drm_fd, conn->encoders[0]);
int g_crtc_id;
for (int j = 0; j < res->count_crtcs; j++) {
if (encoder->possible_crtcs & (1 << j)) {
g_crtc_id = res->crtcs[j];
}
}
struct drm_buffer pingpong_buffers[2];
__u32 color[2] = {0x00FF0000, 0x0000FF00};
for(int i = 0; i < 2; i++){
struct drm_buffer *buf = &pingpong_buffers[i];
struct drm_mode_create_dumb creq;
struct drm_mode_map_dumb mreq;
memset(&creq, 0, sizeof(creq));
creq.width = WIDTH;
creq.height = HEIGHT;
creq.bpp = 32; //ARGB
ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
__u32 buffer_id;
ret = drmModeAddFB(drm_fd, WIDTH, HEIGHT, creq.bpp, creq.bpp,
creq.pitch, creq.handle, &buffer_id);
memset(&mreq, 0, sizeof(mreq));
mreq.handle = creq.handle;
ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
//buf->handle = creq.handle;
buf->buf_id = buffer_id;
buf->fb_base = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED,
drm_fd, mreq.offset);
for (int j = 0; j < (creq.size / 4); j++)
buf->fb_base[j] = color[i];
}
drmModeModeInfo mode;
memcpy(&mode, &conn->modes[0], sizeof(mode));
__u32 *conn_id = &(res->connectors[0]);
int current_buffer = 0;
while(1){
ret = drmModeSetCrtc(drm_fd, g_crtc_id, pingpong_buffers[current_buffer].buf_id,
0, 0, conn_id, 1, &mode);
current_buffer ^= 1;
//here paint the other buffer
sleep(1);
}
return 0;
}