libX11 未使用で linux で OpenGL アプリを作る?!

今やレガシー扱いとされている X11 ですが、wayland への移行もあまり進んでいないようです。期待していた Fedora 24 でも採用が見送られてしまいました。 今回はそんな X11アプリを libX11 を使わないで組んでみたいと思います。 OpenGL を使ってアプリケーションを作る場合には X11 のオブジェクト群はほとんど使用しないで済むので最初のウィンドウだけ作ればなんとかなるんじゃないでしょうか.


xcb_and_ogl

XCBの紹介

libX11 使わないで作るためには、どうするかというと XCB (libXCB) を使用します。これは、Xlib を置換することを目的としたライブラリです。 名前の由来は “X C Binding” (X Window System のC言語バインディング) となっています。

特徴

Xプロトコルに直接アクセスすることができます。またライブラリのサイズが小さく単純化されています。
libX11 の下位レイヤーでも libXCB の使用に置き換わっているらしいです。

シンプルなウィンドウ生成

通常の Xlib 使用したものと libXCB を使用した実装とを比較してみます。

Xlib使用

#include 
#include 
#include 

/*
  gcc base_x11 -o base_x11 -lX11
*/
#define WIDTH  640
#define HEIGHT 480

Display *display;
Window   window;

int main() {
  XTextProperty ct;
  char *window_title = "BasicX11Window";
  
  display = XOpenDisplay(NULL);
  
  window = XCreateSimpleWindow(
    display, 
    DefaultRootWindow(display),
    0,0,
    WIDTH, HEIGHT,
    1, BlackPixel(display,0), WhitePixel(display,0) 
    );
    
  XmbTextListToTextProperty( display, &window_title, 1, XCompoundTextStyle, &ct );
  XSetWMName( display, window, &ct );
  
  XSelectInput( display, window, ExposureMask | KeyPressMask );
  XMapWindow( display, window );
  
  while(1) {
    XEvent ev;
    XNextEvent(display, &ev );
    if( ev.type == Expose ) {
    }
    if( ev.type == KeyPress ) {
      break;
    }
  }
  XCloseDisplay(display);
  return 0;
}

およそ50行いかない程度でウィンドウの生成ができます。

libXCB使用

一方 libXCB では以下のようになります。コードの行数としてはおよそ倍になっていますね。

#include 
#include 
#include 
#include 

/*
  gcc xcb_x11 -o xcbx11 -lxcb
*/
#define WIDTH  640
#define HEIGHT 480

int main()
{
  xcb_connection_t *conn;
  xcb_screen_t     *screen;
  xcb_drawable_t    window;
  xcb_gcontext_t    foreground, background;
  xcb_generic_event_t *event;
  uint32_t mask = 0;
  uint32_t values[2];
  char title[] = "BasicXcbWindow";
  int bLoop = 1;
  
  conn = xcb_connect( NULL, NULL );
  screen = xcb_setup_roots_iterator( xcb_get_setup(conn) ).data;
  window = screen->root;
  
  foreground = xcb_generate_id(conn);
  mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
  values[0] = screen->black_pixel;
  values[1] = 0;
  xcb_create_gc( conn, foreground, window, mask, values );
  
  background = xcb_generate_id(conn);
  mask = XCB_GC_BACKGROUND | XCB_GC_GRAPHICS_EXPOSURES;
  values[0] = screen->white_pixel;
  values[1] = 0;
  xcb_create_gc( conn, background, window, mask, values );
  
  /* ウィンドウの生成 */
  window = xcb_generate_id(conn);
  mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
  values[0] = screen->white_pixel;
  values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS;
  xcb_create_window( 
    conn,
    XCB_COPY_FROM_PARENT,
    window,
    screen->root,
    0, 0,
    WIDTH, HEIGHT,
    1,
    XCB_WINDOW_CLASS_INPUT_OUTPUT,
    screen->root_visual,
    mask,
    values
    );
  
  xcb_change_property(
    conn,
    XCB_PROP_MODE_REPLACE,
    window,
    XCB_ATOM_WM_NAME,
    XCB_ATOM_STRING,
    8,
    strlen(title),
    title );

  xcb_map_window( conn, window );
  xcb_flush(conn);
  
  while( (event=xcb_wait_for_event(conn)) ) {
    switch(event->response_type & ~0x80) {
    case XCB_EXPOSE:
      break;
    case XCB_KEY_PRESS:
      bLoop = 0;
      break;
    }
    free(event);
    if( bLoop == 0 ) {
      break;
    }
  }
  
  return 0;
}

比較

このくらいだと Xlib の各関数の中身が XCB でどう表現されるのか対応がわかりやすいです。下位レイヤーに位置するだけあってXlibの1関数がXCBの複数関数によって実現されています。

ファイルサイズの比較をしてみると以下のようでした。

libX11版 libXCB版
8.8 KB 13 KB

意外にも XCB 版のほうがファイルサイズは大きい状態でした。
ただし依存関係は libXCB 版のほうが少なくなっています(当然). 各バイナリを ldd したときの依存関係の抜粋を以下に示しておきます。

libX11使用:
 linux-vdso.so.1
 libX11.so.6
 libc.so.6
 libxcb.so.1
 libdl.so.2
 /lib64/ld-linux-x86-64.so.2
 libXau.so.6
 libXdmcp.so.6

libXCB使用:
 linux-vdso.so.1
 libxcb.so.1
 libc.so.6
 libXau.so.6
 libXdmcp.so.6
 /lib64/ld-linux-x86-64.so.2

個人的にはファイルサイズよりも依存関係少ない方が好みなので、libXCB 版のほうがしっくり来ています。

OpenGL を使ったアプリケーションの生成

では早速OpenGLを使ったアプリケーションを試してみたいと思います。

立方体を回していますが、この部分は共通のためコード掲載を省略します。

Xlibを使用したもの

次のようなプログラムになりました。基本的な X Window 使用時の OpenGL プログラムとなっています。

#include 
#include 
#include 

#include 
#include 
#include 

#include "draw_cube.h"

/*
  gcc base_x11 -o base_x11 -lX11
*/
#define WIDTH  640
#define HEIGHT 480

int attribs[] = {
  GLX_DOUBLEBUFFER, True,
  GLX_RGBA, True,
  GLX_RED_SIZE, 8, 
  GLX_GREEN_SIZE, 8, 
  GLX_BLUE_SIZE, 8, 
  GLX_ALPHA_SIZE, 8, 
  GLX_DEPTH_SIZE, 24,
  None,
};

int main() {
  Display *display;
  Window   window;
  Colormap cmap;
  XVisualInfo *vi;
  GLXContext glc;

  XTextProperty ct;
  char *window_title = "BasicX11Window";
  XSetWindowAttributes swa;
  int   rootWindow;
  int   bLoop = 1;
  
  display = XOpenDisplay(NULL);
  
  vi = glXChooseVisual( display, DefaultScreen(display), attribs );
  if( vi == NULL ) { 
    fprintf(stderr, "VisualInfo is NULL\n"); 
    return -1;
  }
  
  rootWindow = RootWindow( display, vi->screen );
  
  cmap = XCreateColormap(
          display,
          rootWindow,
          vi->visual,
          AllocNone
          );
  swa.border_pixel = 0;
  swa.colormap = cmap;
  window = XCreateWindow(
    display,
    rootWindow,
    0,0,
    WIDTH, HEIGHT,
    0, vi->depth, 
    InputOutput, vi->visual, 
    CWBorderPixel | CWColormap, 
    &swa
    );

  XmbTextListToTextProperty( display, &window_title, 1, XCompoundTextStyle, &ct );
  XSetWMName( display, window, &ct );
  
  XSelectInput( display, window, ExposureMask | KeyPressMask );
  XMapWindow( display, window );
  
  // OpenGL コンテキストの初期化.
  glc = glXCreateContext( display, vi, NULL, GL_TRUE );
  glXMakeCurrent( display, window, glc );
  printf( "GL_RENDERER : %s\n", glGetString( GL_RENDERER ) );
  printf( "GL_VENDOR   : %s\n", glGetString( GL_VENDOR ) );
 
  while(bLoop) {
    XEvent ev;
    while( XPending(display) > 0 )  {
      XNextEvent(display, &ev );
      switch( ev.type ){
      case Expose:
        break;
      case DestroyNotify:
      case KeyPress:
        bLoop = 0;
        break;
      }
    }
    if( bLoop ) {
      glViewport( 0, 0, WIDTH, HEIGHT );

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluPerspective(30, (float)WIDTH/(float)HEIGHT, 0.1, 30.0 );
  
      glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      glEnable( GL_DEPTH_TEST );
      drawCube();
      glXSwapBuffers( display, window );
    }
  }
  
  glXMakeCurrent( display, window, NULL );
  glXDestroyContext( display, glc );
  XDestroyWindow( display, window );
  XCloseDisplay(display);
  return 0;
}

約115行です。

XCBを使用したもの

以下のようなソースコードになりました。約145行ほどになりました。
思ったよりはコード増えていない感想です。

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include "draw_cube.h"

/*
  gcc xcb_x11 -o xcbx11 -lxcb
  apt-get install libx11-xcb-dev
*/
#define WIDTH  640
#define HEIGHT 480

int attribs[] = {
  GLX_DOUBLEBUFFER, True,
  GLX_RGBA, True,
  GLX_RED_SIZE, 8, 
  GLX_GREEN_SIZE, 8, 
  GLX_BLUE_SIZE, 8, 
  GLX_ALPHA_SIZE, 8, 
  GLX_DEPTH_SIZE, 24,
  GLX_RENDER_TYPE, GLX_RGBA_BIT,
  GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
  GLX_X_RENDERABLE, True,
  GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
  None,
};
int main()
{
  Display *display;
  xcb_connection_t *conn;
  xcb_screen_t     *screen;
  xcb_drawable_t    window;
  xcb_colormap_t cmap;
  xcb_generic_event_t *event;
  GLXFBConfig *fbconfigs = 0;
  GLXContext glc;
  int num_configs = 0;
  uint32_t mask = 0;
  uint32_t eventmask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS;
  uint32_t values[3];
  char title[] = "BasicXcbWindow";
  int bLoop = 1;
  int visualID = 0;
   
  display = XOpenDisplay(NULL);
  conn = XGetXCBConnection(display);
  XSetEventQueueOwner( display, XCBOwnsEventQueue );
  screen = xcb_setup_roots_iterator( xcb_get_setup(conn) ).data;
  window = screen->root;
  
  fbconfigs = glXChooseFBConfig( 
                display, 
                DefaultScreen(display),
                attribs,
                &num_configs );
  if( num_configs == 0 ) {
    return -1;
  }
    
  visualID = screen->root_visual;
  glXGetFBConfigAttrib( display, fbconfigs[0], GLX_VISUAL_ID, &visualID );
  printf( "VisualID = 0x%0x\n", visualID );
     
  cmap = xcb_generate_id(conn);
  xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, cmap, screen->root, visualID );
  
  
  /* ウィンドウの生成 */
  window = xcb_generate_id(conn);
  mask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
  values[0] = eventmask;
  values[1] = cmap;
  values[2] = 0;

  xcb_create_window( 
    conn,
    XCB_COPY_FROM_PARENT,
    window,
    screen->root,
    0, 0,
    WIDTH, HEIGHT,
    0,
    XCB_WINDOW_CLASS_INPUT_OUTPUT,
    visualID,
    mask,
    values
    );
  
  xcb_change_property(
    conn,
    XCB_PROP_MODE_REPLACE,
    window,
    XCB_ATOM_WM_NAME,
    XCB_ATOM_STRING,
    8,
    strlen(title),
    title );

  xcb_map_window( conn, window );
  xcb_flush(conn);
  
  glc = glXCreateNewContext( display, fbconfigs[0], GLX_RGBA_TYPE, 0, True );
  glXMakeCurrent( display, window, glc );
  printf( "GL_RENDERER : %s\n", glGetString( GL_RENDERER ) );
  printf( "GL_VENDOR   : %s\n", glGetString( GL_VENDOR ) );
  
  while( bLoop ) {
    while( event = xcb_poll_for_event(conn) ) {
      switch(event->response_type & ~0x80) {
      case XCB_KEY_PRESS:
        bLoop = 0;
        break;
      case XCB_EXPOSE:
        break;
      }
      free(event);
    }
    
    if( bLoop ) {
      glViewport( 0, 0, WIDTH, HEIGHT );

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluPerspective(30, (float)WIDTH/(float)HEIGHT, 0.1, 30.0 );
  
      glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      glEnable( GL_DEPTH_TEST );
      drawCube();
      glXSwapBuffers( display, window );      
    }
  }
   
  glXDestroyContext( display, glc );   
  xcb_destroy_window( conn, window );
  return 0;
}

Xlib の関数が、それらを構成するための XCB の関数を複数呼び出すような感じになるため、このようなコードになりました。待避して比べてみるとそんなに違わないことがわかるかと思います。

さて、このコードを見て気づいたかと思います。
そうです、「Xlib 未使用になっていない」んです。ディスプレイを開くためには XOpenDisplay が必要で、ディスプレイ部分がどうしても Xlib を使う必要が出てきました。
このことから、依存関係が全く減っていません。

まとめ

結局のところ、現時点においては windowシステムありきで OpenGL を使う場合には Xlib 使ってウィンドウを作るのが必要ということです。ただ XCB 使うと Xlib よりは細かい単位で制御ができそうな感じではあるので、融通は利きそうな感触を受けました。
2種類作ってみた感想としては、わざわざ XCB にするメリットはないな~という結論となりました。

参考

XCB The big picture: OpenGL, Xlib and GLX

注意点としては、上記の説明にあるコードをそのままでは正常に動作しませんでした。
理由としては要求しているフォーマットと VisualID らでとれる情報が食い違うことがあるため、です。自分の試した環境では常に正常に使えない組み合わせばかりだったため、上記で紹介したコードへと書き直して実験しました。

OpenGLプログラミング
すらりんをフォローする
すらりん日記

コメント

タイトルとURLをコピーしました