X11: ダブルバッファ

ダブルバッファ
ダブルバッファ拡張機能 (DBE) を使うと、ウィンドウのダブルバッファリングを実現できます。

主に、アニメーション・動画・ゲームなどで、ウィンドウの画面を、ちらつきなく表示したい時に使います。
(ダブルバッファリングを使ったとしても、モニタの更新タイミングなどによって、ちらつきが起こる可能性があるため、完全に防げるわけではありません)

ダブルバッファリングでは、現在画面に表示されている「フロントバッファ」と、画面に表示されない状態で次のイメージを描画するための「バックバッファ」の、2つのイメージを使います。

次に表示するイメージをバックバッファに描画しておき、それをフロントバッファと交換することで、ウィンドウ画面を (可能な限り) 一瞬で切り替えます。
DBE
<X11/extensions/Xdbe.h> のインクルードと、-lXext のリンクが必要です。

初期化などについての説明は省略します。
詳細は、Double Buffer 拡張機能 をご覧ください。
使い方
DBE では、1つの X ウィンドウに複数のバックバッファを作成して、それをウィンドウに関連付けることができます。
フロントバッファ= X ウィンドウの描画内容となります。
バックバッファの作成
指定ウィンドウにバックバッファを追加するには、以下の関数を使います。

//バックバッファの作成
XdbeBackBuffer XdbeAllocateBackBufferName(Display *dpy,
    Window window, XdbeSwapAction swap_action);

//バックバッファの削除
Status XdbeDeallocateBackBufferName(Display *dpy, XdbeBackBuffer buffer);

XdbeBackBuffer は、Drawable 型 (つまり XID) です。

バックバッファに描画をしたい時は、X のグラフィック関数の描画先に、この XdbeBackBuffer の ID を指定します。
フロントバッファに描画したい場合は、通常通り Window の ID を指定することになります。

※バックバッファの実際のイメージデータは X サーバーが保持しているので、直接バッファを参照することはできません。

バックバッファはウィンドウに関連付けられているので、ウィンドウが破棄された時は、バックバッファも自動的に破棄されます。

swap_action
XdbeAllocateBackBufferName() 時に指定する swap_action は、フロントバッファとバックバッファの交換時に、交換後のバックバッファの内容がどうなるかという、ヒントを指定します。
(実際の交換時には、改めてアクションを指定できるため、この値はあくまで目安として扱われますが、出来る限り用途に合ったアクションを指定してください)

XdbeUndefinedバックバッファの内容は未定義 (交換後に内容がどうなっても構わない)
XdbeBackgroundバックバッファは、ウィンドウの背景色で塗りつぶされる
XdbeUntouchedバックバッファの内容は、交換によって変更されない (そのままの状態を維持)
XdbeCopiedバックバッファは、フロントバッファのコピーとなる

スワップした後、バックバッファ全体を改めて再描画する場合は、XdbeUndefined で構いません。
背景色でクリアしてから、バックバッファへの次の描画を行いたい場合は、XdbeBackground にします。
バックバッファの内容を、スワップ前の状態で維持して、更新する部分だけを描画したい場合は、XdbeUntouched にします。
フロントバッファ (画面のウィンドウ内容) をコピーして、更新する部分だけ描画したい場合は、XdbeCopied にします。
スワップ
フロントバッファとバックバッファを交換したい場合、以下の関数を使います。
複数のウィンドウをまとめてスワップできます。

Status XdbeSwapBuffers(Display *dpy, XdbeSwapInfo *swap_info, int num_windows);

typedef struct
{
  Window         swap_window; //ウィンドウ
  XdbeSwapAction swap_action; //アクション
} XdbeSwapInfo;

スワップすると、画面が更新されます。
プログラム
起動時に、バックバッファに対応しているビジュアルのリストを表示します。

左クリックすると、バックバッファを使って、ウィンドウの内容を赤 <-> 緑に切り替えます。
閉じるボタンで終了します。

本来ならアニメーションを表示したいところですが、アイドル時のタイマー処理を実装しなければならないので、簡単なサンプルとしました。

$ cc -o run e03-dbe.c util.c -lX11 -lXext

<e03-dbe.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xdbe.h>
#include "util.h"

static void _put_info(Display *disp)
{
    XdbeScreenVisualInfo *info;
    XdbeVisualInfo *vi;
    int i,num = 1;

    info = XdbeGetVisualInfo(disp, &g_disp.root, &num);
    if(!info) return;

    vi = info->visinfo;

    for(i = 0; i < info->count; i++)
    {
        printf("[%d] visual(0x%lx) depth(%d) plevel(%d)\n",
            i, vi[i].visual, vi[i].depth, vi[i].perflevel);
    }

    XdbeFreeVisualInfo(info);
}

static void _draw(Display *disp,Drawable dst,int count)
{
    int rgb;

    rgb = (count)? 0xff0000: 0x00ff00;

    XSetForeground(disp, g_disp.gc, rgb_to_pixel(rgb));

    XFillRectangle(disp, dst, g_disp.gc, 0, 0, 200, 200);
}

int main(int argc,char **argv)
{
    Display *disp;
    Window win,back;
    XEvent ev;
    XdbeSwapInfo swap;
    int major,minor,count = 0;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    //DBE

    if(!XdbeQueryExtension(disp, &major, &minor))
        printf("unsupported DBE\n");
    else
        printf("DBE ver %d.%d\n", major, minor);

    _put_info(disp);

    //

    win = create_test_window2(disp, 200, 200, 0, ButtonPress);

    //バックバッファ

    back = XdbeAllocateBackBufferName(disp, win, XdbeUndefined);

    _draw(disp, back, count);

    swap.swap_window = win;
    swap.swap_action = XdbeUntouched;

    //イベント

    XMapWindow(disp, win);

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

        if(event_quit(&ev)) break;

        if(ev.type == ButtonPress && ev.xbutton.button == Button1)
        {
            XdbeSwapBuffers(disp, &swap, 1);

            count ^= 1;
            _draw(disp, back, count);
        }
    }

    XCloseDisplay(disp);

    return 0;
}