Graphic services use an in-memory buffer to make the screen image and write the whole buffer or only the modified parts of the display into the screen device (e.g. clipping).
So, here are two slightly enhanced versions of the program:
- The first one writes first in a in-memory buffer and then write the whole buffer into the frame buffer
- The second one writes first in a mmapped in-memory file (memfd_create()) and use the sendfile() system call to copy the file into the frame buffer
In both, a call to gettimeofday() has also been added to measure the elapsed time during the display.
#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;
}
Second program with 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;
}
If the program is launched without parameter, it pauses until the user type CTRL-C otherwise, it returns immediately after the display. On a Raspberry Pi 3 B+ running Linux 32-bits:
$ 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
The original program in the page that you shared is slower in the same conditions (I modified the loop to make it write the whole screen as in the previous two examples, I added inline and static keywords and compiled it with -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