这是一个关于如何编程的文档,因此,请在你编译或执行例子之前,正确配置你的framebuffer设备。
用framebuffer设备,你可以把你的计算机屏幕当成一个真正的图形设备。 你可以修改分辨率,刷新率,色彩深度等。最好的一点是,你可以把像素点绘在任何你想要的地方。framebuffer设备不是一个图形库,而更确切的是一 个低级的通用设备。这样创造了巨大的灵活性,但同时也有它的缺点。想使用framebuffer设备,你应该做以下事情:
- 断定出你使用的设备
- 打开设备
- 取回或改变屏幕设置
- 映射(Map)屏幕内存
通常要打开的设备是/dev/fb0,但是如果用户有多个视频卡和监视器的话,设备也可能不同。大多数应用通过读取环境变量FRAMEBUFFER (用getenv();)
来决定该使用哪个设备。如果该环境变量不存在,那么就用/dev/fb0。
通过open()调用打开设备,读设备意味着读取屏幕内存(可称之为显存)。用$cat /dev/fb0 >screenshot将屏幕内存导入一个文件,恢复刚才的屏幕截图则可使用:$cat
screenshot >/dev/fb0。
显然,用上述方法使用屏幕内存并不经济方便。在读或写之前持续的寻址(见man lseek)将会导致很多的开销。这就是为什么你要映射你的屏幕内存。当你将屏幕内存映射到你的应用程序时,你将得到一个直接指向屏幕内存的指针。
在我们可以映射屏幕内存之前,我们需要知道我们能够映射多少,以及我们需要映射多 少。第一件要做的事情就是从我们新得到的framebuffer设备取回信息。有两个结构包含着我们需要的信息,第一个包含固定的屏幕信息,这部分是由硬 件和驱动的能力决定的;第二个包含着可变的屏幕信息,这部分是由硬件的当前状态决定的,可以由用户空间的程序调用ioctl()来改变。
- struct fb_fix_screeninfo {
- char id[16]; ???????????????????????
-
-
- unsigned long smem_start;
-
-
-
- __u32 smem_len;
- __u32 type;
- __u32 type_aux;
-
- __u32 visual;
- __u16 xpanstep;
-
- __u16 ypanstep;
-
- __u16 ywrapstep;
-
- __u32 line_length;
-
-
- unsigned long mmio_start;
-
-
-
- __u32 mmio_len;
-
-
- __u32 accel;
-
- __u16 reserved[3];
-
-
- };
在这里非常重要的域是smem_len和line-length。smem-len告诉我们framebuffer设备的大小,第二个域告诉我们指针应该前进多少字节去得到下一行的数据。第二个结构则要有意思的多,它给了我们可以改变的信息。
-
-
- struct fb_bitfield {
- __u32 offset;
- __u32 length;
- __u32 msb_right;
-
- };
-
- struct fb_var_screeninfo {
- __u32 xres;
- __u32 yres;
- __u32 xres_virtual;
- __u32 yres_virtual;
- __u32 xoffset;
- __u32 yoffset;
- __u32 bits_per_pixel;
- __u32 grayscale;
-
-
- struct fb_bitfield red;
-
-
- struct fb_bitfield green;
-
-
- struct fb_bitfield blue;
- struct fb_bitfield transp;
-
- __u32 nonstd;
- __u32 activate;
- __u32 height;
-
- __u32 width;
-
- __u32 accel_flags;
-
-
- __u32 pixclock;
- __u32 left_margin;
- __u32 right_margin;
- __u32 upper_margin;
- __u32 lower_margin;
- __u32 hsync_len;
- __u32 vsync_len;
- __u32 sync;
- __u32 vmode;
- __u32 reserved[6];
-
- };
前几个成员决定了分辨率。xres和yres 是在屏幕上可见的实际分辨率,在通常的vga模式将为640和400(也许是480,by highbar)。*res-virtual决定了构建屏幕时视频卡读取屏幕内存的方式。当实际的垂直分辨率为400,虚拟分辨率可以是800。这意味着 800行的数据被保存在了屏幕内存区中。因为只有400行可以被显示,决定从那一行开始显示就是你的事了。这个可以通过设置*offset来实现。给 yoffset赋0将显示前400行,赋35将显示第36行到第435行,如此重复。这个功能在许多情形下非常方便实用。它可以用来做双缓冲。双缓冲就是 你的程序分配了可以填充两个屏幕的内存。将offset设为0,将显示前400行(假设是标准的vga),同时可以秘密的在400行到799行构建另一个 屏幕,当构建结束时,将yoffset设为400,新的屏幕将立刻显示出来。现在将开始在第一块内存区中构建下一个屏幕的数据,如此继续。这在动画中十分 有用。
另外一个应用就是用来平滑的滚动整个屏幕。就像在前面屏幕中一样,在内存分配800行的空间。每隔10毫秒设定一个定时器(timer,见man settimer和man
signal / man sigaction),将offset设为1或是比上次更多,瞧,你看到了一个平滑滚动的屏幕。确保你的信号(signal)不要因为最佳输出的原因被信号处理程序阻塞。
将
bits_per_pixel 设为1,2,4,8,16,24或32来改变颜色深度(color depth)。不是所有的视频卡和驱动都支持全部颜色深度。当颜色深度改变,驱动将自动改变fb-bitfields。这些指出,在一个特定的颜色基准 上,多少和哪些比特被哪种颜色使用。如果bits-per-pixel小于8,则fb-bitfields将无定义而且颜色映射将启用。
在fb-var-screeninfo结构结尾的定时的设置是当你选择一个新的分辨率的时候用来设定视频定时的。(
EXAMINE AND EXPLAIN TIMINGS! )
现在我们知道了结构的细节了,但还不清楚如何去获得或设置它们。这里有一些ioctl的命令可以参考。
C:
- #include
- int main () {
- int framebuffer_handler;
- struct fb_fix_screeninfo fixed_info;
- struct fb_var_screeninfo variable_info;
- open ("/dev/fb0", O_RDWR);
- ioctl (framebuffer_handler,FBIOGET_VSCREENINFO, &variable_info);
- variable_info.bits_per_pixel = 32;
- ioctl(framebuffer_handler, FBIOPUT_VSCREENINFO, &variable_info);
- ioctl (framebuffer_handler,FBIOGET_FSCREENINFO, &fixed_info);
- variable_info.yoffset = 513;
- ioctl (framebuffer_handler,FBIOPAN_DISPLAY, &variable_info);
- }
这些 FBIOGET_*?的ioctl命令将请求的信息写入最后一个变量所指向的结构体中。FBIOPUT_VSCREENINFO将所有提供的信息复制回内核。如果内核不能激活新的设置,将返回-1。而
FBIOPAN_DISPLAY 也从用户复制信息,但并不重新初始化视频模式。最好在只有xoffset或yoffset改变时使用。
去访问内存本身,它可以被映射,然后直接访问。但这些需要一些步骤:
-
计算出需要映射多少
-
映射内存
-
决定如何构建屏幕
-
向屏幕中写入数据
这里有一个样例:
C:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- int main() {
- int framebuffer_device;
- int line_size,buffer_size, *i;
- int *screen_memory;
- struct fb_var_screeninfo var_info;
- framebuffer_device = open ( "/dev/fb0" , O_RDWR);
- ioctl (framebuffer_device, FBIOGET_VSCREENINFO, &var_info);
- line_size = var_info.xres * var_info.bits_per_pixel / 8;
- buffer_size = line_size * var_info.yres;
- var_info.xoffset = 0;
- var_info.yoffset = 0;
- ioctl (framebuffer_device, FBIOPAN_DISPLAY,&var_info) == -1);
- screen_memory = (char *) mmap (513, buffer_size, PROT_READ
- | PROT_WRITE, MAP_SHARED, framebuffer_device, 0);
- for (i=0;i < buffer_size ; i++ )
- {
- *(screen_memory+i) = i%236;
- }
- sleep(2);
- return 0;
- }
你可以看到我们采用了从上一部分提到的信息取回,在这一部分新用到的是mmap函 数。第一个变量在这种情形下可以忽略,第二个是映射的内存大小,第三个变量声明我们将共享内存进行读和写。第四个变量表示这段内存将和其他进程共享。在 framebuffer上面建一个MAP_PRIVATE是不可能的。 通常这意味着你需要中断控制台的切换去备份和恢复屏幕内容,而且不在自己没有权利的时候向屏幕内存写东西。
关于mmap的更多信息请见man mmap.
---------------------------------
Framebuffer简单编程例子(转贴)
http://shinco-js.spaces.live.com/Blog/cns!94EC79D6B044343C!629.entry
大家都知道Unix/Linux系统是 由命令驱动的。那么最基本的系统是命令行的(就是想DOS一样的界面)。X-Window-System是 Unix/Linux上的图形系统,它是通过X-Server来控制硬件的。但有一些Linux的发行版在引导的时候就会在屏幕上出现图形,这时的图形是 不可能由X来完成的,那是什么机制呢?答案是FrameBuffer。
FrameBuffer不是一个图形系统,更不是窗口系统。它比X要低级,简单来说FrameBuffer就是一种机制的实现。这种机制是把屏幕上的每个 点映射成一段线性内存空间,程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。X的高度可移植性就是来自于这种机制,不管是在那种图形环境下,只 要有这种机制的实现就可以运行X。所以在几乎所有的平台上都有相应的X版本的移植。
好了,闲话少说,下面我们来看看可以利用FrameBuffer来干点什么。首先看看你是否有了相应的驱动:找一下在/dev/下是否有fb*这个设备文件,这是个字符类的特殊文件。
代码:
- ls -l /dev/fb0 (Enter)
- crw-rw---- 1 root video 29, 0 Jan 27 15:32 /dev/fb0
如果没有这个文件也可以找找其他的比如:/dev/fb1,/dev/fb2...如果找不到这些文件,那就得重新编译内核了。下面假设存在这个文件/dev/fb0,这就是FrameBuffer的设备文件。
有了这个我们可以play with FrameBuffer了。(一下的操作不一定要在X下,可以在启动了FrameBuffer的虚拟控制台下)
代码:
- cat /dev/fb0 > sreensnap
- ls -l sreensnap
- -rw-r--r-- 1 wsw wsw 6291456 Jan 27 21:30 sreensnap
我们得到了一个恰好6M的文件,再做下面的操作:
代码:
- clear /*清楚屏幕的输出*/
- cat sreensnap > /dev/fb0
是不是奇怪的事情发生了?好像是中了病毒一般?屏幕又恢复了以前的状态?不用着急,
通过以上的操作,我想你也猜到了。文件/dev/fb0就是控制屏幕上的每一点的颜色的文件。我们可以写程序来改变这个文件的内容,就可以方便的在屏幕上画图了:-)
我下面就来写一个小程序,探测一下屏幕的属性。
代码:
- #include <unistd.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <linux/fb.h>
- #include <sys/mman.h>
-
- int main () {
- int fp=0;
- struct fb_var_screeninfo vinfo;
- struct fb_fix_screeninfo finfo;
- fp = open ("/dev/fb0",O_RDWR);
-
- if (fp < 0){
- printf("Error : Can not open framebuffer device\n");
- exit(1);
- }
-
- if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo)){
- printf("Error reading fixed information\n");
- exit(2);
- }
-
- if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)){
- printf("Error reading variable information\n");
- exit(3);
- }
-
- printf("The mem is :%d\n",finfo.smem_len);
- printf("The line_length is :%d\n",finfo.line_length);
- printf("The xres is :%d\n",vinfo.xres);
- printf("The yres is :%d\n",vinfo.yres);
- printf("bits_per_pixel is :%d\n",vinfo.bits_per_pixel);
- close (fp);
- }
struct fb_var_screeninfo 和 struct fb_fix_screeninfo 两个数据结构是在/usr/include/linux/fb.h中定义的,里面有些有趣的值:(都是无符号32位的整数)
在fb_fix_screeninfo中有
__u32 smem_len 是这个/dev/fb0的大小,也就是内存大小。
__u32 line_length 是屏幕上一行的点在内存中占有的空间,不是一行上的点数。
在fb_var_screeninfo 中有
__u32 xres ,__u32 yres 是x和y方向的分辨率,就是两个方向上的点数。
__u32 bits_per_pixel 是每一点占有的内存空间。
把上面的程序编译以后运行,在我的机器上的结果如下:
代码:
- The mem is :6291456
- The line_length is :4096
- The xres is :1024
- The yres is :768
- bits_per_pixel is :32
内存长度恰好是6M,每行占有4M的空间,分辨率是1024x768,色彩深度是32位。细心的你可能已经发现有些不 对。屏幕上的点有1024x768= 786432个,每个点占有32比特。屏幕一共的占有内存数为32x786432=25165824 就是3145728字节,恰好是3M但是上面的程序告诉我们有6M的存储空间。这是因为在现代的图形系统中大多有缓冲技术,显存中存有两页屏幕数据,这是 方便快速的改变屏幕内容实现动画之类比较高的要求。关于这种缓冲技术有点复杂,我们目前先不讨论。对于我们来说只有这3M内存来存放这一个屏幕的颜色数 据。
好了,现在你应该对FrameBuffer有一个大概的了解了吧。那么接下来你一定会想在屏幕上画一些东西,让我们先从画一个点开始吧。先说说我的想法: 在类Unix系统中,一切东西都是文件。我们对屏幕的读写就可以转换成对/dev/fb0的读写。那么就把/dev/fb0用open打开,再用 lseek定位要读写的位置,最后调用read或者write来操作。通过这么一大段的操作我们才完成了对一个点的读或者写。这种方法开销太大了。还有一 种方法,我们把/dev/fb0映射到程序进程的内存空间中来,然后得到一个指向这段存储空间的指针,这样就可以方便的读写了。但是我们要知道能映射多少 和该映射多少,这能很方便的从上面一个程序得出的参数来决定。
下面是程序代码:
代码:
- #include <unistd.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <linux/fb.h>
- #include <sys/mman.h>
-
- int main () {
- int fp=0;
- struct fb_var_screeninfo vinfo;
- struct fb_fix_screeninfo finfo;
- long screensize=0;
- char *fbp = 0;
- int x = 0, y = 0;
- long location = 0;
- fp = open ("/dev/fb0",O_RDWR);
-
- if (fp < 0){
- printf("Error : Can not open framebuffer device\n");
- exit(1);
- }
-
- if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo)){
- printf("Error reading fixed information\n");
- exit(2);
- }
-
- if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)){
- printf("Error reading variable information\n");
- exit(3);
- }
-
- screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
-
- fbp =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp,0);
- if ((int) fbp == -1)
- {
- printf ("Error: failed to map framebuffer device to memory.\n");
- exit (4);
- }
-
- x = 100;
- y = 100;
- location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length;
-
- *(fbp + location) = 100;
- *(fbp + location + 1) = 15;
- *(fbp + location + 2) = 200;
- *(fbp + location + 3) = 0;
- munmap (fbp, screensize);
- close (fp);
- return 0;
-
- }
因为这是对线性存储空间的读写,所以代码有点不清晰,不易理解。但是有了这个基本的代码实现,我们可以很容易写一些DrawPoint之类的函数去包装一下低层的对线性存储空间的读写。但是有了画点的程序,再写出画线画圆的函数就不是非常困难了。