77
70

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 1 year has passed since last update.

作って学ぶWayland

Last updated at Posted at 2017-04-13

はじめに

以前Waylandとは?というエントリで簡単なWaylandの紹介をしましたが、これだけではWaylandに対する理解はまだまだ漠然としたものに留まるのではと思います。

ここでは、実際にコンポジタを実装することでWaylandに対する理解を深めます。
(注意:この記事では画面への出力までは実装しません)
言語はC++14, ビルドはMesonでおこないます。

コンポジタとは何か?

普段みなさんが使っているWebブラウザ等のウインドウアプリケーションはそれ自体では物理的なディスプレイに表示する機能は持っていません。実はコンポジタと呼ばれるアプリケーションがその役割を担っているのです。

コンポジタと各アプリケーションの関係を単純化すると以下のようになります。

  1. 各アプリケーションが(メイン/GPU)メモリに描いた絵を
  2. どうにかしてコンポジタが受け取り
  3. 画面によろしく表示する

Waylandはこの2番で役割を果たすものであるといえます。

なお、コンポジタまたはそれに準ずる機能は現代的なGUIを備えたOSではおおよそ搭載されています。余談ですが以下にコンポジタ様のソフトウェアを列挙します。

  • SurfaceFlinger(Android)
  • WindowServer(macOS/iOS)
  • DWM(Windows)

準備

必要なパッケージをインストールします。Debian系OSでは以下の通り。westonは動作確認のためのクライアント(/usr/lib/weston/weston-simple-egl)が欲しいのでインストールします。

$ sudo apt-get install build-essential libwayland-bin libwayland-dev wayland-protocols weston python3 python3-pip ninja-build
$ pip3 install --user meson

mesonを実行するために$HOME/.local/bin/がPATHになければ追加してください

$ export PATH=$PATH:$HOME/.local/bin
$ which meson
/home/maueki/.local/bin/meson

イベントループ

まずはサーバーの基本となるイベントループを実装します。

main.cpp
#include <cstdio>
#include <wayland-server.h>

int main(int argc, char *argv[])
{
    wl_display* display = wl_display_create();

    printf("hello, wayland\n");
    wl_display_run(display);

    return 0;
}
meson.build
project('learning-wayland', 'cpp', 'c', default_options: ['cpp_std=c++14'])
wl_server = dependency('wayland-server')
executable('server', 'main.cpp', dependencies : wl_server)

以下のコマンドでビルドして実行します。

$ meson build
$ cd build
$ ninja
$ ./server
hello, wayland

イベントループに入るため制御は戻りません。Ctrl+cで終了させてください。

通信の待ち受け

今のままではserverは何もできません。とりあえずクライアントからの接続を受け付けるようにしてみましょう。

main.cpp
#include <cstdio>
#include <cstdlib>
#include <error.h>
#include <wayland-server.h>

int main(int argc, char *argv[])
{
    wl_display* display = wl_display_create();

    const char* socket_name = wl_display_add_socket_auto(display);
    assert(socket_name);

    printf("hello, wayland\n");
    wl_display_run(display);

    return 0;
}

前回からの変更はwl_displayに対してwl_display_add_socket_auto()を呼んだだけですが、これでクライアントの待ち受けができるようになります。
サーバー起動後にクライアントを立ち上げてみましょう。

$ /usr/lib/weston/weston-simple-egl
weston-simple-egl: clients/simple-egl.c:157: init_egl: Assertion `ret == EGL_TRUE' failed.
[1]    32435 abort      /usr/lib/weston/weston-simple-egl

落ちてしまいました……。

通信のデバッグ

環境変数WAYLAND_DEBUG=1をつけて実行することで、Waylandの通信ログが出力されるようになります。
先程クライアントが落ちた処理を環境変数付きで実行したものが以下です。

$ WAYLAND_DEBUG=1 ./server
hello, wayland
[889739.744] wl_display@1.get_registry(new id wl_registry@2)
[889739.857] wl_display@1.sync(new id wl_callback@3)
[889739.956]  -> wl_callback@3.done(0)
[889739.972]  -> wl_display@1.delete_id(3)
[889740.797] wl_display@1.get_registry(new id wl_registry@3)
[889740.853] wl_display@1.sync(new id wl_callback@4)
[889740.886]  -> wl_callback@4.done(0)
[889740.913]  -> wl_display@1.delete_id(4)
[889742.296] wl_display@1.get_registry(new id wl_registry@4)
[889742.473] wl_display@1.sync(new id wl_callback@5)
[889742.512]  -> wl_callback@5.done(0)
[889742.539]  -> wl_display@1.delete_id(5)
$ WAYLAND_DEBUG=1 /usr/lib/weston/weston-simple-egl
[889738.838]  -> wl_display@1.get_registry(new id wl_registry@2)
[889738.981]  -> wl_display@1.sync(new id wl_callback@3)
[889740.159] wl_display@1.delete_id(3)
[889740.252] wl_callback@3.done(0)
[889740.527]  -> wl_display@1.get_registry(new id wl_registry@3)
[889740.619]  -> wl_display@1.sync(new id wl_callback@4)
[889741.097] wl_display@1.delete_id(4)
[889741.184] wl_callback@4.done(0)
[889741.215]  -> wl_display@1.get_registry(new id wl_registry@4)
[889741.273]  -> wl_display@1.sync(new id wl_callback@5)
[889742.654] wl_display@1.delete_id(5)
[889742.732] wl_callback@5.done(0)
weston-simple-egl: clients/simple-egl.c:157: init_egl: Assertion `ret == EGL_TRUE' failed.
[1]    32833 abort      WAYLAND_DEBUG=1 /usr/lib/weston/weston-simple-egl

->がついているログは送信を表し、ついていないものは受信を表しています。
現在のコードにはイベントループ程度しか実装してありませんが、wl_registryインターフェイスに関してはlibwayland側で実装してくれているので、それなりにクライアントとやり取りをしてくれます。
例えば1行目からは
クライアントがwl_display(インスタンスID:1)に対してget_registryリクエストを送信し新しいインスタンスwl_registry(インスタンスID:2)を取得していることがわかります。

EGL初期化

先程クライアントが落ちてしまった原因ですが、クライアントがEGLで描画するのに対してサーバーがEGLをサポートしていないためです。
サーバー側でEGLDisplayを作成しeglBindWaylandDsiplayWL()を用いてwl_displayEGLDisplayをバインドすることで、解決できます。

実装したものが以下になります。
https://github.com/maueki/learning-wayland/tree/tag_init_egl

サーバー側のログです。

$ WAYLAND_DEBUG=1 ./server
hello, wayland
[1618205.647] wl_display@1.get_registry(new id wl_registry@2)
[1618205.728]  -> wl_registry@2.global(1, "wl_drm", 2)
[1618205.780] wl_display@1.sync(new id wl_callback@3)
[1618205.870]  -> wl_callback@3.done(0)
[1618205.901]  -> wl_display@1.delete_id(3)
[1618206.395] wl_display@1.get_registry(new id wl_registry@3)
[1618206.441]  -> wl_registry@3.global(1, "wl_drm", 2)
[1618206.488] wl_display@1.sync(new id wl_callback@4)
[1618206.520]  -> wl_callback@4.done(0)
[1618206.546]  -> wl_display@1.delete_id(4)
[1618207.014] wl_registry@3.bind(1, "wl_drm", 2, new id [unknown]@5)
[1618207.100]  -> wl_drm@5.device("/dev/dri/card0")
[1618207.127]  -> wl_drm@5.format(875713089)
[1618207.153]  -> wl_drm@5.format(875713112)
[1618207.177]  -> wl_drm@5.format(909199186)
[1618207.201]  -> wl_drm@5.format(961959257)
[1618207.225]  -> wl_drm@5.format(825316697)
[1618207.249]  -> wl_drm@5.format(842093913)
[1618207.272]  -> wl_drm@5.format(909202777)
[1618207.296]  -> wl_drm@5.format(875713881)
[1618207.320]  -> wl_drm@5.format(842094158)
[1618207.343]  -> wl_drm@5.format(909203022)
[1618207.367]  -> wl_drm@5.format(1448695129)
[1618207.392]  -> wl_drm@5.capabilities(1)
[1618207.417] wl_display@1.sync(new id wl_callback@4)
[1618207.448]  -> wl_callback@4.done(0)
[1618207.473]  -> wl_display@1.delete_id(4)
[1618208.286] wl_drm@5.authenticate(6)
[1618208.638]  -> wl_drm@5.authenticated()
[1618208.680] wl_display@1.sync(new id wl_callback@4)
[1618208.714]  -> wl_callback@4.done(0)
[1618208.740]  -> wl_display@1.delete_id(4)

クライアント側のログです。

$ WAYLAND_DEBUG=1 /usr/lib/weston/weston-simple-egl
[1618205.151]  -> wl_display@1.get_registry(new id wl_registry@2)
[1618205.340]  -> wl_display@1.sync(new id wl_callback@3)
[1618205.978] wl_display@1.delete_id(3)
[1618206.043] wl_registry@2.global(1, "wl_drm", 2)
[1618206.093] wl_callback@3.done(0)
[1618206.283]  -> wl_display@1.get_registry(new id wl_registry@3)
[1618206.338]  -> wl_display@1.sync(new id wl_callback@4)
[1618206.592] wl_display@1.delete_id(4)
[1618206.624] wl_registry@3.global(1, "wl_drm", 2)
[1618206.677]  -> wl_registry@3.bind(1, "wl_drm", 2, new id [unknown]@5)
[1618206.922] wl_callback@4.done(0)
[1618206.955]  -> wl_display@1.sync(new id wl_callback@4)
[1618207.523] wl_display@1.delete_id(4)
[1618207.555] wl_drm@5.device("/dev/dri/card0")
[1618207.745]  -> wl_drm@5.authenticate(6)
[1618207.778] wl_drm@5.format(875713089)
[1618207.936] wl_drm@5.format(875713112)
[1618207.963] wl_drm@5.format(909199186)
[1618207.987] wl_drm@5.format(961959257)
[1618208.010] wl_drm@5.format(825316697)
[1618208.033] wl_drm@5.format(842093913)
[1618208.057] wl_drm@5.format(909202777)
[1618208.081] wl_drm@5.format(875713881)
[1618208.104] wl_drm@5.format(842094158)
[1618208.127] wl_drm@5.format(909203022)
[1618208.150] wl_drm@5.format(1448695129)
[1618208.174] wl_drm@5.capabilities(1)
[1618208.197] wl_callback@4.done(0)
[1618208.225]  -> wl_display@1.sync(new id wl_callback@4)
[1618208.827] wl_display@1.delete_id(4)
[1618208.876] wl_drm@5.authenticated()
[1618208.896] wl_callback@4.done(0)
has EGL_EXT_buffer_age and EGL_EXT_swap_buffers_with_damage
[1]    37587 segmentation fault  WAYLAND_DEBUG=1 /usr/lib/weston/weston-simple-egl

また別のところで落ちてしまいましたが、先程のエラーが改善されています。
先程のログと異なるところはwl_drmというインターフェイスのログが新たに現れているところです。これはeglBindWaylandDsiplayWL()でwl_displayがEGLDisplayとバインドされることにより、EGLの実装であるMesaがwl_drmというプロトコルを話すようになるためです。
これによりクライアント側でEGLでの処理が可能となります。

wayland-scannerを使ってコードを生成する

ここまではすでに実装されているものを使ってクライアントとやり取りしてきましたが、ここからはいよいよ自前でwayland.xmlの内容を実装していくことになります。
wayland.xmlはwayland-scannerを使用することでCのコードを生成することができ、そのコードを利用して実装を行います。

試しに使ってみましょう。

$ wayland-scanner server-header /usr/share/wayland/wayland.xml >( cat )
(snip)

サーバー用のヘッダファイルが標準出力に表示されたはずです(ちなみに>( cat )は標準出力をファイルに見せかけるおまじないです)。同様にwayland-scanner code [inputfile outputfile]と指定することでソースコードが生成できます。

Mesonに組み込む

custom_target()を使うことでコード生成をMesonに組み込むことができます。

meson.build
project('learning-wayland', 'cpp', 'c')

wl_server = dependency('wayland-server')
egl = dependency('egl')
glesv2 = dependency('glesv2')
x11 = dependency('x11')
xcb = dependency('xcb')
x11_xcb = dependency('x11-xcb')

scanner = dependency('wayland-scanner')
scanner_path = scanner.get_pkgconfig_variable('wayland_scanner')

wayland_server_header = custom_target('wayland-server-header',
                                      output: 'wayland-server-protocol.h',
                                      input: '/usr/share/wayland/wayland.xml',
                                      command: [scanner_path, 'server-header' ,'@INPUT@', '@OUTPUT@'])

wayland_server_code = custom_target('wayland-server-code',
                                    output: 'wayland-server-protocol.c',
                                    input:  '/usr/share/wayland/wayland.xml',
                                    command: [scanner_path, 'code' ,'@INPUT@', '@OUTPUT@'])

executable('server', 'main.cpp', 'egl.cpp',
           wayland_server_header, wayland_server_code,
           dependencies : [wl_server, egl, glesv2, x11, xcb, x11_xcb])

グローバルオブジェクトについて

実装を開始する前にもう少しだけ。
Waylandには「グローバルオブジェクト」と呼ばれるものがあります。これは、サーバ側でwl_global_create()関数によって作られ、クライアントの接続時にwl_registry.globalイベントによって、クライアント側に通知されます。クライアントはここで通知されたグローバルオブジェクトを通してサーバとやり取りを行うことが可能となります。

例えばweston-simple-eglでwl_registry.globalイベントを受け取るregistry_handle_global()を見てみましょう。

simple-egl.c
static void
registry_handle_global(void *data, struct wl_registry *registry,
                       uint32_t name, const char *interface, uint32_t version)
{
        struct display *d = data;

        if (strcmp(interface, "wl_compositor") == 0) {
                d->compositor =
                        wl_registry_bind(registry, name,
                                         &wl_compositor_interface, 1);
        } else if (strcmp(interface, "xdg_shell") == 0) {
                d->shell = wl_registry_bind(registry, name,
                                            &xdg_shell_interface, 1);
                xdg_shell_add_listener(d->shell, &xdg_shell_listener, d);
                xdg_shell_use_unstable_version(d->shell, XDG_VERSION);
        } else if (strcmp(interface, "wl_seat") == 0) {
                d->seat = wl_registry_bind(registry, name,
                                           &wl_seat_interface, 1);
                wl_seat_add_listener(d->seat, &seat_listener, d);
        } else if (strcmp(interface, "wl_shm") == 0) {
                d->shm = wl_registry_bind(registry, name,
                                          &wl_shm_interface, 1);
                d->cursor_theme = wl_cursor_theme_load(NULL, 32, d->shm);
                if (!d->cursor_theme) {
                        fprintf(stderr, "unable to load default theme\n");
                        return;
                }
                d->default_cursor =
                        wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr");
                if (!d->default_cursor) {
                        fprintf(stderr, "unable to load default left pointer\n");
                        // TODO: abort ?
                }
        } else if (strcmp(interface, "ivi_application") == 0) {
                d->ivi_application =
                        wl_registry_bind(registry, name,
                                         &ivi_application_interface, 1);
        }
}

wl_registry_bind()wl_registry.globalイベントの引数を用いてクライアント側のグローバルオブジェクトを作成する関数です。
weston-simple-eglはwl_compositor, xdg_shell, wl_seat, wl_shm, ivi_applicationというグローバルオブジェクトを使用していることがわかります。
なおこれらは全て必須というわけではなく、例えばweston-simple-eglであればxdg_shellivi_applicationはどちらかさえサーバーが実装していれば良いものです。

wl_compositor実装

ではいよいよ実装していきましょう。
weston-simple-eglはグローバルオブジェクトとしてはwl_compositorzxdg_shell_v6を実装すれば動作してくれます。まずはwl_compositorを実装していきます。

最初なのでwayland.xmlを確認しておきましょう。

wayland.xml
  <interface name="wl_compositor" version="4">
    <description summary="the compositor singleton">
      A compositor.  This object is a singleton global.  The
      compositor is in charge of combining the contents of multiple
      surfaces into one displayable output.
    </description>

    <request name="create_surface">
      <description summary="create new surface">
        Ask the compositor to create a new surface.
      </description>
      <arg name="id" type="new_id" interface="wl_surface" summary="the new surface"/>
    </request>

    <request name="create_region">
      <description summary="create new region">
        Ask the compositor to create a new region.
      </description>
      <arg name="id" type="new_id" interface="wl_region" summary="the new region"/>
    </request>
  </interface>

クライアントから飛んでくるwl_compositor.create_surfacewl_compositor.create_regionのコールバック関数を作成・登録すれば良いことがわかります。
これらのコールバック関数の宣言はwayland-scannerで生成したヘッダに有ります。

wayland-server-protocol.h
/**
 * @ingroup iface_wl_compositor
 * @struct wl_compositor_interface
 */
struct wl_compositor_interface {
    /**
     * create new surface
     *
     * Ask the compositor to create a new surface.
     * @param id the new surface
     */
    void (*create_surface)(struct wl_client *client,
                   struct wl_resource *resource,
                   uint32_t id);
    /**
     * create new region
     *
     * Ask the compositor to create a new region.
     * @param id the new region
     */
    void (*create_region)(struct wl_client *client,
                  struct wl_resource *resource,
                  uint32_t id);
};

以下実装です。

compositor.h
#pragma once

#include <wayland-server.h>

class Compositor {
    wl_display* display_;

public:
    Compositor(wl_display* display);

    static void global_bind(wl_client* client, void* data, uint32_t version,
                            uint32_t id);

    static void create_surface(wl_client* client, wl_resource* resource,
                               uint32_t id);

    static void create_region(struct wl_client* client,
                              struct wl_resource* resource, uint32_t id);
};
compositor.cpp

#include "compositor.h"
#include <wayland-server-protocol.h>
#include <cassert>
#include <cstdio>

static struct wl_compositor_interface compositor_interface = {
    &Compositor::create_surface, &Compositor::create_region};

Compositor::Compositor(wl_display* display)
    : display_(display) {
    wl_global_create(display_, &wl_compositor_interface, 4, this,
                     &Compositor::global_bind);
}

void Compositor::global_bind(wl_client* client, void* data, uint32_t version,
                             uint32_t id) {
    wl_resource* resource =
        wl_resource_create(client, &wl_compositor_interface, version, id);
    assert(resource);

    auto self = static_cast<Compositor*>(data);
    wl_resource_set_implementation(resource, &compositor_interface, self,
                                   nullptr);
}

void Compositor::create_surface(wl_client* client, wl_resource* resource,
                                uint32_t id) {
    fprintf(stderr, "Compositor::create_surface not implemented\n");
}

void Compositor::create_region(struct wl_client* client,
                               struct wl_resource* resource, uint32_t id) {
    fprintf(stderr, "Compositor::create_region not implemented\n");
}

Compositorのコンストラクタ内でwl_global_create()を用いてグローバルオプジェクトを作成します。
クライアントがwl_registry_bind()を呼び出すとwl_global_create()の引数で指定したCompositor::global_bind()が呼び出され、wl_resourceの作成とインターフェイスの紐付けが行われます。

最後にmain.cppにCompositorを追加します。

main.cpp
@@ -2,8 +2,11 @@
 #include <cstdlib>
 #include <error.h>
 #include <cassert>
+#include <memory>
 #include <wayland-server.h>

+#include "compositor.h"
+
 bool egl_init(wl_display* wl_disp);

 int main(int argc, char *argv[])
@@ -16,6 +19,8 @@ int main(int argc, char *argv[])
     const char* socket_name = wl_display_add_socket_auto(display);
     assert(socket_name);

+    auto compositor = std::make_unique<Compositor>(display);
+
     printf("hello, wayland\n");
     wl_display_run(display);

WAYLAND_DEBUG=1をつけて実行してみるとwl_compositorに関する通信が増えていることが確認できます。以下ログの抜粋です。

server_log
[301169.474] wl_display@1.get_registry(new id wl_registry@2)
[301169.543]  -> wl_registry@2.global(1, "wl_drm", 2)
[301169.628]  -> wl_registry@2.global(2, "wl_compositor", 4)
(snip)
[301170.512] wl_registry@2.bind(2, "wl_compositor", 1, new id [unknown]@4)
client_log
[301170.034] wl_registry@2.global(2, "wl_compositor", 4)
[301170.085]  -> wl_registry@2.bind(2, "wl_compositor", 1, new id [unknown]@4)

xdg_shellの実装

続いてxdg_shellです。現時点でクライアントがabortするのはxdg_shell(またはivi-application)が実装されていないためです。
xdg_shellを定義しているxdg-shell-unstable-v6.xmlはwayland-protocolsパッケージをインストールしていれば、/usr/share/wayland-protocols/unstable/xdg-shell以下にあります。

まずは、mesonにコード生成を記述します。

meson.build
@@ -20,6 +20,18 @@ wayland_server_code = custom_target('wayland-server-code',
                                     input:  '/usr/share/wayland/wayland.xml',
                                     command: [scanner_path, 'code' ,'@INPUT@', '@OUTPUT@'])

+xdg_shell_server_header = custom_target('xdg-shell-header',
+                                        output: 'xdg-shell-server-protocol.h',
+                                        input: '/usr/share/wayland-protocols/unstable/xdg-shell/xdg-shell-unstable-v6.xml',
+                                        command: [scanner_path, 'server-header' ,'@INPUT@', '@OUTPUT@'])
+
+xdg_shell_server_code = custom_target('xdg-shell-code',
+                                        output: 'xdg-shell-server-protocol.c',
+                                        input: '/usr/share/wayland-protocols/unstable/xdg-shell/xdg-shell-unstable-v6.xml',
+                                        command: [scanner_path, 'code' ,'@INPUT@', '@OUTPUT@'])
+
+
 executable('server', 'main.cpp', 'egl.cpp', 'compositor.cpp',
            wayland_server_header, wayland_server_code,
+           xdg_shell_server_header, xdg_shell_server_code,
            dependencies : [wl_server, egl, glesv2, x11, xcb, x11_xcb])

コードは省略。以下のshell.h, shell.cpp, main.cppを見てください。
https://github.com/maueki/learning-wayland/tree/tag_impl_shell

この状態で動作させると今度は次のようなエラーになります。

client_log
[2720220.595] wl_display@1.error(wl_display@1, 1, "invalid arguments for zxdg_shell_v6@5.get_xdg_surface")
wl_display@1: error 1: invalid arguments for zxdg_shell_v6@5.get_xdg_surface

これは現時点の実装ではCompositor::create_surface()(wl_compositor.create_surfaceに対応)が空実装であるため、本来作られるはずのwl_surfaceオブジェクトが作られておらず、さらにその作られていないはずのwl_surfaceオブジェクトを引数としてShell::get_xdg_surface()(zxdg_shell_v6.get_xdg_surface)が呼び出されたために起きたエラーです。

wl_surface実装

エラーはでているものの、westno-simple-eglが必要とするグローバルオブジェクトは実装できました。続いてグローバルオブジェクトが生成する個々のオブジェクトについて実装していきます。まずは、wl_surfaceです。
wl_surfaceはコンポジタ上の描画領域です。クライアントがwl_surfaceに実際の描画内容を指すwl_bufferをアタッチ(wl_suraface.attachリクエスト)することによりコンポジタは実際に画面に絵を表示できるようになります。

実装は以下を参照してください。
https://github.com/maueki/learning-wayland/tree/tag_impl_surface

wl_compositorとそれほど違いはないので異なることろだけかいつまんで説明していきます。

shell.cpp
static void destroy_surface(wl_resource *resource) {
    auto surface = static_cast<Surface *>(wl_resource_get_user_data(resource));
    delete surface;
}

Surface::Surface(wl_client *client, uint32_t id) {
    wl_resource *resource =
        wl_resource_create(client, &wl_surface_interface, 4, id);
    assert(resource);

    wl_resource_set_implementation(resource, &surface_interface, this,
                                   &destroy_surface);
}

グローバルオブジェクトと異なり、クライアントが死んだときなどにはSurfaceのインスタンスも一緒に死んでほしいため、Compositorのときにはnullptrだったwl_resource_set_implementation()の第4引数にデストラクタ関数を指定しています。
これにより何らかの理由でwl_resourceが消滅するとき一緒にSurfaceもdeleteされます。

compositor.cpp
void Compositor::create_surface(wl_client* client, wl_resource* resource,
                                uint32_t id) {
    auto surface = new Surface(client, id);
    assert(surface);
}

Compositor::create_surface()でSurfaceを生成します。これにより、クライアントからのwl_compositor.create_surfaceリクエストで、wl_surfaceオブジェクトが生成されることになります。
一見メモリリークするように見えますが、Surfaceインスタンスはwl_resourceに紐ついて管理されるため問題はありません。

zxdg_surface_v6, zxdg_toplevel_v6実装

更にweston-simple-eglが必要とするzxdg_surface_v6, zxdg_toplevel_v6を実装します。
こちらもwl_surfaceとあまり違いはないので実装は省略以下を参照してください。

さてここまで実装したところで動作させてみると、エラーは出なくなりましたがログが止まってしまいました。
weston-simple-eglは常に描画し続けるため、正常に動いているならばログが出続けるはずです。何が問題でしょうか。

configureイベントの送信

実はクライアントはzxdg_surface_v6作成後にzxdg_surface_v6.configureイベントが来るのをじっと待ちます。
お約束として、シェルサーフェス作成後にコンポジタはクライアントに対してそのサーフェスがどのように表示されるかをクライアントに通知し、最後にconfigureイベントを送信します。クライアントはconfigureイベント受け取り後にconfigureイベントの前に送られた情報を元に描画内容を変更することが可能です。

今回はとりあえずconfigureイベントだけ送ってクライアントを待ちから解放しましょう。

shell-surface.cpp
@@ -23,6 +23,8 @@ ShellSurface::ShellSurface(wl_client* client, uint32_t id) {

     wl_resource_set_implementation(resource, &shell_surface_interface, this,
                                    &destroy_shell_surface);
+
+    zxdg_surface_v6_send_configure(resource, 1);
 }

 void ShellSurface::destroy(wl_client* client, wl_resource* resource) {

実行するとコンポジタからzxdg_surface_v6.configureが送信されクライアントから応答のzxdg_surface_v6.ack_configure`リクエストが来ていることが確認できます。

server_log
[153589.752]  -> zxdg_surface_v6@8.configure(1)
(snip)
[153594.034] zxdg_surface_v6@8.ack_configure(1)

しかしログはまた止まってしまいます。

フレームコールバックの実装

今度の待ちは「描画完了待ち」です。コンポジタが実際に描画を行っている最中にクライアントがコンポジタ使用中のバッファに描き込んでしまわないようにコンポジタからクライアントに描画完了通知ができるようになっています。

具体的には、クライアントが描画指示の前にwl_surface.frameリクエストでwl_callbackオブジェクトを作成します。コンポジタは描画が完了すると先程のオブジェクトに対してwl_callback.doneイベントを送信します。クライアントはwl_callback.doneが届いていることを確認して次の描画を行います。(届いていない場合は待ち続けます)

以下実装です。

surface.cpp
@@ -15,7 +15,7 @@ static void destroy_surface(wl_resource *resource) {
     delete surface;
 }

-Surface::Surface(wl_client *client, uint32_t id) {
+Surface::Surface(wl_client *client, uint32_t id) : callback_(nullptr) {
     wl_resource *resource =
         wl_resource_create(client, &wl_surface_interface, 4, id);
     assert(resource);
@@ -40,7 +40,9 @@ void Surface::damage(wl_client *client, wl_resource *resource, int32_t x,

 void Surface::frame(wl_client *client, wl_resource *resource,
                     uint32_t callback) {
-    fprintf(stderr, "Surface::frame not implemented\n");
+    auto self = static_cast<Surface*>(wl_resource_get_user_data(resource));
+    self->callback_ = wl_resource_create(client, &wl_callback_interface, 4, callback);
+    assert(self->callback_);
 }

 void Surface::set_opaque_region(wl_client *client, wl_resource *resource,
@@ -54,7 +56,12 @@ void Surface::set_input_region(wl_client *client, wl_resource *resource,
 }

 void Surface::commit(wl_client *client, wl_resource *resource) {
-    fprintf(stderr, "Surface::commit not implemented\n");
+    auto self = static_cast<Surface*>(wl_resource_get_user_data(resource));
+    if (self->callback_) {
+        wl_callback_send_done(self->callback_, 0);
+        wl_resource_destroy(self->callback_);
+        self->callback_ = nullptr;
+    }
 }

 void Surface::set_buffer_transform(wl_client *client, wl_resource *resource,
surface.h
@@ -3,6 +3,7 @@
 #include <wayland-server.h>

 class Surface {
+    wl_resource* callback_;
 public:
     Surface(wl_client *client, uint32_t id);

本来は正しく描画完了後にwl_callback_send_done()を実行するのが正しいのですが、今回はwl_surface.commitのタイミングで実行してしまいます。

実行するとようやくログが流れるようになります。が、wl_surface.commitといったログが流れないため何かおかしいです。

バッファの解放処理

(加筆予定)

以下のバッファ解放処理を入れることでクライアントと正しくやり取りできるようになります。

surface.cpp
@@ -15,7 +15,9 @@ static void destroy_surface(wl_resource *resource) {
     delete surface;
 }

-Surface::Surface(wl_client *client, uint32_t id) : callback_(nullptr) {
+Surface::Surface(wl_client *client, uint32_t id)
+    : buffer_(nullptr)
+    , callback_(nullptr) {
     wl_resource *resource =
         wl_resource_create(client, &wl_surface_interface, 4, id);
     assert(resource);
@@ -30,7 +32,8 @@ void Surface::destroy(wl_client *client, wl_resource *resource) {

 void Surface::attach(wl_client *client, wl_resource *resource,
                      wl_resource *buffer, int32_t x, int32_t y) {
-    fprintf(stderr, "Surface::attach not implemented\n");
+    auto self = static_cast<Surface *>(wl_resource_get_user_data(resource));
+    self->buffer_ = buffer;
 }

 void Surface::damage(wl_client *client, wl_resource *resource, int32_t x,
@@ -57,6 +60,12 @@ void Surface::set_input_region(wl_client *client, wl_resource *resource,

 void Surface::commit(wl_client *client, wl_resource *resource) {
     auto self = static_cast<Surface*>(wl_resource_get_user_data(resource));
+
+    if (self->buffer_) {
+        wl_resource_queue_event(self->buffer_, WL_BUFFER_RELEASE);
+        self->buffer_ = nullptr;
+    }
+
     if (self->callback_) {
         wl_callback_send_done(self->callback_, 0);
         wl_resource_destroy(self->callback_);
surface.h
@@ -3,6 +3,7 @@
 #include <wayland-server.h>

 class Surface {
+    wl_resource* buffer_;
     wl_resource* callback_;
 public:
     Surface(wl_client *client, uint32_t id);

実行すると以下のログが流れ続けます

server_log
[3182507.735] wl_surface@6.set_opaque_region(nil)
Surface::set_opaque_region not implemented
[3182507.848] wl_surface@6.frame(new id wl_callback@11)
[3182507.859] wl_surface@6.attach(wl_buffer@13, 0, 0)
[3182507.879] wl_surface@6.damage(0, 0, 2147483647, 2147483647)
Surface::damage not implemented
[3182507.893] wl_surface@6.commit()
[3182507.897]  -> wl_buffer@13.release()
[3182507.901]  -> wl_callback@11.done(0)
[3182507.907]  -> wl_display@1.delete_id(11)

おわりに

最終的なコードはここにあります。
https://github.com/maueki/learning-wayland

画面に表示はされませんが、クライアントとの間でやり取りはできるようになりました。
あとはwl_bufferからeglCreateImageKHR()EGLImageKHRを取得しglEGLImageTragetTexture2DOES()でテクスチャ化すれば表示は簡単にできるようになるので、そこは読者への宿題にしたいと思います。(いってみたかった)

思いがけず長い記事になってしまいましたが、この記事がWayland理解の一助になれば幸いです。

77
70
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
77
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?