图形服务使用内存缓冲区来制作屏幕图像并将整个缓冲区或仅将显示的修改部分写入屏幕设备(例如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, 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
您分享的page中的原始程序在相同条件下速度较慢(我修改了循环使其像前两个示例一样写入整个屏幕,我添加了内联和静态关键字并编译它 - 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