8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Wayland に EGL で三角を描く

Last updated at Posted at 2017-07-05

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

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

Relationship between Wayland and EGL

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

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

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_displaywl_surface は EGL を作る時に必要になるので引数経由で返します。これらは次のような役割を持ちます。

  • wl_display: Wayland サーバと通信する際の経路です。
  • wl_surface: 画面上の四角い領域です。マウスカーソル等もそれぞれ別の wl_surface です。
/*
 * 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 で描画領域を指定します。EGLDisplayEGLSurface を引数経由で返します。それぞれの意味は:

  • EGLDisplay: wl_display に対応する。画面との接続を表す。
  • EGLSurface: wl_surface に対応する四角い領域。実際は wl_egl_window から作られる。
/*
 * 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 の本のサンプルからコピペして単純化しただけです。

/*
 * 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

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;
}
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?