1. 液晶屏的基本概念

  • 像素:
    屏幕上显示颜色的最小单位,英文叫 pixel。注意,位图(如jpg、bmp等格式的常见图片)也是由一个个的像素点构成的,跟屏幕的像素点的概念一样。原理上讲,将一张位图显示到屏幕上,就是将图片上的像素点一个个复制到屏幕像素点上。

  • 分辨率:

    • 宽、高两个维度上的像素点数目。
    • 分辨率越高,所需要的显存越大。

  • 色深:

    • 每个像素所对应的内存字节数,一般有8位、16位、24位或32位
    • GEC6818开发板的屏幕的色深是32位的
    • 32位色深的屏幕一般被称为真彩屏,或1600万色屏。

色深决定了一个像素点所能表达的颜色的丰富程度,色深越大,色彩表现力越强。

2. 内存映射基本原理

虽然LCD设备本质上也可以看作是一个文件,在文件系统中有其对应的设备节点,可以像普通文件一样对其进行读写操作(read/write),但由于对字符设备的读写操作是以字节流的方式进行的,因此除非操作的图像尺寸刚好与屏幕尺寸完全一致,如下图所示,图片的宽高与LCD的宽高完全一致,否则将会画面会乱。

以下是一段直接写设备节点的“不好”的示例代码:

void bad_display()
{// 打开LCD设备int lcd = open("/dev/fb0", O_RDWR);// 从JPG图片中获取ARGB数据char *argbbuf;int   argbsize;argbsize = jpg2rgb("dogs.jpg", &argbbuf);// 将RGB数据直接线性灌入LCD设备节点write(lcd, argbbuf, argbsize);// ...
}

像上述代码这样,直接将数据通过设备节点 /dev/fb0 写入的话,这些数据会自动地从LCD映射内存的入口处(对应LCD屏幕的左上角)开始呈现,并且会以线性的字节流形式逐个字节往后填充,除非图像尺寸与显示器刚好完全一致,否则显示是失败的。

一般而言,图像的尺寸大小是随机的,因此更方便的做法是为LCD做内存映射,将屏幕的每一个像素点跟映射内存一一对应,而映射内存可以是二维数组,因此就可以非常方便地通过操作二维数组中的任意元素,来操作屏幕中的任意像素点了。这里的映射内存,有时被称为显存。

如上图所示,将一块内存与LCD的像素一一对应:

  1. LCD上面显示的图像色彩,由其对应的内存的数据决定
  2. 映射内存的大小至少得等于LCD的真实尺寸大小
  3. 映射内存的大小可以大于LCD的真实尺寸,有利于优化动态画面(视频)体验

下面是屏幕显示为红色的示例代码:

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>int main()
{// 打开液晶屏文件int lcd = open("/dev/fb0", O_RDWR);// 给LCD设备映射一块内存(或称显存)char *p = mmap(NULL, 800*480*4, PROT_WRITE,MAP_SHARED, lcd, 0);// 通过映射内存,将LCD屏幕的每一个像素点涂成红色int red = 0x00FF0000;for(int i=0; i<800*480; i++)memcpy(p+i*4, &red, 4);// 解除映射munmap(p, 800*480*4);return 0;
}

注意,上述代码存在诸多假设,比如屏幕的尺寸是800×480、屏幕色深是4个字节、每个像素内部的颜色分量是ARGB等等,这些信息都是“生搬硬凑”的,只能适用于某一款特定的LCD屏,如果屏幕的这些参数变了,上述代码就无法正常运行了,要想让程序在其他规格尺寸的屏幕下也能正常工作,就得让程序自动获取这些硬件参数信息。

3. 屏幕参数设定

首先明确,屏幕的硬件参数,都是由硬件驱动工程师,根据硬件数据手册和内核的相关规定,填入某个固定的地方的,然后再由应用开发工程师,使用特定的函数接口,将这些特定的信息读出来。

对于GEC6818开发板而言,上述所谓“某个固定的地方”,指的是如下这些重要的结构体(节选):

struct fb_fix_screeninfo
{char id[16];              /* identification string eg "TT Builtin" */unsigned long smem_start; /* Start of frame buffer mem *//* (physical address) */__u32 smem_len;           /* Length of frame buffer mem */__u32 type;               /* see FB_TYPE_*        */__u32 type_aux;           /* Interleave for interleaved Planes */__u32 visual;             /* see FB_VISUAL_*        */ __u16 xpanstep;           /* zero if no hardware panning  */__u16 ypanstep;           /* zero if no hardware panning  */__u16 ywrapstep;          /* zero if no hardware ywrap    */__u32 line_length;        /* length of a line in bytes    */......
};struct fb_var_screeninfo
{__u32 xres;           /* 可见区宽度(单位:像素) */__u32 yres;           /* 可见区高度(单位:像素) */__u32 xres_virtual;   /* 虚拟区宽度(单位:像素) */__u32 yres_virtual;   /* 虚拟区高度(单位:像素) */__u32 xoffset;        /* 虚拟区到可见区x轴偏移量 */__u32 yoffset;        /* 虚拟区到可见区y轴偏移量 */__u32 bits_per_pixel; /* 色深 */// 像素内颜色结构struct fb_bitfield red;   // 红色  struct fb_bitfield green; // 绿色struct fb_bitfield blue;  // 蓝色struct fb_bitfield transp;// 透明度......
};struct fb_bitfield
{__u32 offset;   /* 颜色在像素内偏移量 */__u32 length;   /* 颜色占用数位长度 */......
};

上述结构体的具体定义在系统的如下路径中:

/usr/include/linux/fb.h

如上图所示,如果板卡已经具备LCD的驱动程序,那么应用程序就可以通过 ioctl() 来检索LCD的硬件参数信息。以粤嵌GEC6818开发板配套的群创AT070TN92-7英寸液晶显示屏为例,具体代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <fcntl.h>int lcd;
struct fb_fix_screeninfo fixinfo; // 固定属性
struct fb_var_screeninfo varinfo; // 可变属性void get_fixinfo()
{if(ioctl(lcd, FBIOGET_FSCREENINFO, &fixinfo) != 0){perror("获取LCD设备固定属性信息失败");return;}}void get_varinfo()
{if(ioctl(lcd, FBIOGET_VSCREENINFO, &varinfo) != 0){perror("获取LCD设备可变属性信息失败");return;}
}void show_info()
{// 获取LCD设备硬件fix属性get_fixinfo();printf("\n获取LCD设备固定属性信息成功:\n");printf("[ID]: %s\n", fixinfo.id);printf("[FB类型]: ");switch(fixinfo.type){case FB_TYPE_PACKED_PIXELS:      printf("组合像素\n");break;case FB_TYPE_PLANES:             printf("非交错图层\n");break;case FB_TYPE_INTERLEAVED_PLANES: printf("交错图层\n");break;case FB_TYPE_TEXT:               printf("文本或属性\n");break;case FB_TYPE_VGA_PLANES:         printf("EGA/VGA图层\n");break;}printf("[FB视觉]: ");switch(fixinfo.visual){case FB_VISUAL_MONO01:             printf("灰度. 1=黑;0=白\n");break;case FB_VISUAL_MONO10:             printf("灰度. 0=黑;1=白\n");break;case FB_VISUAL_TRUECOLOR:          printf("真彩色\n");break;case FB_VISUAL_PSEUDOCOLOR:        printf("伪彩色\n");break;case FB_VISUAL_DIRECTCOLOR:        printf("直接彩色\n");break;case FB_VISUAL_STATIC_PSEUDOCOLOR: printf("只读伪彩色\n");break;}printf("[行宽]: %d 字节\n", fixinfo.line_length);// 获取LCD设备硬件var属性get_varinfo();printf("\n获取LCD设备可变属性信息成功:\n");printf("[可见区分辨率]: %d×%d\n", varinfo.xres, varinfo.yres);printf("[虚拟区分辨率]: %d×%d\n", varinfo.xres_virtual, varinfo.yres_virtual);printf("[从虚拟区到可见区偏移量]: (%d,%d)\n", varinfo.xoffset, varinfo.yoffset);printf("[色深]: %d bits\n", varinfo.bits_per_pixel);printf("[像素内颜色结构]:\n");printf("  [红] 偏移量:%d, 长度:%d bits\n", varinfo.red.offset, varinfo.red.length);printf("  [绿] 偏移量:%d, 长度:%d bits\n", varinfo.green.offset, varinfo.green.length);printf("  [蓝] 偏移量:%d, 长度:%d bits\n", varinfo.blue.offset, varinfo.blue.length);printf("  [透明度] 偏移量:%d, 长度:%d bits\n", varinfo.transp.offset, varinfo.transp.length);printf("\n");
}int main()
{lcd = open("/dev/fb0", O_RDWR);if(lcd == -1){perror("打开 /dev/fb0 失败");exit(0);}// 显示LCD设备属性信息show_info();return 0;
}

「课堂练习1」

根据以上示例代码,采用自动获取屏幕硬件参数的方式,在开发板上轮流显示红绿蓝三原色。

4. 多缓冲机制

仔细观察上述显示单色的程序运行效果,会发现屏幕上的颜色不是一瞬间整体显示的,而是有一个很明显的从上到下刷屏的过程,这实际上是由于我们是一个个像素点从左到右,从上到下刷屏导致的,如果不是速度比较快,我们将会看到屏幕上的点是一个个亮起来的,而不是整屏统一更新,这显然不是最佳的体验。

解决这个问题,可以采用多缓冲的办法,首先要搞明白所谓可见区和虚拟区的关系:

  1. 可见区、虚拟区都是内存区域,可见区是虚拟区的一部分,因此可见区尺寸至少等于虚拟区。
  2. 一般而言,可见区尺寸就是屏幕尺寸,比如800×480;而虚拟区是显示设备能支持的显存大小,比如800×480、800×960等。
  3. 为了提高画面体验,一般先在不可见区操作显存数据,然后在调整可见区位置,使得图像“瞬间”呈现,避免闪屏。

下面以示例代码的形式,来分析如何使用多缓冲机制提高画面体验。

  • 1. 设定虚拟区
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>int main()
{// 打开LCD设备int lcd = open("/dev/fb0", O_RDWR|O_EXCL);struct fb_var_screeninfo vinfo; // 显卡设备的可变属性结构体ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo); // 获取可变属性// 获得当前显卡所支持的虚拟区显存大小unsigned long VWIDTH  = vinfo.xres_virtual;unsigned long VHEIGHT = vinfo.yres_virtual;unsigned long BPP = vinfo.bits_per_pixel;printf("虚拟区显存大小为: %d×%d\n", VWIDTH, VHEIGHT);// 申请一块虚拟区大小的映射内存char *p = mmap(NULL, VWIDTH * VHEIGHT * BPP/8,PROT_READ|PROT_WRITE,MAP_SHARED, lcd, 0); if(p != MAP_FAILED){printf("申请显存成功\n");}
}

在开发板运行结果:

[root@GEC6818 ~]#./a.out
虚拟区显存大小为: 800×1440
申请显存成功
[root@GEC6818 ~]#

从上述执行结果来看,粤嵌GEC6818开发板配套的群创AT070TN92-7英寸液晶显示屏支持三倍与屏幕尺寸的虚拟显存的设定。当然,在实际设定的时候,不一定要三倍,也可以是两倍大小,比如800×960。

  • 2. 显示A区,但在B区作画

    为了方便讨论,假设设定两倍屏幕尺寸的虚拟区内存,上半部分为A区,下半部分为B区。如下图所示:

将A区设定为可见区,代码如下:

struct fb_var_screeninfo vinfo; // 显卡设备的可变属性结构体
ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo); // 获取可变属性// 获得当前显卡所支持的虚拟区显存大小
unsigned long width  = vinfo.xres;
unsigned long height = vinfo.yres;
unsigned long bpp    = vinfo.bits_per_pixel;
unsigned long screen_size = width * height * bpp/8;// 申请一块两倍与屏幕的映射内存
char *p = mmap(NULL, 2 * screen_size,PROT_READ|PROT_WRITE,MAP_SHARED, lcd, 0); // 将可见区设定为A区
vinfo.xoffset = 0;
vinfo.yoffset = 0;
ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);// 在B区绘图
int red = 0x00FF0000;
for(int i=0; i<width*height; i++)memcpy(p+screen_size+i*4, &red, 4);

执行上述代码,会发现虽然在B区已经填充了某些图像数据,但是屏幕上没有出现任何反应。

  • 3. 将可见区设定为B区,瞬间出现画面,避免了“闪屏”

    为了方便讨论,假设设定两倍屏幕尺寸的虚拟区内存,上半部分为A区,下半部分为B区。如下图所示:

将B区设定为可见区,代码如下:

vinfo.xoffset = 0;
vinfo.yoffset = 480;
ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);

容易想到,只要交替地改变可见区,使得填充数据的过程对用户不可见,等到数据填充完毕,再通过以上代码瞬间调整可见区区域,用户就能感受到画面流程呈现的体验,避免尴尬的闪屏。

下面是完整的使用“双缓冲”机制交替呈现红绿蓝的代码及演示效果图。

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>int main()
{// 打开LCD设备int lcd = open("/dev/fb0", O_RDWR|O_EXCL);struct fb_var_screeninfo vinfo; // 显卡设备的可变属性结构体ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo); // 获取可变属性// 获得当前显卡所支持的虚拟区显存大小unsigned long width  = vinfo.xres;unsigned long height = vinfo.yres;unsigned long bpp    = vinfo.bits_per_pixel;unsigned long screen_size = width * height * bpp/8;// 申请一块两倍与屏幕的映射内存char *p = mmap(NULL, 2 * screen_size,PROT_READ|PROT_WRITE,MAP_SHARED, lcd, 0); bzero(p, 2*screen_size);// 将起始可见区设定为B区vinfo.xoffset = 0;vinfo.yoffset = 480;ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);int colors[] = {0x00FF0000, 0x0000FF00, 0x000000FF};for(int k=0,n=0;; n++,k++,k%=3){for(int i=0; i<width*height; i++)memcpy(p+ screen_size*(n%2) +i*4, &colors[k], 4);vinfo.xoffset = 0;vinfo.yoffset = 480*(n%2);ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);sleep(1);}
}

节选自:粤嵌-嵌入式课堂笔记

联系人:18028569040(曾小美老师·微信)

Linux-FrameBuffer双缓冲机制显示图像相关推荐

  1. Linux framebuffer双缓冲防止闪烁

    昨天写了一篇文章: 使用Linux Framebuffer绘制32位真彩图形: https://blog.csdn.net/dog250/article/details/90113737 并发了朋友圈 ...

  2. 今儿新学会一个写日志技能:双缓冲机制

    摘要:通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞. 本文分享自华为云社区<奇妙的双缓冲机制写日志(Java实现)>,作者: 洛叶飘 . 写日志面临的问题 写日志在Web ...

  3. QT5开发及实例学习之十七Qt5双缓冲机制

    文章目录 一.原理与设计 二.绘图区的实现 三.主窗口的实现 一.原理与设计   所谓双缓冲机制,是指在绘制控件时,首先将要绘制的内容绘制在一个图片中,再将图片一次性地绘制到控件上.在早期的 Qt 版 ...

  4. 关于Surface的底层双缓冲机制学习

    双缓冲机制 问题的由来 CPU访问内存的速度要远远快于访问屏幕的速度.如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低.这就跟CPU和内 ...

  5. Android SurfaceView的双缓冲机制,引起的闪屏问题

    SurfaceView相关目录 SurfaceView要点 SurfaceView拥有独立的Surface(绘图表面) SurfaceView是用Zorder排序的,他默认在宿主Window的后面,S ...

  6. android缓冲机制,Android自定义View之双缓冲机制和SurfaceView

    Android自定义View系列 双缓冲机制 问题的由来 CPU访问内存的速度要远远快于访问屏幕的速度.如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕, ...

  7. linux 标准IO缓冲机制探究

    一.什么是缓存I/O(Buffered I/O) 缓存I/O又被称作标准I/O,大多数文件系统默认I/O操作都是缓存I/O.在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓 ...

  8. MFC---定时器和双缓冲机制绘制旋转的金刚石图案

    双缓冲原理 MFC中绘制动画的基本思路是在固定时间间隔内绘制图像,然后擦除旧图像再绘制新图像,这样连续         起来就在人类的视觉上形成动画.为了实现这种"绘制-擦除-再绘制&quo ...

  9. 多线程异步日志系统,高效、强悍的实现方式-双缓冲

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 目录 文章目录 单片机中常用的环形缓冲区 多线程异步日志:双缓冲机制 双缓冲机制为什么高效 尽可能的降低 Lock 的时 ...

最新文章

  1. 劝大家逃离互联网!某前互联网员工自述:从互联网到传统行业,工资多,不加班,有户口,能买房!...
  2. 影像组学视频学习笔记(11)-支持向量机(SVM)(理论)、Li‘s have a solution and plan.
  3. 有始有终,设计一个结构合理的下载模块
  4. 计算机视觉——利用openCV与Socket结合进行远程摄像头实时视频传输并保存图片数据
  5. Python中单个下划线“_”变量的目的是什么?
  6. 转:SQL Server游标的使用
  7. 《Android进阶之光》--View体系与自定义View
  8. webService上传图片
  9. php url 合法字符串_PHP函数补完:http_build_query()构造URL字符串
  10. Android自定义弹窗页面,Android编程实现的自定义弹窗(PopupWindow)功能示例
  11. settimeout在各个浏览器的最小时间
  12. 运筹学 美国人在计算机上实现的四,运筹学试卷及答案.
  13. mysql hsqldb_HSQLDB的使用方法
  14. 【Ubuntu】用g++生成动态库
  15. style是什么意思
  16. Python-猫耳MF
  17. 应届生软件测试面经_软件测试实习生面试经验 - 共180条真实软件测试实习生面试经验分享 - 职业圈...
  18. MacOS系统上有什么好用的思维导图软件?
  19. Aquarius's Trial F - 6 HDU - 2102 A计划
  20. ubuntu小知识点--常用命令以及操作

热门文章

  1. python爬虫-豆瓣爬取数据保存为html文件
  2. RPG Maker MV-游戏边框
  3. BM00008——|bookmarks|V|DOS常用命令|
  4. 我,一个靠GitHub打赏谋生的码农,年入十万美元
  5. asp从服务器下载文件的几种方法
  6. 大学物理·第2章牛顿定律
  7. linux 虚拟机安装(red hat)
  8. 基于html5的音乐网站,基于C#和HTML5的在线音乐网站设计
  9. 【人工智能简史】第三章 第一个AI研究的黄金时代
  10. IDEA中启动服务器和访问项目