Wayland - ウィンドウ用イメージの作成について

ウィンドウを表示する手順
Wayland には「ウィンドウを作成する」という関数はありません。

ウィンドウを表示する場合は、ウィンドウのイメージを用意し、その中にウィンドウの画面を書き込み、それをウィンドウとして表示させる、という手順が必要になります。
イメージの種類
Wayland では、画面上に表示できるイメージは、2種類あります。

「共有メモリ」を使って作る、直接アクセス出来るバッファと、「OpenGL ES (EGL)」のイメージです。

OpenGL を使って描画するなら EGL を使った方が良いですが、直接バッファにアクセスして描画したいなら、共有メモリのバッファを使います。

ただし、両方とも、イメージを作成するまでの手順は長いです。

この講座では、共有メモリを使ったイメージバッファを使って進めていきます。
共有メモリのイメージを使う場合
  1. wl_shellwl_compositorwl_shm をバインド。
  2. wl_compositor から wl_surface を作成 (ウィンドウに表示するイメージの操作)
  3. wl_shell から wl_shell_surface を作成 (ウィンドウの操作)
  4. wl_shm から wl_shm_pool を作成。
  5. wl_shm_pool から wl_buffer を作成 (イメージのバッファ)
  6. ウィンドウの内容をイメージバッファに書き込んで描画し、その内容を画面上に表示するように、wl_surface に要求する。
  7. その後、サーバーがイメージバッファにアクセスし、デスクトップ画面に合成する。
プログラム
まずは、サーバーが対応している、共有メモリイメージのフォーマットを列挙してみます。

$ cc -o test 03_shmformat.c -lwayland-client

03_shmformat.c
/******************************
 * 共有メモリイメージのフォーマットを列挙
 ******************************/

#include <stdio.h>
#include <string.h>
#include <wayland-client.h>

struct wl_shm *g_shm;


//---------------------
// wl_shm
//---------------------


static void _shm_format(void *data,struct wl_shm *shm,uint32_t format)
{
    if(format == WL_SHM_FORMAT_ARGB8888)
        printf("%u: ARGB8888\n", format);
    else if(format == WL_SHM_FORMAT_XRGB8888)
        printf("%u: XRGB8888\n", format);
    else
    {
        printf("0x%08X: '%c%c%c%c'\n",
            format,
            (char)(format >> 24),
            (char)(format >> 16),
            (char)(format >> 8),
            (char)format);
    }
}

static const struct wl_shm_listener g_shm_listener = { _shm_format };


//---------------------
// wl_registry
//---------------------


static void _registry_global(
    void *data,struct wl_registry *registry,
    uint32_t id,const char *interface,uint32_t version)
{
    if(strcmp(interface, "wl_shm") == 0)
    {
        //wl_shm をバインド
        
        g_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);

        wl_shm_add_listener(g_shm, &g_shm_listener, NULL);
    }
}

static void _registry_global_remove(void *data,struct wl_registry *registry,uint32_t id)
{

}

static const struct wl_registry_listener g_reg_listener = {
    _registry_global, _registry_global_remove
};


//---------------------


int main(void)
{
    struct wl_display *display;
    struct wl_registry *reg;

    display = wl_display_connect(NULL);
    if(!display) return 1;

    //wl_registry

    reg = wl_display_get_registry(display);
    wl_registry_add_listener(reg, &g_reg_listener, NULL);

    //wl_registry のイベントを待つ

    wl_display_roundtrip(display);

    //wl_shm のイベントを待つ

    wl_display_roundtrip(display);

    //

    wl_shm_destroy(g_shm);
    wl_registry_destroy(reg);

    wl_display_disconnect(display);

    return 0;
}

実行結果
## GNOME

0: ARGB8888
1: XRGB8888

## weston

0: ARGB8888
1: XRGB8888
0x36314752: '61GR'
0x32315559: '21UY'
0x3231564E: '21VN'
0x56595559: 'VYUY'
解説
wl_shm
まず、共有メモリを使うために、wl_shm をバインドします。

現時点での最大バージョンは「1」なので、1 で固定しています。

そして、バインドと同時に、wl_shm_add_listener() でフォーマット列挙のためのハンドラをセットしています。

wl_shm : format イベント
static void _shm_format(void *data,struct wl_shm *shm,uint32_t format)
{
...
}

サーバーで対応しているイメージフォーマットの識別子が、一つずつ送られてきます。

識別子は wayland-client-protocol.h にて、enum wl_shm_format の列挙型で定義されており、色々なフォーマットがあります。

enum wl_shm_format {
    /**
     * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian
     */
    WL_SHM_FORMAT_ARGB8888 = 0,
    /**
     * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian
     */
    WL_SHM_FORMAT_XRGB8888 = 1,
    /**
     * 8-bit color index format, [7:0] C
     */
    WL_SHM_FORMAT_C8 = 0x20203843,
...

値を見てみると、WL_SHM_FORMAT_ARGB8888WL_SHM_FORMAT_XRGB8888 以外は、上位バイトから順に「ASCII 文字 x 4」で構成されていることがわかります。

基本的に、WL_SHM_FORMAT_ARGB8888WL_SHM_FORMAT_XRGB8888 は常に対応されていると見てよさそうです。
それ以外のフォーマットに関しては、各サーバーの実装によります。
wl_display_roundtrip()
wl_display_roundtrip() は、サーバーがクライアントからの要求を処理するまで待つ関数です。

ここでは、この関数を2回実行していますが、
1回目は wl_registry のイベントが処理され、2回目は wl_shm のイベントが処理されています。

wl_registry の global イベント内で、wl_shm をバインドしていますが、ここで生成された wl_shm のイベントは、1回目の wl_display_roundtrip() では実行されないので、もう一度実行しています。

初期化処理に関して
Wayland におけるクライアントプログラムでは、必要な各グローバルオブジェクトをバインドして、ポインタを取得しなければ、何事も始まりません。

よって、wl_registry を使って行う、バインドなどの初期化処理に関しては、イベントループ内で実行させるのではなく、wl_display_roundtrip() を使って、すべてのイベントなどの処理が終わるまで待ち、必要な情報をすべて取得してから、イベントループへ移行するのが一番良い方法です。

今は wl_display_roundtrip() を数回実行するだけで十分ですが、もう少し複雑になってきたら、それらの方法を解説します。