MiniGUI 体系结构之四 图形抽象层和输入抽象层及 Native Engine 的实现(二)

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 质量,比如我们先在内存生成一幅位图,再画到屏幕上,这样给用户的视觉效果就比较好。

首先介绍几个变量。

下面介绍各接口函数:

4 Native 输入引擎的实现

4.1 鼠标驱动程序

鼠标驱动程序非常简单,抽象意义上讲,初始化鼠标后,每次用户移动鼠标,就可以得到一个X 和 Y 方向上的位移值,驱动程序内部维护鼠标的当前位置,用户移动了鼠标后,当前位置被加上位移值,并通过上层Cursor支持,反映到屏幕上,用户就会认为鼠 标被他正确地“移动”了。

事实上,鼠标驱动程序的实现是利用内核或者其他驱动程序提供的接口来完成任务的。Linux 内核驱动程序使用设备文件对大多数硬件进行了抽象,比如,我们眼中的 ps/2 鼠标就是 /dev/psaux, 鼠标驱动程序接口如清单 7 所示。

                       清单 7  Native 输入引擎的鼠标驱动程序接口

typedef struct _mousedevice {
int (*Open)(void);
void (*Close)(void);
int (*GetButtonInfo)(void);
void (*GetDefaultAccel)(int *pscale,int *pthresh);
int (*Read)(int *dx,int *dy,int *dz,int *bp);
void (*Suspend)(void);
void (*Resume)(void);
} MOUSEDEVICE;

现在有各种各样的鼠标,例如ms 鼠标, ps/2 鼠标,总线鼠标,gpm 鼠标,它们的主要差别在于初始化和数据包格式上。

例如,打开一个GPM 鼠标非常简单,只要将设备文件打开就可以了,当前终端被切换到图形模式时,GPM 服务程序就会把鼠标所有的位移信息放到设备文件中去。

static int GPM_Open(void)
{
mouse_fd = open(GPM_DEV_FILE, O_NONBLOCK);
if (mouse_fd < 0)
return -1;
return mouse_fd;
}

对于PS/2 鼠标,不但要打开它的设备文件,还要往该设备文件写入控制字符以使得鼠标能够开始工作。

static int PS2_Open(void)
{
uint8 initdata_ps2[] = { PS2_DEFAULT, PS2_SCALE11, PS2_ENABLE };
mouse_fd = open(PS2_DEV_FILE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (mouse_fd < 0)
return -1;
write(mouse_fd, initdata_ps2, sizeof(initdata_ps2));
return mouse_fd;
}

各鼠标的数据包格式是不一样的。而且在读这些数据时,首先要根据内核驱动程序提供的格式读数据,还要注意同步:每次扫描到一个头,才能读后面相应的数据,象Microwindows由于没有同步,在某些情况下,鼠标就会不听“指挥”。

鼠标驱动程序中,还有一个“加速”的概念。程序内部用两个变量:scale 和thresh 来表示。当鼠标的位移超过 thresh 时,就会被放大 scale 倍。这样,最后的位移就是:

		dx = thresh + (dx - thresh) * scale;
dy = thresh + (dy - thresh) * scale;

至此,mouse driver 基本上很清楚了,上面的接口函数中GetButtonInfo用来告诉调用者该鼠标支持那些button, suspend 和resume 函数是用来支持虚屏切换的,下面的键盘驱动程序也一样。

4.2 键盘驱动程序

在实现键盘驱动程序中遇到的第一个问题就是使用设备文件 /dev/tty还是 /dev/tty0。

# echo 1 > /dev/tty0
# echo 1 > /dev/tty

结果都将把1 输入到当前终端上。另外,如果从伪终端上运行它们,则第一条指令会将 1 输出到控制台的当前终端,而第二条指令会把 1 输出到当前伪终端上。 从而 tty0 表示当前控制台终端,tty 表示当前终端(实际是当前进程控制终端的别名而已)。

tty0 的设备号是 4,0

tty1 的设备号是 5, 0

/dev/tty 是和进程的每一个终端联系起来的,/dev/tty 的驱动程序所做的只是把所有的请求送到合适的终端。

缺省情况下,/dev/tty 是普通用户可读写的,而/dev/tty0 则只有超级用户能够读写,主要是基于这个原因,我们目前使用 /dev/tty 作为设备文件。后面所有有关终端处理的程序的都采用它作为当前终端文件,这样也可以和传统的 Unix 相兼容。

键盘驱动程序接口如清单 8 所示。

                     清单 8  Native 输入引擎的键盘驱动程序接口

typedef struct _kbddevice {
int (*Open)(void);
void (*Close)(void);
void (*GetModifierInfo)(int *modifiers);
int (*Read)(unsigned char *buf,int *modifiers);
void (*Suspend)(void);
void (*Resume)(void);
} KBDDEVICE;

基本原理非常简单,初始化时打开 /dev/tty,以后就从该文件读出所有的数据。由于MiniGUI 需要捕获 KEY_DOWN 和 KEY_UP 消息,键盘被置于原始(raw)模式。这样,程序从 /dev/tty 中直接读出键盘的扫描码,比如用户按下A 键,就可以读到158,放下,又读到30。原始模式下,程序必须自己记下各键的状态,特别是shift,ctrl,alt,caps lock 等,所以程序维护一个数组,记录了所有键盘的状态。

这里说明一下鼠标移动,按键等事件是如何被传送到上层消息队列的。MiniGUI工作在用户态,所以它不可能利用中断这种高效的 机制。没有内核驱动程序的支持,它也很难利用信号等Unix系统的IPC机制。MiniGUI可以做到的就是看 /dev/tty, /dev/mouse 等文件是否有数据可以读。上层通过不断调用 GAL_WaitEvent 尝试读取这些文件。这也是线程Parser的主要任务。GAL_WaitEvent 主要利用了系统调用select 这一类Unix系统中地位仅次于ioctl的系统调用完成该功能。并将等待到的事件作为返回值返回。

至此介绍了键盘和鼠标的驱动程序,作为简单的输入设备,它们的驱动是非常简单的。事实上,它们的实现代码也比较少,就是在嵌入式系统中要使用的触摸屏,如果操作系统内核支持,其驱动程序也是非常简单的:它只不过是一种特殊的鼠标。

5 特定嵌入式系统上图形引擎和输入引擎实现

如前所述,基于 Linux 的嵌入式系统,其内核一般具备对 FrameBuffer 的支持,从而可以利用已有的 Native 图形引擎。在 MiniGUI 代码中,可通过调整 src/gal/native/native.h 中的宏定义而定义函数库中是否包含特定的图形引擎驱动程序(清单 9):

                          清单 9  定义 Native 引擎的子驱动程序


16 /* define or undefine these macros
17 to include or exclude specific fb driver.
18 */
19 #undef _FBLIN1_SUPPORT
20 // #define _FBLIN1_SUPPORT 1
21
22 #undef _FBLIN2_SUPPORT
23 // #define _FBLIN2_SUPPORT 1
24
22 #undef _FBLIN_2_SUPPORT
23 // #define _FBLIN_2_SUPPORT 1
24
25 //#undef _FBLIN4_SUPPORT
26 #define _FBLIN4_SUPPORT 1
27
28 // #undef _FBLIN8_SUPPORT
29 #define _FBLIN8_SUPPORT 1
30
31 // #undef _FBLIN16_SUPPORT
32 #define _FBLIN16_SUPPORT 1
33
34 #undef _FBLIN24_SUPPORT
35 // #define _FBLIN24_SUPPORT 1
36
37 #undef _FBLIN32_SUPPORT
38 // #define _FBLIN32_SUPPORT 1
39
40 #define HAVETEXTMODE 1 /* =0 for graphics only systems*/

其中,HAVETEXTMODE 定义系统是否有文本模式,可将 MiniGUI 中用来关闭文本模式的代码屏蔽掉。_FBLIN_2_SUPPORT 和_FBLIN2_SUPPORT 分别用来定义 big endian 和 little endian 的 2bpp 驱动程序。

对于输入引擎来说,情况就有些不同了。因为目前还没有统一的处理输出设备的接口,而且每个嵌入式系统的输入设备也各不相同,所 以,我们通常要针对特定嵌入式系统重新输入引擎。下面的代码就是针对 ADS 基于 StrongARM 的嵌入式开发系统编写的输入引擎(清单 10):

           清单 10  为 ADS 公司基于 StrongARM 的嵌入式开发系统编写的输入引擎

30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <sys/io.h>
36 #include <sys/ioctl.h>
37 #include <sys/poll.h>
38 #include <linux/kd.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42
43 #include "common.h"
44 #include "misc.h"
45 #include "ads_internal.h"
46 #include "ial.h"
47 #include "ads.h"
48
49 #ifndef NR_KEYS
50 #define NR_KEYS 128
51 #endif
52
53 static int ts;
54 static int mousex = 0;
55 static int mousey = 0;
56 static POS pos;
57
58 static unsigned char state[NR_KEYS];
59
60 /************************ Low Level Input Operations **********************/
61 /*
62 * Mouse operations -- Event
63 */
64 static int mouse_update(void)
65 {
66 return 0;
67 }
68
69 static int mouse_getx(void)
70 {
71 return mousex;
72 }
73
74 static int mouse_gety(void)
75 {
76 return mousey;
77 }
78
79 static void mouse_setposition(int x, int y)
80 {
81 }
82
83 static int mouse_getbutton(void)
84 {
85 return pos.b;
86 }
87
88 static void mouse_setrange(int minx,int miny,int maxx,int maxy)
89 {
90 }
91
92 static int keyboard_update(void)
93 {
94 return 0;
95 }
96
97 static char * keyboard_getstate(void)
98 {
99 return (char *)state;
100 }
101
102 #ifdef _LITE_VERSION
103 static int wait_event (int which, int maxfd, fd_set *in, fd_set *out, fd_set *except,
104 struct timeval *timeout)
105 {
106 fd_set rfds;
107 int e;
108
109 if (!in) {
110 in = &rfds;
111 FD_ZERO (in);
112 }
113
114 if (which & IAL_MOUSEEVENT) {
115 FD_SET (ts, in);
116 if (ts > maxfd) maxfd = ts;
117 }
118
119 e = select (maxfd + 1, in, out, except, timeout) ;
120
121 if (e > 0) {
122 if (ts >= 0 && FD_ISSET (ts, in))
123 {
124 FD_CLR (ts, in);
125 read (ts, &pos, sizeof (POS));
126 if ( pos.x !=-1 && pos.y !=-1) {
127 mousex = pos.x;
128 mousey = pos.y;
129 }
130 pos.b = ( pos.b > 0 ? 4:0);
131 return IAL_MOUSEEVENT;
132 }
133
134 } else if (e < 0) {
135 return -1;
136 }
137 return 0;
138 }
139 #else
140 static int wait_event (int which, fd_set *in, fd_set *out, fd_set *except,
141 struct timeval *timeout)
142 {
143 struct pollfd ufd;
144 if ( (which & IAL_MOUSEEVENT) == IAL_MOUSEEVENT)
145 {
146 ufd.fd = ts;
147 ufd.events = POLLIN;
148 if ( poll (&ufd, 1, timeout) > 0)
149 {
150 read (ts, &pos, sizeof(POS));
151 return IAL_MOUSEEVENT;
152 }
153 }
154 return 0;
155 }
156 #endif
157
158 static void set_leds (unsigned int leds)
159 {
160 }
161
162 BOOL InitADSInput (INPUT* input, const char* mdev, const char* mtype)
163 {
164 int i;
165
166 ts = open ("/dev/ts", O_RDONLY);
167 if ( ts < 0 ) {
168 fprintf (stderr, "IAL: Can not open touch screen!\n");
169 return FALSE;
170 }
171
172 for(i = 0; i < NR_KEYS; i++)
173 state[i] = 0;
174
175 input->update_mouse = mouse_update;
176 input->get_mouse_x = mouse_getx;
177 input->get_mouse_y = mouse_gety;
178 input->set_mouse_xy = mouse_setposition;
179 input->get_mouse_button = mouse_getbutton;
180 input->set_mouse_range = mouse_setrange;
181
182 input->update_keyboard = keyboard_update;
183 input->get_keyboard_state = keyboard_getstate;
184 input->set_leds = set_leds;
185
186 input->wait_event = wait_event;
187 mousex = 0;
188 mousey = 0;
189 return TRUE;
190 }
191
192 void TermADSInput (void)
193 {
194 if ( ts >= 0 )
195 close(ts);
196 }
197

在上述输入引擎中,完全忽略了键盘相关的函数实现,代码集中在对触摸屏的处理上。显然,输入引擎的编写并不是非常困难的。

6 小结

本文详细介绍了 MiniGUI 的 GAL 和 IAL 接口,并以 Native 图形引擎和输入引擎为例,介绍了具体图形引擎和输入引擎的实现。当然,MiniGUI 目前的 GAL 和 IAL 接口还有许多不足之处,比如和上层的 GDI 耦合程度不高,从而对效率有些损失。在 MiniGUI 将来的开发中,我们将重新设计 GDI 以及底层的图形引擎接口,以便针对窗口系统进行优化。

posted @ 2009-05-17 09:16  旅人  阅读(399)  评论(0编辑  收藏  举报