4. Screen图形系统基础:Screen上下文与窗口、Screen缓冲区管理、Screen事件处理机制
好,咱们今天来聊聊Screen图形系统的基础。这部分内容,说白了就是你在QNX上做图形开发必须跨过的第一道坎。我记得我刚接触QNX图形栈那会儿,光理解Screen上下文和窗口的关系就花了两天时间。嗯,其实没那么玄乎,咱们一步步拆开看。
4.1 Screen上下文与窗口
先说说Screen上下文。你可以把它理解成一个“画板管理员”。它管理着显示连接、窗口资源、缓冲区这些底层的东西。每个进程想要画图,都得先创建一个Screen上下文。
核心概念:Screen上下文是进程与图形系统之间的桥梁。没有它,你连个像素都画不出去。
创建上下文其实很简单,就一个函数调用:
#include <screen/screen.h>
screen_context_t ctx;
screen_create_context(&ctx, SCREEN_APPLICATION_CONTEXT);
这里有个小坑。第二个参数我习惯用 SCREEN_APPLICATION_CONTEXT,这是给普通应用用的。如果你在做系统服务或者后台渲染,可能会用到 SCREEN_WINDOW_MANAGER_CONTEXT。我在项目中遇到过有人用错了上下文类型,结果窗口死活显示不出来——嗯,查了半天才发现是权限问题。
有了上下文,接下来就是创建窗口。窗口是什么?它就是屏幕上的一块矩形区域,用来展示你的内容。每个窗口都绑定到一个上下文上。
screen_window_t win;
screen_create_window(&win, ctx);
// 设置窗口属性
int size[2] = {800, 600};
screen_set_window_property_iv(win, SCREEN_PROPERTY_SIZE, size);
screen_set_window_property_iv(win, SCREEN_PROPERTY_POSITION, (int[]){100, 50});
你想想看,窗口的属性其实挺多的。大小、位置、透明度、Z序……这些都可以通过 screen_set_window_property_* 系列函数来设置。我个人建议,创建窗口后先把基本属性设好,再去做渲染,不然容易出一些莫名其妙的问题。
小技巧:窗口的Z序决定了谁在上面谁在下面。用 SCREEN_PROPERTY_ZORDER 可以控制。数值越大,越靠上。
4.2 Screen缓冲区管理
缓冲区管理这块,是很多初学者容易懵的地方。说白了,缓冲区就是一块内存,用来存放你要显示的像素数据。Screen系统支持多种缓冲区模式,最常用的是双缓冲和三缓冲。
为什么会需要多个缓冲区?你想想看,如果你直接在屏幕上画图,画到一半的时候屏幕刷新了,用户就会看到撕裂的画面——一半是新的,一半是旧的。这体验太差了。
双缓冲的机制是这样的:
- 一个前台缓冲区(正在显示的内容)
- 一个后台缓冲区(你正在画的内容)
- 画完后,交换前后台
代码实现起来也不复杂:
// 创建窗口时指定使用双缓冲
int format = SCREEN_FORMAT_RGBA8888;
int usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_NATIVE;
screen_create_window_buffers(win, 2); // 2个缓冲区 = 双缓冲
// 获取后台缓冲区
screen_buffer_t buffer;
screen_get_window_property_pv(win, SCREEN_PROPERTY_RENDER_BUFFERS, (void**)&buffer);
// 画完后提交
screen_post_window(win, buffer, 0, NULL, 0);
我曾经在一个高帧率项目里用过三缓冲。为什么?因为双缓冲在某些情况下还是会有卡顿。三缓冲多了一个缓冲区,让GPU和显示器之间有了更大的缓冲空间,帧率更稳定。不过代价就是内存占用多了50%。
注意:缓冲区数量不是越多越好。每个缓冲区都会占用显存。嵌入式设备上显存本来就紧张,我建议一般用双缓冲就够了。除非你做的是60fps以上的动画,才考虑三缓冲。
缓冲区格式也很重要。常用的格式有:
| 格式 | 说明 | 字节/像素 |
|---|---|---|
| SCREEN_FORMAT_RGBA8888 | 32位真彩色,带Alpha通道 | 4 |
| SCREEN_FORMAT_RGB565 | 16位高彩色,无Alpha | 2 |
| SCREEN_FORMAT_RGBA4444 | 16位,带Alpha | 2 |
我个人习惯,只要内存够用,就用RGBA8888。颜色准确,处理起来也方便。如果是在低端设备上,RGB565也能凑合,但颜色渐变的地方会有明显的色阶。
4.3 Screen事件处理机制
事件处理,说白了就是怎么让程序知道用户点了哪里、按了什么键。Screen系统的事件机制是基于消息队列的。
基本流程是这样的:
- 程序注册感兴趣的事件类型
- 系统把事件放到队列里
- 程序从队列里取事件,然后处理
代码示例:
// 注册事件
screen_create_event(&event);
screen_set_event_property_iv(event, SCREEN_PROPERTY_TYPE,
(int[]){SCREEN_EVENT_TOUCH});
// 事件循环
while (1) {
screen_get_event(ctx, event, -1); // -1表示阻塞等待
int type;
screen_get_event_property_iv(event, SCREEN_PROPERTY_TYPE, &type);
if (type == SCREEN_EVENT_TOUCH) {
int pos[2];
screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, pos);
printf("触摸位置: (%d, %d)\n", pos[0], pos[1]);
}
}
这里有个细节要注意。事件类型有很多种:触摸、键盘、鼠标、窗口大小变化等等。你不需要处理所有事件,只注册你关心的就行。我见过有人把所有事件都注册了,结果事件循环里一堆switch-case,代码乱得不行。
经验之谈:事件处理一定要放在主线程里。我曾经把事件循环放到子线程,结果窗口操作和事件处理不同步,界面卡死。嗯,后来老老实实改回主线程了。
还有一个容易忽略的点——事件的坐标系统。触摸事件的坐标是相对于窗口的,不是相对于屏幕的。如果你有多个窗口,每个窗口的事件坐标都是从自己的左上角算起的。这个坑我踩过,调试了半天才发现坐标偏移了。
最后说一句,事件处理要快。如果你在事件回调里做耗时操作,整个界面就会卡住。我建议事件处理只做简单的状态记录,真正的渲染逻辑放到单独的渲染循环里去做。
好了,这一章的内容就这些。Screen上下文、缓冲区管理、事件处理,这三个东西是Screen图形系统的基石。搞懂了它们,后面的内容就好办了。下一章咱们聊聊具体的渲染管线,到时候会用到今天讲的知识。