X11: MIT-SHM (共有メモリイメージ)

拡張機能 MIT-SHM
X の拡張機能 MIT-SHM を使って、XImage のイメージバッファを共有メモリで確保すると、パフォーマンスが向上します。
※X サーバーとクライアントが同じマシン上にある場合に使用できます。

通常の XImage の場合、クライアントが確保したバッファにイメージデータがあるため、それを X サーバー上で処理するためには、データを X サーバーに転送する必要があります。

共有メモリを使うと、別のプロセスが確保したメモリを、別のプロセスが参照することができるので、X サーバーにデータを転送することなく、イメージデータを参照することができます。
X の拡張機能について
X サーバーによって、各拡張機能がサポートされている場合と、サポートされていない場合があります。

X の拡張機能は複数ありますが、あまり使われなくなったような古い拡張機能は、X サーバーから削除されている場合があるので、注意してください。

拡張機能のインクルードファイルは、<X11/extensions/...> にあります。
クライアントが使うライブラリ関数の定義 (基本的に大文字の X で始まるファイル名) と、X プロトコルなどを定義したファイルに分かれています。

また、各拡張機能のクライアント用のライブラリをリンクする必要があります。
基本的な拡張機能は -lXext でリンクします。
MIT-SHM で必要なもの
インクルードファイルは、以下が必要です。

#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>

また、-lXext で、拡張機能のライブラリをリンクする必要があります。
プログラム
共有メモリの XImage を作成して、赤のグラデーションを出力します。
ポインタボタンを押すと終了します。

$ cc -o run 20-shm util.c -lX11 -lXext

<20-shm.c>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include "util.h"

#define WIDTH  200
#define HEIGHT 200

static void _draw_image(XImage *img)
{
    uint8_t *pd,*pdY;
    int ix,iy,bytes;
    uint32_t pixcol;

    pdY = (uint8_t *)img->data;
    bytes = img->bits_per_pixel / 8;

    for(iy = 0; iy < img->height; iy++)
    {
        pd = pdY;

        pixcol = rgb_to_pixel_sep(
            iy * 255 / (img->height - 1), 0, 0);
        
        for(ix = 0; ix < img->width; ix++)
        {
            ximage_setbuf(img, pd, pixcol);
            
            pd += bytes;
        }

        pdY += img->bytes_per_line;
    }
}

int main(int argc,char **argv)
{
    Display *disp;
    XSetWindowAttributes attr;
    Window win;
    XEvent ev;
    XImage *ximg = NULL;
    XShmSegmentInfo si;
    int completion_type;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    //MIT-SHM

    if(!XShmQueryExtension(disp))
    {
        printf("unsupported MIT-SHM\n");
        XCloseDisplay(disp);
        return 1;
    }

    completion_type = XShmGetEventBase(disp) + ShmCompletion;

    //ウィンドウ作成

    attr.event_mask = ButtonPressMask | ExposureMask;

    win = XCreateWindow(disp, DefaultRootWindow(disp),
        0, 0, WIDTH, HEIGHT, 0,
        CopyFromParent, CopyFromParent, CopyFromParent,
        CWEventMask, &attr);

    //----- XImage

    //XImage 作成

    ximg = XShmCreateImage(disp, NULL, g_disp.depth,
        ZPixmap, NULL, &si, WIDTH, HEIGHT);

    if(!ximg)
    {
        printf("failed XCreateImage\n");
        goto END;
    }

    printf("depth: %d\nbits_per_pixel: %d\nbytes_per_line: %d\n",
        ximg->depth, ximg->bits_per_pixel, ximg->bytes_per_line);

    //共有メモリを作り、識別子を取得 (負の値で失敗)

    si.shmid = shmget(IPC_PRIVATE,
        ximg->bytes_per_line * ximg->height, IPC_CREAT | 0777);

    printf("shmid: %d\n", si.shmid);

    //共有メモリをプロセスのアドレス空間にアタッチ

    si.shmaddr = ximg->data = shmat(si.shmid, 0, 0);

    si.readOnly = False;

    //共有メモリセグメントを X サーバーにアタッチ

    XShmAttach(disp, &si);

    printf("shmseg: 0x%lx\n", si.shmseg);

    //

    _draw_image(ximg);

    //---- イベント

    XMapWindow(disp, win);

    while(1)
    {
        XNextEvent(disp, &ev);

        if(ev.type == completion_type)
        {
            printf("[ShmCompletion] shmseg:0x%lx\n",
                ((XShmCompletionEvent *)&ev)->shmseg);
            fflush(stdout);
            continue;
        }

        switch(ev.type)
        {
            case Expose:
                XShmPutImage(disp, win, g_disp.gc, ximg,
                    ev.xexpose.x, ev.xexpose.y,
                    ev.xexpose.x, ev.xexpose.y,
                    ev.xexpose.width, ev.xexpose.height, True);

                printf("[Expose] (%d,%d)-(%dx%d)\n",
                    ev.xexpose.x, ev.xexpose.y, ev.xexpose.width, ev.xexpose.height);
                fflush(stdout);
                break;
            case ButtonPress:
                goto END;
        }
    }

END:
    if(ximg)
    {
        XShmDetach(disp, &si);
        XDestroyImage(ximg);

        //共有メモリをプロセスのアドレス空間から切り離す
        shmdt(si.shmaddr);
        //共有メモリの解放
        shmctl(si.shmid, IPC_RMID, 0);
    }

    XCloseDisplay(disp);

    return 0;
}
解説
拡張機能がサポートされているか
Bool XShmQueryExtension(Display *display);

Bool XShmQueryVersion(Display *display, int *major, int *minor, Bool *pixmaps);

X サーバーで MIT-SHM の拡張機能がサポートされているかどうかを確認するには、上記のいずれかの関数を使います。
サポートされていれば、True が返ります。

XShmQueryVersion は、X サーバー上の拡張機能のバージョンを返し、pixmaps には、共有メモリ Pixmap がサポートされているかが返ります。
共有メモリ XImage の作成手順
まず、XShmCreateImage 関数で、共有メモリ用の XImage を作成します。
基本的には XCreateImage() と同じで、XImage 構造体を確保して、各値を設定するだけです。

XCreateImage
XImage *XShmCreateImage(Display *display, Visual *visual, unsigned int depth, int format,
    char *data, XShmSegmentInfo *shminfo, unsigned int width, unsigned int height);

typedef struct {
    ShmSeg shmseg;    /* リソースID */
    int shmid;        /* 共有メモリ ID */
    char *shmaddr;    /* バッファアドレス */
    Bool readOnly;    /* 読み込み専用 */
} XShmSegmentInfo;

dataバッファは後で確保するので、NULL で構いません。
shminfoXImage の obdata にこのポインタがセットされるだけなので、XShmSegmentInfo 構造体は初期化する必要はありません。
このポインタは XImage を使う際に必要になるので、常に参照可能な状態にしておく必要があります。

その後の手順
後は、共有メモリを作成して、XShmSegmentInfo 構造体に値を設定します。

si.shmid = shmget(IPC_PRIVATE,
    ximg->bytes_per_line * ximg->height, IPC_CREAT | 0777);

si.shmaddr = ximg->data = shmat(si.shmid, 0, 0);

si.readOnly = False;

XShmAttach(disp, &si);

最後に、XShmAttach() で、X サーバーに共有メモリをアタッチします。
XShmSegmentInfo 構造体の shmseg に、X リソース ID がセットされます。

これで作成の手順は終了です。
後は通常の XImage と同じように使うことができます。
解放
共有メモリ XImage を解放する場合は、以下の手順で削除します。

XShmDetach(disp, &si);

XDestroyImage(ximg);

//共有メモリをプロセスのアドレス空間から切り離す
shmdt(si.shmaddr);
//共有メモリの解放
shmctl(si.shmid, IPC_RMID, 0);
出力とイベント
共有メモリ XImage は、XShmPutImage 関数で出力できます。

Bool XShmPutImage(Display *display, Drawable d, GC gc, XImage *image,
    int src_x, int src_y, int dest_x, int dest_y,
    unsigned int width, unsigned int height, Bool send_event);

XPutImage() とほぼ同じですが、最後に send_event 引数が追加されています。

これが True の場合、X サーバーによってイメージの書き込みが完了したときに、ShmCompletion イベントが生成されます。

このイベントが来た後は、XImage の共有メモリを操作しても安全であることが保証されています。

拡張機能のイベント
拡張機能は、それぞれ、X サーバーによってサポートされていたりしなかったりするので、拡張機能によって生成されるイベントのタイプは、固定値として使うことができません。
そのため、拡張機能ごとに、動的に値を取得する必要があります。

int XShmGetEventBase(Display *dpy);

MIT-SHM で使われるイベントタイプのベース値を取得します。

拡張機能のイベントは、このベース値を元に、+0, +1, +2... と相対的に加算されていきます。

MIT-SHM で使われるイベントは、ShmCompletion (0) の1つだけです。
「XShmGetEventBase(display) + ShmCompletion」が、実際のイベントタイプ値となります。

ShmCompletion イベント
typedef struct {
    int type;
    unsigned long serial;
    Bool send_event;
    Display *display;
    Drawable drawable;     /* 描画先 */
    int major_code;        /* ShmReqCode */
    int minor_code;        /* X_ShmPutImage */
    ShmSeg shmseg;         /* リソースID */
    unsigned long offset;  /* 使用される ShmSeg へのオフセット */
} XShmCompletionEvent;

XShmPutImage() の send_event が True の場合、書き込みが終了した時にこのイベントが来ます。

XEvent 構造体からは直接参照できないので、XEvent 構造体の変数のポインタを、(XShmCompletionEvent *) に型変換して使用する必要があります。