首页 > 解决方案 > 使用 C 程序写入帧缓冲区非常慢(Raspberry Pi)

问题描述

我想做一个非常密集的模拟。我需要 Raspberry Pi 提供尽可能多的电力。为了做到这一点,我将带有 OS Lite(无桌面)的卡刷到 Micro SD 卡上,并使用此示例使用 C 程序写入帧缓冲区。

结果非常慢。我可以看到图像正在更新并从上到下扫描。它很长:0.2s左右。这意味着我永远不会获得 30 或 60 fps。

有没有更好(更快)的方法来做到这一点?Raspberry Pi OS 的 X 窗口管理器也必须以某种方式写入帧缓冲区才能工作,所以必须有更快的方法......

标签: clinuxraspberry-piframebuffer

解决方案


图形服务使用内存缓冲区来制作屏幕图像并将整个缓冲区或仅将显示的修改部分写入屏幕设备(例如剪辑)。

因此,这里有两个稍微增强的程序版本:

  1. 第一个先写入内存缓冲区,然后将整个缓冲区写入帧缓冲区
  2. 第二个首先写入 mmapped 内存文件 ( memfd_create() ) 并使用sendfile()系统调用将文件复制到帧缓冲区

在两者中,还添加了对gettimeofday()的调用,以测量显示期间经过的时间。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/time.h>

// 'global' variables to store screen info
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
size_t screensize = 0;

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
void draw() {

  int x, y, line;
  char *buffer;
  struct timeval before, after, delta;

  buffer = (char *)malloc(screensize);

  for (y = 0; y < vinfo.yres; y++) {
    line = y * finfo.line_length;
    for (x = 0; x < vinfo.xres; x++) {

      // color based on the 16th of the screen width
      int c = 16 * x / vinfo.xres;
    
      // call the helper function
      buffer[x + line] = c;
    }
  }

  gettimeofday(&before, NULL);  
  memcpy(fbp, buffer, screensize);
  gettimeofday(&after, NULL);
  timersub(&after, &before, &delta);

  printf("Display duration: %lu s, %lu us\n", delta.tv_sec, delta.tv_usec);

  free(buffer);
}

void sighdl(int sig)
{
  printf("SIGINT\n");
}

// application entry point
int main(int ac, char* av[])
{
  int fbfd = 0;
  struct fb_var_screeninfo orig_vinfo;

  signal(SIGINT, sighdl);

  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // Store for reset (copy vinfo to vinfo_orig)
  memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

  // Change variable info
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }

  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  // map fb to user mem 
  screensize = vinfo.xres * vinfo.yres;
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 
                    0);

  if ((int)fbp == -1) {
    printf("Failed to mmap.\n");
  }
  else {
    // draw...
    draw();

    // If no parameter, pause until a CTRL-C...
    if (ac == 1)
      pause();
  }

  // cleanup
  munmap(fbp, screensize);
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
    printf("Error re-setting variable information.\n");
  }
  close(fbfd);

  return 0;
  
}

第二个程序sendfile()

#define _GNU_SOURCE  // for memfd_create()
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/sendfile.h>
#include <sys/time.h>

// 'global' variables to store screen info
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
int fbfd = 0;
size_t screensize = 0;

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
void draw() {

  int x, y, line;
  int memfd;
  off_t offset;
  char *mem;
  struct timeval before, after, delta;

  memfd = memfd_create("framebuf", 0);
  if (memfd < 0) {
    fprintf(stderr, "memfd_create(): %m");
    return;
  }

  ftruncate(memfd, screensize);

  mem = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    memfd,
                    0);
  if (mem == MAP_FAILED) {
    fprintf(stderr, "mmap(): %m");
    return;
  }

  // Fill the memory buffer
  for (y = 0; y < vinfo.yres; y++) {
    line = y * finfo.line_length;
    for (x = 0; x < vinfo.xres; x++) {

      // color based on the 16th of the screen width
      int c = 16 * x / vinfo.xres;

      mem[x + line] = c;
    }
  }

  // Copy the buffer into the framebuffer
  offset = 0;
  gettimeofday(&before, NULL);
  sendfile(fbfd, memfd, &offset, screensize);
  gettimeofday(&after, NULL);
  timersub(&after, &before, &delta);

  printf("Display duration: %lu s, %lu us\n", delta.tv_sec, delta.tv_usec);

  munmap(mem, screensize);
  close(memfd);

}


void sighdl(int sig)
{
  printf("SIGINT\n");
}


// application entry point
int main(int ac, char* av[])
{
  struct fb_var_screeninfo orig_vinfo;

  signal(SIGINT, sighdl);

  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // Store for reset (copy vinfo to vinfo_orig)
  memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

  // Change variable info
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }

  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  screensize = vinfo.xres * vinfo.yres;

  // draw...
  draw();

  // If no parameter, pause until a CTRL-C...
  if (ac == 1)
    pause();

  // cleanup
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
    printf("Error re-setting variable information.\n");
  }
  close(fbfd);

  return 0;
  
}

如果程序在没有参数的情况下启动,它会暂停,直到用户键入 CTRL-C 否则,它会在显示后立即返回。在运行 Linux 32 位的 Raspberry Pi 3 B+ 上:

$ gcc fb1.c -o fb1
$ gcc fb2.c -o fb2
$ ./fb1 arg  # argument to make it return immediately
The framebuffer device was opened successfully.
Original 1920x1080, 32bpp
Display duration: 0 s, 2311 us
$ ./fb2 arg   # argument to make it return immediately
The framebuffer device was opened successfully.
Original 1920x1080, 32bpp
Display duration: 0 s, 2963 us

您分享的页面中的原始程序在相同条件下速度较慢(我修改了循环使其像前面两个示例一样写入整个屏幕,我添加了 inline 和 static 关键字并使用 -O3 编译它):

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/time.h>

// 'global' variables to store screen info
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

// helper function to 'plot' a pixel in given color
static inline void put_pixel(int x, int y, int c)
{
  // calculate the pixel's byte offset inside the buffer
  unsigned int pix_offset = x + y * finfo.line_length;

  // now this is about the same as 'fbp[pix_offset] = value'
  *((char*)(fbp + pix_offset)) = c;

}

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
static void draw() {

  int x, y;
  struct timeval before, after, delta;

  gettimeofday(&before, NULL);
  for (y = 0; y < vinfo.yres; y++) {
    for (x = 0; x < vinfo.xres; x++) {

      // color based on the 16th of the screen width
      int c = 16 * x / vinfo.xres;
    
      // call the helper function
      put_pixel(x, y, c);

    }
  }
  gettimeofday(&after, NULL);
  timersub(&after, &before, &delta);

  printf("Display duration: %lu s, %lu us\n", delta.tv_sec, delta.tv_usec);
}

static void sighdl(int sig)
{
  printf("SIGINT\n");
}

// application entry point
int main(int ac, char* av[])
{

  int fbfd = 0;
  struct fb_var_screeninfo orig_vinfo;
  long int screensize = 0;

  signal(SIGINT, sighdl);

  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // Store for reset (copy vinfo to vinfo_orig)
  memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

  // Change variable info
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }

  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  // map fb to user mem 
  screensize = vinfo.xres * vinfo.yres;
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 
                    0);

  if ((int)fbp == -1) {
    printf("Failed to mmap.\n");
  }
  else {
    // draw...
    draw();

    // If no parameter, pause until a CTRL-C...
    if (ac == 1)
      pause();
  }

  // cleanup
  munmap(fbp, screensize);
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
    printf("Error re-setting variable information.\n");
  }
  close(fbfd);

  return 0;
  
}
$ gcc -O3 fb0.c -o fb0
$ ./fb0 arg      # argument to make it return immediately
The framebuffer device was opened successfully.
Original 1920x1080, 32bpp
Display duration: 0 s, 88081 us

推荐阅读