硬件软件系统方案应用范例智能车大赛技术支持学习园地关于我们恩智浦官网

DRM用户层代码篇 (3) Pingpong buffer乒乓缓冲

云实验室案例- 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;
}