Windows上でOpenGLのExtensionを使う場合について
OpenGL Registryについて
OpenGLの機能拡張であるExtensionは、OpenGL Registeyというwebページ上で公開されて一元管理されています。
ここには、各々のExtensionの仕様が記載されたドキュメントがあります。ARB Extensionと、EXTおよびVendor Extensionにはそれぞれシリアル番号が付いており、新規にExtensionが追加されれば、各々の末尾に追加されます。したがって、新規に追加されるExtensionの有無をチェックするには、このページでARBとEXTの番号の末尾を確認すれば良いわけです。また、このページには、Core API and Extension Header Files の項目に、glext.h, wglext.hが置いてあります。
Windows上での使用にあたって
Windows上でOpenGLのExtensionを使う場合は、上記で紹介したインクルードファイルだけでは不十分です。Windows上ではExtensionの関数のアドレスは、wglGetProcAddress()という関数を用いて、呼び出したい関数名を文字列で渡して、そのアドレスを取得する必要があります。そのため、Windows上では他のプラットフォームに比べて、Extensionを使用する場合は、プログラムが煩雑になります。ただし、この関数があることで、ディスプレイドライバ側がライブラリを提供する必要がないので、Vendor Extensionの類はディスプレイドライバ側に機能を組み込むだけで、すぐにExtensionが使えるようになります。また、ライブラリをリンクしないので、プログラム作成時とまったく違うディスプレイドライバが使用されている環境でも、プログラムの実行自体に問題が起きることはありません(所望のExtensionがサポートされていないケースはありますが)。
Extension関数をlazy loadingする
Extension関数のアドレスを初期化時にすべて読み込むはなかなか骨の折れる作業です。使用するExtensionが増えるにしたがって、ロードしなければならない関数が増えます。その一方で、まったく使用する可能性のないExtension関数も数多くあります。ここでは、初回の呼び出し時に、関数のアドレスを問い合わせ、関数名のグローバル変数にアドレスを格納し、2回目以降の呼び出しは、初回に格納した値をそのまま使う方法を紹介します。こうすれば、必要以外の関数を取得することもありませんし、2回目以降は、初回に取得したポインタを直接使用するので、呼び出しのオーバーヘッドも最小限になります。例として、glGetTextureHandleNVのExtension関数で以下の様に記述します。
#ifdef WINDOWS_GLEXT_DEFINE_FUNCTIONPTR extern PFNGLGETTEXTUREHANDLENVPROC glGetTextureHandleNV; #endif #ifdef WINDOWS_GLEXT_CREATE_FUNCTIONPTR static GLuint64 APIENTRY PFNGLGETTEXTUREHANDLENVPROC_InitProc (GLuint texture) { glGetTextureHandleNV = (PFNGLGETTEXTUREHANDLENVPROC)wglGetProcAddress("glGetTextureHandleNV"); return (glGetTextureHandleNV)(texture); } PFNGLGETTEXTUREHANDLENVPROC glGetTextureHandleNV = PFNGLGETTEXTUREHANDLENVPROC_InitProc; #endif
宣言してあるdefineに応じて、関数ポインタと、初回ロード時の関数の実体が作成されるファイルと、関数ポインタの外部参照宣言が切り替わります。初回はInitProcが呼び出され、InitProc内でwglGetProcAddressで取得したポインタを代入します。その後関数を呼び出します。2度目以降は、取得したアドレスが直接呼び出されます。このInitProcはwglGetProcAddressがNULLを返した場合を想定していません。必要があれば適切なエラーハンドリングを記述する必要があります。
Perlを使って作成する
上記の様に記述すれば、関数をlazy loadingするのは簡単ですが、すべてのExtension関数で、この記述を行うのは骨が折れます。ただし、glext.hとwglext.hにおける、関数のプロトタイプ宣言は、非常に特徴的に記述されています。したがって、これをPerlで加工することで、目的の関数を作成するのは簡単です。Perlで以下のように記述すれば良いわけです。wglGetProcAddressがNULLを返した場合のハンドリングも簡単に追加することが出来ます。コンパイラの設定を変えて、warning 等が出るようになっても自分で書き換えてしまえば済むわけです。
if (/^GLAPI.+APIENTRY.+/) { my $funcName = &getFuncName($_, "APIENTRY"); my $PFNPROC = "PFN" . (uc $funcName) . "PROC"; $funcTable{$PFNPROC} = $funcName; } elsif (/^typedef\s+(\w+\s*\**\s*)\(APIENTRYP (\w+)\) \((.*)\);/) { my $PFNRET = $1; my $PFNPROC = $2; my $PFNARG = $3; if (! exists $funcTable{$PFNPROC}) { die "Failed to get functionName ${PFNPROC}\n"; } my $funcName = $funcTable{$PFNPROC}; my $callArg = getCallArg($PFNARG); print (OUT "#ifdef WINDOWS_GLEXT_DEFINE_FUNCTIONPTR\n"); print (OUT "extern $PFNPROC ${funcName};\n"); print (OUT "#endif\n"); print (OUT "#ifdef WINDOWS_GLEXT_CREATE_FUNCTIONPTR\n"); print (OUT "static $PFNRET APIENTRY ${PFNPROC}_InitProc (${PFNARG}) {\n"); print (OUT " $funcName = (${PFNPROC})wglGetProcAddress(\"${funcName}\");\n"); print (OUT " return (${funcName})(${callArg}); }\n"); print (OUT "$PFNPROC $funcName = ${PFNPROC}_InitProc;\n"); print (OUT "#endif\n"); }
Perlで加工したもの
2012/06/18版のglext.hとwglext.hをperlで加工したものが以下になります。
glext_win-zip.jpg(保存して拡張子変えてください)
プログラム中で一箇所のみ、関数とポインタの実体を作る必要があるので、以下のように記述します。
#define WINDOWS_GLEXT_DEFINE_FUNCTIONPTR 1 #define WINDOWS_GLEXT_CREATE_FUNCTIONPTR 1 #include "glext_win.h" #include "wglext_win.h" #undef WINDOWS_GLEXT_CREATE_FUNCTIONPTR #undef WINDOWS_GLEXT_DEFINE_FUNCTIONPTR
それ以外の箇所で外部参照する際は、下記の様に記述します。
#define WINDOWS_GLEXT_DEFINE_FUNCTIONPTR 1 #include "glext_win.h" #include "wglext_win.h" #undef WINDOWS_GLEXT_DEFINE_FUNCTIONPTR