2010年8月13日 星期五

善用 XShm Extension 加速貼圖

Standard
如今的 Xorg 千變萬化,發展出各種(DRI/DRI2/EAA/EXA/UXA/AIGLX)等加速機制,更有極絢麗的 3D 視窗技術,一點也不讓 Mac OS 和 Windows 上的 3D 視窗特效專美於前。話雖如此,很多人們早已遺忘 X Window System 的歷史原由,我們反觀當年的 XFree86 就有如醜小鴨般,其為 Networking 考量的 Client/Server 架構,就是舊 Unix 終端機時代所留下的東西。到目前為止,隨著硬體速度越來越快,Client/Server 的界限雖被 DRI/DRI2/AIGLX 等技術逐漸打破,但無論如何,Xorg 大底上還是維持著 Client/Server 的本質。只不過應用程式層的軟體,因為經過各種 GTK/GDK/Qt 等 Graphical Toolkits 包裝後,多數開發者和使用者已經不會再看到 X 底層的運作模式。

這裡記載著一個簡單的範例程式,用來說明 X 如何利用 XImage 做貼圖運算,其主要是將 buffer(100x100x32bits 的影像)畫在視窗上,該 buffer 可以是讀取圖檔或是使用各種資料來源,為一塊記憶體。此程式將會用 XCreateImage() 建立一組 Structure,然後使用 XPutImage() 將該 Structure 和影像資料吃從 XClient 透過 X Protocol(Socket)傳送到 X Server,X Server 再依 XImage 的敘述將圖形畫在視窗上。

XImage 範例程式:
void main(void)
{
    Window win;
    Display display = XOpenDisplay(getenv("DISPLAY"));    XImage *ximage;
    unsigned char *buffer;

    /* Initial Window */
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)),
                                                        0, 0, 100, 100, 0,
                                                        BlackPixel(display, DefaultScreen(display)),
                                                        BlackPixel(display, DefaultScreen(display)));

    XMapWindow(display, win);
    XSync(display, False);

    /* Allocate image memory for 100x100x32bits */
    buffer = (unsigned char *)malloc(sizeof(char) * 100 * 100 * 4);

    /* Create XImage structure and map image memory on it */
    ximage = XCreateImage(display, DefaultVisual(display, 0), DefaultDepth(display, 0), ZPixmap, 0, buffer, 100, 100, 32, 0);

    /* Put XImage into X Server to display */
    XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, 100, 100);

    /* Loop */
    while(1) {};

    /* Release */
    XDestroyImage(ximage);
    XCloseDisplay(display);
}

不過,眼尖的人或許已經注意到,此程式完全曝露出 X Client/Server 架構所存在的缺陷,無論要貼什麼圖,都必需先透過 X Protocol 傳送至 X Server,如此大量的資料複製,當然取而代之的就是效能低落。一種改善效能的方法就是使用 Shm(Shared Memory),使 X Client 和 X Server 共享一塊記憶體,讓原先的大量複製資料,改由傳記憶體位置取代,為此,X 就支援了 XShm Extension 來實作此種方法。

XShm Extension 所提供能函式與原生的 XImage 相關函式相差不大,這邊用另一個範例程式做說明:
void main(void)
{
    Window win;
    Display display = XOpenDisplay(getenv("DISPLAY"));    XImage *ximage;
    unsigned char *buffer;
    XShmSegmentInfo shminfo;

    /* XShm */
    XShmQueryExtension(display);

    /* Initial Window */
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)),
                                                        0, 0, 100, 100, 0,
                                                        BlackPixel(display, DefaultScreen(display)),
                                                        BlackPixel(display, DefaultScreen(display)));

    XMapWindow(display, win);
    XSync(display, False);

    /* Allocate image memory for 100x100x32bits */
    buffer = (unsigned char *)malloc(sizeof(char) * 100 * 100 * 4);

    /* Create XImage structure and map image memory on it */
    ximage = XShmCreateImage(display, DefaultVisual(display, 0), DefaultDepth(display, 0), ZPixmap, 0, &shminfo, 100, 100);

    /* Setting SHM */
    shminfo.shmid = shmget(IPC_PRIVATE, 100 * 100 * 4, IPC_CREAT | 0777);
    if (shminfo.shmid < 0)
        exit(-1);

    shminfo.shmaddr = ximage->data = (unsigned char *)shmat(shminfo.shmid, 0, 0);
    if(shminfo.shmaddr == (char *) -1)
        exit(-1);

    shminfo.readOnly = False;
    XShmAttach(display, &shminfo);

    /* Put XImage into X Server to display */
    XShmPutImage(display, win, DefaultGC(display, 0), ximage, 0, 0, 0, 0, 100, 100, False);
    XFlush(display);
    /* Loop */
    while(1) {};

    /* Release */
    XDestroyImage(ximage);
    XCloseDisplay(display);
}

大體上,主要就是 XCreateImage 換成 XShmPutImage,XPutImage 換成 XShmPutImage,更多資料可以參考 XFree86 MIT-SHM 文件:
http://www.xfree86.org/current/mit-shm.html