日別アーカイブ: 2013年8月12日

OpenGL のエラー判定と OpenGL 4.3 Debug Output

OpenGL のエラー状態を調べるには glGetError() を使います。
3~5種類程度のエラーコードが得られるので、マニュアルを頼りに
API 毎に発生条件を照らし合わせていきます。

どのタイミングで発生したのか特定するには、OpenGL の命令呼び出し毎に
glGetError() をチェックしなければなりません。

● glGetError() の判定

glActiveTexture( GL_TEXTURE0 + 32 );
GLenum error_code= glGetError();
if( error_code != GL_NO_ERROR ){
   // ERROR
}

↑これを毎回手で行うのは大変なので、何らかの形で自動化します。
現在は、下記のようなヘッダを作って対処しています。

// GLErrorLayer_ES2.h
// __gl2_h_
inline void ActiveTexture( GLenum texture )
{
    glActiveTexture( texture );
    FL_GL_ERROR_TRAP( "glActiveTexture" );
}
inline void AttachShader( GLuint program, GLuint shader )
{
    glAttachShader( program, shader );
    FL_GL_ERROR_TRAP( "glAttachShader" );
}
~
inline GLint GetAttribLocation( GLuint program, const GLchar * name )
{
    GLint ret= glGetAttribLocation( program, name );
    FL_GL_ERROR_TRAP( "glGetAttribLocation" );
    return  ret;
}
~

GLES2/gl2.h, GLES3/gl3.h, GL4/glcorearb.h などのヘッダファイルを元に
スクリプトで自動生成しています。
下記のように include して使います。

// GLErrorLayer.h
namespace el {

#if flDEBUG
# define  FL_GL_ERROR_TRAP( func_name )	gw::ErrorHandle( func_name )
#else
# define  FL_GL_ERROR_TRAP( func_name )
#endif

#include  "gw/entry/GLErrorLayer_ES2.h"
}

↑コンパイル時にエラー判定を行うかどうか選択できます。
実際のエラー判定は下記のような関数です。

bool ErrorHandle( const char* name )
{
    GLenum   error_code= glGetError();
    if( error_code == GL_NO_ERROR ){
        return  true;
    }
    do{
        const char* msg= "";
        switch( error_code ){
        case GL_INVALID_ENUM:      msg= "INVALID_ENUM";      break;
        case GL_INVALID_VALUE:     msg= "INVALID_VALUE";     break;
        case GL_INVALID_OPERATION: msg= "INVALID_OPERATION"; break;
        case GL_OUT_OF_MEMORY:     msg= "OUT_OF_MEMORY";     break;
        case GL_INVALID_FRAMEBUFFER_OPERATION:  msg= "INVALID_FRAMEBUFFER_OPERATION"; break;
        default:  msg= "Unknown"; break;
	}
        FL_ERROR( "GLErrorLayer:ERROR:%04x'%s' %s\n", error_code, msg, name );
        error_code= glGetError();
    }while( error_code != GL_NO_ERROR );
    FL_ABORT();
    return  false;
}

利用時は el:: をつけて下記のような感じです。

el::ActiveTexture( GL_TEXTURE );

これはエラー判定の一例ですが、楽をしたければそれなりの準備が必要で、
結構手間もかかります。

生成したヘッダは下記の場所に置いてみました。

 ・OpenGL ES 2.0 : GLErrorLayer_ES2.h
 ・OpenGL ES 3.0 : GLErrorLayer_ES3.h
 ・OpenGL 4.4 : GLErrorLayer_GL4.h

● Debug Output (OpenGL 4.3)

OpenGL のエラー出力にはいくつかの例外があります。
例えば Shader API では、Compiler や Linker のエラー内容をプログラマ向けの
テキスト情報で受け取ることができます。

同じように OpenGL 4.3 では、API のエラーメッセージを
わかりやすい形↓で受け取れるようになっています。

// GeForce GTX 650 (OpenGL 4.4 beta)
GL_INVALID_VALUE error generated. Element exceeds maximum supported number of combined texture image units.

GL_INVALID_ENUM error generated.  enum is invalid; expected GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_R, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_GENERATE_MIPMAP, GL_TEXTURE_COMPARE_MODE, etc. (19 others).
// RADEON HD 7750 (OpenGL 4.3 beta)
glActiveTexture has generated an error (GL_INVALID_ENUM)

glTexParameteri parameter  has an invalid enum '0x285a' (GL_INVALID_ENUM)

Debug Output はちょうど Direct3D で言えば Debug Layer に相当します。
Direct3D 10 以降は GPU が汎用化されてできることが増えたのですが、
それでも GPU は CPU と違ってリソースの扱いでもさまざまな制約があります。

ちょっとした細かい設定やフラグの組み合わせなど、気付きにくいようなミスも
D3D Debug Layer は (少々うるさいくらいに) 報告してくれました。
デバッグだけでなく機能の理解にも役に立ちました。

OpenGL の場合はいくつかの準備が必要です。

1. Context 作成時に WGL_CONTEXT_FLAGS_ARB に WGL_CONTEXT_DEBUG_BIT_ARB を設定
2. glDebugMessageCallbackARB() で callback 関数を登録する

RADEON の場合下記のように GL_VERSION 文字列も変化します。

GL_VERSION: 4.3.12438 Core Profile/Debug Context 13.200.0.0
GL_RENDERER: AMD Radeon HD 7700 Series
GL_VENDOR: ATI Technologies Inc.
GL_SHADING_LANGUAGE_VERSION: 4.30
static int  att_command[]= {
    WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
    WGL_CONTEXT_MINOR_VERSION_ARB, 3,
    WGL_CONTEXT_FLAGS_ARB,         WGL_CONTEXT_DEBUG_BIT_ARB,
    WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
    0
};
hrc= wglCreateContextAttribsARB( hDC, 0, att_command );

wglMakeCurrent() で OpenGL API が使えるようになったら Callback 関数を
登録します。

glDebugMessageCallback( DebugCallback, reinterpret_cast( this ) );
glEnable( GL_DEBUG_OUTPUT );

Debug Context はデフォルトで glEnable( GL_DEBUG_OUTPUT ) になるので
2行目は無くても構いません。

OpenGL の命令でエラーが発生すると Callback 関数が呼ばれます。

void GWModule::DebugCallbackFunc( GLenum source, GLenum type, GLuint eid, GLenum severity, GLsizei length, const GLchar* message, const void* user_param )
{
    ~
    FL_LOG( "%s\n", message );
}

手間もかからずにエラー内容も発生場所もわかるので、非常に便利になりました。

● OpenGL 4.2

GL_ARB_debug_output があれば同様のエラー情報を利用することができます。
関数名の最後に ARB が付くだけです。
RADEON HD 6570M OpenGL 4.2 で動作を確認できました。

● OpenGL ES 3.0

Mali-T604 では、Android 4.3 の OpenGL ES 3.0 利用時に同様の機能
GL_KHR_debug があります。

いろいろ試したのですが、eglGetProcAddress() でも
glDebugMessageCallbackKHR() などの関数を取ることができませんでした。
この辺りはもう少し調べたいと思っています。

関連エントリ
OpenGL のはじめ方