职位:技术总监 1、精通c++(linux平台、vc++Mfc、qt)、java、php、unity3d,略懂python 2、用c++开发过嵌入式产品,用c++开发过大型银行运维产品 3、用java开发大型银行运维产品,学校教务系统 4、用php开发进销存系统(在销售中),用php开发淘宝小程序 5、用unity3d开发衣柜设计软件,在运营中
分类: 嵌入式
2010-03-18 07:51:41
本文是 MiniGUI 体系结构系列文章的第四篇。图形抽象层(GAL)和输入抽象层(IAL)大大提高了 MiniGUI 的可移植性,并将底层图形设备和上层接口分离开来。这里将重点介绍 MiniGUI 的 GAL 和 IAL 接口,并以最新的 MiniGUI-Lite 版本为例,介绍基于 Linux FrameBuffer 的 Native 图形引擎的实现,以及特定嵌入式系统上输入引擎的实现。1 引言
在 MiniGUI 0.3.xx 的开发中,我们引入了图形和输入抽象层(Graphics and Input Abstract Layer,GAL 和 IAL)的概念。抽象层的概念类似 Linux 内核虚拟文件系统的概念。它定义了一组不依赖于任何特殊硬件的抽象接口,所有顶层的图形操作和输入处理都建立在抽象接口之上。而用于实现这一抽象接口的底层代码称为“图形引擎”或“输入引擎”,类似操作系统中的驱动程序。这实际是一种面向对象的程序结构。利用 GAL 和 IAL,MiniGUI 可以在许多已有的图形函数库上运行,比如 SVGALib 和 LibGGI。并且可以非常方便地将 MiniGUI 移植到其他 POSIX 系统上,只需要根据我们的抽象层接口实现新的图形引擎即可。比如,在基于 Linux 的系统上,我们可以在 Linux FrameBuffer 驱动程序的基础上建立通用的 MiniGUI 图形引擎。实际上,包含在 MiniGUI 1.0.00 版本中的私有图形引擎(Native Engine)就是建立在 FrameBuffer 之上的图形引擎。一般而言,基于 Linux 的嵌入式系统均会提供 FrameBuffer 支持,这样私有图形引擎可以运行在一般的 PC 上,也可以运行在特定的嵌入式系统上。
相比图形来讲,将 MiniGUI 的底层输入与上层相隔显得更为重要。在基于 Linux 的嵌入式系统中,图形引擎可以通过 FrameBuffer 而获得,而输入设备的处理却没有统一的接口。在 PC 上,我们通常使用键盘和鼠标,而在嵌入式系统上,可能只有触摸屏和为数不多的几个键。在这种情况下,提供一个抽象的输入层,就显得格外重要。
本文将介绍 MiniGUI 的 GAL 和 IAL 接口,并介绍私有图形引擎和特定嵌入式系统下的输入引擎实现。
2 MiniGUI 的 GAL 和 IAL 定义
GAL 和 IAL 的结构是类似的,我们以 GAL 为例说明 MiniGUI GAL 和 IAL 抽象层的结构。
2.1 GAL 和图形引擎参见图 1。系统维护一个已注册图形引擎数组,保存每个图形引擎数据结构的指针。系统利用一个指针保存当前使用的图形引擎。一般而言,系统中至少有两个图形引擎,一个是“哑”图形引擎,不进行任何实际的图形输出;一个是实际要使用的图形引擎,比如 LibGGI 或者 SVGALib,或者 Native Engine。每个图形引擎的数据结构定义了该图形引擎的一些信息,比如标识符、属性等,更重要的是,它实现了 GAL 所定义的各个接口,包括初始化和终止、图形上下文管理、画点处理函数、画线处理函数、矩形框填充函数、调色板函数等等。
图1 GAL 和图形引擎如果在某个实际项目中所使用的图形硬件比较特殊,现有的图形引擎均不支持。这时,我们就可以安照 GAL 所定义的接口实现自己的图形引擎,并指定 MiniGUI 使用这种私有的图形引擎即可。这种软件技术实际就是面向对象多态性的具体体现。
利用 GAL 和 IAL,大大提高了 MiniGUI 的可移植性,并且使得程序的开发和调试变得更加容易。我们可以在 X Window 上开发和调试自己的 MiniGUI 程序,通过重新编译就可以让 MiniGUI 应用程序运行在特殊的嵌入式硬件平台上。
在代码实现上,MiniGUI 通过 GFX 数据结构来表示图形引擎,见清单 1。
清单 1 MiniGUI 中的图形引擎结构(src/include/gal.h)
55 typedef struct tagGFX
56 {
57 char* id;
58
59 // Initialization and termination
60 BOOL (*initgfx) (struct tagGFX* gfx);
61 void (*termgfx) (struct tagGFX* gfx);
62
63 // Phisical graphics context
64 GAL_GC phygc;
65 int bytes_per_phypixel;
66 int bits_per_phypixel;
67 int width_phygc;
68 int height_phygc;
69 int colors_phygc;
70 BOOL grayscale_screen;
71
72 // GC properties
73 int (*bytesperpixel) (GAL_GC gc);
74 int (*bitsperpixel) (GAL_GC gc);
75 int (*width) (GAL_GC gc);
76 int (*height) (GAL_GC gc);
77 int (*colors) (GAL_GC gc);
78
79 // Allocation and release of graphics context
80 int (*allocategc) (GAL_GC gc, int width, int height, int depth,
81 GAL_GC* newgc);
82 void (*freegc) (GAL_GC gc);
83 void (*setgc) (GAL_GC gc);
84
85 // Clipping of graphics context
86 void (*enableclipping) (GAL_GC gc);
87 void (*disableclipping) (GAL_GC gc);
88 int (*setclipping) (GAL_GC gc, int x1, int y1, int x2, int y2);
89 int (*getclipping) (GAL_GC gc, int* x1, int* y1, int* x2, int* y2);
90
91 // Background and foreground colors
92 int (*getbgcolor) (GAL_GC gc, gal_pixel* color);
93 int (*setbgcolor) (GAL_GC gc, gal_pixel color);
94 int (*getfgcolor) (GAL_GC gc, gal_pixel* color);
95 int (*setfgcolor) (GAL_GC gc, gal_pixel color);
96
97 // Convertion between gal_color and gal_pixel
98 gal_pixel (*mapcolor) (GAL_GC gc, gal_color *color);
99 int (*unmappixel) (GAL_GC gc, gal_pixel pixel, gal_color* color);
100 int (*packcolors) (GAL_GC gc, void* buf, gal_color* colors, int len);
101 int (*unpackpixels) (GAL_GC gc, void* buf, gal_color* colors, int len);
102
103 // Palette operations
104 int (*getpalette) (GAL_GC gc, int s, int len, gal_color* cmap);
105 int (*setpalette) (GAL_GC gc, int s, int len, gal_color* cmap);
106 int (*setcolorfulpalette) (GAL_GC gc);
107
108 // Box operations
109 size_t (*boxsize) (GAL_GC gc, int w, int h);
110 int (*fillbox) (GAL_GC gc, int x, int y, int w, int h,
111 gal_pixel pixel);
112 int (*putbox) (GAL_GC gc, int x, int y, int w, int h, void* buf);
113 int (*getbox) (GAL_GC gc, int x, int y, int w, int h, void* buf);
114 int (*putboxmask) (GAL_GC gc, int x, int y, int w, int h, void* buf, gal_pixel cxx);
115 int (*putboxpart) (GAL_GC gc, int x, int y, int w, int h, int bw,
116 int bh, void* buf, int xo, int yo);
117 int (*putboxwithop) (GAL_GC gc, int x, int y, int w, int h,
118 void* buf, int raster_op);
119 int (*scalebox) (GAL_GC gc, int sw, int sh, void* srcbuf,
120 int dw, int dh, void* dstbuf);
121
122 int (*copybox) (GAL_GC gc, int x, int y, int w, int h, int nx, int ny);
123 int (*crossblit) (GAL_GC src, int sx, int sy, int sw, int sh,
124 GAL_GC dst, int dx, int dy);
125
126 // Horizontal line operaions
127 int (*drawhline) (GAL_GC gc, int x, int y, int w, gal_pixel pixel);
128 int (*puthline) (GAL_GC gc, int x, int y, int w, void* buf);
129 int (*gethline) (GAL_GC gc, int x, int y, int w, void* buf);
130
131 // Vertical line operations
132 int (*drawvline) (GAL_GC gc, int x, int y, int h, gal_pixel pixel);
133 int (*putvline) (GAL_GC gc, int x, int y, int h, void* buf);
134 int (*getvline) (GAL_GC gc, int x, int y, int h, void* buf);
135
136 // Pixel operations
137 int (*drawpixel) (GAL_GC gc, int x, int y, gal_pixel pixel);
138 int (*putpixel) (GAL_GC gc, int x, int y, gal_pixel color);
139 int (*getpixel) (GAL_GC gc, int x, int y, gal_pixel* color);
140
141 // Other drawing
142 int (*circle) (GAL_GC gc, int x, int y, int r, gal_pixel pixel);
143 int (*line) (GAL_GC gc, int x1, int y1, int x2, int y2,
144 gal_pixel pixel);
145 int (*rectangle) (GAL_GC gc, int l, int t, int r, int b,
146 gal_pixel pixel);
147
148 // Simple Character output
149 int (*putchar) (GAL_GC gc, int x, int y, char c);
150 int (*putstr) (GAL_GC gc, int x, int y, const char* str);
151 int (*getcharsize) (GAL_GC gc, int* width, int* height);
152 int (*setputcharmode) (GAL_GC gc, int bkmode);
153 int (*setfontcolors) (GAL_GC gc,
154 gal_pixel fg, gal_pixel bg);
155
156 // Asynchronous mode support
157 void (*flush) (GAL_GC gc);
158 void (*flushregion) (GAL_GC gc, int x, int y, int w, int h);
159
160 // Panic
161 void (*panic) (int exitcode);
162
163 } GFX;
164
165 extern GFX* cur_gfx;系统启动之后,将根据配置寻找特定的图形引擎作为当前的图形引擎,并且对全局变量 cur_gfx 赋值。之后,当 MiniGUI 需要在屏幕上进行绘制之后,调用当前图形引擎的相应功能函数。比如,在画水平线时如下调用:
(*cur_gfx->drawhline) (gc, x, y, w, pixel);
显然,只要在系统初始化时能够根据设定对 cur_gfx 进行适当的赋值,MiniGUI 就能够在相应的图形引擎之上进行绘制。
对底层图形引擎的调用,主要集中在 MiniGUI 的 GDI 函数中。比如,要绘制一条直线,MiniGUI 的 LineTo 函数定义如清单 2 所示:
清单 2 LineTo 函数(src/gdi/draw-lite.c)
255 void GUIAPI LineTo (HDC hdc, int x, int y)
256 {
257 PCLIPRECT pClipRect;
258 PDC pdc;
259 RECT rcOutput;
260 int startx, starty;
261
262 pdc = dc_HDC2PDC(hdc);
263
264 if (dc_IsGeneralHDC(hdc)) {
265 if (!dc_GenerateECRgn (pdc, FALSE)) {
266 return;
267 }
268 }
269
270 // Transfer logical to device to screen here.
271 startx = pdc->CurPenPos.x;
272 starty = pdc->CurPenPos.y;
273
274 // Move the current pen pos.
275 pdc->CurPenPos.x = x;
276 pdc->CurPenPos.y = y;
277
278 coor_LP2SP(pdc, &x, &y);
279 coor_LP2SP(pdc, &startx, &starty);
280 rcOutput.left = startx - 1;
281 rcOutput.top = starty - 1;
282 rcOutput.right = x + 1;
283 rcOutput.bottom = y + 1;
284 NormalizeRect(&rcOutput);
285
286 IntersectRect (&rcOutput, &rcOutput, &pdc->ecrgn.rcBound);
287 if( !dc_IsMemHDC(hdc) ) ShowCursorForGDI(FALSE, &rcOutput);
288
289 // set graphics context.
290 GAL_SetGC (pdc->gc);
291 GAL_SetFgColor (pdc->gc, pdc->pencolor);
292
293 pClipRect = pdc->ecrgn.head;
294 while(pClipRect)
295 {
296 if (DoesIntersect (&rcOutput, &pClipRect->rc)) {
297 GAL_SetClipping (pdc->gc, pClipRect->rc.left, pClipRect->rc.top,
298 pClipRect->rc.right - 1, pClipRect->rc.bottom - 1);
299
300 if(starty == y) {
301 if (startx > x)
302 GAL_DrawHLine (pdc->gc, x, y, startx - x, pdc->pencolor);
303 else
304 GAL_DrawHLine (pdc->gc, startx, y, x - startx, pdc->pencolor);
305 }
306 else
307 GAL_Line (pdc->gc, startx, starty, x, y, pdc->pencolor);
308 }
309
310 pClipRect = pClipRect->next;
311 }
312
313 if (!dc_IsMemHDC (hdc)) ShowCursorForGDI (TRUE, &rcOutput);
314 }
在 MiniGUI 的所有绘图函数中,要依次做如下几件事:
- 进行逻辑坐标到设备坐标的转换;
- 检查是否应该重新生成有效剪切域;
- 计算输出矩形,并判断是否隐藏鼠标;
- 由于底层引擎尚不支持剪切域,因此,对剪切域中的每个剪切矩形,调用底层绘图函数输出一次。
在上面的四个步骤当中,第 3 步和第 4 步实际可以放到底层引擎当中,从而能够大大提高 MiniGUI 的绘图效率。不过,这种性能上的提高,对块输出,比如填充矩形、输出位图来讲,并不是非常明显。在将来的底层图形引擎当中,我们将针对上述两点,进行较大的优化以提高图形输出效率。
2.2 IAL 和输入引擎如前所属,MiniGUI IAL 结构和 GAL 结构类似。在代码实现上,MiniGUI 通过 INPUT数据结构来表示输入引擎,见清单 3。
清单 3 MiniGUI 中的输入引擎结构(src/include/ial.h)
34 typedef struct tagINPUT
35 {
36 char* id;
37
38 // Initialization and termination
39 BOOL (*init_input) (struct tagINPUT *input, const char* mdev, const char* mtype);
40 void (*term_input) (void);
41
42 // Mouse operations
43 int (*update_mouse) (void);
44 int (*get_mouse_x) (void);
45 int (*get_mouse_y) (void);
46 void (*set_mouse_xy) (int x, int y);
47 int (*get_mouse_button) (void);
48 void (*set_mouse_range) (int minx, int miny,int maxx,int maxy);
49
50 // Keyboard operations
51 int (*update_keyboard) (void);
52 char* (*get_keyboard_state) (void);
53 void (*suspend_keyboard) (void);
54 void (*resume_keyboard) (void);
55 void (*set_leds) (unsigned int leds);
56
57 // Event
58 #ifdef _LITE_VERSION
59 int (*wait_event) (int which, int maxfd, fd_set *in, fd_set *out, fd_set *except,
60 struct timeval *timeout);
61 #else
62 int (*wait_event) (int which, fd_set *in, fd_set *out, fd_set *except,
63 struct timeval *timeout);
64 #endif
65 }INPUT;
66
67 extern INPUT* cur_input;
系统启动之后,将根据配置寻找特定的输入引擎作为当前的输入引擎,并且对全局变量 cur_input 赋值。
(*cur_gfx->drawhline) (gc, x, y, w, pixel);
为方便程序书写,我们还定义了如下 C 语言宏:
69 #define IAL_InitInput (*cur_input->init_input)
70 #define IAL_TermInput (*cur_input->term_input)
71 #define IAL_UpdateMouse (*cur_input->update_mouse)
72 #define IAL_GetMouseX (*cur_input->get_mouse_x)
73 #define IAL_GetMouseY (*cur_input->get_mouse_y)
74 #define IAL_SetMouseXY (*cur_input->set_mouse_xy)
75 #define IAL_GetMouseButton (*cur_input->get_mouse_button)
76 #define IAL_SetMouseRange (*cur_input->set_mouse_range)
77
78 #define IAL_UpdateKeyboard (*cur_input->update_keyboard)
79 #define IAL_GetKeyboardState (*cur_input->get_keyboard_state)
80 #define IAL_SuspendKeyboard (*cur_input->suspend_keyboard)
81 #define IAL_ResumeKeyboard (*cur_input->resume_keyboard)
82 #define IAL_SetLeds(leds) if (cur_input->set_leds) (*cur_input->set_leds) (leds)
83
84 #define IAL_WaitEvent (*cur_input->wait_event)
在 src/kernel/event.c 中,我们如下调用底层的输入引擎,从而将输入引擎的数据转换为 MiniGUI 上层能够理解的消息(以 MiniGUI-Lite 为例),见清单 4:
清单 4 MiniGUI 对底层输入事件的处理
172 #ifdef _LITE_VERSION
173 BOOL GetLWEvent (int event, PLWEVENT lwe)
174 {
175 static LWEVENT old_lwe = {0, 0};
176 unsigned int interval;
177 int button;
178 PMOUSEEVENT me = &(lwe->data.me);
179 PKEYEVENT ke = &(lwe->data.ke);
180 unsigned char* keystate;
181 int i;
182 int make; /* 0 = release, 1 = presse */
183
184 if (event == 0) {
185 if (timer_counter >= timeout_count) {
186
187 timeout_count = timer_counter + repeat_threshold;
188
189 // repeat last event
190 if (old_lwe.type == LWETYPE_KEY
191 && old_lwe.data.ke.event == KE_KEYDOWN) {
192 memcpy (lwe, &old_lwe, sizeof (LWEVENT));
193 lwe->data.ke.status |= KE_REPEATED;
194 return 1;
195 }
196
197 if (!(old_lwe.type == LWETYPE_MOUSE
198 && (old_lwe.data.me.event == ME_LEFTDOWN ||
199 old_lwe.data.me.event == ME_RIGHTDOWN ||
200 old_lwe.data.me.event == ME_MIDDLEDOWN))) {
201 // reset delay time
202 timeout_count = timer_counter + timeout_threshold;
203 }
204
205 // reset delay time
206 lwe->type = LWETYPE_TIMEOUT;
207 lwe->count = timer_counter;
208 return 1;
209 }
210 return 0;
211 }
212
213 timeout_count = timer_counter + timeout_threshold;
214 // There was a event occurred.
215 if (event & IAL_MOUSEEVENT) {
216 lwe->type = LWETYPE_MOUSE;
217 if (RefreshCursor(&me->x, &me->y, &button)) {
218 me->event = ME_MOVED;
219 time1 = 0;
220 time2 = 0;
221
222 if (oldbutton == button)
223 return 1;
224 }
225
226 if ( !(oldbutton & IAL_MOUSE_LEFTBUTTON) &&
227 (button & IAL_MOUSE_LEFTBUTTON) )
228 {
229 if (time1) {
230 interval = timer_counter - time1;
231 if (interval <= dblclicktime)
232 me->event = ME_LEFTDBLCLICK;
233 else
234 me->event = ME_LEFTDOWN;
235 time1 = 0;
236 }
237 else {
238 time1 = timer_counter;
239 me->event = ME_LEFTDOWN;
240 }
241 goto mouseret;
242 }
243
244 if ( (oldbutton & IAL_MOUSE_LEFTBUTTON) &&
245 !(button & IAL_MOUSE_LEFTBUTTON) )
246 {
247 me->event = ME_LEFTUP;
248 goto mouseret;
249 }
250
251 if ( !(oldbutton & IAL_MOUSE_RIGHTBUTTON) &&
252 (button & IAL_MOUSE_RIGHTBUTTON) )
253 {
254 if (time2) {
255 interval = timer_counter - time2;
256 if (interval <= dblclicktime)
257 me->event = ME_RIGHTDBLCLICK;
258 else
259 me->event = ME_RIGHTDOWN;
260 time2 = 0;
261 }
262 else {
263 time2 = timer_counter;
264 me->event = ME_RIGHTDOWN;
265 }
266 goto mouseret;
267 }
268
269 if ( (oldbutton & IAL_MOUSE_RIGHTBUTTON) &&
270 !(button & IAL_MOUSE_RIGHTBUTTON) )
271 {
272 me->event = ME_RIGHTUP;
273 goto mouseret;
274 }
275 }
276
277 if(event & IAL_KEYEVENT) {
278 lwe->type = LWETYPE_KEY;
279 keystate = IAL_GetKeyboardState ();
280 for(i = 0; i < NR_KEYS; i++) {
281 if(!oldkeystate[i] && keystate[i]) {
282 ke->event = KE_KEYDOWN;
283 ke->scancode = i;
284 olddownkey = i;
285 break;
286 }
287 if(oldkeystate[i] && !keystate[i]) {
288 ke->event = KE_KEYUP;
289 ke->scancode = i;
290 break;
291 }
292 }
293 if (i == NR_KEYS) {
294 ke->event = KE_KEYDOWN;
295 ke->scancode = olddownkey;
296 }
297
298 make = (ke->event == KE_KEYDOWN)?1:0;
299
300 if (i != NR_KEYS) {
301 unsigned leds;
302
303 switch (ke->scancode) {
304 case SCANCODE_CAPSLOCK:
305 if (make && caps_off) {
306 capslock = 1 - capslock;
307 leds = slock | (numlock << 1) | (capslock << 2);
308 IAL_SetLeds (leds);
309 status = (DWORD)leds << 16;
310 }
311 caps_off = 1 - make;
312 break;
313
314 case SCANCODE_NUMLOCK:
315 if (make && num_off) {
316 numlock = 1 - numlock;
317 leds = slock | (numlock << 1) | (capslock << 2);
318 IAL_SetLeds (leds);
319 status = (DWORD)leds << 16;
320 }
321 num_off = 1 - make;
322 break;
323
324 case SCANCODE_SCROLLLOCK:
325 if (make & slock_off) {
326 slock = 1 - slock;
327 leds = slock | (numlock << 1) | (capslock << 2);
328 IAL_SetLeds (leds);
329 status = (DWORD)leds << 16;
330 }
331 slock_off = 1 - make;
332 break;
333
334 case SCANCODE_LEFTCONTROL:
335 control1 = make;
336 break;
337
338 case SCANCODE_RIGHTCONTROL:
339 control2 = make;
340 break;
341
342 case SCANCODE_LEFTSHIFT:
343 shift1 = make;
344 break;
345
346 case SCANCODE_RIGHTSHIFT:
347 shift2 = make;
348 break;
349
350 case SCANCODE_LEFTALT:
351 alt1 = make;
352 break;
353
354 case SCANCODE_RIGHTALT:
355 alt2 = make;
356 break;
357
358 }
359
360 status &= 0xFFFFF0C0;
361
362 status |= (DWORD)((capslock << 8) |
363 (numlock << 7) |
364 (slock << 6) |
365 (control1 << 5) |
366 (control2 << 4) |
367 (alt1 << 3) |
368 (alt2 << 2) |
369 (shift1 << 1) |
370 (shift2));
371
372 // Mouse button status
373 if (oldbutton & IAL_MOUSE_LEFTBUTTON)
374 status |= 0x00000100;
375 else if (oldbutton & IAL_MOUSE_RIGHTBUTTON)
376 status |= 0x00000200;
377 }
378 ke->status = status;
379 SHAREDRES_SHIFTSTATUS = status;
380 memcpy (oldkeystate, keystate, NR_KEYS);
381 memcpy (&old_lwe, lwe, sizeof (LWEVENT));
382 return 1;
383 }
384
385 old_lwe.type = 0;
386 return 0;
387
388 mouseret:
389 status &= 0xFFFFF0FF;
390 oldbutton = button;
391 if (oldbutton & IAL_MOUSE_LEFTBUTTON)
392 status |= 0x00000100;
393 if (oldbutton & IAL_MOUSE_RIGHTBUTTON)
394 status |= 0x00000200;
395 me->status = status;
396 SHAREDRES_SHIFTSTATUS = status;
397 memcpy (&old_lwe, lwe, sizeof (LWEVENT));
398 return 1;
399 }
#endif
从这段代码中可以看出,对定点设备来讲,比如鼠标或者触摸屏,MiniGUI 能够自动识别移动信息,也能够自动识别用户的单击和双击事件。这样,底层引擎只需提供位置信息和当前的按键状态信息就可以了。对类似键盘的东西,MiniGUI 也能够自动进行重复处理。当一个按键按下一段时间之后,MiniGUI 将连续发送该按键的消息给上层处理。对特定的嵌入式系统来讲,可以将某些按键映射为 PC 的某些键盘键,上层只需处理这些键盘键消息的按下和释放即可。这样,嵌入式系统上的某些键的功能就可以在 PC 上进行模拟了。
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,PutBoxMask
Get* 函数用于从屏幕拷贝像素到一块内存区,而Put*函数用于将存放于内存区的像素画到屏幕上。PutBoxMask 与PutBox的唯一区别是要画的像素如果是白色,就不会被画到屏幕上,从而达到一种透明的效果。
从上面可以看到,这些函数的第一个参数是GAL类型而不是PSD类型,这是因为它们需要GAL层的信息以便在函数内部实现剪切功能。之所以不和其他函数一样在上层实现剪切,是因为这里的剪切比较特殊。比如PutBox,
在剪切输出域时,要同时剪切在缓冲中待输出的像素:超出剪切域的像素不应该被输出。所以,剪切已经不单纯是对线,矩形等GDI对象的剪切。对像素的剪切当然需要知道像素的格式,这些只是为底层所有,所以为了实现高效的剪切,我们选择在底层实现它们。这里所有的函数都有两个部分:先是剪切,再是读或者写像素。
- Blit,CopyBox
Blit 用于在不同的屏幕设备(物理的或者内存的)之间拷贝一块像素点,CopyBox则用于在同一屏幕上实现区域像素的拷贝。如果使用的是线性模式,Blit的实现非常简单,直接memcpy 就可以了,而CopyBox 为了防止覆盖问题,必须根据不同的情况,采用不同的拷贝方式,比如从底到顶底拷贝,当新老位置在同一水平位置并且重复时,则需要利用缓冲间接拷贝。如果使用平面显示模式,这里就比较复杂了。因为内存设备总是采用线性模式的,所以就要判断是物理设备还是内存设备,再分别处理。这也大大地增加了fbvga16实现的代码。
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 以及底层的图形引擎接口,以便针对窗口系统进行优化。