先尝试 wp_presentation,预想 Compositor 会在合适的时机发送 presented 或者 discarded 事件,客户端在事件回调中进行渲染。但实际上仅收到一次 presented 事件,没有后续事件。具体原因还不清楚。
后来找到一个教程,才知道核心协议 wl_surface.frame() 已经提供了控制机制。wl_surface_frame() 请求返回一个 wl_callback 对象,通过此对象注册监听器,Compositor 会在适合进行下次渲染时发送事件,引起监听器回调函数的被调用。客户端在回调函数中进行渲染,所以渲染发生在 Wayland 通讯线程内。
这一方式有两点需要注意:(1) 必须在渲染(wl_surface.commit())之前提交 wl_surface_frame() 请求,(2) wl_surface_frame() 请求及事件通知是一次性的,即一次请求只会收到一次通知。因此,客户端的程序流程/结构可以这样组织:
- 首先发送
wl_surface_frame()请求,然后进行首次渲染 - 在事件回调中,首先发送
wl_surface_frame()请求,以期将来继续收到下一次的事件通知。然后进行渲染
示例代码:
typedef void (*render_fn)(uint32_t timestamp);
static struct
{
...
} wayland_objects = {0};
static struct
{
...
} egl_objects = {0};
static struct
{
uint32_t timestamp_base; // in ms
} render_state;
static void render_init()
{
// Clear
glClearColor(0, 0, 0, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
eglSwapBuffers(egl_objects.display, egl_objects.surface);
}
static void render(uint32_t timestamp)
{
static GLfloat red = 0;
glClearColor(red, 0, 0, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
eglSwapBuffers(egl_objects.display, egl_objects.surface);
red += .1f;
if (red > 1)
{
red = 0;
}
}
static void render2(uint32_t timestamp);
static void render1(uint32_t timestamp)
{
render_state.timestamp_base = timestamp;
request_frame(render2);
render(0);
}
static void render2(uint32_t timestamp)
{
timestamp -= render_state.timestamp_base;
request_frame(render2);
render(timestamp);
}
int main()
{
...
request_frame(render1);
render_init();
while (1)
{
wl_display_dispatch(wayland_objects.display);
}
return 0;
}
static void request_frame(render_fn render)
{
static struct wl_callback_listener l = {
.done = on_wayland_surface_frame_callback_done,
};
struct wl_callback *callback = wl_surface_frame(wayland_objects.surface);
wl_callback_add_listener(callback, &l, (void *)render);
}
static void on_wayland_surface_frame_callback_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
render_fn render = (render_fn)data;
request_frame(render);
render(callback_data);
}
函数 request_frame() 发送 wl_surface_frame() 请求。看到在代码第 83 行把渲染函数作为数据参数传递给监听器。而在回调函数中,又将其转换回函数(代码第 88 行),并进行调用。同时注意在调用渲染函数之前再次发送了 wl_surface_frame() 请求。
函数 render1(), render2() 是对渲染函数 render() 的封装,目的是将事件回调提供的时间戳调整为以 0 为起始。仅第一次渲染调用 render1(),记录下起始时间戳。后续渲染都是调用 render2(),将事件通知所提供的时间戳减去起始值,然后调用 render()。
严格地说,render_init() 才是真正的首次渲染,而 render1() 是以事件回调方式的首次渲染。也即程序的渲染序列是 render_init(), render1(), render2(), render2()...
有了时间戳,即可实现对渲染帧速的控制。