Wayland 控制渲染帧速

350 阅读2分钟

先尝试 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()...

有了时间戳,即可实现对渲染帧速的控制。