微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用C程序写入帧缓冲区非常慢Raspberry Pi

如何解决使用C程序写入帧缓冲区非常慢Raspberry Pi

我想做一个非常密集的模拟。我需要RaspBerry Pi尽可能多的功能。为此,我将带有OS Lite(不带桌面)的卡闪存到Micro SD卡上,并使用此example使用C程序将其写入帧缓冲区。

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

是否有更好(更快)的方法? RaspBerry Pi OS的X Window Manager还必须以某种方式写入帧缓冲区才能正常工作,因此必须有一种更快的方法...

解决方法

该示例程序正在使用put_pixel函数,这是一种经典的图形反模式,它使新手感到仿佛他们无法写出任何不慢的东西。在足够高的优化级别上(如果函数是静态的,则很有可能),编译器也许可以内联它,并且将为每个写入的像素计算帧缓冲区中的偏移量的所有效率排除在外,但这仅仅是错误的抽象层。不应有put_pixel

帧缓冲区只是一个数组,您想直接将其作为数组写入,而不要使用免费的辅助函数。此外,您不需要一遍又一遍地进行类似y*stride+x的操作。如果您要使用某个位置作为数组(或指针)中的索引,则可以通过添加或减去1(水平)或添加或减去步幅(垂直)来寻址相邻像素。这里的“跨距”是通常用于行之间偏移的术语。可能是线宽,也可能是由于对齐或其他考虑而导致的较大值。

,

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

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

  1. 第一个首先写入内存缓冲区,然后将整个缓冲区写入帧缓冲区
  2. 第二个首先写入映射的内存文件(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,&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,line;
  int memfd;
  off_t offset;
  char *mem;
  struct timeval before,delta;

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

  ftruncate(memfd,screensize);

  mem = (char*)mmap(0,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,&offset,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,&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,&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,2963 us

在相同条件下,您共享的原始程序page速度较慢(我修改了循环以使其像前面的两个示例一样写入整个屏幕,我添加了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,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,c);

    }
  }
  gettimeofday(&after,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,&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,88081 us

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。