日別アーカイブ: 2009年9月1日

OpenGL のはじめ方

Windows 上で DirectX ばかり使ってきたので、いざ OpenGL に手をつけてみようと思っても
何を使えばいいのか良くわかりませんでした。DirectX10/11 はすでにシェーダーのみとなって
いるため、OpenGL 入門によく載っているようなレガシーな命令もちょっと違います。
WindowsSDK に付属している gl.h ヘッダは OpenGL v1.1 ですが、すでに OpenGL は
3.2 まで進化しているようです。

Windows の場合、OS との間を取り持つ wgl~ という API 群があります。
wgl と gl は、Direct3D でいえば DXGI と D3D の関係に似ています。

MSDN Win32 Extensions to OpenGL

以前 Maya plug-in を作ったときも GL_ARB_vertex_program など OpenGL の
Extension 関数を呼び出すために wglGetProcAddress() を使いました。
Core API の場合も全く同じ方法でした。

(1) 最新のヘッダファイルを入手する
(2) wgl 命令で描画の初期化
(3) ヘッダの定義を元に wglGetProcAddress() でアドレスを取り出す

OpenGL Registry

上のページに gl3.h や glext.h, wglext.h 等が登録されています。
関数インターフェースの定義はこの中で行われており、必要ならプロトタイプ宣言もしてくれます。
wglGetProcAddress() を用いて関数名から実際のエントリアドレスを取得すれば
API を呼び出せるようになります。

たとえば gl3.h の中では、OpenGL 2.0 の命令として glCreateShader() が定義されています。

#define APIENTRY
#define APIENTRYP APIENTRY *
#define GLAPI extern

GLAPI GLuint APIENTRY glCreateShader (GLenum);
~
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);

上がプロトタイプ宣言、下が関数ポインタの型宣言です。APIENTRYP はただの ‘*’。
今回はエントリアドレスを直接取り出すため、下の関数ポインタ型の宣言しか使いません。
プロトタイプ宣言はデフォルトで無効になっています。

この glCreateShader() を呼び出すには下記のようにすればよいわけです。

// 取り出し
PFNGLCREATESHADERPROC glCreateShader=
        (PFNGLCREATESHADERPROC)wglGetProcAddress( "glCreateShader" );

// 呼び出し
glCreateShader( shader );

もちろん使えるのはディスプレイドライバが対応している場合だけです。
ドライバが対応していなければ wglGetProcAddress() はエラーを返します。

このように関数名からエントリアドレスを取得するやりかたは DLL と同じです。
DirectX の場合は COM なので、関数単独ではなく class 単位で API を取り出しますが
基本は同じです。

上の例のように、OpenGL 1.2~3.2 の膨大な関数を手作業で追加するのは酷です。
自動化します。

ヘッダのプロトタイプ宣言から、必要な関数名だけ抜き出すスクリプトを作っておきます。

import  re
import  sys
mp_api= re.compile( "^GLAPI\s+[a-zA-Z0-9_*]+\s+APIENTRY\s+(.*)\s+\(" )
def ProcList( filename ):
    f= open( filename, 'r' )
    for line in f:
        pat_api= mp_api.search( line )
        if pat_api != None:
            func= pat_api.group(1)
            print "GLDEFPROC( PFN"+ func.upper() + "PROC, " + func + " )"
    f.close()
ProcList( sys.argv[1] )

関数名を全部大文字にして PFN ~ PROC で囲めばインターフェースの型宣言になります。
上のスクリプトは gl3.h ヘッダから関数名だけ抜き出して下記のような一覧を生成します。

GLDEFPROC( PFNGLATTACHSHADERPROC, glAttachShader )
GLDEFPROC( PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation )
GLDEFPROC( PFNGLCOMPILESHADERPROC, glCompileShader )
GLDEFPROC( PFNGLCREATEPROGRAMPROC, glCreateProgram )
GLDEFPROC( PFNGLCREATESHADERPROC, glCreateShader )
GLDEFPROC( PFNGLDELETEPROGRAMPROC, glDeleteProgram )
GLDEFPROC( PFNGLDELETESHADERPROC, glDeleteShader )
GLDEFPROC( PFNGLDETACHSHADERPROC, glDetachShader )
~

そのまま gl3.h を渡すと OpenGL 1.1 など余計な関数まで再定義してしまうので、
必要な 1.2~3.2 部分だけ切り出してから与えることにします。
Core API だけでなく拡張命令も同じように追加できるでしょう。

出力ファイル名を GLExtensionList.inc としておきます。
これを C ソースで include し、GLDEFPROC() を用途に応じて再定義してして使い分けます。

ヘッダ側

// GLFunction.h
#include  
#include  
#include  

#define GLDEFPROC(type,name)     extern type name;
# include  "GLExtensionList.inc"
#undef  GLDEFPROC

extern void InitOpenGL();

ソース側

// GLFunction.cpp

#define GLDEFPROC(type,name)      type name;
# include    "GLExtensionList.inc"
#undef  GLDEFPROC

struct T_ProcList {
    const char* Proc;
    void**      Addr;
};

static T_ProcList   ProcList[]= {

#define GLDEFPROC(type,name)      { #name, (void**)&name },
# include    "GLExtensionList.inc"
#undef  GLDEFPROC

    {   NULL,   NULL,   },
};

void InitOpenGL()
{
    const T_ProcList* tp= ProcList;
    for(; tp->Proc ; tp++ ){
        *tp->Addr= wglGetProcAddress( tp->Proc );
        if( !*tp->Addr ){
            int errcode= GetLastError();
            ERROR_UTF8( "'%s' not found %d/%x\n", tp->Proc, errcode, errcode );
            //*tp->Addr= _UnknownAPI_Func;
        }
    }
}

これで GLFunction.h を include すれば、OpenGL 3.2 までの命令をそのまま
使えるようになります。追加 lib は WindowsSDK の OpenGL32.lib です。
使用可能なのはドライバが対応している場合だけ。
また起動時は最初に一度 InitOpenGL() を呼び出しておく必要があります。

OpenGL ES 2.0 用に作成した描画コードが、一カ所直しただけでそのままコンパイルできました。
GLSL のシェーダーコードも共有しており、きちんど動作します。
ドライバと環境は GeForce 9800GT + 190.62 + Windows7 x64 。
モデルデータもテクスチャも GL ES 2.0 と全く同じ物です。
修正したのは下記の点

glClearDepthf( depth ); // GL ES のみ
    ↓
glClearDepth( depth );

OpenGL 3.2 に厳密に従うには、ピクセルフォーマットなどテクスチャの読み込み部も
手を入れる必要がありそうです。

関連エントリ
OpenGL ES 2.0 Emulator
OpenGL ES 2.0
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理