OpenGLの話 第3回
デバッグ出力 GL_ARB_debug_output 13/12/15 up
今年最後の更新になる予定ですが、今回はデバッグ出力をやってみました。
簡単なものではありますが開発するうえでは非常に重要な機能のはずです。
その割に日本語ではあまり情報がなかったりする…まあ、簡単すぎるから説明するほどでもない、ってことかもしれません。
DirectXでは、一部の命令はHRESULT型の戻り値を返してきます。
特にオブジェクト生成関係(テクスチャオブジェクト生成やシェーダ生成など)では大半の命令がHRESULTを返すはずです。
HRESULTはS_OKであればエラーが発生しておらず、それ以外なら何らかのエラーが発生していると判断されます。
OpenGLではほとんどの命令が成功/失敗の戻り値を返しません。
戻り値としてオブジェクトIDを返すもの(glCreateShaderなど)もありますが、それ以外はほぼ何も返してきません。
HRESULTに対応するものとしてはglGetError()という関数があり、これによって直近の発生したエラーを取得することができます。
glGetError()はこの命令を最後に呼んでから発生したエラーを取得することができますが、どの命令の何が間違っていたのかはほぼわかりません。
使うのであれば、gl~という命令を呼んだ直後に必ずglGetError()を呼び出し、エラーが発生したかどうかチェックするということをすべきでしょう。
しかし、はっきり言って面倒なことこの上ありません。それに、この命令で取得できるエラーでは細かいところまではわかりません。
細かいところまではわからない、というのはDirectXのHRESULTも同様です。
引数が間違ってる、とか命令の使用方法が間違ってるとか言われても何がどう間違ってるのかわからないものです。
ではどうするか?
DirectX10からはデバイスを生成する際にデバッグデバイスとして作成するためのフラグが存在しています。
このデバッグデバイスは各種命令の際にエラーチェックを行い、何らかの問題が発生した場合は何がどう間違っているのかデバッグ出力をしてくれます。
その分速度面では不利になるので、製品リリース版では切っておくのが普通です。
このデバッグ出力は非常に便利で、正直な話、これなしで開発を行うのは厳しいと言わざるを得ません。
古いOpenGLでは存在していないようなのですが、OpenGLでも同様の機能が存在しています。
それがGL_ARB_debug_output拡張です。
もともとはAMD拡張だったんですかね、これ?これ関連の命令にはARBとAMDが存在していますので。
少なくとも、現在の大半のOpenGL実装では使えるのではないかと思います。GLESは知りませんが。
glGetString(GL_EXTENSIONS)にて取得した拡張機能の中にGL_ARB_debug_outputが存在しているのであれば使用可能です。
使い方もコールバックを登録するだけなので簡単です。
ただし、複数のコンテキストを利用してマルチスレッド対応する場合は出力時に注意が必要かもしれません。
まず、この機能を使用する際にはデバイスコンテキストをデバッグモードで作成しなければなりません。
wglを使用する場合はWGL_CONTEXT_DEBUG_BIT_ARBというフラグを立てる必要があります。
GLFWを使用する場合はglfwWindowHint()でGLFW_OPENGL_DEBUG_CONTEXTを1にする必要があります。
設定しない場合はデフォルトでデバッグデバイスとして作成されませんので、必ず立てるようにしてください。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 8);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "GL Sample", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
次にデバッグ出力が行われた際に呼び出されるコールバック関数を作成します。
今回はアプリ起動時に作成されるコンソールウィンドウにprintf()命令で出力するだけの簡易なものを作成しました。
もちろん、デバッグ出力をファイルに行うことも可能ですし、ユーザによる引数も設定可能なのでそこにファイルハンドルなどを渡してもいいでしょう。
static void __stdcall OutputGLDebugMessage(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, GLvoid *userParam)
{
static const char* kSourceStrings[] = {
"OpenGL API",
"Window System",
"Shader Compiler",
"Third Party",
"Application",
"Other",
};
int sourceNo = (GL_DEBUG_SOURCE_API_ARB <= source && source <= GL_DEBUG_SOURCE_OTHER_ARB)
? (source - GL_DEBUG_SOURCE_API_ARB)
: (GL_DEBUG_SOURCE_OTHER_ARB - GL_DEBUG_SOURCE_API_ARB);
static const char* kTypeStrings[] = {
"Error",
"Deprecated behavior",
"Undefined behavior",
"Portability",
"Performance",
"Other",
};
int typeNo = (GL_DEBUG_TYPE_ERROR_ARB <= type && type <= GL_DEBUG_TYPE_OTHER_ARB)
? (type - GL_DEBUG_TYPE_ERROR_ARB)
: (GL_DEBUG_TYPE_OTHER_ARB - GL_DEBUG_TYPE_ERROR_ARB);
static const char* kSeverityStrings[] = {
"High",
"Medium",
"Low",
};
int severityNo = (GL_DEBUG_SEVERITY_HIGH_ARB <= type && type <= GL_DEBUG_SEVERITY_LOW_ARB)
? (type - GL_DEBUG_SEVERITY_HIGH_ARB)
: (GL_DEBUG_SEVERITY_LOW_ARB - GL_DEBUG_SEVERITY_HIGH_ARB);
printf("Source : %s Type : %s ID : %d Severity : %s Message : %s\n"
, kSourceStrings[sourceNo], kTypeStrings[typeNo], id, kSeverityStrings[severityNo], message);
}
引数として重要なのはやはりmessageです。
この引数がエラーの原因をわかりやすい(かどうかは人によるかも)文字列で示してくれています。
その他の引数はどの種のAPIでエラーが発生したかや、どのIDが入ってきたか、重要性などを示しています。
最後の引数のuserParamはユーザが指定可能なパラメータです。
ファイル出力したい場合などにはここにファイルハンドルを渡したりしてもいいでしょう。
この関数はglDebugMessageCallbackARB()命令で設定します。
glDebugMessageCallbackARB(OutputGLDebugMessage, NULL);
第2引数はNULLを指定していますが、ここにユーザパラメータを指定することができます。
これにより、OpenGLの命令でエラーが発生した直後にコールバック関数に処理が入ってきます。
細かくエラーをチェックしたい場合はコールバック関数内にブレークポイントを貼っておいてもいいかもしれませんね。
あまりにも大きな問題であればAssertionで止めてしまうのもありでしょう。
今回のサンプルではglCreateShader()に不正な引数を与えている個所が1カ所だけあります。
コンソールウィンドウにはその旨のエラーが出力されているはずです。
いくつかの命令でいろいろ不正なことをやってみたのですが、DirectXのデバッグ出力と比べると簡素な感じがしますね。
DirectXのデバッグ出力はともすればうるさいくらいなのですが、下手にヘルプをチェックするよりよっぽどわかりやすい情報を出力してくれます。
そのレベルには足りないかもしれませんが、それでもglGetError()のみでなんとかするのに比べたら何十倍もマシなはずです。
サンプルはリリースビルドですが、デバッグ出力が有効になっています。
ちゃんとしたアプリを作成する際にはリリース時にデバッグデバイスを使用しないようにしましょう。
次回更新は来年予定です。