DRI2

DRI2 to allocate memory

DRI (Direct Rendering Infrastructure) 是各家 open source 驅動程式

以 Intel 的 945GM 為例,在 kernel 有 drm.ko 與 i915.ko 兩個模組,對映到 userspace

  • 有 drm 提供的libdrm.so 與 libdrm_intel.so
  • 在 xserver 有 libglx.so 與 libdri2.so 這兩個 extension
  • xf86-video-intel 提供的 intel_drv.so
  • mesa 也有 libGL.so 以及 i915_dri.so。

intel_drv.so 被稱作 2D driver 或 DDX driver,i915_dri.so 則是 3D driver 或 DRI driver。 從一個 3D 程式被執行時到顯示在螢幕上,參與這個看似普通的過程背後就是上面這些檔案。

DRI 最重要的內涵在於,一個 X client 可以向 X server 取得視窗的 buffer(s),並利用硬體加速在上面直接繪圖, 不再透過 X server。Kernel 在 DRI 扮演的主要角色是讓多個 process 可以共享 (GEM) buffer, 還有接受與執行 process 傳來的硬體指令。這邊困難的地方之一在於 CPU 跟 GPU 都有各自的 MMU 與 cache,所以有許多同步的問題要處理。

libGL.so 在 DRI 架構裡扮演了 loader 的角色,它跑在 client 這邊。就像 OpenGL 與平台無關一樣, DRI driver 也與平台無關。loader 一方面載入 DRI driver,一方面又提供適當的 hook 讓 DRI driver 呼叫, 並實作了平台相關的部份。所以在 DRI driver 真的要用到 buffer 的時候,它會呼叫 loader 的 getBuffers 函數取得視窗的 buffer 與大小。 libglx.so 跟 libdri2.so 實作了 GLX 與 DRI2 這兩個 X extensions。在 X 的平台上, 對於 DRI driver 的要求,libGL.so 會利用 GLX 與 DRI2 協定取得所需的資訊,並傳回給 DRI driver。

一個快速驗證上面說法的方法是直接檢視系統的 /usr/lib/libGL.so/usr/lib/dri/i915_dri.solibGL.so 只是 loader, 檔案大小應該要比 i915_dri.so 小不少;而 i915_dri.so 與平台無關, 所以 ldd 應該不會看到 X11 的函式庫。如果再進一步想,那是不是只要提供適當的 loader, DRI driver 也能用在 X 以外的平台?答案是肯定的。

在 compiz 剛出來的時候,有很多名詞跟著它一起冒出來。像是 XGL、GLX_EXT_texture_from_pixmap、還有 AIGLX 等。在 libGL.so 無法成功跟 X server 溝通使用 DRI 時,client 的 3D 繪圖指令會改經由 GLX 傳給 server 處理,這被稱作 indirect rendering。在早先,libglx.so 會利用純軟體的方式執行這些 3D 指令,所以 indirect rendering 跟 software rendering 是等價的。這個情形在 AIGLX 引入後有很大的改變。在 AIGLX 下,libglx.so 除了提供 GLX extension 實作外,它同時也是一個 DRI loader,可以利用 DRI driver 進行 3D 加速。因為 AIGLX,indirect rendering 不再直接被視為慢、無法接受,雖然它還是多了 client 到 server 的 overhead。AIGLX 即是 Accelerated Indirect GLX 的縮寫,非常直接。

DRI2 with GEM

目前 linux graphic 架構下統一由 DRM 來做管理, CRTC, GEM, 還有一些其他

而 DRM master 一般是由 display server (常用的是 Xorg) 掌握著, 以控制 CRTC, gpu memory allocate 狀況 然而 user 也需要 gpu memory 時, 但又不能使用 DRM master 這時, 透過 DRI2 + magic token 的方式 allocate GPU memory

假如要讓 Xorg 幫你畫, export flink name, 並 put image 至 x server 就可, 可參考 xvimage

#include <unistd.h>
// DRM
#include <drm/drm.h>
#include <drm/i915_drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>


#include <cstring>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>


static Bool DRI2Authenticate(Display *dpy, XID window, unsigned int magic)
{
        XExtDisplayInfo *info = DRI2FindDisplay(dpy);
        xDRI2AuthenticateReq *req;
        xDRI2AuthenticateReply rep;

        XextCheckExtension(dpy, info, dri2ExtensionName, False);

        LockDisplay(dpy);
        GetReq(DRI2Authenticate, req);
        req->reqType = info->codes->major_opcode;
        req->dri2ReqType = X_DRI2Authenticate;
        req->window = window;
        req->magic = magic;

        if (!_XReply(dpy, (xReply *)&rep, 0, xFalse)) {
                UnlockDisplay(dpy);
                SyncHandle();
                return False;
        }

        UnlockDisplay(dpy);
        SyncHandle();

        return rep.authenticated;
}

int dri2_open(Display *dpy)
{
        drm_auth_t auth;
        char *driver, *device;
        int fd;
        // connect to Xserver
        if (!DRI2Connect(dpy, DefaultRootWindow(dpy), &driver, &device))
                return -1;

        fd = open(device, O_RDWR);
        if (fd < 0)
                return -1;

        // get magic token from kernel
        if (drmGetMagic(fd, &auth.magic)) {
                goto err_fd;
        }

        if (ioctl(fd, DRM_IOCTL_GET_MAGIC, &auth))
                printf("Failed on drm ioctl DRM(get_magic) test\n");
        else
                printf("Success on drm ioctl DRM(get_magic) test\n");

        drm_stats_t stats;
        if (ioctl(fd, DRM_IOCTL_GET_STATS, &stats))
                printf("Failed on drm ioctl DRM(get_status) test\n");
        else
                printf("Success on drm ioctl DRM(get_status) test\n");

        // send magic token to xserver
        if (!DRI2Authenticate(dpy, DefaultRootWindow(dpy), auth.magic))
                goto err_fd;

        return fd;

err_fd:
        fprintf(stderr, "could not open right fd\n");
        close(fd);
        return -1;
}


#define WIDTH 300
#define STRIDE (WIDTH * 3)
#define HEIGHT 300
#define SIZE (HEIGHT * STRIDE)
#define DEPTH 24

int main(int argc, char const *argv[])
{
        Display *dpy;
        Screen *scr;
        struct drm_i915_gem_create create;
        struct drm_gem_flink flink;
        struct drm_i915_gem_mmap_gtt map;
        struct x11_overlay *priv;
        unsigned int count, i, j;
        int fd, x, y, w, h;

        w = WIDTH;
        h = HEIGHT;
        XvAdaptorInfo *info;
        XvImage *image;
        XvPortID port = -1;
        void *ptr, *mem;
        GC gc;

        dpy = XOpenDisplay(NULL);
        if (dpy == NULL)
                return -1;

        scr = ScreenOfDisplay(dpy, DefaultScreen(dpy));
        gc = XCreateGC(dpy, DefaultRootWindow(dpy), 0, NULL);
        fd = dri2_open(dpy);
        if (fd < 0)
                goto err_dpy;



        create.handle = 0;
        create.size = ALIGN(create.size, 4096);
        drmIoctl(fd, DRM_IOCTL_I915_GEM_CREATE, &create);
        if (create.handle == 0) {
                fprintf(stderr, "could not create i915 gem buffer\n");
                goto err_image;
        }

        // create GEM flink, export GEM name, let anyone access this memory by name
        flink.handle = create.handle;
        if (drmIoctl(fd, DRM_IOCTL_GEM_FLINK, &flink))
                goto err_create;
        map.handle = create.handle;

        // map memory
        if (drmIoctl(fd, DRM_IOCTL_I915_GEM_MMAP_GTT, &map))
                goto err_create;

        ptr = mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
                   map.offset);
        if (ptr == (void *)-1)
                goto err_create;

        // do something

        // close gem memory
        drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &create.handle);

err_fd:
        close(fd);
err_dpy:
        XCloseDisplay(dpy);
        return -1;
}