用 C 程序写入帧缓冲区非常慢 (Raspberry Pi)
Writing into framebuffer with a C program is very slow (Raspberry Pi)
我想做一个非常密集的模拟。我需要 Raspberry Pi 尽可能多的能量。为了做到这一点,我将带有 OS Lite(没有桌面)的卡闪存到 Micro SD 卡上,并使用此 example.
通过 C 程序写入帧缓冲区
结果非常慢。我可以看到图像正在更新并从上到下扫描。它很长:0.2 秒左右。这意味着我将永远无法获得 30 或 60 fps。
有没有更好(更快)的方法来做到这一点? Raspberry Pi OS 的 X Window 管理器也必须以某种方式写入帧缓冲区才能工作,因此必须有更快的方法...
该示例程序使用的是 put_pixel
函数,这是一种经典的图形反模式,它会让新手感到困惑,觉得他们写不出任何不慢的东西。在足够高的优化级别(如果函数是静态的,则更有可能),编译器可能能够将其内联并排除为您编写的每个像素计算帧缓冲区中的偏移量的所有低效率,但这只是在错误的抽象层工作。不应该有 put_pixel
.
帧缓冲区只是一个数组,您想直接将其作为数组写入,而不是使用无偿的辅助函数。此外,您不需要一遍又一遍地执行 y*stride+x
之类的操作。如果你有一个你正在工作的位置作为数组中的索引(或指针),你可以通过添加或减去 1(水平)或添加或减去步幅(垂直)来寻址相邻像素。这里的“步幅”是一个常用的术语,用于表示行与行之间的偏移;它可能是线宽,或者由于对齐或其他考虑而导致的一些更大的值。
图形服务使用内存缓冲区制作屏幕图像,并将整个缓冲区或仅显示的修改部分写入屏幕设备(例如 clipping)。
所以,这里有两个稍微增强的程序版本:
- 第一个先写入内存缓冲区,然后将整个缓冲区写入帧缓冲区
- 第二个首先写入内存映射文件(memfd_create()) and use the 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 否则,它会在显示后立即 returns。在 Raspberry Pi 3 B+ 运行 Linux 32 位上:
$ 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
你分享的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, 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
我想做一个非常密集的模拟。我需要 Raspberry Pi 尽可能多的能量。为了做到这一点,我将带有 OS Lite(没有桌面)的卡闪存到 Micro SD 卡上,并使用此 example.
通过 C 程序写入帧缓冲区结果非常慢。我可以看到图像正在更新并从上到下扫描。它很长:0.2 秒左右。这意味着我将永远无法获得 30 或 60 fps。
有没有更好(更快)的方法来做到这一点? Raspberry Pi OS 的 X Window 管理器也必须以某种方式写入帧缓冲区才能工作,因此必须有更快的方法...
该示例程序使用的是 put_pixel
函数,这是一种经典的图形反模式,它会让新手感到困惑,觉得他们写不出任何不慢的东西。在足够高的优化级别(如果函数是静态的,则更有可能),编译器可能能够将其内联并排除为您编写的每个像素计算帧缓冲区中的偏移量的所有低效率,但这只是在错误的抽象层工作。不应该有 put_pixel
.
帧缓冲区只是一个数组,您想直接将其作为数组写入,而不是使用无偿的辅助函数。此外,您不需要一遍又一遍地执行 y*stride+x
之类的操作。如果你有一个你正在工作的位置作为数组中的索引(或指针),你可以通过添加或减去 1(水平)或添加或减去步幅(垂直)来寻址相邻像素。这里的“步幅”是一个常用的术语,用于表示行与行之间的偏移;它可能是线宽,或者由于对齐或其他考虑而导致的一些更大的值。
图形服务使用内存缓冲区制作屏幕图像,并将整个缓冲区或仅显示的修改部分写入屏幕设备(例如 clipping)。
所以,这里有两个稍微增强的程序版本:
- 第一个先写入内存缓冲区,然后将整个缓冲区写入帧缓冲区
- 第二个首先写入内存映射文件(memfd_create()) and use the 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 否则,它会在显示后立即 returns。在 Raspberry Pi 3 B+ 运行 Linux 32 位上:
$ 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
你分享的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, 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