问题描述
我想做一个非常密集的模拟。我需要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)。
因此,这是该程序的两个稍微增强的版本:
- 第一个首先写入内存缓冲区,然后将整个缓冲区写入帧缓冲区
- 第二个首先写入映射的内存文件(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