Framebuffer介绍与编程

 

 

(以下为Wiebe Zoon ;wiebe@linvision.com的一部分,原文为英文版,本文由highbar翻译)

 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];  /*dentification 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???*/
unsigned long mmio_start; /*Start of Memory Mapped I/O?? */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O?*/
__u32 accel; /* Type of acceleration available */
__u16 reserved[3]; /* Reserved for future compatibility*/
};

在这里非常重要的域是smem_len和line-length。smem-len告诉我们framebuffer设备的大小,第二个域告诉我们指针应该前进多少字节去得到下一行的数据。第二个结构则要有意思的多,它给了我们可以改变的信息。

/* more kernel header files copied shamelessly */

struct fb_bitfield {
__u32 offset; /* beginning of bitfield */
__u32 length; /* length of bitfield */
__u32 msb_right; /* != 0 : Most significant bit is */
/* right */
};

struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */

__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Graylevels instead of colors*/

struct fb_bitfield red; /*bitfield in fb mem if true color, */
struct fb_bitfield green; /*else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /*transparency */

__u32 nonstd; /* != 0 Non standard pixel format */

__u32 activate; /* see FB_ACTIVATE_* */

__u32 height; /* height of picture in mm*/
__u32 width; /* width of picture in mm*/

__u32 accel_flags; /* acceleration flags (hints) */

/* Timing: All values in pixclocks, except pixclock (ofcourse) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 reserved[6]; /* Reserved for future compatibility*/
};

前几个成员决定了分辨率。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:

  1. #include
  2. int main () {
  3. int framebuffer_handler;
  4. struct fb_fix_screeninfo fixed_info;
  5. struct fb_var_screeninfo variable_info;
  6. open ("/dev/fb0", O_RDWR); /*in real life, check every ioctl if it returns -1 */
  7. ioctl (framebuffer_handler,FBIOGET_VSCREENINFO, &variable_info); variable_info.bits_per_pixel = 32;
  8. ioctl(framebuffer_handler, FBIOPUT_VSCREENINFO, &variable_info);
  9. ioctl (framebuffer_handler,FBIOGET_FSCREENINFO, &fixed_info);
  10. variable_info.yoffset = 513;
  11. ioctl (framebuffer_handler,FBIOPAN_DISPLAY, &variable_info);
  12. }

 

这些 FBIOGET_*?的ioctl命令将请求的信息写入最后一个变量所指向的结构体中。FBIOPUT_VSCREENINFO将所有提供的信息复制回内核。如果内核不能激活新的设置,将返回-1。而
FBIOPAN_DISPLAY 也从用户复制信息,但并不重新初始化视频模式。最好在只有xoffset或yoffset改变时使用。

去访问内存本身,它可以被映射,然后直接访问。但这些需要一些步骤:

  • 计算出需要映射多少

  • 映射内存

  • 决定如何构建屏幕

  • 向屏幕中写入数据

这里有一个样例:

 

C:

  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7. #include
  8. int main() {
  9. int framebuffer_device;
  10. int line_size,buffer_size, *i;
  11. int *screen_memory;
  12. struct fb_var_screeninfo var_info;
  13. framebuffer_device = open ( "/dev/fb0" , O_RDWR);
  14. ioctl (framebuffer_device, FBIOGET_VSCREENINFO, &var_info);
  15. line_size = var_info.xres * var_info.bits_per_pixel / 8;
  16. buffer_size = line_size * var_info.yres;
  17. var_info.xoffset = 0;
  18. var_info.yoffset = 0;
  19. ioctl (framebuffer_device, FBIOPAN_DISPLAY,&var_info) == -1);
  20. screen_memory = (char *) mmap (513, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_device, 0);
  21. for (i=0;i < buffer_size ; i++ )
  22. {
  23. *(screen_memory+i) = i%236;
  24. }
  25. sleep(2);
  26. return 0;
  27. }

 

你可以看到我们采用了从上一部分提到的信息取回,在这一部分新用到的是mmap函数。第一个变量在这种情形下可以忽略,第二个是映射的内存大小,第三个变量声明我们将共享内存进行读和写。第四个变量表示这段内存将和其他进程共享。在framebuffer上面建一个MAP_PRIVATE是不可能的。 通常这意味着你需要中断控制台的切换去备份和恢复屏幕内容,而且不在自己没有权利的时候向屏幕内存写东西。

 

 

先读一个完整的freamebuffer程序设计

#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#include <linux/fb.h>
//a framebuffer device structure;
typedef struct fbdev{
   int fb;
   unsigned long fb_mem_offset;
   unsigned long fb_mem;
   struct fb_fix_screeninfo fb_fix;
   struct fb_var_screeninfo fb_var;
   char dev[20];
} FBDEV, *PFBDEV;
//open & init a frame buffer to use this function,you must set FBDEV.dev="/dev/fb0" or "/dev/fbX",it's your frame uffer.
int fb_open(PFBDEV pFbdev);
//close a frame buffer
int fb_close(PFBDEV pFbdev);
//get display depth
int get_display_depth(PFBDEV pFbdev);
//full screen clear
void fb_memset(void *addr, int c, size_t len);
#endif
//fbtools.c 代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <asm/page.h>
#include "fbtools.h"
#define TRUE 1
#define FALSE 0
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
//open & init a frame buffer
int fb_open(PFBDEV pFbdev)
{  pFbdev->fb = open(pFbdev->dev, O_RDWR);
   if(pFbdev->fb < 0)
   {  printf("Error opening %s: %m. Check kernel config//n", pFbdev->dev);
      return FALSE;
   }
   if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_var)))
   {  printf("ioctl FBIOGET_VSCREENINFO//n");
      return FALSE;
   }
   if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fix)))
   {  printf("ioctl FBIOGET_FSCREENINFO//n");
      return FALSE;
   }  
   //map physics address to virtual address
   pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) & (~PAGE_MASK);
   pFbdev->fb_mem = (unsigned long int)mmap(NULL, pFbdev->fb_fix.smem_len +
   pFbdev->fb_mem_offset,
   PROT_READ | PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);
   if (-1L == (long) pFbdev->fb_mem)
   {  printf("mmap error! mem:%d offset:%d//n", pFbdev->fb_mem,
       pFbdev->fb_mem_offset);
      return FALSE;
   }
   return TRUE;
}
//close frame buffer
int fb_close(PFBDEV pFbdev)
{   close(pFbdev->fb);
   pFbdev->fb=-1;
}
//get display depth
int get_display_depth(PFBDEV pFbdev);
{  if(pFbdev->fb<=0)
   {  printf("fb device not open, open it first//n");
      return FALSE;
   }
   return pFbdev->fb_var.bits_per_pixel;
}
//full screen clear
void fb_memset (void *addr, int c, size_t len)
{  memset(addr, c, len);
}
//use by test
#define DEBUG
#ifdef DEBUG
main()
{      FBDEV fbdev;
   memset(&fbdev, 0, sizeof(FBDEV));
   strcpy(fbdev.dev, "/dev/fb0");
   if(fb_open(&fbdev)==FALSE)
   {  printf("open frame buffer error//n");
      return;
   }
   fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0, fbdev.fb_fix.smem_len);
   fb_close(&fbdev);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值