Wayland に EGL で三角を描く

shm http://qiita.com/propella/items/d180dd0425ecd99efd42 に引き続いて EGL で三角を描く練習。

Wayland と EGL でややこしいのは、沢山の構造体が出てきて色々な依存関係で結びついている所です。一見不必要なくらい内部が露出した API な気がしますが、恐らく柔軟な制御のためには必要なのでしょう。どうにも覚えきれないので、各構造体と関数の依存関係について図にまとめました。青い箱が Wayland の関数。赤い箱が EGL の関数です。この図は例えば、「eglCreateContext の引数には eglChooseConfig で取得した EGLConfig と eglGetDisplay で取得した EGLDisplay が必要です」というような事を表現しています。

![Relationship between Wayland and EGL](https://qiita-image-store.s3.amazonaws.com/0/35350/125b77d9-4060-e09b-eb71-dc5404453bb9.png)

複雑ですが、Wayland と EGL の間で共通に使われる物は `wl_display` (EGLNativeDisplayType) と `wl_egl_window` (EGLNativeWindowType) の二つだけなので、ここに気をつければ移植性の高いコードが書けそうです。

さて、今回 Wayland に接続すると compositor と shell という二つの global を使うので、コールバックで取得します。コールバックの最初の引数 data には好きなポインタを指定出来るので、ここに WaylandGlobals という構造体を渡して global への参照を入れる事にします。

```c
struct WaylandGlobals {
    struct wl_compositor* compositor;
    struct wl_shell* shell;
};

static void registry_global(void* data, struct wl_registry* registry, uint32_t id, const char* interface, uint32_t version)
{
    struct WaylandGlobals* globals = (struct WaylandGlobals *)data;
    if (strcmp(interface, "wl_compositor") == 0) {
        globals->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        globals->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
    }
}

static const struct wl_registry_listener registry_listener = { registry_global, NULL };
```

Wayland と接続してコールバックを呼ぶやり方は shm と同じです。ここで取得した `wl_display` と `wl_surface` は EGL を作る時に必要になるので引数経由で返します。これらは次のような役割を持ちます。

* `wl_display`: Wayland サーバと通信する際の経路です。
* `wl_surface`: 画面上の四角い領域です。マウスカーソル等もそれぞれ別の `wl_surface` です。

```c
/*
 * Connect to the Wayland display and return the display and the surface
 * output wlDisplay
 * output wlSurface
 */
static void initWaylandDisplay(struct wl_display** wlDisplay, struct wl_surface** wlSurface)
{
    struct WaylandGlobals globals = {0};

    *wlDisplay = wl_display_connect(NULL);
    assert(*wlDisplay != NULL);

    struct wl_registry* registry = wl_display_get_registry(*wlDisplay);
    wl_registry_add_listener(registry, &registry_listener, (void *) &globals);

    wl_display_dispatch(*wlDisplay);
    wl_display_roundtrip(*wlDisplay);
    assert(globals.compositor);
    assert(globals.shell);

    *wlSurface = wl_compositor_create_surface(globals.compositor);
    assert(*wlSurface != NULL);

    struct wl_shell_surface* shellSurface = wl_shell_get_shell_surface(globals.shell, *wlSurface);
    wl_shell_surface_set_toplevel(shellSurface);
}
```

EGL を作るためには `wl_display` (EGLNativeDisplayType) と `wl_egl_window` (EGLNativeWindowType) が必要です。`wl_display` は先ほど `initWaylandDisplay` で取得しました。`wl_egl_window` は後ほど作ります。特に Wayland に依存した EGL の初期化というのは無く、他のプラットフォームと同様 eglChooseConfig で必要な項目を設定して eglMakeCurrent で描画領域を指定します。`EGLDisplay` と `EGLSurface` を引数経由で返します。それぞれの意味は:

* `EGLDisplay`: `wl_display` に対応する。画面との接続を表す。
* `EGLSurface`: `wl_surface` に対応する四角い領域。実際は `wl_egl_window` から作られる。

```c
/*
 * Configure EGL and return necessary resources
 * input nativeDisplay
 * input nativeWindow
 * output eglDisplay
 * output eglSurface
 */
static void initEGLDisplay(EGLNativeDisplayType nativeDisplay, EGLNativeWindowType nativeWindow, EGLDisplay* eglDisplay, EGLSurface* eglSurface)
{
    EGLint number_of_config;
    EGLint config_attribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };
    
    static const EGLint context_attribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    
    *eglDisplay = eglGetDisplay(nativeDisplay);
    assert(*eglDisplay != EGL_NO_DISPLAY);
    
    EGLBoolean initialized = eglInitialize(*eglDisplay, NULL, NULL);
    assert(initialized == EGL_TRUE);
    
    EGLConfig configs[1];
    
    eglChooseConfig(*eglDisplay, config_attribs, configs, 1, &number_of_config);
    assert(number_of_config);
    
    EGLContext eglContext = eglCreateContext(*eglDisplay, configs[0], EGL_NO_CONTEXT, context_attribs);
    
    *eglSurface = eglCreateWindowSurface(*eglDisplay, configs[0], nativeWindow, NULL);
    assert(*eglSurface != EGL_NO_SURFACE);
    
    EGLBoolean makeCurrent = eglMakeCurrent(*eglDisplay, *eglSurface, *eglSurface, eglContext);
    assert(makeCurrent == EGL_TRUE);
}
```

Wayland 内で EGL を使うには `wl_egl_window_create` を使って `wl_surface` から `wl_egl_window` を取得します。この `wl_egl_window` は EGLNativeWindowType にキャストする事が出来ます。実際に描画する時に必要になるので、この関数では wl_display, EGLDisplay, EGLSurface を引数のポインタ経由で返しています。これで EGL が取得出来るのであとは絵を描くだけです。幅と高さは `wl_egl_window_create` で指定します。という事は、wl_surface を作る時は大きさの情報は不要みたいです。不思議ですね。(shm の時も `wl_shm_pool_create_buffer` ではじめて幅と高さを指定しました。)

```
/*
 * Connect Wayland and make EGL
 * input width
 * input height
 * output wlDisplay
 * output eglDisplay
 * output eglSurface
 */
static void createWindow(GLint width, GLint height, struct wl_display** wlDisplay, EGLDisplay* eglDisplay, EGLSurface* eglSurface)
{
    struct wl_surface* wlSurface;
    initWaylandDisplay(wlDisplay, &wlSurface);

    struct wl_egl_window* wlEglWindow = wl_egl_window_create(wlSurface, width, height);
    assert(wlEglWindow != NULL);

    initEGLDisplay((EGLNativeDisplayType) *wlDisplay, (EGLNativeWindowType) wlEglWindow, eglDisplay, eglSurface);
}
```

OpenGL ES で絵を描く部分はまだ良くわからないので、OpenGL ES の本のサンプルからコピペして単純化しただけです。

```c
/*
 * Return the loaded and compiled shader
 */
GLuint LoadShader(GLenum type, const char* shaderSrc)
{
    GLuint shader = glCreateShader(type);
    assert(shader);

    glShaderSource(shader, 1, &shaderSrc, NULL);
    glCompileShader(shader);

    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    assert(compiled);

    return shader;
}

/*
 * Initialize the shaders and return the program object
 */
GLuint initProgramObject()
{
    char vShaderStr[] = "#version 300 es                          \n"
                        "layout(location = 0) in vec4 vPosition;  \n"
                        "void main()                              \n"
                        "{                                        \n"
                        "   gl_Position = vPosition;              \n"
                        "}                                        \n";

    char fShaderStr[] = "#version 300 es                              \n"
                        "precision mediump float;                     \n"
                        "out vec4 fragColor;                          \n"
                        "void main()                                  \n"
                        "{                                            \n"
                        "   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
                        "}                                            \n";

    GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
    GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);

    GLuint programObject = glCreateProgram();
    assert(programObject);

    glAttachShader(programObject, vertexShader);
    glAttachShader(programObject, fragmentShader);

    glLinkProgram(programObject);

    GLint linked;
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
    assert(linked);

    return programObject;
}

/*
 * Draw a triangle
 */
void draw(GLuint programObject, GLint width, GLint height)
{
    GLfloat vVertices[] = { 0.0f, 1.0f, 0.0f,
        -1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f };

    glViewport(0, 0, width, height);
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(programObject);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}
```

これらを main で一つにまとめます。描画したきり更新しないので簡単です。ソース全体: https://gist.github.com/propella/949261f89d83a6e184fb745a48de27c3#file-triangle_simple-c

```c
int main(int argc, char** argv)
{
    int width = 320;
    int height = 240;

    struct wl_display* wlDisplay;
    EGLDisplay eglDisplay;
    EGLSurface eglSurface;

    createWindow(width, height, &wlDisplay, &eglDisplay, &eglSurface);

    GLuint programObject = initProgramObject();
    assert(programObject);

    draw(programObject, width, height);
    eglSwapBuffers(eglDisplay, eglSurface);

    while (wl_display_dispatch(wlDisplay) != -1) {
    }

    glDeleteProgram(programObject);

    wl_display_disconnect(wlDisplay);

    return 0;
}
```