想定読者
Weston/Waylandについての初歩的な知識を身に着けたいプログラマ。
Linuxデスクトップ上でwestonを起動できる環境が望ましい。
動作環境
以下Westonの動作を確認する場合があるが、Westonのバージョンは14を想定している。他のバージョンを使用している場合は若干動作が異なる可能性がある。
コンポジタとは
Westonはコンポジタである、といわれる。ではコンポジタとは一体なんであろうか。
Webブラウザ等のGUIアプリケーションは、ざっくりいえば「メモリ上に自身のウインドウを描画する」ことしかできない。
そのため、各アプリケーションがメモリ上に描いた絵を受け取り、ウインドウ配置を考慮して合成(コンポジション)して一枚の絵にした上で実際に物理ディスプレイに表示する役割を担う存在が必要になる。これが、一般に「コンポジタ」と呼ばれるアプリケーションである。
実際の主要なOSにおける代表的なコンポジタは以下の通り、私達は意識せずこれらのアプリケーションの恩恵を受けているわけである。
- Android:SurfaceFlinger
- macOS/iOS:WindowServer
- Windows:DWM(Desktop Window Manager)
- Linux:gnome-shell、kwin等
上記でWestonは物理ディスプレイに表示する役割1を持っていると書いたが、下図のようにデバッグ用途として(Westonを含む)別のコンポジタ上に表示したりすることもできる。
Waylandについて
Webブラウザ等のクライアントがコンポジタに自身の描画結果を渡し、コンポジタが物理ディスプレイに描画する、というのが前述の説明であった。
では、クライアントとサーバー間でどのようなやり取りをして描画結果等の受け渡しを行っているのだろうか。ここで登場するのがWaylandプロトコルである。
weston-simple-eglという三角形が回転するだけのアプリを例にして通信の様子を見てみる。下図はMesa2を利用している場合を想定している。
wayland通信を実際に確認する
環境変数WAYLAND_DEBUG
を利用するとwayland通信のログを取得することができる。
$ WAYLAND_DEBUG=1 weston-simple-egl > wayland-log.txt 2>&1
以下はログの一部である。
[3201586.879] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
[3201586.906] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
[3201587.031] {Display Queue} wl_display#1.delete_id(3)
[3201587.067] {Default Queue} -> wl_registry#2.bind(13, "wl_shm", 1, new id [unknown]#7)
->
がついているものは送信(ここではweston-simple-eglからwestonへの送信)を表しており、->
が無いものは受信を表している。
では実際にどのような通信をおこなっているか、重要ななものを抜粋してみていく。
以降Wayland通信においてはコンポジタ(ここではWeston)は基本的にサーバーとして振る舞うためサーバーと記載がある場合はWestonのことだと思ってもらいたい。
通信のはじまり
[3201586.879] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
上記は通信の始まりである。通信はいずれも以下のような構造をしており、上記通信では「id=1のwl_display
オブジェクトに対してget_registry()
リクエストをおこなっている」と読める。
<オブジェクト名>#<id>.<リクエスト名 or イベント名> (<引数>)
このようにwayland通信はオブジェクトとそれに対するリクエストという形で通信をおこなう。なおクライアントからサーバーへの送信は「 リクエスト(request) 」、サーバーからクライアントへの送信は「 イベント(event) 」と呼ばれることを覚えておこう。
wl_display
オブジェクトは全てのクライアントが最初から持っているオブジェクトであり、最初の通信ではget_registry()
リクエストでwl_registry
オブジェクト(id=2)を要求している。
サーバーからインターフェイスの通知
wl_registry
が取得されるとサーバーからglobal()
イベントという形で使用可能なインターフェイスが通知されてくる。
[3201602.290] {Default Queue} wl_registry#2.global(14, "wl_drm", 2)
[3201602.315] {Default Queue} wl_registry#2.global(16, "wl_seat", 7)
[3201602.328] {Default Queue} wl_registry#2.global(18, "wl_output", 4)
これは簡単に言うとサーバーから「私(サーバー)はwl_drm
,wl_seat
,wl_output
という機能が使えますよ」という通知である。
クライアントは通知されてきたインターフェイスの中から必要なものに対してbind()
リクエストを送りオブジェクトを取得する。
[3201602.328] {Default Queue} wl_registry#2.global(18, "wl_output", 4)
[3201602.331] {Default Queue} -> wl_registry#2.bind(18, "wl_output", 2, new id [unknown]#10)
このログではwl_output
に対してbind()
リクエストを送信し、id=10のwl_output
オブジェクトを取得している。(注意: 「取得する」と書いてあるが、わかりやすさのためそう書いているだけであり、サーバーから応答が返ってくるわけではない)
なお必要な機能が通知されてこない場合多くのクライアントは異常終了する。
Mesaの挙動
前述の図ではweston-simple-egl以外にMesaもwayland通信をおこなっていたがその様子はログからも窺うことができる。
{mesa egl display queue}
のログはまさにMesaがWayland通信している様子である。
[3201613.952] {mesa egl display queue} -> wl_display#1.get_registry(new id wl_registry#3)
[3201613.961] {mesa egl display queue} -> wl_display#1.sync(new id wl_callback#12)
[3201614.093] {mesa egl display queue} wl_registry#3.global(1, "wl_compositor", 5)
[3201614.094] {mesa egl display queue} wl_registry#3.global(2, "wl_subcompositor", 1)
[3201614.095] {mesa egl display queue} wl_registry#3.global(3, "wp_viewporter", 1)
サーフェス作成
次にWeston上に描画を行うための領域であるサーフェスを作る。
[3201619.717] {Default Queue} -> wl_compositor#4.create_surface(new id wl_surface#14)
[3201619.724] {Default Queue} -> xdg_wm_base#11.get_xdg_surface(new id xdg_surface#15, wl_surface#14)
[3201619.726] {Default Queue} -> xdg_surface#15.get_toplevel(new id xdg_toplevel#16)
[3201619.727] {Default Queue} -> xdg_toplevel#16.set_title("simple-egl")
[3201619.729] {Default Queue} -> xdg_toplevel#16.set_app_id("org.freedesktop.weston.simple-egl")
[3201619.730] {Default Queue} -> wl_surface#14.commit()
ここではサーフェスを作りタイトルやapp_idを設定している。設定したものはcommit()
リクエストをおこなうことで確定される。
実はクライアントコード的には「EGLのサーフェス作成関数(eglCreateWindowSurface()
)」を呼び出しているだけであり、Mesaの持つEGLの実装が勝手にWayland通信をおこなってサーフェスを作っているというのが実情。
この後もクライアントコードはWaylandをあまり意識せずEGLやOpenGLの処理を呼び出し、EGLの内部実装がWayland通信をおこなうといった状態になっている。
バッファ切り替え
サーフェスが作成させると後はクライアントは以下を繰り返し続けることになる。
- OpenGLを使用しての画面描画
- ダブルバッファの切り替え
1は自身の持つバッファへの描画をおこなうだけなのでWayland通信はおこなわれないが、2については実際に画面に表示されるバッファの切り替えをWestonに伝える必要があるため、Wayland通信がおこなわれる。なおここでもクライアントのプログラムはEGLのバッファ切り替え関数であるeglSwapBuffers()
を読んでいるだけであり実際のWayland通信はMesaがもつEGL実装がおこなう。
1回の通信は以下の通り。
[3202054.015] {mesa egl surface queue} wl_buffer#22.release()
[3202054.024] {mesa egl surface queue} wl_callback#20.done(2841281643)
[3202054.035] {mesa egl surface queue} -> wl_surface#14.frame(new id wl_callback#20)
[3202054.046] {mesa egl surface queue} -> wl_surface#14.attach(wl_buffer#24, 0, 0)
[3202054.056] {mesa egl surface queue} -> wl_surface#14.damage_buffer(61, 62, 127, 127)
[3202054.064] {mesa egl surface queue} -> wl_surface#14.commit()
-
wl_buffer#22.release()
- id22のバッファが使い終わった(物理ディスプレイに描画し終えた)とWestonから報告
-
wl_callback#20.done(2841281643)
- 描画が終わったとの通知。クライアントは次のバッファの準備を開始する
-
-> wl_surface#14.frame(new id wl_callback#20)
- Westonの次回の描画完了通知を要求
-
-> wl_surface#14.attach(wl_buffer#24, 0, 0)
- サーフェスにid24のバッファをアタッチ(これによりid14サーフェスの描画内容はid24のバッファということになる)
-
-> wl_surface#14.damage_buffer(61, 62, 127, 127)
- サーフェスの描画利用域の内変更が合った箇所を指定
-
-> wl_surface#14.commit()
- サーフェスの変更内容を確定
更新履歴
- 2025-07-09 ディスプレイサーバーという表記をコンポジタに改め、ディスプレイサーバーについての説明を脚注に追加。Mesaの脚注を追加。