3 Native 图形引擎的实现
Native 图形引擎的图形驱动程序已经提供了基于Linux内核提供FrameBuffer之上的驱动,目前包括对线性 2 bpp、4bpp、8bpp和 16bpp 显示模式的支持。前面已经看到,GAL提供的接口函数大多数与图形相关,它们主要就是通过调用图形驱动程序来完成任务的。图形驱动程序屏蔽了底层驱动的细节,完成底层驱动相关的功能,而不是那么硬件相关的一些功能,如一些画圆,画线的GDI 函数。
下面基于已经实现的基于FrameBuffer 的驱动程序,讲一些实现上的细节。首先列出的核心数据结构 SCREENDEVICE。这里主要是为了讲解方便,所以删除了一些次要的变量或者函数。
清单 5
Native 图形引擎的核心数据结构
typedef struct _screendevice {
int xres;
/* X screen res (real) */
int yres;
/* Y screen res (real) */
int planes;
/* # planes*/
int bpp;
/* # bits per pixel*/
int linelen;
/* line length in bytes for bpp 1,2,4,8, line length in pixels for bpp 16, 24, 32*/
int size;
/* size of memory allocated*/
gfx_pixel gr_foreground;
/* current foreground color */
gfx_pixel gr_background;
/* current background color */
int
gr_mode;
int flags;
/* device flags*/
void *
addr;
/* address of memory allocated (memdc or fb)*/
PSD (*Open)(PSD psd);
void
(*Close)(PSD psd);
void
(*SetPalette)(PSD psd,int first,int count,gfx_color *cmap);
void
(*GetPalette)(PSD psd,int first,int count,gfx_color *cmap);
PSD (*AllocateMemGC)(PSD psd);
BOOL
(*MapMemGC)(PSD mempsd,int w,int h,int planes,int bpp, int linelen,int size,void *addr);
void
(*FreeMemGC)(PSD mempsd);
void
(*FillRect)(PSD psd,int x,int y,int w,int h,gfx_pixel c);
void
(*DrawPixel)(PSD psd, int x, int y, gfx_pixel c);
gfx_pixel (*ReadPixel)(PSD psd, int x, int y);
void
(*DrawHLine)(PSD psd, int x, int y, int w, gfx_pixel c);
void
(*PutHLine) (GAL gal, int x, int y, int w, void* buf);
void
(*GetHLine) (GAL gal, int x, int y, int w, void* buf);
void
(*DrawVLine)(PSD psd, int x, int y, int w, gfx_pixel c);
void
(*PutVLine) (GAL gal, int x, int y, int w, void* buf);
void
(*GetVLine) (GAL gal, int x, int y, int w, void* buf);
void (*Blit)(PSD dstpsd, int dstx, int dsty, int w, int h, PSD srcpsd, int srcx, int srcy);
void
(*PutBox)( GAL gal, int x, int y, int w, int h, void* buf );
void
(*GetBox)( GAL gal, int x, int y, int w, int h, void* buf );
void
(*PutBoxMask)( GAL gal, int x, int y, int w, int h, void *buf);
void
(*CopyBox)(PSD psd,int x1, int y1, int w, int h, int x2, int y2);
} SCREENDEVICE;
上面PSD 是 SCREENDEVICE 的指针,GAL 是GAL 接口的数据结构。
我们知道,图形显示有个显示模式的概念,一个像素可以用一位比特表示,也可以用2,4,8,15,16,24,32个比特表示,另外,VGA16标准模式使用平面图形模式,而VESA2.0使用的是线性图形模式。所以即使是同样基于Framebuffer 的驱动,不同的模式也要使用不同的驱动函数:画一个1比特的单色点和画一个24位的真彩点显然是不一样的。
所以图形驱动程序使用了子驱动程序的概念来支持各种不同的显示模式,事实上,它们才是最终的功能函数。为了保持数据结构在层次上不至于很复杂,我们通过图形驱动程序的初始函数Open直接将子驱动程序的各功能函数赋到图形驱动程序的接口函数指针,从而初始化结束就使用一个简单的图形驱动接口。下面是子图形驱动程序接口(清单 6)。
清单 6
Native 图形引擎的子驱动程序接口
typedef struct {
int
(*Init)(PSD psd);
void
(*DrawPixel)(PSD psd, int x, int y, gfx_pixel c);
gfx_pixel (*ReadPixel)(PSD psd, int x, int y);
void
(*DrawHLine)(PSD psd, int x, int y, int w, gfx_pixel c);
void
(*PutHLine) (GAL gal, int x, int y, int w, void* buf);
void
(*GetHLine) (GAL gal, int x, int y, int w, void* buf);
void
(*DrawVLine)(PSD psd, int x, int y, int w, gfx_pixel c);
void
(*PutVLine) (GAL gal, int x, int y, int w, void* buf);
void
(*GetVLine) (GAL gal, int x, int y, int w, void* buf);
void (*Blit)(PSD dstpsd, int dstx, int dsty, int w, int h, PSD srcpsd, int srcx, int srcy);
void
(*PutBox)( GAL gal, int x, int y, int w, int h, void* buf );
void
(*GetBox)( GAL gal, int x, int y, int w, int h, void* buf );
void
(*PutBoxMask)( GAL gal, int x, int y, int w, int h, void *buf);
void
(*CopyBox)(PSD psd,int x1, int y1, int w, int h, int x2, int y2);
} SUBDRIVER, *PSUBDRIVER;
可以看到,该接口中除了 Init 函数指针外,其他的函数指针都与图形驱动程序接口中的函数指针一样。这里的Init 函数主要用来完成图形驱动部分与显示模式相关的初始化任务。
下面介绍SCREENDEVICE数据结构,这样基本上就可以清楚图形引擎了。
一个SCREENDEVICE代表一个屏幕设备,它即可以对应物理屏幕设备,也可以对应一个内存屏幕设备,内存屏幕设备的存在主要是为了提高GDI 质量,比如我们先在内存生成一幅位图,再画到屏幕上,这样给用户的视觉效果就比较好。
首先介绍几个变量。
xres 表示屏幕的宽 (以像素为单位);
yres 表示屏幕的高 (以像素为单位);
planes :当处于平面显示模式时,planes 用于记录所使用的平面数,如平面模式相对的时线性模式,此时该变量没有意义。通常将其置为0。
bpp :表示每个像素所使用的比特数,可以为1,2,4,8,15,16,24,32。
linelen :对与1,2,4,8比特每像素模式,它表示一行像素使用的字节数,对于大于8比特每像素模式,它表示一行的像素总数。
size :表示该显示模式下该设备使用的内存数。linelen 和 size 的存在主要是为了方便为内存屏幕设备分配内存。
gr_foreground 和 gr_background :表示该内存屏幕的前景颜色和背景颜色,主要被一些GDI 函数使用。
gr_mode :说明如何将像素画到屏幕上,可选值为:MODE_SET MODE_XOR MODE_OR MODE_AND MODE_MAX,比较常用的是MODE_SET和MODE_XOR
flags :该屏幕设备的一些选项,比较重要的是 PSF_MEMORY 标志,表示该屏幕设备代表物理屏幕设备还是一个内存屏幕设备。
addr :每个屏幕设备都有一块内存空间用来作为存储像素。addr 变量记录了这个空间的起始地址。
下面介绍各接口函数:
Open,Close
基本的初始化和终结函数。前面已经提到,在 Open 函数里要选择子图形驱动程序,将其实现的函数赋给本 PSD 结构的函数指针。这里我讲讲基于Frambebuffer 的图形引擎的初始化。
fb_open 首先打开Framebuffer的设备文件 /dev/fb0,然后利用 ioctl 读出当前Framebuffer的各种信息。填充到PSD 结构中。并且根据这些信息选出子驱动程序。程序当前支持fbvga16,fblin16,fblin8,即VGA16 标准模式,VESA线性16位模式,VESA线性8位模式。然后将当前终端模式置于图形模式。并保存当前的一些系统信息如调色板信息。最后,系统利用mmap 将 /dev/fb0 映射到内存地址。以后程序访问 /dev/fb0 就像访问一个数组一样简单。当然,这是对线性模式而言的,如果是平面模式,问题要复杂的多。光从代码来看,平面模式的代码是线性模式的实现的将近一倍。后面的难点分析里将讲解这个问题。
SetPalette,GetPalette
当使用8位或以下的图形模式时,要使用系统调色板。这里是调色板处理函数,它们和Windows API 中的概念类似,linux 系统利用 ioctl 提供了处理调色板的接口。
AllocateMemGC,MapMemGC,FreeMemGC
前面屡次提到内存屏幕的概念,内存屏幕是一个伪屏幕,在对屏幕图形操作过程中,比如移动窗口,我们先生成一个内存屏幕,将物理屏幕的一个区域拷贝到内存屏幕,再拷贝到物理屏幕的新位置,这样就减少了屏幕直接拷贝的延时。AllocateMemGC 用于给内存屏幕分配空间,MapMemGC 做一些初始化工作,而FreeMemGC 则释放内存屏幕。
DrawPixel,ReadPixel,DrawHLine,DrawVLine,FillRect
这些是底层图形函数。分别是画点,读点,画水平线,画竖直线,画一个实心矩形。之所以在底层实现这么多函数,是为了提高效率。图形函数支持多种画图模式,常用的有直接设置,亦或,Alpha混合模式,从而可以支持各种图形效果。
PutHLine,GetHLine,PutVLine,GetVLine,PutBox,GetBox