日別アーカイブ: 2014年2月8日

Linux 他 X11 で OpenGL 4 を使う

X11 (X window) では Platform Interface に GLX を用います。
Display が本当に意味を持つのは X11 ならではで、egl の API 仕様も
X window のプログラムを見ると納得です。

Display* display= NULL;
int      screen= 0;

display= XOpenDisplay( NULL );
screen= DefaultScreen( display );

(1) pixel format の選択

static int attrib_list[]= {
    GLX_RENDER_TYPE,    GLX_RGBA_BIT,
    GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT,
    GLX_DOUBLEBUFFER,   True,
    GLX_RED_SIZE,       8,
    GLX_GREEN_SIZE,     8,
    GLX_BLUE_SIZE,      8,
    GLX_ALPHA_SIZE,     8,
    GLX_DEPTH_SIZE,     24,
    GLX_STENCIL_SIZE,   8,
    None
};
int  n_items= 0;
GLXFBConfig*  config_array= glXChooseFBConfig( display, screen, attrib_list, &n_items );
〜
if( n_items ){
    GLXFBConfig fb_config= config_array[0];
    〜
}
XFree( config_array );

サーバーが対応しているピクセルフォーマットから必要なものを選択しています。
フォーマットの組み合わせは GPU やドライバによって異なります。

X native の API にも似たような仕組みとして XVisualInfo があります。
GLXFBConfig は GLX による拡張で、XVisualInfo* の代わりに用いることが可能です。

Windows でも Win32 API に PIXELFORMATDESCRIPTOR (ChoosePixelFormat())
がありました。WGL Extension の WGL_ARB_pixel_format
(wglChoosePixelFormatARB()) はこれを置き換えるもので、
XVisualInfo と GLXFBConfig との関係に似ています。

もし XCreateColormap などで Visual (XVisualInfo) が必要になったら
下記のように取得すれば OK です。

// GLXFBConfig -> XVisualInfo
XVisualInfo* visual_info= glXGetVisualFromFBConfig( display, fb_config );
〜
XFree( visual_info );

上の visual_info は config_array 同様 XFree() が必要です。
egl のような wrapper を作る場合、メモリ管理を伴うのは少々都合が悪い
場合があります。
GLXFBConfig をそのまま保持せずに、ID に紐付けておくと管理が楽になります。

// GLXFBConfig -> fb_config_id
int  fb_config_id= 0;
glXGetFBConfigAttrib( display, fb_config, GLX_FBCONFIG_ID, &fb_config_id );

この fb_config_id があれば GLXConfig が一意に定まるわけです。
fb_config_id から GLXFBConfig を取り出す方法は下記の通り。

// fb_config_id -> GLXFBConfig
int attr_list[]= { GLX_FBCONFIG_ID, fb_config_id, None };
int n_items= 0;
GLXFBConfig* fb_config_ptr= glXChooseFBConfig( display, screen, attr_list, &n_items );
assert( n_items == 1 );
GLXFBConfig fb_config= fb_config_ptr[0];
〜
XFree( fb_config_ptr );

XVisualInfo を得る方法はすでに述べた通りですが、GLXFBConfig から
直接 VisualID を求めることもできます。

// GLXFBConfig -> VisualID
int  visual_id= 0;
glXGetFBConfigAttrib( display, config_array[0], GLX_VISUAL_ID, &visual_id );

(2) Context の作成

任意の Version の OpenGL Context を作成するには GLX_ARB_create_context
( glXCreateContextAttribsARB() ) が必要です。
試した下記の環境ではデフォルトで OpenGL 4.3 が選択されていたので、
場合によっては使わなくても問題ないかもしれません。

・Ubuntu 13.10 desktop x86_64
・GeForce GTX650
・NVIDIA binary Xorg driver 319

Core Profile のみ、OpenGL ES 2.0 互換 Profile の指定など、細かい設定を
行うならやはり glXCreateContextAttribsARB() が必要です。

// Extension API glXCreateContextAttribsARB() の取り出し
PFNGLXCREATECONTEXTATTRIBSARBPROC f_glXCreateContextAttribsARB=
  (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(
       (const GLubyte*)"glXCreateContextAttribsARB" );

// OpenGL 4.3 context 作成
static int  att_command[]= {
    GLX_CONTEXT_MAJOR_VERSION_ARB,  4,
    GLX_CONTEXT_MINOR_VERSION_ARB,  3,
    GLX_CONTEXT_FLAGS_ARB,          0,
    GLX_CONTEXT_PROFILE_MASK_ARB,   GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
    None
};
GLXContext  context= f_glXCreateContextAttribsARB( display, fb_config, 0, True, att_command );

他にも glX extension を利用する場合が考えられるので、
API の取得はまとめて管理した方が良いでしょう。
glX extension だけでなく、GL 4.x の各命令でも API の取得が必要になります。

4番目の True は Direct Rendering の指定です。
本来の X protocol はネットワーク透過なのでアプリケーションからドライバに
直接アクセスできませんが、ローカルサーバー利用時は条件によって可能になります。
現在の context が Direct Rendering 可能な状態 (DRI) かどうかは
glXIsDirect() で確認することができます。

GL_ARB_create_context

(3) Window の作成

GLXFBConfig が決まった時点で Window を作れるので (2) との順番は任意。

XVisualInfo* visual_info= glXGetVisualFromFBConfig( display, fb_config );
Window  root= RootWindow( NativeDisplay, screen );
XSetWindowAttributes    attr;
attr.background_pixel= 0;
attr.border_pixel= 0;
attr.colormap= XCreateColormap( display, root, visual_info->visual, AllocNone );
attr.event_mask= 0
    |StructureNotifyMask
    |ExposureMask
    |KeyPressMask
    |KeyReleaseMask
    |ButtonPressMask
    |ButtonReleaseMask
    ;
Window	win= XCreateWindow( display, root, x, y, width, height,
    0,	// border
    visual_info->depth, InputOutput, visual_info->visual,
    CWBorderPixel|CWColormap|CWEventMask, &attr );
XFree( visual_info );
visual_info= NULL;

XMapWindow( display, win );
XFlush( display );

(4) MakeCurrent

glXMakeCurrent( display, win, context );

Window は GLXDrawable です。
これでレンダリングできますが、OpenGL 2 以降の API を用いるには
個別に glXGetProcAddress() して関数を用意する必要があります。

(5) OpenGL API の取得

ゼロから始めるとここが一番手間かもしれません。
ただしこの流れは Windows で OpenGL を用いる場合と完全に同一です。
API 名 Table の作成、API の取り出しコードは Windows と共有することができます。

OpenGL のはじめ方 (Windows)

下記は API Table の例です。
OpenGL 4.x の glcorearb.h からスクリプトで自動変換して生成しています。
この方法だと、新しい API や extension 対応ドライバがリリースされたら
即座に対応することができます。

// flatlib3 GL_HeaderParser.py
// linux/GLFunction_GL4.inc

void (APIENTRY *p_glBlendColor)( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha );
void (APIENTRY *p_glBlendEquation)( GLenum mode );
void (APIENTRY *p_glDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices );
〜
void (APIENTRY *p_glTexPageCommitmentARB)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident );
// total=525


ImportListType ImportList_GL4[]= {
{ "glBlendColor", (void*)&p_glBlendColor },
{ "glBlendEquation", (void*)&p_glBlendEquation },
{ "glDrawRangeElements", (void*)&p_glDrawRangeElements },
{ "glTexImage3D", (void*)&p_glTexImage3D },
{ "glTexSubImage3D", (void*)&p_glTexSubImage3D },
〜
{ "glGetNamedStringARB", (void*)&p_glGetNamedStringARB },
{ "glGetNamedStringivARB", (void*)&p_glGetNamedStringivARB },
{ "glTexPageCommitmentARB", (void*)&p_glTexPageCommitmentARB },
{ NULL, NULL }

};
// total=525
// flatlib3 GL_HeaderParser.py
// linux/GLFunction_GL4.h

extern void (APIENTRY *p_glBlendColor)( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha );
inline void glBlendColor( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha )
{
    FL_GLAPI_ASSERT( p_glBlendColor );
    return p_glBlendColor( red, green, blue, alpha );
}
extern void (APIENTRY *p_glBlendEquation)( GLenum mode );
inline void glBlendEquation( GLenum mode )
{
    FL_GLAPI_ASSERT( p_glBlendEquation );
    return p_glBlendEquation( mode );
}
extern void (APIENTRY *p_glDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices );
inline void glDrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices )
{
    FL_GLAPI_ASSERT( p_glDrawRangeElements );
    return p_glDrawRangeElements( mode, start, end, count, type, indices );
}
〜

Platform Interface のまとめ

Windows         WGL         GL4.x   wglGetProcAddress()
Linux X11       GLX         GL4.x   glXGetProcAddress()
OSX             NSOpenGL    GL4.1
OSX + X11       GLX         GL2.1   (XQuartz + HD4000)
Linux X11 + ES  EGL         ES2/3
Android         EGL         ES2/3
iOS             GLK/EAGL    ES2/3

Windows/Linux X11 は GetProcAddress() が必要ですが対応ドライバがあれば
新しい API をすぐ使えます。
OSX はそのまま OpenGL を使えて手間いらずですが 10.9 は OpenGL 4.1 まで。
OSX + XQuartz (HD4000) は残念ながら 2.1 でした。

Nexus 7 (2012) + Ubuntu desktop のように OpenGL ES 2.0 対応 GPU の
Linux では GLX ではなく X11 上で EGL + OpenGL ES 2.0 API が使えます。

Raspberry Pi や 旧 NetWalker のように、X が動いても OpenGL 未対応の場合
フレームバッファに直接レンダリングすることになります。
この場合も EGL なので、Window の有無にかかわらず利用できる EGL は互換性が
取りやすくなっています。

関連エントリ
Mac OS X 10.9 Mavericks の OpenGL 4.1 対応と Windows 8.1 Intel HD 4000 Driver
Mac OS X で OpenGL の描画 (Xcode5/retina)
Nexus 7 (2012) Ubuntu desktop 上での開発とバッテリー設定
NetWalker PC-Z1 i.MX515 OpenGL ES 2.0 (3)
OpenGL のはじめ方