Wayland - ウィンドウ用イメージを作る

ウィンドウ用イメージを作る
ここでは、実際に、共有メモリでウィンドウ表示用のイメージを作成してみます。

今回はイメージを作成するだけなので、まだウィンドウは表示できません。
プログラム
$ cc -o test 04_shmimg.c -lwayland-client -lrt

shm_open() を使うのに librt が必要なので、リンクを追加します。

04_shmimg.c
/******************************
 * 共有メモリイメージを作成
 ******************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <wayland-client.h>

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

struct wl_shm *g_shm;
unsigned int g_shm_cnt = 0; //共有メモリ名用

typedef struct
{
    struct wl_shm_pool *pool;
    struct wl_buffer *buffer;
    void *data;
    int size;
}imagebuf;

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


/* POSIX 共有メモリオブジェクトを作成
 *
 * return: shm_open() の戻り値 (成功時はファイルディスクリプタ) */

static int _create_posix_shm()
{
    char name[64];
    int ret;

    while(1)
    {
        snprintf(name, 64, "/wayland-test-%d", g_shm_cnt);
        
        ret = shm_open(name, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);

        if(ret >= 0)
        {
            //成功
            
            shm_unlink(name);

            g_shm_cnt++;
            break;
        }
        else if(errno == EEXIST)
            //同名が存在する -> カウントを+1して次へ
            g_shm_cnt++;
        else if(errno != EINTR)
        {
            printf("failed shm_open()\n");
            break;
        }
    }

    return ret;
}

/* wl_shm_pool 作成
 *
 * size: バッファサイズ
 * ppbuf: バッファのポインタが入る */

static struct wl_shm_pool *_create_shm_pool(int size,void **ppbuf)
{
    int fd;
    void *data;
    struct wl_shm_pool *pool;

    fd = _create_posix_shm();
    if(fd < 0) return NULL;

    printf("posix shm: %d\n", fd);

    //サイズ変更

    if(ftruncate(fd, size) < 0)
    {
        close(fd);
        return NULL;
    }

    //メモリにマッピング

    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(data == MAP_FAILED)
    {
        close(fd);
        return NULL;
    }

    printf("mmap: %p\n", data);

    //wl_shm_pool 作成

    pool = wl_shm_create_pool(g_shm, fd, size);

    //クローズしてもアンマップはされない
    close(fd);

    *ppbuf = data;

    return pool;
}

/* イメージバッファ作成 */

static imagebuf *imagebuf_create(int width,int height)
{
    imagebuf *img;
    struct wl_shm_pool *pool;
    struct wl_buffer *buffer;
    void *buf;
    int size;

    size = width * 4 * height;

    //wl_shm_pool 作成

    pool = _create_shm_pool(size, &buf);
    if(!pool) return NULL;

    printf("create shm pool: ok\n");

    //wl_buffer 作成

    buffer = wl_shm_pool_create_buffer(pool,
        0, //offset
        width, height,
        width * 4, //Y1行のバイト数
        WL_SHM_FORMAT_XRGB8888);

    if(buffer)
        printf("create buffer: ok\n");

    //imagebuf

    img = (imagebuf *)malloc(sizeof(imagebuf));
    if(!img) return NULL;

    img->pool = pool;
    img->buffer = buffer;
    img->data = buf;
    img->size = size;

    return img;
}

/* imagebuf 削除 */

static void imagebuf_destroy(imagebuf *p)
{
    if(p)
    {
        wl_buffer_destroy(p->buffer);
        wl_shm_pool_destroy(p->pool);
        munmap(p->data, p->size);
        
        free(p);
    }
}


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


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

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;
    imagebuf *img;

    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_display_roundtrip(display);

    //イメージ作成

    img = imagebuf_create(100, 100);
    
    imagebuf_destroy(img);

    //

    wl_shm_destroy(g_shm);
    wl_registry_destroy(reg);

    wl_display_disconnect(display);

    return 0;
}

共有メモリイメージを作成して、すぐに削除するだけのプログラムです。

posix shm: 4
mmap: 0x7fa8981bd000
create shm pool: ok
create buffer: ok
解説
後々使いやすいように、イメージデータとして imagebuf 構造体を作って、共有メモリイメージを作成&削除できるようにしました。

  1. wl_shm をバインド。
  2. shm_open() で POSIX 共有メモリオブジェクトを作成。
  3. 上記関数で得たファイルディスクリプタを使い、メモリにマッピングしてバッファを取得。
  4. 同じく、fd を使って、wl_shm から wl_shm_pool を作成。
  5. wl_shm_pool から wl_buffer を作成。

イメージバッファ用のファイルディスクリプタを得るには、shm_open() 関数で POSIX 共有メモリオブジェクトを作成する他に、任意のディレクトリにファイルを作る方法もあります。

今回は shm_open() を使います。
POSIX 共有メモリオブジェクトの作成
共有メモリ用のファイルディスクリプタを取得するために、shm_open() を使います。

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);

[!] librt のリンクが必要。

name は、最大 NAME_MAX (255) 文字の文字列で、先頭はスラッシュ (/) から始まり、以降は任意の文字列にします。
すでに存在する名前と重複しないように、適当な名前を付けてください。
同じ名前がすでに存在している場合は、errnoEEXIST が入ります。

今回は一つのイメージしか作成しないので、名前は固定名でも問題ありませんが、通常は複数のイメージを作ることにより、その分だけファイルディスクリプタが必要になると思うので、作成するごとに数値のカウントを増やして、名前に付加する形にしています。

shm_open() の戻り値は、-1 で失敗。
0 以上の場合は成功で、ファイルディスクリプタの値となります。

shm_unlink() 関数で、指定名のオブジェクトを削除します。

ファイルの unlink() と同じで、shm_unlink() が実行された後も、オブジェクトが使用されている間は実体が存在していますが、アンマップされた時点で自動的に破棄されます。

なので、今回は作成直後に shm_unlink() して、解放処理を一つ省略しています。
wl_shm_pool の作成
wl_shm_pool は、共有メモリバッファ用の「プール (溜める場所)」です。

wl_shm_pool は、イメージ一つにつき一個作成するという使い方もできますが、複数の小さいイメージ用に大きなサイズの wl_shm_pool を一個作っておいて、そこから一部分の範囲のバッファを各イメージが使う、といった使い方も出来ます。

サイズ変更
shm_open() で作成した共有メモリオブジェクトは、O_CREAT を指定しているため、初期サイズが 0 となっています。

まずは、ftruncate(fd, size) で、必要なバッファのサイズに変更します。

今回は WL_SHM_FORMAT_XRGB8888 フォーマットを使うので、「1 px = 4 byte」により、「幅×高さ×4 byte」 のサイズになります。

メモリにマッピング
次に、mmap() で、メモリにマッピングします。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void *addr, size_t length);

mmap() の戻り値は、成功すればバッファのポインタです。
失敗時は、MAP_FAILED が返ります。

wl_shm_pool 作成
メモリにマッピングできたら、wl_shm_create_pool()wl_shm_pool を作成します。

struct wl_shm_pool *wl_shm_create_pool(struct wl_shm *wl_shm, int32_t fd, int32_t size);

その後、close(fd) でファイルディスクリプタをクローズしています。

mmap() されたバッファは、munmap() を行うまでは、fd をクローズしてもアンマップされないので、解放処理の省略のため、ここでクローズしています。
wl_buffer の作成
wl_shm_pool が作成できたら、wl_shm_pool_create_buffer()wl_buffer を作成し、これでようやく Wayland のイメージバッファが作れました。

struct wl_buffer *wl_shm_pool_create_buffer(
  struct wl_shm_pool *wl_shm_pool,
  int32_t offset, int32_t width, int32_t height,
  int32_t stride, uint32_t format);

offset は、プール内のバッファのオフセット位置です。
一つのプールで複数の wl_buffer を作る場合は、その先頭位置を指定します。

stride は、Y 1 行のバイト数です。

解放処理
必要な解放処理は、以下の通りです。

wl_buffer_destroy(p->buffer);
wl_shm_pool_destroy(p->pool);
munmap(p->data, p->size);