LoginSignup
6
1

More than 5 years have passed since last update.

Wayland の shm にビットマップで絵を描く

Posted at

今後 X を置き換える Wayland を勉強中。簡単なプログラムを書いてみました。https://jan.newmarch.name/Wayland/SharedMemory/ をちょっと書き換えました。

wayland-shm.png

前提とする環境

  • Ubuntu 16.04.2 LTS を VirtualBox 内で利用。ホストは Mac OS。
  • sudo apt install weston で weston をインストール
  • lightdm (起動時の画面) で Weston を選択
  • ~/.config/weston.ini の内容。idle-time=0 にしないとロックされてうざい。

    [core]
    idle-time=0
    

wl_display の取得

まず最初に wl_display を取得します。今後 Wayland サーバとの通信は wl_display を通じて行います。

    struct wl_display *display = wl_display_connect(NULL);
    if (display == NULL) {
        fprintf(stderr, "Can't connect to display\n");
        exit(1);
    }

wl_compositor、wl_shell、wl_shm の取得

Wayland に描画するには色々な方法がありますが今回 shm を使います。shm は最も単純な方法で、ビットマップメモリに直接書き込みます。shm を使うには、wl_display から wl_compositor、wl_shell、wl_shm といったグローバルオブジェクトを取得する必要があります。Wayland は非同期で動くので、グローバルオブジェクトの取得には二種類のコールバックを設定します。

ここで、global_registry_handler はオブジェクトが見つかった時に呼ばれる関数、global_registry_remover が削除された時に呼ばれる関数です。global_registry_handler で欲しいオブジェクトが来たらグローバル変数に取っておきます。これらを global_registry_remover に束ねます。

void global_registry_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
    if (strcmp(interface, "wl_compositor") == 0) {
        compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
    } else if (strcmp(interface, "wl_shm") == 0) {
        shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
        wl_shm_add_listener(shm, &shm_listener, NULL);
    }
}

void global_registry_remover(void *data, struct wl_registry *registry, uint32_t id)
{
}

const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover
};

コールバックを実行するには、wl_registry を使います。wl_registry_add_listener でコールバックを設定した後 wl_display_dispatch でリクエストして、wl_display_roundtrip でリクエスト完了を待ちます。

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    wl_display_dispatch(display);
    wl_display_roundtrip(display);

wl_shell_surface の取得

次に、wl_shell_surface という物を取得します。Wayland には色々なユースケースがありますが、その中で shell というのは普通の箱型の Window の中に各アプリが描画する場合に使います。ここは手順通りにやるだけです。wl_shell_surface_set_toplevel を使うと、全画面じゃない普通のアプリになります。

    struct wl_surface *surface = wl_compositor_create_surface(compositor);
    if (surface == NULL) {
        fprintf(stderr, "Can't create surface\n");
        exit(1);
    }

    struct wl_shell_surface *shell_surface = wl_shell_get_shell_surface(shell, surface);
    if (shell_surface == NULL) {
        fprintf(stderr, "Can't create shell surface\n");
        exit(1);
    }

    wl_shell_surface_set_toplevel(shell_surface);

shell_surfeace にもコールバックを登録します。shell_surface に設定するコールバックは三種類です。

  • ping: 生きているか確認。マウスを動かしたりすると呼ばれる。
  • configure(data, shell_surface, edges, width, height): リサイズを検知
  • popup_done: ポップアップのキャンセルを通知
void handle_ping(void *_data, struct wl_shell_surface *shell_surface, uint32_t serial)
{
    wl_shell_surface_pong(shell_surface, serial);
}

void handle_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height)
{
}

void handle_popup_done(void *data, struct wl_shell_surface *shell_surface)
{
}

const struct wl_shell_surface_listener shell_surface_listener = {
    handle_ping,
    handle_configure,
    handle_popup_done
};

コールバックの登録には wl_shell_surface_add_listener を使います。

wl_shell_surface_add_listener(shell_surface, &shell_surface_listener, NULL);

共有バッファ用ファイルの作成

ここが一番ややこしい所です。shm を使うには Wayland アプリはファイルに mmap した共有メモリを作ってそのファイル記述子を wl_shm_create_pool で Wayland サーバに渡します。ファイルの位置は環境変数 XDG_RUNTIME_DIR で指定します。ファイルを作っても実際にファイルへの書き込みは行われないそうです。ファイル名は使わないのですぐ unlink してしまいます。Weston 付属のサンプルでは、ファイルを作るのに open では無く mkstemp を使ってランダムなファイル名にしていました。すぐ unlink するとはいえ、mkstemp の方が良いのかも知れません。

int create_shared_fd(off_t size)
{   
    char name[1024] = "";

    const char *path = getenv("XDG_RUNTIME_DIR");
    if (!path) {
        fprintf(stderr, "Please define environment variable XDG_RUNTIME_DIR\n");
        exit(1);
    }

    strcpy(name, path);
    strcat(name, "/shm-test");

    int fd = open(name, O_RDWR | O_EXCL | O_CREAT);
    if (fd < 0) {
        fprintf(stderr, "File cannot open: %s\n", name);
        exit(1);
    } else {
        unlink(name);
    }

    if (ftruncate(fd, size) < 0) {
         fprintf(stderr, "ftruncate failed: fd=%i, size=%li\n", fd, size);
         close(fd);
         exit(1);
    }

    return fd;
}

ウインドウの作成

作成した共有メモリを surface に割り当てるとウインドウになります。共有メモリは後ほど実際の描画に使います。描画フォーマットとして 32 ビットARGB を使います。

void * create_window(struct wl_surface *surface) {
    int stride = WIDTH * 4; // 4 bytes per pixel
    int size = stride * HEIGHT;

    int fd = create_shared_fd(size);
    void *shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm_data == MAP_FAILED) {
        fprintf(stderr, "mmap failed: %m\n");
        close(fd);
        exit(1);
    }

    struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
    struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, WIDTH, HEIGHT, stride, WL_SHM_FORMAT_ARGB8888);
    wl_shm_pool_destroy(pool);

    wl_surface_attach(surface, buffer, 0, 0);
    wl_surface_commit(surface);
    return shm_data;
}

描画

描画は何でも良いですが、横軸に応じて 8 色、縦軸に応じてアルファ値が変化するような絵を描いてみました。

今更ですが、アルファ値というのは透明度を直接表す値だと思っていたのですが、正確には違うという事に気が付きました。例えば、7fffffff のような値を指定しても不透明の白になっていまいます。透明の重ね合わせの演算に便利な何かの値のようです。

void paint_pixels(uint32_t *pixel) {

    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            int mx = x / 20;
            int my = y / 20;
            uint32_t color = 0;
            if (mx % 2 == 0 && my % 2 == 0) {
                uint32_t code = (mx / 2) % 8; // X axis determines a color code from 0 to 7.
                uint32_t red = code & 1 ? 0xff0000 : 0;
                uint32_t green = code & 2 ? 0x00ff00 : 0;
                uint32_t blue = code & 4 ? 0x0000ff : 0;
                uint32_t alpha = (my / 2) % 8 * 32 << 24; // Y axis determines alpha value from 0 to 0xf0
                color = alpha + red + green + blue;
            }
            pixel[x + (y * WIDTH)] = color;
        }
    }
}

全体

ソース全体を一応貼ります。 https://gist.github.com/propella/3996112562b33778038bf41b3721e3fb

/*
 * Wayland shm example
 * Based on https://jan.newmarch.name/Wayland/SharedMemory/
 * Build: cc -o shm shm.c -lwayland-client
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <unistd.h>

struct wl_compositor *compositor = NULL;
struct wl_shell *shell;
struct wl_shm *shm;

int WIDTH = 320;
int HEIGHT = 320;

void paint_pixels(uint32_t *pixel) {

    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            int mx = x / 20;
            int my = y / 20;
            uint32_t color = 0;
            if (mx % 2 == 0 && my % 2 == 0) {
                uint32_t code = (mx / 2) % 8; // X axis determines a color code from 0 to 7.
                uint32_t red = code & 1 ? 0xff0000 : 0;
                uint32_t green = code & 2 ? 0x00ff00 : 0;
                uint32_t blue = code & 4 ? 0x0000ff : 0;
                uint32_t alpha = (my / 2) % 8 * 32 << 24; // Y axis determines alpha value from 0 to 0xf0
                color = alpha + red + green + blue;
            }
            pixel[x + (y * WIDTH)] = color;
        }
    }
}

/*
* Return a newly created and resized file descriptor.
* For real use case, mkstemp instead of open is better.
* See create_tmpfile_cloexec in https://cgit.freedesktop.org/wayland/weston/tree/shared/os-compatibility.c
*/
int create_shared_fd(off_t size)
{
    char name[1024] = "";

    const char *path = getenv("XDG_RUNTIME_DIR");
    if (!path) {
        fprintf(stderr, "Please define environment variable XDG_RUNTIME_DIR\n");
        exit(1);
    }

    strcpy(name, path);
    strcat(name, "/shm-test");

    int fd = open(name, O_RDWR | O_EXCL | O_CREAT);
    if (fd < 0) {
        fprintf(stderr, "File cannot open: %s\n", name);
        exit(1);
    } else {
        unlink(name);
    }

    if (ftruncate(fd, size) < 0) {
         fprintf(stderr, "ftruncate failed: fd=%i, size=%li\n", fd, size);
         close(fd);
         exit(1);
    }

    return fd;
}

/*
 * Create a window and return the attached buffer
 */
void * create_window(struct wl_surface *surface) {
    int stride = WIDTH * 4; // 4 bytes per pixel
    int size = stride * HEIGHT;

    int fd = create_shared_fd(size);
    void *shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm_data == MAP_FAILED) {
        fprintf(stderr, "mmap failed: %m\n");
        close(fd);
        exit(1);
    }

    struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
    struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, WIDTH, HEIGHT, stride, WL_SHM_FORMAT_ARGB8888);
    wl_shm_pool_destroy(pool);

    wl_surface_attach(surface, buffer, 0, 0);
    wl_surface_commit(surface);
    return shm_data;
}

void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
}

struct wl_shm_listener shm_listener = {
    shm_format
};

/*
 * Event listeners for wl_registry_add_listener
 */
void global_registry_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
    if (strcmp(interface, "wl_compositor") == 0) {
        compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
    } else if (strcmp(interface, "wl_shm") == 0) {
        shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
        wl_shm_add_listener(shm, &shm_listener, NULL);
    }
}

void global_registry_remover(void *data, struct wl_registry *registry, uint32_t id)
{
}

const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover
};

/*
 * Event listeners for wl_shell_surface_add_listener
 */
void handle_ping(void *_data, struct wl_shell_surface *shell_surface, uint32_t serial)
{
    wl_shell_surface_pong(shell_surface, serial);
}

void handle_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height)
{
}

void handle_popup_done(void *data, struct wl_shell_surface *shell_surface)
{
}

const struct wl_shell_surface_listener shell_surface_listener = {
    handle_ping,
    handle_configure,
    handle_popup_done
};

int main(int argc, char **argv) {

    struct wl_display *display = wl_display_connect(NULL);
    if (display == NULL) {
        fprintf(stderr, "Can't connect to display\n");
        exit(1);
    }
    printf("connected to display\n");

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    wl_display_dispatch(display);
    wl_display_roundtrip(display);

    if (compositor == NULL) {
        fprintf(stderr, "Can't find compositor\n");
        exit(1);
    }

    struct wl_surface *surface = wl_compositor_create_surface(compositor);
    if (surface == NULL) {
        fprintf(stderr, "Can't create surface\n");
        exit(1);
    }

    struct wl_shell_surface *shell_surface = wl_shell_get_shell_surface(shell, surface);
    if (shell_surface == NULL) {
        fprintf(stderr, "Can't create shell surface\n");
        exit(1);
    }

    wl_shell_surface_set_toplevel(shell_surface);
    wl_shell_surface_add_listener(shell_surface, &shell_surface_listener, NULL);

    void *shm_data = create_window(surface);
    paint_pixels(shm_data);

    while (wl_display_dispatch(display) != -1) {
    ;
    }

    wl_display_disconnect(display);
    printf("disconnected from display\n");

    exit(0);
}
6
1
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
6
1