2013/08/30
Adreno 320 の OpenGL ES 3.0 と Uniform Block
前回 Nexus 7 の Adreno 320 で OpenGL ES 3.0 のシェーダーが正しく動作しないと
書いたのですが、条件が絞れてきました。
UniformBlock の変数を直接演算に用いると Shader Compiler の
glLinkProgram() で Internal Error が発生するか直後にクラッシュします。
下記のように、Uniform Block 内の変数を一旦ローカル変数にコピーすると
問題なく動くようになります。
Fragment Shader も同じです。
OpenGL ES 2.0 では Uniform Block が無いので問題ありません。
OpenGL ES 3.0 でも Uniform Block を使わない ( default Uniform Block だけ使う)
場合も問題ありませんでした。
Adreno 320 以外ではどちらのコードも動きます。
Mali-T604 実機, Adreno SDK の OpenGL ES 3.0 Emulator,
Mali Developer Tools の OpenGL ES 3.0 Emulator いずれも問題ありませんでした。
症状を確認した本体
・Nexus 7 (2013)
・Android 4.3 JSS15Q
購入直後一度システムソフトウエアの更新があり、GL_VERSION の内容が
置き換わっています。上記は更新後です。
更新後も同じ症状が出ています。
回避方法がわかったので、とりあえず Adreno 320 でも OpenGL ES 3.0 用
シェーダーの動作確認ができました。
他に Intel HD 4000 (9.18.10.3165) でも Uniform Block で問題が出ています。
Uniform Block 内で mat3x4 の配列を宣言すると、index の計算を間違えるようです。
(layout std140 指定済み)
すべて vec4 宣言し、自分で matrix に組み立てることで回避できます。
関連エントリ
・Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)
書いたのですが、条件が絞れてきました。
UniformBlock の変数を直接演算に用いると Shader Compiler の
glLinkProgram() で Internal Error が発生するか直後にクラッシュします。
#version 300 es // GLSL: VertexShader precision highp float; uniform SceneBlock { mat4 PView; mat4 ViewMatrix; }; in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec3 onormal; out vec2 otexcoord; void main() { vec4 opos= vec4( POSITION.xyz, 1.0 ) * PView; // ← ここ opos.z= 2.0 * opos.z - opos.w; gl_Position= opos; onormal= vec3( 0.0, 0.0, -1.0 ); otexcoord= TEXCOORD.xy; }
下記のように、Uniform Block 内の変数を一旦ローカル変数にコピーすると
問題なく動くようになります。
void main() { mat4 pv= PView; // Uniform Block の変数をローカルにコピー vec4 opos= vec4( POSITION.xyz,1.0 ) * pv; opos.z= 2.0 * opos.z - opos.w; gl_Position= opos; onormal= vec3( 0.0, 0.0, -1.0 ); otexcoord= TEXCOORD.xy; }
Fragment Shader も同じです。
OpenGL ES 2.0 では Uniform Block が無いので問題ありません。
OpenGL ES 3.0 でも Uniform Block を使わない ( default Uniform Block だけ使う)
場合も問題ありませんでした。
Adreno 320 以外ではどちらのコードも動きます。
Mali-T604 実機, Adreno SDK の OpenGL ES 3.0 Emulator,
Mali Developer Tools の OpenGL ES 3.0 Emulator いずれも問題ありませんでした。
症状を確認した本体
・Nexus 7 (2013)
・Android 4.3 JSS15Q
GL_VERSION: OpenGL ES 3.0 V@14.0 AU@ (CL@) GL_RENDERER: Adreno (TM) 320 GL_VENDOR: Qualcomm GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 3.00
購入直後一度システムソフトウエアの更新があり、GL_VERSION の内容が
置き換わっています。上記は更新後です。
更新後も同じ症状が出ています。
回避方法がわかったので、とりあえず Adreno 320 でも OpenGL ES 3.0 用
シェーダーの動作確認ができました。
他に Intel HD 4000 (9.18.10.3165) でも Uniform Block で問題が出ています。
Uniform Block 内で mat3x4 の配列を宣言すると、index の計算を間違えるようです。
(layout std140 指定済み)
すべて vec4 宣言し、自分で matrix に組み立てることで回避できます。
関連エントリ
・Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)
Nexus 7 (2013) が発売されたので、
Adreno 320 上でも OpenGL ES 3.0 のテストができるようになりました。
現時点で OpenGL ES 3.0 を走らせるには、
Android 4.3 と OpenGL ES 3.0 対応 GPU が必要です。
現在 OpenGL ES 3.0 + Android 4.3 に対応しているのは上記 2機種です。
(おそらく明後日 30日発売の Nexus 4 も可能だと思われます)
新しい Nexus 7 を下記ページに追加しました。
・CPU/GPU OpenGL ES Extension (Mobile GPU)
Qualcomm Snapdragon APQ8064 自体は、昨年末から採用端末が多数
登場しているので、今となってはあまり目新しいものではないかもしれません。
ですが、AQP8064 は CPU core, GPU core の両方が世代交代した
アーキテクチャとなっており、開発用途では非常に興味深い内容となっています。
当初から ETC2 など OpenGL ES 3.0 の仕様が部分的に含まれていましたし、
3DMark でもかなり優秀な成績を収めています。
でもこれはまだ DX10 対応 GPU で DX9 のデモを走らせているようなもの。
Android 4.3 + OpenGL ES 3.0 でようやく GPU の世代に一致したシェーダーを
走らせることができるようになります。
以下 Nexus 7 (2013) からの抜粋L
下記は Nexus 10 の Mali-T604 との比較
Mali では Fragment で使える Uniform 数の方が Vertex よりも多いですが
Adreno では少なくなっているようです。
少々特殊な 896 / 224 という数値は OpenGL ES 3.0 の仕様を満たしています。
むしろ Adreno に合わせた数値(仕様)だと思われます。
実際にプログラムを走らせてみたのですが、残念ながら
Adreno 320 の OpenGL ES 3.0 ではまだ複雑なシェーダーが正しく動いていません。
簡単なシェーダーと、OpenGL ES 2.0 互換モードでは動作を確認できました。
Shader の Link 時に Internal error が発生しているので、
原因を調べているところです。
関連エントリ
・Android 4.3 と OpenGL ES 3.0
・Nexus 10 CPU Cortex-A15 の速度
・Nexus 10 GPU Mali-T604
・3DMark Android 版の結果から
Adreno 320 上でも OpenGL ES 3.0 のテストができるようになりました。
現時点で OpenGL ES 3.0 を走らせるには、
Android 4.3 と OpenGL ES 3.0 対応 GPU が必要です。
SoC CPU GPU --------------------------------------------------------------- Nexus 10 Exynos5 Dual Cortex-A15 x2 Mali-T604 Nexus 7 (2013) Snapdragon APQ8064 Krait x4 Adreno 320
現在 OpenGL ES 3.0 + Android 4.3 に対応しているのは上記 2機種です。
(おそらく明後日 30日発売の Nexus 4 も可能だと思われます)
新しい Nexus 7 を下記ページに追加しました。
・CPU/GPU OpenGL ES Extension (Mobile GPU)
Qualcomm Snapdragon APQ8064 自体は、昨年末から採用端末が多数
登場しているので、今となってはあまり目新しいものではないかもしれません。
ですが、AQP8064 は CPU core, GPU core の両方が世代交代した
アーキテクチャとなっており、開発用途では非常に興味深い内容となっています。
当初から ETC2 など OpenGL ES 3.0 の仕様が部分的に含まれていましたし、
3DMark でもかなり優秀な成績を収めています。
でもこれはまだ DX10 対応 GPU で DX9 のデモを走らせているようなもの。
Android 4.3 + OpenGL ES 3.0 でようやく GPU の世代に一致したシェーダーを
走らせることができるようになります。
以下 Nexus 7 (2013) からの抜粋L
GL_VERSION: OpenGL ES 3.0 V@14.0 AU@ (CL@) GL_RENDERER: Adreno (TM) 320 GL_VENDOR: Qualcomm GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 3.00 Extension: GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor GL_AMD_program_binary_Z400 GL_EXT_debug_label GL_EXT_debug_marker GL_EXT_robustness GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24 GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8 GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2 GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float GL_EXT_color_buffer_half_float
下記は Nexus 10 の Mali-T604 との比較
Nexus 7 (2013) Nexus 10 Adreno 320 Mali-T604 ----------------------------------------------------------------- === GL3:texture GL_MAX_3D_TEXTURE_SIZE 1024 4096 GL_MAX_TEXTURE_SIZE 4096 4096 GL_MAX_ARRAY_TEXTURE_LAYERS 256 4096 GL_MAX_TEXTURE_LOD_BIAS 15.984375 255.996094 GL_MAX_CUBE_MAP_TEXTURE_SIZE 4096 4096 GL_MAX_RENDERBUFFER_SIZE 4096 4096 GL_MAX_DRAW_BUFFERS 4 4 GL_MAX_COLOR_ATTACHMENTS 4 4 GL_MAX_VIEWPORT_DIMS 4096 4096 === GL3:elements GL_MAX_ELEMENTS_INDICES -1 16777216 GL_MAX_ELEMENTS_VERTICES -1 16777216 === GL3:vertex GL_MAX_VERTEX_ATTRIBS 16 16 GL_MAX_VERTEX_OUTPUT_COMPONENTS 69 64 === GL3:pixel GL_MAX_FRAGMENT_INPUT_COMPONENTS 71 60 === GL3:program GL_MIN_PROGRAM_TEXEL_OFFSET -8 -8 GL_MAX_PROGRAM_TEXEL_OFFSET 7 7 GL_MAX_VARYING_COMPONENTS 64 60 GL_MAX_VARYING_VECTORS 16 15 === GL3:output stream GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 64 64 GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 4 4 GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 4 4 GL_MAX_SAMPLES 4 4 === GL3:uniform block GL_MAX_VERTEX_UNIFORM_COMPONENTS 1024 1024 GL_MAX_VERTEX_UNIFORM_VECTORS 256 256 GL_MAX_VERTEX_UNIFORM_BLOCKS 12 12 GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS -- 50176 GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 896 4096 GL_MAX_FRAGMENT_UNIFORM_VECTORS 224 1024 GL_MAX_FRAGMENT_UNIFORM_BLOCKS 12 GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 197504 53248 GL_MAX_UNIFORM_BUFFER_BINDINGS 24 36 GL_MAX_UNIFORM_BLOCK_SIZE 65536 16384 GL_MAX_COMBINED_UNIFORM_BLOCKS 24 24 === GL3:tex GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 16 16 GL_MAX_TEXTURE_IMAGE_UNITS 16 16 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 32 32
Mali では Fragment で使える Uniform 数の方が Vertex よりも多いですが
Adreno では少なくなっているようです。
少々特殊な 896 / 224 という数値は OpenGL ES 3.0 の仕様を満たしています。
むしろ Adreno に合わせた数値(仕様)だと思われます。
実際にプログラムを走らせてみたのですが、残念ながら
Adreno 320 の OpenGL ES 3.0 ではまだ複雑なシェーダーが正しく動いていません。
簡単なシェーダーと、OpenGL ES 2.0 互換モードでは動作を確認できました。
Shader の Link 時に Internal error が発生しているので、
原因を調べているところです。
関連エントリ
・Android 4.3 と OpenGL ES 3.0
・Nexus 10 CPU Cortex-A15 の速度
・Nexus 10 GPU Mali-T604
・3DMark Android 版の結果から
2013/08/18
OpenGL 3.x/4.x のシェーダー GLSL とメモリアクセス命令
OpenGL のシェーダーはバージョンごとに機能が拡張されています。
バッファやメモリ周りの命令は似たようなものが複数存在しており、
複雑なのでまとめてみました。
● texelFetch()
OpenGL 3.0 以降対応。OpenGL ES 3.0 対応。
指定は通常の glBindTexture() と sampler 宣言ですが、
読み取り命令が違います。
uv ではなく整数座標で直接画素値を読み取ります。
圧縮テクスチャを bind した場合は展開後の画素を読み取ります。
Texture と同じ bind point を使います。
Direct3D HLSL で Sampler を指定しない Load 系命令に相当します。
● Uniform Block
OpenGL 3.1 以降対応。OpenGL ES 3.0 でも利用できます。
GLSL の Uniform 変数を buffer として割り当てます。
詳しくは下記でまとめています。
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
Uniform Block は専用の bind point が割り当てられています。
● Texture Buffer
OpenGL 3.1 以降対応です。
OpenGL ES 3.0 では利用できません。
Buffer を Texture として bind できるようにします。
例えば Transform Feedback (Stream Output) の結果をシェーダーから参照する
場合などに利用できます。
Vertex Shader にはそのまま Uniform Block を割り当てます。
Fragment Shader には Uniform Block を Texture Buffer として渡してみます。
全く同じバッファの値を、Vertex Shader は Uniform としてアクセスし、
Fragment Shader は texelFetch() を使って読み取っています。
Texture と同じ bind point です。
● Atomic Counter Buffer
OpenGL 4.2 以降で使えます。
uniform 変数のように宣言しますが特殊な型です。バッファを割り当てて使います。
この型にアクセスできるのは組み込みの関数だけです。
下記でページでも解説しています。
・OpenGL 4.2/4.3 Shader Resource と Buffer API
利用可能なカウンタの個数はハードウエアで決められています。
他の書き込み可能なリソースにも atomic なオペレーション命令が複数存在しますが、
Counter は increment/decrement に特化した機能となっているようです。
専用の bind point です。
● Texture Image Load and Store
OpenGL 4.2 以降での対応です。
読み書き可能なテクスチャ命令です。
GLSL の sampler* の代わりに image* 宣言を用い、
専用の imageLoad() / imageStore() 命令で読み書きします。
Direct3D の UAV に相当します。
Texture Object の生成や初期化は通常のテクスチャと全く同じです。
Bind のみ専用の glBindImageTexture() 命令を使います。
Bind Point は新規に用意されており、通常の画像テクスチャとは別の
番号付けが行われます。
Image Load/Store が利用できる Unit の数は GL_MAX_IMAGE_UNITS です。
これまでの Texture で使われていた Texture Image Unit
(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) とよく似ていますが別のものです。
画素フォーマットを指定するので、メモリ配置と一致しない場合は正しく読み取る
ことが出来ないようです。
texelFetch() では圧縮テクスチャの展開が可能でしたが、
image 系命令ではそのままでは出来ませんでした。
メモリイメージがそのまま読み込まれているのかもしれません。
RADEON では bind error となります。
● Shader Storage Buffer
OpenGL 4.3 以降に対応します。
読み書き可能な点では Texture Image Load and Store によく似ていますが、
Uniform Block のように変数としてアクセスすることができます。
Compute Shader を使って、Image (Texture Image Load and Store) から
Shader Storage Buffer に画像をコピーしてるだけです。
Fragment Shader では Shader Storage Buffer の内容をテクスチャとして描画します。
少々手抜きで、Shader Storage Buffer は 4倍のメモリを確保していることになります。
RADEON の場合 Image 命令と Shader Storage Buffer の共存がうまくいっておりません。
過去記事 でも書いたように bind point は独自のものになっています。
Texture Image Load and Store と機能が似ていますが上記のように違いが見られます。
RADEON の場合 Image Unit は fsh/csh だけでした。
Shader Storage はどのシェーダーでも有効になっています。
● Shared Memory
OpenGL 4.3 以降で利用できます。
Shared Memory は Compute Shader だけが利用できるローカルメモリです。
GLSL 内で shared 宣言します。
種類も多いので、機能の違いや使い方を理解するまでは大変かもしれません。
実際に使ってみると思ったよりも扱いやすい印象でした。
特に OpenGL 4.3 以降は bind 値を全部 Shader 側で指定できるので
手間がかなり減っています。
関連エントリ
・OpenGL 4.x Program Pipeline Object (Separate Shader Object)
・OpenGL 4.2/4.3 Shader Resource と Buffer API
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
バッファやメモリ周りの命令は似たようなものが複数存在しており、
複雑なのでまとめてみました。
● texelFetch()
OpenGL 3.0 以降対応。OpenGL ES 3.0 対応。
指定は通常の glBindTexture() と sampler 宣言ですが、
読み取り命令が違います。
// OpenGL glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, Texture );
#version 430 // GLSL: fsh layout(binding=0) uniform sampler2D ColorMap; in vec2 otexcoord; out vec4 out_FragColor; void main() { ivec2 iuv= ivec2( otexcoord.x * 255, otexcoord.y * 255 ); vec4 color= texelFetch( ColorMap, iuv, 0 ); out_FragColor= color; }
uv ではなく整数座標で直接画素値を読み取ります。
圧縮テクスチャを bind した場合は展開後の画素を読み取ります。
Texture と同じ bind point を使います。
Direct3D HLSL で Sampler を指定しない Load 系命令に相当します。
● Uniform Block
OpenGL 3.1 以降対応。OpenGL ES 3.0 でも利用できます。
GLSL の Uniform 変数を buffer として割り当てます。
詳しくは下記でまとめています。
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
// OpenGL glBindBufferBase( GL_UNIFORM_BUFFER, 0, Uniform ); glUniformBlockBinding( program, 0, 0 );
#version 430 // GLSL: vsh layout(std140,column_major) uniform MatData { mat4x4 PVW; }; in vec3 POSITION; void main() { vec4 opos= vec4( POSITION.xyz, 1.0f ) * PVW; opos.z= 2.0f * opos.z - opos.w; gl_Position= opos; }
Uniform Block は専用の bind point が割り当てられています。
● Texture Buffer
OpenGL 3.1 以降対応です。
OpenGL ES 3.0 では利用できません。
Buffer を Texture として bind できるようにします。
例えば Transform Feedback (Stream Output) の結果をシェーダーから参照する
場合などに利用できます。
// OpenGL init struct MatData { math::Matrix4 PView; math::Matrix4 World; math::Vect4 UVOffset; }; // Uniform Buffer を作る GLuint MatData_Uniform= 0; glGenBuffers( 1, &MatData_Uniform ); glBindBuffer( GL_UNIFORM_BUFFER, sizeof(MatData), NULL, GL_DYNAMIC_DRAW ); // Texture Buffer を作る GLuint MatData_Texture= 0; glGenTextures( 1, &MatData_Texture ); glBindTexture( GL_TEXTURE_BUFFER, MatData_Texture ); glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, MatData_Uniform ); glUniformBlockBinding( program, 0, 0 );
// OpenGL render // uniform の更新 MatData data; data.PView.MulCopy( GetProjectionMatrix(), GetViewMatrix() ); data.Wrold.SetIdentity(); data.Wrold.RotationY( rotate_angle ); data.UVOffset.Set( 0.5f, 0.7f, 0.0f, 0.0f ); glBindBuffer( GL_UNIFORM_BUFFER, MatData_Uniform ); glBufferSubData( GL_UNIFORM_BUFFER, 0, sizeof(MatData), &data ); glUserProgram( program ); glBindVertexArray( input_layout ); // Uniform Buffer glBindBufferBase( GL_UNIFORM_BUFFER, 0, MatData_Uniform ); // Texture glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, Texture1 ); // Texture Buffer glActiveTexture( GL_TEXTURE1 ); glBindTexture( GL_TEXTURE_BUFFER, MatData_Texture ); glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
Vertex Shader にはそのまま Uniform Block を割り当てます。
#version 430 // GLSL: vsh layout(std140,column_major,binding=0) uniform MatData { mat4x4 PView; mat4x4 World; vec4 UVOffset; }; in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec3 onormal; out vec2 otexcoord; void main() { vec4 opos= vec4( POSITION.xyz, 1.0f ) * (World * PView); opos.z= 2.0f * opos.z - opos.w; gl_Position= opos; onormal= NORMAL.xyz * mat3x3( World ); otexcoord= TEXCOORD.xy + UVOffset.xy; }
Fragment Shader には Uniform Block を Texture Buffer として渡してみます。
#version 430 // GLSL: fsh in vec3 onormal; in vec2 otexcoord; out vec4 out_FragColor; layout(binding=0) uniform sampler2D ColorMap; layout(binding=1) uniform samplerBuffer BufferMap; void main() { float diff= clamp( dot( vec3( 0.0f, 0.0f, -1.0f ), normalize( onormal.xyz ) ), 0.0f, 1.0f ); vec4 uvoffset= texelFetch( BufferMap, 8 ); vec2 uv= otexcoord.xy - uvoffset.xy; out_FragColor.xyz= texture( ColorMap, uv ).xyz * diff; out_FragColor.w= 1.0f; }
全く同じバッファの値を、Vertex Shader は Uniform としてアクセスし、
Fragment Shader は texelFetch() を使って読み取っています。
Texture と同じ bind point です。
● Atomic Counter Buffer
OpenGL 4.2 以降で使えます。
uniform 変数のように宣言しますが特殊な型です。バッファを割り当てて使います。
この型にアクセスできるのは組み込みの関数だけです。
下記でページでも解説しています。
・OpenGL 4.2/4.3 Shader Resource と Buffer API
// OpenGL init glBindBuffer( GL_ATOMIC_COUNTER_BUFFER, CounterBlock ); glBufferData( GL_ATOMIC_COUNTER_BUFFER, sizeof(CounterData), NULL, GL_DYNAMIC_DRAW );
// OpenGL render CounterData data; memset( &data, 0, sizeof(CounterData) ); glBindBuffer( GL_ATOMIC_COUNTER_BUFFER, CounterBlock ); glBufferSubData( GL_ATOMIC_COUNTER_BUFFER, 0, sozeof(CounterData), &data ); ~ glBindBufferBase( GL_ATOMIC_COUNTER_BUFFER, 0, CounterBlock ); ~
// GLSL: psh layout(binding=0, offset=0) uniform atomic_uint Counter; void main() { uint c= atomicCounter( Counter ); ~ atomicCounterIncrement( Counter ); }
利用可能なカウンタの個数はハードウエアで決められています。
他の書き込み可能なリソースにも atomic なオペレーション命令が複数存在しますが、
Counter は increment/decrement に特化した機能となっているようです。
専用の bind point です。
● Texture Image Load and Store
OpenGL 4.2 以降での対応です。
読み書き可能なテクスチャ命令です。
GLSL の sampler* の代わりに image* 宣言を用い、
専用の imageLoad() / imageStore() 命令で読み書きします。
Direct3D の UAV に相当します。
#version 430 // GLSL: psh in vec2 otexcoord; out vec4 out_FragColor; layout(binding=3,rgba8) uniform readonly image2D ColorMap2; void main() { ivec2 iuv= ivec2( otexcoord.x * 255, otexcoord.y * 255 ); out_FragColor= imageLoad( ColorMap2, ivec2( xp, yp ) ); }
// OpenGL render glUseProgram( program1 ); glBindVertexArray( input_layout ); ~ glBindImageTexture( 3, texture1, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8 ); glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
Texture Object の生成や初期化は通常のテクスチャと全く同じです。
Bind のみ専用の glBindImageTexture() 命令を使います。
Bind Point は新規に用意されており、通常の画像テクスチャとは別の
番号付けが行われます。
Image Load/Store が利用できる Unit の数は GL_MAX_IMAGE_UNITS です。
これまでの Texture で使われていた Texture Image Unit
(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) とよく似ていますが別のものです。
GL_MAX_*_IMAGE_UNIFORMS vsh fsh tcsh tesh gsh csh combined unit --------------------------------------------------------------------- GeForce GTX 650 4.4 8 8 8 8 8 8 48 8 RADEON HD7750(GCN) 4.3 0 32 0 0 0 32 32 32 RADEON HD6750M(VLIW) 4.3 0 8 0 0 0 8 8 8
画素フォーマットを指定するので、メモリ配置と一致しない場合は正しく読み取る
ことが出来ないようです。
texelFetch() では圧縮テクスチャの展開が可能でしたが、
image 系命令ではそのままでは出来ませんでした。
メモリイメージがそのまま読み込まれているのかもしれません。
RADEON では bind error となります。
● Shader Storage Buffer
OpenGL 4.3 以降に対応します。
読み書き可能な点では Texture Image Load and Store によく似ていますが、
Uniform Block のように変数としてアクセスすることができます。
#version 430 // GLSL: csh layout(std140,binding=0) buffer CopyBlock { writeonly uint Color[]; }; layout(binding=3,rgba8) uniform readonly image2D ColorMap2; layout(local_size_x=1,local_size_y=1) in; void main() { uvec2 pos= gl_GlobalInvocationID.xy; vec4 param= imageLoad( ColorMap2, ivec2( pos.x, pos.y ) ); uint index= gl_GlobalInvocationID.y * 256 + gl_GlobalInvocationID.x; param.xyzw= param.yxzw; Color[index]= packUnorm4x8( param ); }
#version 430 // GLSL: psh in vec3 onormal; in vec2 otexcoord; out vec4 out_FragColor; layout(std140,binding=0) buffer CopyBlock { readonly uint Color[]; }; void main() { float diff= clamp( dot( vec3( 0.0f, 0.0f, -1.0f ), normalize( onormal.xyz ) ), 0.0f, 1.0f ); vec2 uv= otexcoord.xy; uint xp= uint( uv.x * 255.0f ); uint yp= uint( uv.y * 255.0f ); uint index= yp * 256 + xp; vec4 color2= unpackUnorm4x8( Color[index] ); out_FragColor.xyz= color2.xyz * diff; out_FragColor.w= 1.0f; }
// OpenGL init struct Packed8888 { unsigned char color[4]; }; glGenBuffers( 1, &Color_SSB ); glBindBuffer( GL_SHADER_STORAGE_BUFFER, Color_SSB ); glBufferData( GL_SHADER_STORAGE_BUFFER, sizeof(Packed8888) * 256 * 256 * 4, NULL, GL_DYNAMIC_DRAW );
// OpenGL render // Compute Shader glUseProgram( csh_program ); glBindImageTexture( 3, texture1, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8 ); glBindBufferBase( GL_SHADER_STORAGE_BUFFER, 0, Color_SSB ); glDispatchCompute( 256, 256, 1 ); // Render glUseProgram( program1 ); glBindVertexArray( input_layout ); glBindBufferBase( GL_UNIFORM_BUFFER, 2, MatData_Uniform ); glBindBufferBase( GL_SHADER_STORAGE_BUFFER, 0, Color_SSB ); glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
Compute Shader を使って、Image (Texture Image Load and Store) から
Shader Storage Buffer に画像をコピーしてるだけです。
Fragment Shader では Shader Storage Buffer の内容をテクスチャとして描画します。
少々手抜きで、Shader Storage Buffer は 4倍のメモリを確保していることになります。
RADEON の場合 Image 命令と Shader Storage Buffer の共存がうまくいっておりません。
過去記事 でも書いたように bind point は独自のものになっています。
GL_MAX_*_SHADER_STORAGE_BLOCKS vsh fsh tcsh tesh gsh csh combined size ---------------------------------------------------------------------- GeForce GTX 650 4.4 16 16 16 16 16 16 96 2GB RADEON HD7750(GCN) 4.3 16 16 16 16 16 16 16 16MB RADEON HD6750M(VLIW) 4.3 8 8 8 8 8 8 8 16MB
Texture Image Load and Store と機能が似ていますが上記のように違いが見られます。
RADEON の場合 Image Unit は fsh/csh だけでした。
Shader Storage はどのシェーダーでも有効になっています。
● Shared Memory
OpenGL 4.3 以降で利用できます。
Shared Memory は Compute Shader だけが利用できるローカルメモリです。
GLSL 内で shared 宣言します。
#version 430 // GLSL: csh ~ shared uint flags[1024];
GL_COMPUTE_SHARED_MEMORY_SIZE csh --------------------------------------- GeForce GTX 650 4.4 49152 byte RADEON HD7750(GCN) 4.3 32768 byte RADEON HD6750M(VLIW) 4.3 32768 byte
種類も多いので、機能の違いや使い方を理解するまでは大変かもしれません。
実際に使ってみると思ったよりも扱いやすい印象でした。
特に OpenGL 4.3 以降は bind 値を全部 Shader 側で指定できるので
手間がかなり減っています。
関連エントリ
・OpenGL 4.x Program Pipeline Object (Separate Shader Object)
・OpenGL 4.2/4.3 Shader Resource と Buffer API
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
OpenGL のシェーダーは Direct3D と違い、API レベルでシンボル管理が行われます。
Direct3D の Native な API では、Vertex Shader, Pixel Shader,
Geometry Shader などの、各シェーダー毎に Object が分かれています。
ステートやバインド情報もそれぞれ別で、レンダリングのタイミングで
必要な情報を Direct3D の DeviceContext に割り当てます。
Constant (Unifrom) の管理も各シェーダー毎に別空間です。
例えば Camera 座標など、Vertex Shader, Pixel Shader 両方が
アクセセスするパラメータがあった場合は
PSSetConstantBuffers(), VSSetConstantBuffers()
両方に設定しなければなりません。
これはプログラマの仕事です。
OpenGL の場合 Vertex Shader, Fragment Shader, Geometry Shader など、
描画に必要なシェーダーを集めて最初に Program Object を作っておきます。
同時に Uniform や Block 等のシンボルの共有化が行われており、
どのシェーダーから参照されているのか情報が保持されます。
各シンボルの参照状態は OpenGL 4.3 以降は
glGetProgramResourceiv( GL_REFERENCED_BY_*_SHADER )
を使って調べることが出来ます。
プログラマはどのシェーダーで使われているのか意識することなく
単一のシンボルに対して設定すればよいだけです。
もちろん Vertex Shader, Fragment Shader 両方から参照されているなら
両方とも同じ値に更新されます。
OpenGL 4.1 から利用可能となった Program Pipeline Object は、
このような OpenGL Program Object の一括管理とは正反対のものです。
OpenGL 上で Direct3D のような、より低レベルだけど自由度の高い
シェーダー管理を可能にします。
GPU 周りの進化は非常に速いので、単一の仕組みだけでは様々な
要求に応えることが難しくなってきているのかもしれません。
●Shader の組み合わせ
レンダリングに使われるシェーダーのバリエーションはかなり膨大な量になります。
手で管理することは不可能なので、描画用のエンジン開発では
最初にシェーダー管理の仕組みを構築することになります。
ある程度の組み合わせに融通は利くので、用途に応じて
Pixel Shader (Fragment Shader) だけ変えたり、
部分的に使いまわしたりすることがありました。
OpenGL の場合 Shader Object レベルでは組み合わせを作ることが出来ます。
実際の利用時は Program Object が必要となるので、組み合わせの数だけ
Program Object が出来上がります。
Program Pipeline Object は、既存の OpenGL API の仕組みを利用したまま
動的に Shader を入れ替えられるようにしています。
1. Shader 単独の Program Object を作る
2. Pipeline Object にそれぞれ登録する
3. 描画時に glUseProgram() の代わりに glBindProgramPipeline() を使う
Pipeline Object を利用する場合は下記の通り。
↑一見複雑ですが、Program Object を作るための便利な命令↓が増えています。
↑簡単になりました。
●情報アクセス
Pipeline Object は複数の Program Object を取りまとめる器に過ぎず、
いつでも Program Object の追加と削除ができます。
Link のように大掛かりなデータ共有は行わず、入出力など最低限の
マッチングだけ行います。
入出力のマッチングはシンボル名または Location を元に行われるようです。
Program Object が持っていた情報もシェーダー個別に扱うことになります。
例えば Attribute の Location、Default Uniform Block などがそうです。
もともと Program Object のアクセス API は OpenGL の他の API と構造が
異なっており、多くのケースで Bind を必要としていませんでした。
引数として Program Object を取るものが多いのですが、唯一
glUniform() 系の API だけが glUseProgram() に依存しています。
方法は 3通り
1. Default Uniform Block を使わない
2. 直接 Program Object を引数にとる新しい API を使う
3. glActiveShaderProgram() を使う
glActiveShaderProgram() を使うと従来の Uniform 命令を使うことができます。
●Bind Point
Pipeline Object は今までの OpenGL API にできるだけ影響を与えないように
作られています。
Bind Point は新たに追加されており、他のステートにも干渉しません。
例えば glUseProgram() が有効な場合、glUseProgram() が優先され
glBindProgramPipeline() は無視します。
Pipeline Object を有効にするにはこれまでの例で示したように
glUseProgram( 0 ) が必要です。
●実際のシェーダーと最適化の違い
Pipeline Object を使う場合、gl_Position も自分で定義しておきます。
UVOffset は Vertex と Fragment で同名です。
Pipeline Object を使った場合は異なる値に設定することが可能です。
上のコードのシンボル情報を調べてみます。
前回の API を使って情報を取り出したのが下記の結果です。
GeForce もほぼ同じでした。
次に Fragment Shader の texture 命令を削除してみます。
↓VertexShader の方は変化していませんが、Fragment Shader の出力が
大きく減っていることがわかります。
texture()が無いので不要と判断され UVOffset も消えました。
↓RADEON はちょっとだけ違います。
↓同じシェーダーを Pipeline Object を使わずに作ってみます。
Separable (GL_PROGRAM_SEPARABLE) の値で区別できます。
texture() を削除したものが下記の通り。
↑Fragment Shader の最適化が Vertex Shader まで派生していることがわかります。
Fragment Vertex 双方から不要と判断され UVOffset が消えました。
GeForce に至っては Attribute の TEXCOORD まで無くなっています。
このように、今までどおり Link して単一の Program Object に変換する方が
最適化の面ではメリットがあることがわかります。
実パフォーマンスでどの程度影響が生じるかどうかは未確認です。
Direct3D で開発し、すでにシェーダー管理の仕組みができている場合は
互換性の意味でも便利な機能です。
自由な組み合わせが必要な場合とそうでない場合の使い分けが必要かもしれません。
2014/06/23訂正: glPipelineObjects を glProgramPipelines に修正しました。間違った記載をしており申し訳ありませんでした。
小川さんご指摘ありがとうございます。
関連エントリ
・OpenGL 4.2/4.3 Shader Resource と Buffer API
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
Direct3D の Native な API では、Vertex Shader, Pixel Shader,
Geometry Shader などの、各シェーダー毎に Object が分かれています。
ステートやバインド情報もそれぞれ別で、レンダリングのタイミングで
必要な情報を Direct3D の DeviceContext に割り当てます。
Constant (Unifrom) の管理も各シェーダー毎に別空間です。
例えば Camera 座標など、Vertex Shader, Pixel Shader 両方が
アクセセスするパラメータがあった場合は
PSSetConstantBuffers(), VSSetConstantBuffers()
両方に設定しなければなりません。
これはプログラマの仕事です。
OpenGL の場合 Vertex Shader, Fragment Shader, Geometry Shader など、
描画に必要なシェーダーを集めて最初に Program Object を作っておきます。
同時に Uniform や Block 等のシンボルの共有化が行われており、
どのシェーダーから参照されているのか情報が保持されます。
各シンボルの参照状態は OpenGL 4.3 以降は
glGetProgramResourceiv( GL_REFERENCED_BY_*_SHADER )
を使って調べることが出来ます。
プログラマはどのシェーダーで使われているのか意識することなく
単一のシンボルに対して設定すればよいだけです。
もちろん Vertex Shader, Fragment Shader 両方から参照されているなら
両方とも同じ値に更新されます。
OpenGL 4.1 から利用可能となった Program Pipeline Object は、
このような OpenGL Program Object の一括管理とは正反対のものです。
OpenGL 上で Direct3D のような、より低レベルだけど自由度の高い
シェーダー管理を可能にします。
GPU 周りの進化は非常に速いので、単一の仕組みだけでは様々な
要求に応えることが難しくなってきているのかもしれません。
●Shader の組み合わせ
レンダリングに使われるシェーダーのバリエーションはかなり膨大な量になります。
手で管理することは不可能なので、描画用のエンジン開発では
最初にシェーダー管理の仕組みを構築することになります。
ある程度の組み合わせに融通は利くので、用途に応じて
Pixel Shader (Fragment Shader) だけ変えたり、
部分的に使いまわしたりすることがありました。
OpenGL の場合 Shader Object レベルでは組み合わせを作ることが出来ます。
実際の利用時は Program Object が必要となるので、組み合わせの数だけ
Program Object が出来上がります。
Program Pipeline Object は、既存の OpenGL API の仕組みを利用したまま
動的に Shader を入れ替えられるようにしています。
1. Shader 単独の Program Object を作る
2. Pipeline Object にそれぞれ登録する
3. 描画時に glUseProgram() の代わりに glBindProgramPipeline() を使う
//● 通常のシェーダーの場合 // Vertex Shader GLuint vsh_obj= glCreateShader( GL_VERTEX_SHADER ); glShaderSource( vsh_obj, 1, &vsh_source_text ); glCompileShader( vsh_obj ); // Fragment Shader GLuint fsh_obj= glCreateShader( GL_FRAGMENT_SHADER ); glShaderSource( fsh_obj, 1, &fsh_source_text ); glCompileShader( fsh_obj ); // Program Object を作成 (Link) GLuint program_obj= glCreateProgram(); glAttachShader( program_obj, vsh_obj ); glAttachShader( program_obj, fsh_obj ); glLinkProgram( program_obj ); // 利用時 glUseProgram( program_obj );
Pipeline Object を利用する場合は下記の通り。
//● Pipeline Object を使う場合 // Vertex Shader GLuint vsh_obj= glCreateShader( GL_VERTEX_SHADER ); glShaderSource( vsh_obj, 1, &vsh_source_text ); glCompileShader( vsh_obj ); // 単独の Vertex Shader Program に変換 GLuint vsh_program= glCreateProgram(); glProgramParameteri( vsh_program, GL_PROGRAM_SEPARABLE, GL_TURE ); glAttachShader( vsh_program, vsh_obj ); glLinkProgram( vsh_program ); // Fragment Shader GLuint fsh_obj= glCreateShader( GL_FRAGMENT_SHADER ); glShaderSource( fsh_obj, 1, &fsh_source_text ); glCompileShader( fsh_obj ); // 単独の Fragment Shader Program に変換 GLuint fsh_program= glCreateProgram(); glProgramParameteri( fsh_program, GL_PROGRAM_SEPARABLE, GL_TURE ); glAttachShader( fsh_program, fsh_obj ); glLinkProgram( fsh_program ); // Pipeline Object GLuint pipeline= 0;glGenPipelineObjects( 1, &pipeline );glGenProgramPipelines( 1, &pipeline ); glUseProgramStages( pipeline, GL_VERTEX_SHADER_BIT, vsh_program ); glUseProgramStages( pipeline, GL_FRAGMENT_SHADER_BIT, fsh_program ); // 利用時 glUseProgram( 0 ); glBindProgramPipeline( pipeline );
↑一見複雑ですが、Program Object を作るための便利な命令↓が増えています。
//● Pipeline Object を使う場合 GLuint vsh_program= glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &vsh_source_text ); GLuint psh_program= glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &fsh_source_text ); // Pipeline Object GLuint pipeline= 0;glGenPipelineObjects( 1, &pipeline );glGenProgramPipelines( 1, &pipeline ); glUseProgramStages( pipeline, GL_VERTEX_SHADER_BIT, vsh_program ); glUseProgramStages( pipeline, GL_FRAGMENT_SHADER_BIT, fsh_program ); // 利用時 glUseProgram( 0 ); glBindProgramPipeline( pipeline );
↑簡単になりました。
●情報アクセス
Pipeline Object は複数の Program Object を取りまとめる器に過ぎず、
いつでも Program Object の追加と削除ができます。
Link のように大掛かりなデータ共有は行わず、入出力など最低限の
マッチングだけ行います。
入出力のマッチングはシンボル名または Location を元に行われるようです。
Program Object が持っていた情報もシェーダー個別に扱うことになります。
例えば Attribute の Location、Default Uniform Block などがそうです。
もともと Program Object のアクセス API は OpenGL の他の API と構造が
異なっており、多くのケースで Bind を必要としていませんでした。
引数として Program Object を取るものが多いのですが、唯一
glUniform() 系の API だけが glUseProgram() に依存しています。
方法は 3通り
1. Default Uniform Block を使わない
2. 直接 Program Object を引数にとる新しい API を使う
glUseProgram( vsh_program ); glUniform4fv( location, 1, vect ); ↓ glProgramUniform4fv( vsh_program, location, 1, vect );
3. glActiveShaderProgram() を使う
glActiveShaderProgram() を使うと従来の Uniform 命令を使うことができます。
glUseProgram( program ); glUniform4fv( location, 1, vect ); ↓ glUseProgram( 0 ); glBindProgramPipeline( pipeline ); glActiveShaderProgram( pipeline, vsh_program ); glUniform4fv( location, 1, vect );
●Bind Point
Pipeline Object は今までの OpenGL API にできるだけ影響を与えないように
作られています。
Bind Point は新たに追加されており、他のステートにも干渉しません。
例えば glUseProgram() が有効な場合、glUseProgram() が優先され
glBindProgramPipeline() は無視します。
Pipeline Object を有効にするにはこれまでの例で示したように
glUseProgram( 0 ) が必要です。
●実際のシェーダーと最適化の違い
Pipeline Object を使う場合、gl_Position も自分で定義しておきます。
#version 430 // Vertex Shader uniform mat4 PView; uniform mat4 World; uniform vec4 UVOffset; layout(location=0) in vec3 POSITION; layout(location=1) in vec3 NORMAL; layout(location=2) in vec2 TEXCOORD; out vec3 onormal; out vec2 otexcoord; out gl_PerVertex { vec4 gl_Position; }; void main() { mat4 pview= World * PView; vec4 opos= vec4( POSITION.xyz, 1.0 ) * pview; opos.z= 2.0 * opos.z - opos.w; gl_Position= opos; onormal= NORMAL.xyz * mat3x3( World ); otexcoord= TEXCOORD.xy + UVOffset.xy; }
#version 430 // Fragment Shader out vec4 out_FragColor; uniform vec4 UVOffset; in vec3 onormal; in vec2 otexcoord; layout(binding=0) uniform sampler2D ColorMap; void main() { float diff= clamp( dot( vec3( 0, 0, -1 ), normalize( onormal.xyz ) ), 0.0, 1.0 ); vec2 texcoord= otexcoord.xy - UVOffset.xy; vec4 color= texture( ColorMap, texcoord.xy ); out_FragColor.xyz= color.xyz * diff; out_FragColor.w= 1.0; }
UVOffset は Vertex と Fragment で同名です。
Pipeline Object を使った場合は異なる値に設定することが可能です。
上のコードのシンボル情報を調べてみます。
前回の API を使って情報を取り出したのが下記の結果です。
GeForce もほぼ同じでした。
// ●テクスチャあり Pipeline Object // RADEON 4.3 : Vertex Shader Prg: 0011 Del=0 Link=1 Vali=1 Separable=1 Resource [Uniform] 0: FLOAT_MAT4 Array=0 Loc=0 V_____ "PView" 1: FLOAT_VEC4 Array=0 Loc=1 V_____ "UVOffset" 2: FLOAT_MAT4 Array=0 Loc=2 V_____ "World" Resource [Input] 0: FLOAT_VEC3 Array=0 V_____ "NORMAL" 1: FLOAT_VEC3 Array=0 V_____ "POSITION" 2: FLOAT_VEC2 Array=0 V_____ "TEXCOORD" Resource [Output] 0: FLOAT_VEC4 Array=0 V_____ "gl_PerVertex.gl_Position" 1: FLOAT_VEC3 Array=0 V_____ "onormal" 2: FLOAT_VEC2 Array=0 V_____ "otexcoord" // RADEON 4.3 : Fragment Shader Prg: 0012 Del=0 Link=1 Vali=1 Separable=1 Resource [Uniform] 0: FLOAT_VEC4 Array=0 Loc=0 ____F_ "UVOffset" 1: SAMPLER_2D Array=0 Loc=1 ____F_ "ColorMap" Resource [Input] 0: FLOAT_VEC3 Array=0 ____F_ "onormal" 1: FLOAT_VEC2 Array=0 ____F_ "otexcoord" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
次に Fragment Shader の texture 命令を削除してみます。
#version 430 // Fragment Shader out vec4 out_FragColor; uniform vec4 UVOffset; in vec3 onormal; in vec2 otexcoord; layout(binding=0) uniform sampler2D ColorMap; void main() { float diff= clamp( dot( vec3( 0, 0, -1 ), normalize( onormal.xyz ) ), 0.0, 1.0 ); vec2 texcoord= otexcoord.xy - UVOffset.xy; //vec4 color= texture( ColorMap, texcoord.xy ); //** 削除 vec4 color= vec4( 1.0, 1.0, 0.2, 1.0 ); out_FragColor.xyz= color.xyz * diff; out_FragColor.w= 1.0; }
↓VertexShader の方は変化していませんが、Fragment Shader の出力が
大きく減っていることがわかります。
texture()が無いので不要と判断され UVOffset も消えました。
// ●テクスチャ無し Pipeline Object // GeForce 4.4 : Vertex Shader Prg: 0011 Del=0 Link=1 Vali=1 Separable=1 Resource [Uniform] 0: FLOAT_MAT4 Array=0 Loc=0 V_____ "PView" 1: FLOAT_VEC4 Array=0 Loc=1 V_____ "UVOffset" 2: FLOAT_MAT4 Array=0 Loc=2 V_____ "World" Resource [Input] 0: FLOAT_VEC3 Array=0 V_____ "NORMAL" 1: FLOAT_VEC3 Array=0 V_____ "POSITION" 2: FLOAT_VEC2 Array=0 V_____ "TEXCOORD" Resource [Output] 0: FLOAT_VEC3 Array=0 V_____ "onormal" 1: FLOAT_VEC2 Array=0 V_____ "otexcoord" 2: FLOAT_VEC4 Array=0 V_____ "gl_Position" // GeForce 4.4 : Fragment Shader Resource [Input] 0: FLOAT_VEC3 Array=0 ____F_ "onormal" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
↓RADEON はちょっとだけ違います。
// RADEON 4.3 : Fragment Shader Resource [Input] 0: FLOAT_VEC3 Array=0 ____F_ "onormal" 1: FLOAT_VEC2 Array=0 ____F_ "otexcoord" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
↓同じシェーダーを Pipeline Object を使わずに作ってみます。
Separable (GL_PROGRAM_SEPARABLE) の値で区別できます。
// ●テクスチャあり Program Object // GeForce 4.4 : Vertex + Fragment Shader Prg: 0014 Del=0 Link=1 Vali=1 Separable=0 [0] 0012 VERTEX_SHADER Del=1 Compile=1 [1] 0013 FRAGMENT_SHADER Del=1 Compile=1 Resource [Uniform] 0: SAMPLER_2D Array=0 Loc=0 ____F_ "ColorMap" 1: FLOAT_MAT4 Array=0 Loc=1 V_____ "PView" 2: FLOAT_VEC4 Array=0 Loc=2 V___F_ "UVOffset" 3: FLOAT_MAT4 Array=0 Loc=3 V_____ "World" Resource [Input] 0: FLOAT_VEC3 Array=0 V_____ "NORMAL" 1: FLOAT_VEC3 Array=0 V_____ "POSITION" 2: FLOAT_VEC2 Array=0 V_____ "TEXCOORD" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
texture() を削除したものが下記の通り。
// ●テクスチャ無し Program Object // GeForce 4.4 : Vertex + Fragment Shader Prg: 0014 Del=0 Link=1 Vali=1 Separable=0 [0] 0012 VERTEX_SHADER Del=1 Compile=1 [1] 0013 FRAGMENT_SHADER Del=1 Compile=1 Resource [Uniform] 0: FLOAT_MAT4 Array=0 Loc=0 V_____ "PView" 1: FLOAT_MAT4 Array=0 Loc=1 V_____ "World" Resource [Input] 0: FLOAT_VEC3 Array=0 V_____ "NORMAL" 1: FLOAT_VEC3 Array=0 V_____ "POSITION" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
↑Fragment Shader の最適化が Vertex Shader まで派生していることがわかります。
Fragment Vertex 双方から不要と判断され UVOffset が消えました。
GeForce に至っては Attribute の TEXCOORD まで無くなっています。
// RADEON 4.3 : Vertex + Fragment Shader Prg: 0014 Del=0 Link=1 Vali=1 Separable=0 [0] 0010 VERTEX_SHADER Del=1 Compile=1 [1] 0013 FRAGMENT_SHADER Del=1 Compile=1 Resource [Uniform] 0: FLOAT_MAT4 Array=0 Loc=0 V_____ "PView" 1: FLOAT_MAT4 Array=0 Loc=1 V_____ "World" Resource [Input] 0: FLOAT_VEC3 Array=0 V_____ "NORMAL" 1: FLOAT_VEC3 Array=0 V_____ "POSITION" 2: FLOAT_VEC2 Array=0 V_____ "TEXCOORD" Resource [Output] 0: FLOAT_VEC4 Array=0 ____F_ "out_FragColor"
このように、今までどおり Link して単一の Program Object に変換する方が
最適化の面ではメリットがあることがわかります。
実パフォーマンスでどの程度影響が生じるかどうかは未確認です。
Direct3D で開発し、すでにシェーダー管理の仕組みができている場合は
互換性の意味でも便利な機能です。
自由な組み合わせが必要な場合とそうでない場合の使い分けが必要かもしれません。
2014/06/23訂正: glPipelineObjects を glProgramPipelines に修正しました。間違った記載をしており申し訳ありませんでした。
小川さんご指摘ありがとうございます。
関連エントリ
・OpenGL 4.2/4.3 Shader Resource と Buffer API
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
2013/08/16
OpenGL 4.2/4.3 Shader Resource と Buffer API
OpenGL 3.1 以降や OpenGL ES 3.0 ではシェーダーが Uniform Buffer に
アクセスすることが出来ます。
OpenGL 4.2 以降はさらにシェーダーが利用できる Buffer Type が増えています。
OpenGL の API は基本的に下記の構造になっています。
●(1) Object を生成したり情報にアクセスするための Bind Point
・(A) (1) に Bind するための命令
・(B) (1) の Bind Point を使った設定や情報アクセス API
●(2) シェーダーに渡すための Bind Point の配列 (Table)
・(C) (2) に Bind するための専用命令
・(D) シェーダーのシンボルが (2) の Table のどこを見るか関連付ける命令
Texture Object は (A) と (C) の Bind 命令が共通なので、
glActiveTexture() で Bind Point ごと切り替えています。
切り替えるだけなので (1) と (2) のエリアが分かれていません。(詳細)
(A) = glBindTexture()
(C) = glActiveTexture() + glBindTexture()
(D) = glUseProgram() + glUniform1i() ( 4.2 以降 layout(binding=n) )
Vertex Buffer (GL_ARRAY_BUFFER) は glVertexAttribPointer() 命令が
内部で (C) を暗黙のうちに行います。
その後 OpenGL 4.3 で明示的に割り付ける命令が追加されました。
(C) の命令は (1) に影響を与えません。(詳細)
(A) = glBindBuffer( GL_ARRAY_BUFFER )
(C) = glBindVertexBuffer()
(D) = glVertexAttribBinding()
Uniform Buffer は 前回 のとおりです。
(1) と (2) は別の領域ですが、(C) の命令は (1)/(2) の両方に設定を行います。
(A) = glBindBuffer( GL_UNIFORM_BUFFER )
(C) = glBindBufferBase( GL_UNIFORM_BUFFER )
(D) = glUniformBlockBinding() ( 4.2 以降 layout(binding=n) )
OpenGL 4.2 以降は Bind Point を Shader 内に記述できるので、
いくつかのケースでは (D) を用いなくても良くなっています。
OpenGL 4.2 で追加された Atomic Counter Buffer も Shader 側で指定します。
● Atomic Counter Buffer (4.2)
Atomic Counter Buffer (GL_ATOMIC_COUNTER_BUFFER) は Uniform として宣言
しますが、シェーダーが値を更新することができます。
API の構造は Uniform Block と同じです。
atomic_uint は特殊な型で、アクセスには専用の命令が必要となります。
Vertex / Fragment それぞれ実行回数を数えているだけです。
RADEON (13.8beta) では Beta 版 Drive のせいか若干挙動に問題がありました。
vsh/fsh のカウンタが加算された値が返ってきます。
また API の Bind Point の構造が Uniform Block と異なっているようで
GL_ATOMIC_COUNTER_BUFFER_BINDING を glGetIntegeri_v() で取るとエラーでした。
GL_SHADER_STORAGE_BUFFER_BINDING の方も、
(1) の Bind が (2) の 0番と共有されているか、もしくは
glGetIntegerv( GL_SHADER_STORAGE_BUFFER_BINDING ) が (2) の
0 番の内容を返しているように見えます。
● Shader Storage Buffer (4.3)
OpenGL 4.3 の GLSL 4.3 では、Uniform Block 同様の構文で buffer 宣言が
追加されています。
当初 Direct3D の HLSL でいう cbuffer に対する tbuffer のことだと
思っていたのですが、直接データを書き換えることもできるようです。
つまり Direct3D の UnorderedAccessView (UAV) に相当する役目も持っています。
こちらも簡単な動作を確認したのですが、GeForce では意図したとおりに
動くものの RADEON HD7750 の 13.8beta ではまだ少々不安定でした。
(使い方に問題があった可能性もあります)
● GL_ARB_program_interface_query
Uniform Block には Bind したり Program から情報を得るための専用の API がありました。
OpenGL 4.3 では Buffer Type も増えていることから、
専用の API を増やす代わりにより一般的な API が用意されています。
新しい Shader Storage Buffer だけでなく、Atomic Counter Buffer や
従来の Uniform Block, Uniform, Attribute などもこの API に統合されています。
Uniform Block も下記の通り。
下記は実際にいろいろ情報を取り出してみた結果です。
少々わかりにくいですが、Atomic Counter Buffer や Shader Storage Buffer
だけでなく、同じ API で Attribute や出力変数も取り出すことが出来ます。
関連エントリ
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
アクセスすることが出来ます。
OpenGL 4.2 以降はさらにシェーダーが利用できる Buffer Type が増えています。
OpenGL の API は基本的に下記の構造になっています。
●(1) Object を生成したり情報にアクセスするための Bind Point
・(A) (1) に Bind するための命令
・(B) (1) の Bind Point を使った設定や情報アクセス API
●(2) シェーダーに渡すための Bind Point の配列 (Table)
・(C) (2) に Bind するための専用命令
・(D) シェーダーのシンボルが (2) の Table のどこを見るか関連付ける命令
Texture Object は (A) と (C) の Bind 命令が共通なので、
glActiveTexture() で Bind Point ごと切り替えています。
切り替えるだけなので (1) と (2) のエリアが分かれていません。(詳細)
(A) = glBindTexture()
(C) = glActiveTexture() + glBindTexture()
(D) = glUseProgram() + glUniform1i() ( 4.2 以降 layout(binding=n) )
Vertex Buffer (GL_ARRAY_BUFFER) は glVertexAttribPointer() 命令が
内部で (C) を暗黙のうちに行います。
その後 OpenGL 4.3 で明示的に割り付ける命令が追加されました。
(C) の命令は (1) に影響を与えません。(詳細)
(A) = glBindBuffer( GL_ARRAY_BUFFER )
(C) = glBindVertexBuffer()
(D) = glVertexAttribBinding()
Uniform Buffer は 前回 のとおりです。
(1) と (2) は別の領域ですが、(C) の命令は (1)/(2) の両方に設定を行います。
(A) = glBindBuffer( GL_UNIFORM_BUFFER )
(C) = glBindBufferBase( GL_UNIFORM_BUFFER )
(D) = glUniformBlockBinding() ( 4.2 以降 layout(binding=n) )
OpenGL 4.2 以降は Bind Point を Shader 内に記述できるので、
いくつかのケースでは (D) を用いなくても良くなっています。
OpenGL 4.2 で追加された Atomic Counter Buffer も Shader 側で指定します。
● Atomic Counter Buffer (4.2)
Atomic Counter Buffer (GL_ATOMIC_COUNTER_BUFFER) は Uniform として宣言
しますが、シェーダーが値を更新することができます。
// Init struct CounterData { unsigned int Counter1; unsigned int Counter2; }; CounterData counter; memset( &counter, 0, sizeof(CounterData) ); GLuint CounterBlock= 0; glGenBuffers( 1, &CounterBlock ); glBindBuffer( GL_ATOMIC_COUNTER_BUFFER, CounterBlock ); glBufferData( GL_ATOMIC_COUNTER_BUFFER, sizeof(CounterData), &counter, GL_DYNAMIC_DRAW );
// Render // 初期化 CounterData counter; memset( &counter, 0, sizeof(CounterData) ); glBindBuffer( GL_ATOMIC_COUNTER_BUFFER, CounterBlock ); glBufferSubData( GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(CounterData), &counter ); glBindBuffer( GL_ATOMIC_COUNTER_BUFFER, 0 ); ~ glBindBufferBase( GL_ATOMIC_COUNTER_BUFFER, 3, CounterBlock ); // Bind glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
API の構造は Uniform Block と同じです。
atomic_uint は特殊な型で、アクセスには専用の命令が必要となります。
// GLSL : vsh uniform mat4 PVW; uniform mat4 World; layout(binding=3, offset=0) uniform atomic_uint Counter1; in vec3 POSITION; in vec3 NORMAL; out vec3 onormal; void main() { vec4 opos= vec4( POSITION.xyz, 1.0 ) * PVW; opos.z= 2.0 * opos.z - opos.w; gl_Position= opos; atomicCounterIncrement( Counter1 ); onormal= NORMAL.xyz * mat3x3( World ); }
Vertex / Fragment それぞれ実行回数を数えているだけです。
// GLSL : fsh in vec3 onormal; layout(binding=3, offset=4) uniform atomic_uint Counter2; void main() { uint param= atomicCounterIncrement( Counter2 ) & 0xffff; float color= clamp( float( param ) * 0.001, 0.0, 1.0 ); float diff= clamp( dot( vec3( 0.0, 0.0, 1.0 ), normalize( onormal.xyz ) ), 0.0, 1.0 ); out_FragColor.xyz= vec3( color, 1.0, color ) * diff; out_FragColor.w= 1.0; }
RADEON (13.8beta) では Beta 版 Drive のせいか若干挙動に問題がありました。
vsh/fsh のカウンタが加算された値が返ってきます。
また API の Bind Point の構造が Uniform Block と異なっているようで
GL_ATOMIC_COUNTER_BUFFER_BINDING を glGetIntegeri_v() で取るとエラーでした。
GL_SHADER_STORAGE_BUFFER_BINDING の方も、
(1) の Bind が (2) の 0番と共有されているか、もしくは
glGetIntegerv( GL_SHADER_STORAGE_BUFFER_BINDING ) が (2) の
0 番の内容を返しているように見えます。
● Shader Storage Buffer (4.3)
OpenGL 4.3 の GLSL 4.3 では、Uniform Block 同様の構文で buffer 宣言が
追加されています。
当初 Direct3D の HLSL でいう cbuffer に対する tbuffer のことだと
思っていたのですが、直接データを書き換えることもできるようです。
つまり Direct3D の UnorderedAccessView (UAV) に相当する役目も持っています。
// GLSL layout(std140, binding=3) buffer MatBlock { layout(column_major) readonly mat4 MatData1; layout(column_major) readonly mat4 MatData2; };
こちらも簡単な動作を確認したのですが、GeForce では意図したとおりに
動くものの RADEON HD7750 の 13.8beta ではまだ少々不安定でした。
(使い方に問題があった可能性もあります)
● GL_ARB_program_interface_query
Uniform Block には Bind したり Program から情報を得るための専用の API がありました。
OpenGL 4.3 では Buffer Type も増えていることから、
専用の API を増やす代わりにより一般的な API が用意されています。
新しい Shader Storage Buffer だけでなく、Atomic Counter Buffer や
従来の Uniform Block, Uniform, Attribute などもこの API に統合されています。
Uniform Block も下記の通り。
glGetProgramiv( program, GL_ACTIVE_UNIFORM_BLOCKS ) ↓ glGetProgramInterfaceiv( program, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES ) glGetActiveUniformBlockiv( program, index, ~ ); ↓ glGetProgramResourceiv( program, GL_UNIFORM_BLOCK, index, ~ );
下記は実際にいろいろ情報を取り出してみた結果です。
少々わかりにくいですが、Atomic Counter Buffer や Shader Storage Buffer
だけでなく、同じ API で Attribute や出力変数も取り出すことが出来ます。
// RADEON Resource [Uniform] 92e1: 5 (Prog=0012) 0: Loc=0000 FLOAT_MAT4 (8b5c) Array=0 BL=-1 off=-1 ast=-1 mst=-1 RM=0 AC=-1 V_____ "World" 1: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off= 0 ast= 0 mst=16 RM=0 AC=-1 V_____ "Projection" 2: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off=64 ast= 0 mst=16 RM=0 AC=-1 V_____ "ViewMatrix" 3: Loc=ffff UNSIGNED_INT_ATOMIC_COUNTER(92db) Array=0 BL=-1 off=-1 ast=-1 mst=-1 RM=0 AC= 0 V_____ "Counter" 4: Loc=ffff UNSIGNED_INT_ATOMIC_COUNTER(92db) Array=0 BL=-1 off=-1 ast=-1 mst=-1 RM=0 AC= 1 ____F_ "Counter2" Resource [Atomic] 92c0: 2 (Prog=0012) 0: Bind=0000 Size= 4 Uni=1 Ind=2 V_____ "" 1: Bind=0001 Size= 4 Uni=1 Ind=3 ____F_ "" Resource [UniformBlock] 92e2: 1 (Prog=0012) 0: Bind=0000 Size= 128 Uni=2 Ind=1 V_____ "Scene" Resource [ShaderStorage] 92e6: 1 (Prog=0012) 0: Bind=0003 Size= 128 Uni=2 Ind=0 ____F_ "MatBlock" Resource [Input] 92e3: 3 (Prog=0012) 0: Loc=ffff FLOAT_VEC3 (8b51) Array=0 BL=899973093 off=53214 ast= 3 mst=128 RM=2 AC= 0 V_____ "NORMAL" 1: Loc=ffff FLOAT_VEC3 (8b51) Array=0 BL=899973093 off=53214 ast= 3 mst=128 RM=2 AC= 0 V_____ "POSITION" 2: Loc=ffff FLOAT_VEC2 (8b50) Array=0 BL=899973093 off=53214 ast= 3 mst=128 RM=2 AC= 0 V_____ "TEXCOORD" Resource [Output] 92e4: 1 (Prog=0012) 0: Loc=ffff FLOAT_VEC4 (8b52) Array=0 BL=899973093 off=53214 ast= 3 mst=128 RM=2 AC= 0 ____F_ "out_FragColor" Resource [Variable] 92e5: 2 (Prog=0012) 0: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off= 0 ast= 0 mst=16 RM=0 AC= 1 ____F_ "Projection2" 1: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off=64 ast= 0 mst=16 RM=0 AC= 1 ____F_ "ViewMatrix2"
// GeForce Resource [Uniform] 92e1: 5 (Prog=0012) 0: Loc=ffff UNSIGNED_INT_ATOMIC_COUNTER(92db) Array=0 BL= 0 off= 0 ast= 0 mst= 0 RM=0 AC= 0 V_____ "Counter" 1: Loc=ffff UNSIGNED_INT_ATOMIC_COUNTER(92db) Array=0 BL= 0 off= 4 ast= 0 mst= 0 RM=0 AC= 0 ____F_ "Counter2" 2: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off= 0 ast= 0 mst=16 RM=0 AC=-1 V_____ "Projection" 3: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off=64 ast= 0 mst=16 RM=0 AC=-1 V_____ "ViewMatrix" 4: Loc=0004 FLOAT_MAT4 (8b5c) Array=0 BL=-1 off=-1 ast=-1 mst=-1 RM=0 AC=-1 V_____ "World" Resource [Atomic] 92c0: 1 (Prog=0012) 0: Bind=0000 Size= 8 Uni=2 Ind=0 V___F_ "" Resource [UniformBlock] 92e2: 1 (Prog=0012) 0: Bind=0004 Size= 128 Uni=2 Ind=2 V_____ "Scene" Resource [ShaderStorage] 92e6: 1 (Prog=0012) 0: Bind=0003 Size= 128 Uni=1 Ind=0 V_____ "MatBlock" Resource [Input] 92e3: 2 (Prog=0012) 0: Loc=ffff FLOAT_VEC3 (8b51) Array=0 BL=-212702986 off=38875 ast= 3 mst=128 RM=1 AC= 0 V_____ "NORMAL" 1: Loc=ffff FLOAT_VEC3 (8b51) Array=0 BL=-212702986 off=38875 ast= 3 mst=128 RM=1 AC= 0 V_____ "POSITION" Resource [Output] 92e4: 1 (Prog=0012) 0: Loc=ffff FLOAT_VEC4 (8b52) Array=0 BL=-212702986 off=38875 ast= 3 mst=128 RM=1 AC= 0 ____F_ "out_FragColor" Resource [Variable] 92e5: 1 (Prog=0012) 0: Loc=ffff FLOAT_MAT4 (8b5c) Array=0 BL= 0 off=64 ast= 0 mst=16 RM=0 AC=-1 V_____ "ViewMatrix2"
関連エントリ
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
2013/08/14
OpenGL ES 3.0/OpenGL 4.x Uniform Block
Uniform Block は Direct3D の Constant Buffer に相当します。
外部で確保したメモリを、Shader からアクセス可能な Uniform 変数として
割り付けられるようになります。
トータルで Uniform として使えるメモリが増えますし、Shader 間での
データ共有も可能です。
OpenGL の Shader Program は D3D の FX に近い機能を持っていました。
シンボルは Shader 同士リンクされた状態となり、uniform の割り当てや
管理も自動的に行なってくれるため大変扱いやすくなっています。
その反面 GL 3.0 以前は uniform 値も Program Object 内で閉じており、
Camera や Light 等のシーンで共通なパラメータも、シェーダーそれぞれに
コピーする必要がありました。
OpenGL 3.1 と OpenGL ES 3.0 以降は予め Uniform Buffer を作って
おくことができます。
描画時は各シェーダーにバッファを bind するだけです。
その代わり、バッファ内の変数配置を自分で管理する必要があります。
↑シェーダー内では構造体のような書式で uniform block を宣言します。
HLSL の cbuffer と全く同じ仕様で、インスタンス名を宣言しなければ
シンボル名はトップレベルのスコープに含まれます。
main() の中でそのまま ViewMatrix や Projection を参照しています。
↑Uniform Block の作成は通常の Buffer と同じです。
操作対象を glBindBuffer() を使って bind します。
管理しやすいように GLSL 側の block と同じ構造体を宣言しています。
↑描画時の bind 命令は特殊です。
Uniform Block の API は、Texture Object や Sampler Object の
仕組みに似ています。
API 内部に Bind Point の Table が存在しており、Object はこの Table に登録します。
この場合の番号は任意で、利用可能な範囲内 (GL_MAX_UNIFORM_BUFFER_BINDINGS)
なら好きなものを選んで構いません。
シェーダーにはこの Table の番号を登録します。
Texture は glUniform1i() を使いましたが Uniform Block は専用の
命令 glUniformBlockBinding() があります。
Texture の場合と命令を比較すると下記の通り
Texture Object では、Object 操作用の Bind と
描画用の Texture Image Unit 指定の Bind が同一でした。
複数の Unit を切り替えるためには別命令 glActiveTexture() を併用します。
Uniform Block の場合は、Object 操作用の Bind と描画用の BInd Point が
分かれています。
Object 操作は Vertex Buffer や Index Buffer などと同じ glBindBuffer() です。
描画用の Bind 登録には glBindBufferBase() か glBindBufferRange() を使います。
これらの命令は Uniform 以外の Buffer でも用います。
glBindBufferBase() や glBindBufferRange() を実行すると、描画用の
Bind Point だけでなく操作用の Bind Point の両方が置き換わるので注意が必要です。
上記のように glBindBufferBase() は通常の glBindBuffer() の bind point も書き換えます。
ですが glBindBuffer() を実行しても、描画用の Bind Point には影響が出ません。
よって下記のように glDrawElements() の前に
glBindBuffer( GL_UNIFORM_BUFFER, 0 ) を入れても問題ありません。
なお glBindBufferBase() でバッファを登録しても GL_UNIFORM_BUFFER_SIZE
は 0 のままでした。RADEON だけ値が入っています。
下記は Uniform Block の利用可能なサイズを調べた結果です。
Direct3D API では shader 毎に 4096 elements の block を 14 slot まで
利用できます。(OpenGL の components と blocks に相当)
GL API では上記のように GPU 毎に異なる値となっています。
関連エントリ
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
外部で確保したメモリを、Shader からアクセス可能な Uniform 変数として
割り付けられるようになります。
トータルで Uniform として使えるメモリが増えますし、Shader 間での
データ共有も可能です。
OpenGL の Shader Program は D3D の FX に近い機能を持っていました。
シンボルは Shader 同士リンクされた状態となり、uniform の割り当てや
管理も自動的に行なってくれるため大変扱いやすくなっています。
その反面 GL 3.0 以前は uniform 値も Program Object 内で閉じており、
Camera や Light 等のシーンで共通なパラメータも、シェーダーそれぞれに
コピーする必要がありました。
OpenGL 3.1 と OpenGL ES 3.0 以降は予め Uniform Buffer を作って
おくことができます。
描画時は各シェーダーにバッファを bind するだけです。
その代わり、バッファ内の変数配置を自分で管理する必要があります。
// GLSL vsh layout(std140) uniform Scene { mat4 Projection; mat4 ViewMatrix; }; uniform mat4 World; in vec3 POSITION; void main() { mat4 pvw= World * ViewMatrix * Projection; gl_Position.xyzw= vec4( POSITION.xyz, 1.0 ) * pvw; }
↑シェーダー内では構造体のような書式で uniform block を宣言します。
HLSL の cbuffer と全く同じ仕様で、インスタンス名を宣言しなければ
シンボル名はトップレベルのスコープに含まれます。
main() の中でそのまま ViewMatrix や Projection を参照しています。
// C Init struct SceneData { math::Matrix4 Projection; math::Matrix4 ViewMatrix; }; GLuint scene_uniform= 0; glGenBuffers( 1, &scene_uniform ); glBindBuffer( GL_UNIFORM_BUFFER, scene_uniform ); glBufferData( GL_UNIFORM_BUFFER, sizeof(SceneData), &scene_data, GL_STATIC_DRAW ); glBindBuffer( GL_UNIFORM_BUFFER, 0 ); GLuint scene_block_index= glGetUniformBlockIndex( program, "Scene" ); glUniformBlockBinding( program, scene_block_index, 5 );
↑Uniform Block の作成は通常の Buffer と同じです。
操作対象を glBindBuffer() を使って bind します。
管理しやすいように GLSL 側の block と同じ構造体を宣言しています。
// C Render glUseProgram( program ); glBindVertexArray( input_layout ); // shader local glUniformMatrix4fv( world_location, 1, GL_FALSE, matrix ); // global glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform ); glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
↑描画時の bind 命令は特殊です。
Uniform Block の API は、Texture Object や Sampler Object の
仕組みに似ています。
API 内部に Bind Point の Table が存在しており、Object はこの Table に登録します。
この場合の番号は任意で、利用可能な範囲内 (GL_MAX_UNIFORM_BUFFER_BINDINGS)
なら好きなものを選んで構いません。
glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform );
シェーダーにはこの Table の番号を登録します。
glUniformBlockBinding( program, scene_block_index, 5 );
Texture は glUniform1i() を使いましたが Uniform Block は専用の
命令 glUniformBlockBinding() があります。
Texture の場合と命令を比較すると下記の通り
● Texture の場合 // shader 側の Unit 指定 glUseProgram( program ); GLint loc= glGetUniformLocation( "SamplerName" ); glUniform1i( loc, 5 ); // glGetUniformLocation() は 存在しない場合 -1 を返す // object のバインド glActiveTexture( GL_TEXTURE0 + 5 ); glBindTexture( GL_TEXTURE_2D, texture_object );
● Uniform Block の場合 // shader 側の Unit 指定 GLuint bindex= glGetUniformBlockIndex( program, "BlockName" ); glUniformBlockBinding( program, bindex, 5 ); // glGetUniformBlockIndex() は 存在しない場合 GL_INVALID_INDEX を返す // object のバインド glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform );
Texture Object では、Object 操作用の Bind と
描画用の Texture Image Unit 指定の Bind が同一でした。
複数の Unit を切り替えるためには別命令 glActiveTexture() を併用します。
Uniform Block の場合は、Object 操作用の Bind と描画用の BInd Point が
分かれています。
Object 操作は Vertex Buffer や Index Buffer などと同じ glBindBuffer() です。
描画用の Bind 登録には glBindBufferBase() か glBindBufferRange() を使います。
これらの命令は Uniform 以外の Buffer でも用います。
glBindBufferBase() や glBindBufferRange() を実行すると、描画用の
Bind Point だけでなく操作用の Bind Point の両方が置き換わるので注意が必要です。
glBindBuffer( GL_UNIFORM_BUFFER, 0 ); DumpUniformBinding(); // Buf: Uni=0000 通常の glBindBuffer() の値 (0クリア) glBindBufferBase( GL_UNIFORM_BUFFER, 0, scene_uniform ); glBindBufferBase( GL_UNIFORM_BUFFER, 4, scene_uniform ); DumpUniformBinding(); // Buf: Uni=000e 通常の glBindBuffer() の値 (ここも設定される) // [ 0]: Bind=000e (0 - 0) glBindBufferBase( 0 ) の値 // [ 4]: Bind=000e (0 - 0) glBindBufferBase( 4 ) の値 glBindBuffer( GL_UNIFORM_BUFFER, 0 ); DumpUniformBinding(); // Buf: Uni=0000 通常の glBindBuffer() の値 (ここだけ 0) // [ 0]: Bind=000e (0 - 0) glBindBufferBase( 0 ) の値 // [ 4]: Bind=000e (0 - 0) glBindBufferBase( 4 ) の値
上記のように glBindBufferBase() は通常の glBindBuffer() の bind point も書き換えます。
ですが glBindBuffer() を実行しても、描画用の Bind Point には影響が出ません。
よって下記のように glDrawElements() の前に
glBindBuffer( GL_UNIFORM_BUFFER, 0 ) を入れても問題ありません。
glUseProgram( program ); glBindVertexArray( input_layout ); glUniformMatrix4fv( world_location, 1, GL_FALSE, matrix ); glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform ); glBindBuffer( GL_UNIFORM_BUFFER, 0 ); // 問題なし glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
なお glBindBufferBase() でバッファを登録しても GL_UNIFORM_BUFFER_SIZE
は 0 のままでした。RADEON だけ値が入っています。
GPU GL_UNIFORM_BUFFER_SIZE --------------------------------------------- Intel HD 4000 4.0 0 GeForce GTX 650 4.4 0 RADEON HD 6750M 4.3 128 Mali-T604 OpenGL ES 3.0 0
下記は Uniform Block の利用可能なサイズを調べた結果です。
Intel GeForce RADEON RADEON Mali-T604 HD4000 GTX650M HD6750M HD7750(GCN) ------------------------------------------------------------ Vertex: Components 1024 4096 4096 16384 1024 Blocks 12 12 14 15 15 Combined 50176 53248 233472 262144 246784 Fragment: Components 4096 4096 2048 16384 1024 Blocks 12 12 14 15 15 Combined 53248 53248 231424 262144 246784 Hull (tcsh): Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Domain (tesh): Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Geometry: Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Compute: Components 2048 1024 1024 Blocks 14 16 16 Combined 231424 32768 1024 Bindings 36 60 84 75 75 BlockSize 16384 16384 65536 65536 65536 CombindBlocks 24 60 84 75 75
Direct3D API では shader 毎に 4096 elements の block を 14 slot まで
利用できます。(OpenGL の components と blocks に相当)
GL API では上記のように GPU 毎に異なる値となっています。
関連エントリ
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object
2013/08/13
OpenGL の各バージョンと GLSL の互換性
OpenGL のシェーディング言語 GLSL は、OpenGL の API と同様に
活発な更新が行われています。
Direct3D の HLSL よりも種類が多く、バージョン間には機能差があります。
GPU に関する機能だけでなく、言語の構文なども拡張が続けられているようです。
いくつかの機能に関して GLSL バージョン間の違いをまとめてみました。
・GLSL Version
下記の表は部分的にピックアップしています。
GLSL は上位互換性を保っており、コンパイラは
1行目の「#version」行でバージョンを判定しています。
まず OpenGL 2.x 系と OpenGL 3.0 以降で大きな違いがあります。
attribute/varying と in/out のキーワードが変更され、
texture2D() 等の関数名も変わりました。
uint/uvec (unsigned int) や switch 文が使えるようになったのも
OpenGL 3.0 (GLSL 1.3) からです。
double 型の対応は OpenGL 4.0 (GLSL 4.0) 以降。
Uniform Block (Constant Buffer) は OpenGL 3.1 (GLSL 1.4) で追加されましたが、
in/out の Interface Block は 3.2 (GLSL 1.5) で対応しました。
GLSL は C言語や他のシェーダー言語と違い、初期化にはコンストラクタが必要でした。
OpenGL 4.2 (GLSL 4.2) 以降は C言語ライクな初期化リスト {~} が使えるように
なっており下記の書式が通ります。
予想に反して OpenGL 4.0 の Intel HD 4000 (9.18.10.3165) でもコンパイルが通りました。
OpenGL ES 3.0 はいくつか仕様が削られているようです。
特に暗黙の型変換ができません。↓がエラー。
OpenGL ES 2.0 でも自動変換出来なかったので、この場合は 1.0 と記述する
必要がありました。
(OpenGL ES 2.0 だと suffix も使えないので 1.0f と書いてもエラー)
OpenGL ES 3.0 は符号なし整数型でも同様の問題が起こります。
下記のように 'u' が必要です。
暗黙の型変換があるとオーバーロード関数の検索も一気に複雑になるので、
組み込み用途では不要だと判断したのかもしれません。
ただしコンパイラがきちんとエラーを返してくれるので、
これらの問題はすぐに判明します。
●互換性
HD 4000 で Initializer List が使えたので他の GPU でも実験してみました。
GLSL の version を下げると GeForce ではエラーになりますが
RADEON では通りました。
GLSL の仕様上はエラーの方が正解です。
A では動くが他社の B では動かないといった違いは、意外にこの辺りが
原因なのかもしれません。
活発な更新が行われています。
Direct3D の HLSL よりも種類が多く、バージョン間には機能差があります。
GPU に関する機能だけでなく、言語の構文なども拡張が続けられているようです。
いくつかの機能に関して GLSL バージョン間の違いをまとめてみました。
・GLSL Version
下記の表は部分的にピックアップしています。
// GL2 Group OpenGL GLSL version 行 suffix ITC -------------------------------------------------------- OpenGL ES 2.0 GLSL ES 1.0 #version 100 - - OpenGL 2.0 GLSL 1.1 - - OpenGL 2.1 GLSL 1.2 #version 120 f ◎
// GL3/4 Group OpenGL GLSL version 行 suffix ITC UBlock ---------------------------------------------------------------- OpenGL 3.0 GLSL 1.3 #version 130 u,f ◎ - OpenGL 3.1 GLSL 1.4 #version 140 u,f ◎ U OpenGL 3.2 GLSL 1.5 #version 150 u,f ◎ U,I,O OpenGL ES 3.0 GLSL ES 3.0 #version 300 es u,f - U OpenGL 3.3 GLSL 3.3 #version 330 u,f ◎ U,I,O OpenGL 4.0 GLSL 4.0 #version 400 u,f,lf ◎ U,I,O OpenGL 4.1 GLSL 4.1 #version 410 u,f,lf ◎ U,I,O OpenGL 4.2 GLSL 4.2 #version 420 u,f,lf ◎ U,I,O OpenGL 4.3 GLSL 4.3 #version 430 u,f,lf ◎ U,I,O,B OpenGL 4.4 GLSL 4.4 #version 440 u,f,lf ◎ U,I,O,B
GLSL は上位互換性を保っており、コンパイラは
1行目の「#version」行でバージョンを判定しています。
まず OpenGL 2.x 系と OpenGL 3.0 以降で大きな違いがあります。
attribute/varying と in/out のキーワードが変更され、
texture2D() 等の関数名も変わりました。
uint/uvec (unsigned int) や switch 文が使えるようになったのも
OpenGL 3.0 (GLSL 1.3) からです。
double 型の対応は OpenGL 4.0 (GLSL 4.0) 以降。
Uniform Block (Constant Buffer) は OpenGL 3.1 (GLSL 1.4) で追加されましたが、
in/out の Interface Block は 3.2 (GLSL 1.5) で対応しました。
GLSL は C言語や他のシェーダー言語と違い、初期化にはコンストラクタが必要でした。
// GLSL vec3 pos= vec3( 1.0, 2.0, 3.0 ); float a[4]= float[]( 1.0, 2.0, 3.0, 4.0 );
OpenGL 4.2 (GLSL 4.2) 以降は C言語ライクな初期化リスト {~} が使えるように
なっており下記の書式が通ります。
// GLSL vec3 pos= { 1.0, 2.0, 3.0 }; float a[4]= { 1.0, 2.0, 3.0. 4.0 };
予想に反して OpenGL 4.0 の Intel HD 4000 (9.18.10.3165) でもコンパイルが通りました。
OpenGL ES 3.0 はいくつか仕様が削られているようです。
特に暗黙の型変換ができません。↓がエラー。
float a= 1; // int to float
OpenGL ES 2.0 でも自動変換出来なかったので、この場合は 1.0 と記述する
必要がありました。
(OpenGL ES 2.0 だと suffix も使えないので 1.0f と書いてもエラー)
OpenGL ES 3.0 は符号なし整数型でも同様の問題が起こります。
uint i= 32; // int to uint
下記のように 'u' が必要です。
uint i= 32u;
暗黙の型変換があるとオーバーロード関数の検索も一気に複雑になるので、
組み込み用途では不要だと判断したのかもしれません。
ただしコンパイラがきちんとエラーを返してくれるので、
これらの問題はすぐに判明します。
●互換性
HD 4000 で Initializer List が使えたので他の GPU でも実験してみました。
GPU OpenGL GLSL version compile ---------------------------------------------------- Intel HD 4000 OpenGL 4.0 #version 400 OK RADEON HD 6750M OpenGL 4.3 #version 430 OK RADEON HD 6750M OpenGL 4.3 #version 400 OK GeForce GTX 650 OpenGL 4.4 #version 440 OK GeForce GTX 650 OpenGL 4.4 #version 400 Error
GLSL の version を下げると GeForce ではエラーになりますが
RADEON では通りました。
GLSL の仕様上はエラーの方が正解です。
A では動くが他社の B では動かないといった違いは、意外にこの辺りが
原因なのかもしれません。
2013/08/12
OpenGL のエラー判定と OpenGL 4.3 Debug Output
OpenGL のエラー状態を調べるには glGetError() を使います。
3~5種類程度のエラーコードが得られるので、マニュアルを頼りに
API 毎に発生条件を照らし合わせていきます。
どのタイミングで発生したのか特定するには、OpenGL の命令呼び出し毎に
glGetError() をチェックしなければなりません。
● glGetError() の判定
↑これを毎回手で行うのは大変なので、何らかの形で自動化します。
現在は、下記のようなヘッダを作って対処しています。
GLES2/gl2.h, GLES3/gl3.h, GL4/glcorearb.h などのヘッダファイルを元に
スクリプトで自動生成しています。
下記のように include して使います。
↑コンパイル時にエラー判定を行うかどうか選択できます。
実際のエラー判定は下記のような関数です。
利用時は el:: をつけて下記のような感じです。
これはエラー判定の一例ですが、楽をしたければそれなりの準備が必要で、
結構手間もかかります。
生成したヘッダは下記の場所に置いてみました。
・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 のエラーメッセージを
わかりやすい形↓で受け取れるようになっています。
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 文字列も変化します。
wglMakeCurrent() で OpenGL API が使えるようになったら Callback 関数を
登録します。
Debug Context はデフォルトで glEnable( GL_DEBUG_OUTPUT ) になるので
2行目は無くても構いません。
OpenGL の命令でエラーが発生すると Callback 関数が呼ばれます。
手間もかからずにエラー内容も発生場所もわかるので、非常に便利になりました。
● 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 のはじめ方
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 parameterhas 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 のはじめ方
Buffer と同じように Texture を扱う場合も Bind 操作が必要です。
Texture Object の Bind は二つの意味に用いられます。
1. Texture Object を操作する場合
2. 描画で用いる Texture Image Unit に割り当てる場合
Texture Object の種類(タイプ)は下記の通り。
glBindBuffer() と同じように glBindTexture() にタイプを指定します。
タイプの数だけ Bind 状態が保持されます。
● 1. Texture Object を操作する場合
異なるタイプの Texture Object は同時に Bind できます。
それぞれ glTexParameteri() や glTexImage 等の命令を実行することが可能です。
● 2. 描画で用いる Texture Image Unit に割り当てる場合
描画時にシェーダーが参照する Texture も Bind で渡します。
シェーダーは一度に複数のテクスチャにアクセスできます。
まずは割り当てたい Texture Unit (Texture Image Unit) の番号を
先に指定しておきます。
仮にアクセスできる Unit が 8個あるなら、glActiveTexture() の指定も
GL_TEXTURE0 ~ GL_TEXTURE7 の範囲になります。
シェーダー側では Sampler (Sampler2D 等) に設定した glUniform1i() の値で
これらのテクスチャを区別するわけです。
glActiveTexture() の動作を調べてみると、現在の Bind 状態がごっそりと
入れ替わっていることがわかります。
例えば OpenGL ES 3.0 で Total (Combined) の Unit が 32個ある場合、
GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_2D_ARRAY の 4種類のバインド状態が
32個 (4 x 32 = 128) まで保持されることになります。
(1) の実行結果をまとめると下記の表の通り。
ただし、実際の描画で Shader が参照できる Texture Object は
各 Unit 1つにつき 1つだけです。
例えば上の状態でシェーダー内の uniform Sampler2D と SamplerCube の両方が、
Unit 0 ( glActiveTexture( GL_TEXTURE0 ) ) の obj1 と obj2 を
それぞれ参照することはできません。
同じ Unit を参照している場合 glValidateProgram() が失敗します。
まとめると、
1. の用途では同時に複数の Texture Object を Bind することがありますが、
2. の用途では、現在の ActiveTexture (Texture Image Unit) に対して複数
Texture を Bind することはありません。
Bind は複数の用途に用いられるので注意が必要です。
2. の用途で Bind している間に、画像サイズを調べようとして 1. の
つもりで Bind してしまうと状態を破壊してしまうことになります。
● Texture Image Unit
GPU には複数の Image Unit が搭載されています。
シェーダーは Unit の数だけ、同時に異なるテクスチャから画像を
読み込むことができます。
各 Unit には Sampler と Texture Object の二つの状態を割り当てます。
・Sampler : データの読み込み方法
・Texture Object : データそのもの
例えば Fragment Shader (PixelShader) の Texture Image Unit 数
(GL_MAX_TEXTURE_IMAGE_UNITS) が 16個なら、FragmentShader は同時に 16種類の
異なるテクスチャマップにアクセスできます。
uv を変更して同じテクスチャを何度も読み込む場合は特に回数制限はありません。
ShaderModel 2.0 (D3D9) 以降は Sampler と Texture の Load 命令の実行が
分離されているからです。(ShaderModel 1.x までは同一でした)
少なくても上記個数は使用可能となっています。
ただ実際に調べたところ、GPU によっては GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
(total) が必ずしも上記表の最低個数を満たしているとは限らないようです。
描画に使うテクスチャは、glDrawElements() 等の描画命令発行時に Bind された
状態になります。
Fragment Shader だけでなく、Vertex, Geometry 等の各シェーダーがそれぞれ
異なる Texture にアクセスすることが可能です。
例えば vsh, fsh, gsh が 16種類のすべて異なるテクスチャを参照する場合は
48個の Bind 状態が必要になります。
glActiveTexture() に指定する GL_TEXTRE0~ のシンボルは OpenGL 4.4 の
ヘッダを見ても GL_TEXTRE0 ~ GL_TEXTRE31 の 32個しか定義されていません。
OpenGL では GL_TEXTRE0 のオフセット指定が有効だと明記されているので、
32以上も下記の方法でアクセスすることが出来ます。
ちなみに OpenGL 4.2 (GLSL 4.2) 以降は Shader 内でも直接 Texture Image Unit
の番号を設定できるようになっています。
layout の binding は、今まで外部から glUniform1i() で与えていた番号に相当します。
● Sampler Object
Texture の Wrap や Filter mode 等の Sampler State は、Direct3D の場合は
Texture Stage (Sampler) 側の設定値でした。
OpenGL ES 2.0 の場合は Texture Object がこれらのステートを所有します。
例えば下記のように同じオブジェクトを 0,1 の Unit に割り当てます。
(2) で Unit 0 にバインドされたテクスチャの、Filter mode を変更すると、
Unit 1 の設定値も GL_LINEAR に書き換わります。
OpenGL ES 3.0/OpenGL 3.0 以降は Sampler Object が導入され、D3D のように
Image Unit に直接これらのステートを設定できるようになりました。
各パラメータは Sampler Object 優先です。
Image Unit に Sampler Object が設定されていない場合のみ
Texture Object の値が用いられます。
Sampler API は、パラメータの設定時は glActiveTexture() の影響を受けません。
また GL_TEXTURE0 ~ ではなく、直接 Unit 番号 0~ を指定しています。
Texture Object と違い、Object 操作のための Bind (1. 相当) が不要です。
Bind するのは実際に Image Unit に割り当てる場合だけ (2. 相当) なので、
わかりやすくてミスしづらい構造となっています。
OpenGL 4.4 ではさらに、複数の Sampler Object をまとめて Bind できる
glBindSamplers() 命令が追加されました。
これは Direct3D 10/11 の SetSamplers() 系命令によく似ています。
GL_SAMPLER_BINDING を用いて現在の Bind 状態を参照する場合は
glActiveTexture() も影響しています。
この値は glActiveTexture() で切り替わるのですが、RADEON だけ
値が取れませんでした。
2013/08/11 追記: RADEON HD 6750M でも Catalyst 13.8(beta) OpenGL 4.3 で正しく状態を取れることを確認しました。
関連エントリ
・OpenGL ES 3.0 / OpenGL 4.3 VertexArrayObject と VertexAttribBinding
Texture Object の Bind は二つの意味に用いられます。
1. Texture Object を操作する場合
2. 描画で用いる Texture Image Unit に割り当てる場合
Texture Object の種類(タイプ)は下記の通り。
ES2 ES3 GL30 GL31 GL32 GL4 ------------------------------------------------------------ GL_TEXTURE_1D - - ◎ ◎ ◎ ◎ GL_TEXTURE_2D ◎ ◎ ◎ ◎ ◎ ◎ GL_TEXTURE_CUBE_MAP ◎ ◎ ◎ ◎ ◎ ◎ GL_TEXTURE_3D - ◎ ◎ ◎ ◎ ◎ GL_TEXTURE_1D_ARRAY - - ◎ ◎ ◎ ◎ GL_TEXTURE_2D_ARRAY - ◎ ◎ ◎ ◎ ◎ GL_TEXTURE_2D_MULTISAMPLE - - - - ◎ ◎ GL_TEXTURE_2D_MULTISAMPLE_ARRAY - - - - ◎ ◎ GL_TEXTURE_CUBE_MAP_ARRAY - - - - - ◎ GL_TEXTURE_RECTANGLE - - - ◎ ◎ ◎ GL_TEXTURE_BUFFER - - - ◎ ◎ ◎
glBindBuffer() と同じように glBindTexture() にタイプを指定します。
タイプの数だけ Bind 状態が保持されます。
● 1. Texture Object を操作する場合
GLuint tex_obj1= 0; GLuint tex_obj2= 0; glGenTextures( 1, &tex_obj1 ); glGenTextures( 2, &tex_obj2 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glBindTexture( GL_TEXTURE_3D, tex_obj2 ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glBindTexture( GL_TEXTURE_2D, 0 ); glBindTexture( GL_TEXTURE_3D, 0 );
異なるタイプの Texture Object は同時に Bind できます。
それぞれ glTexParameteri() や glTexImage 等の命令を実行することが可能です。
● 2. 描画で用いる Texture Image Unit に割り当てる場合
描画時にシェーダーが参照する Texture も Bind で渡します。
シェーダーは一度に複数のテクスチャにアクセスできます。
まずは割り当てたい Texture Unit (Texture Image Unit) の番号を
先に指定しておきます。
glActiveTexture( GL_TEXTURE0 ); // Texture Image Unit 0 glBindTexture( GL_TEXTURE_2D, texture_object1 ); glActiveTexture( GL_TEXTURE1 ); // Texture Image Unit 1 glBindTexture( GL_TEXTURE_2D, texture_object2 );
仮にアクセスできる Unit が 8個あるなら、glActiveTexture() の指定も
GL_TEXTURE0 ~ GL_TEXTURE7 の範囲になります。
シェーダー側では Sampler (Sampler2D 等) に設定した glUniform1i() の値で
これらのテクスチャを区別するわけです。
glActiveTexture() の動作を調べてみると、現在の Bind 状態がごっそりと
入れ替わっていることがわかります。
// (1) glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glBindTexture( GL_TEXTURE_CUBE_MAP, tex_obj2 ); glActiveTexture( GL_TEXTURE1 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glBindTexture( GL_TEXTURE_3D, tex_obj3 ); glActiveTexture( GL_TEXTURE3 ); glBindTexture( GL_TEXTURE_3D, tex_obj4 );
例えば OpenGL ES 3.0 で Total (Combined) の Unit が 32個ある場合、
GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_2D_ARRAY の 4種類のバインド状態が
32個 (4 x 32 = 128) まで保持されることになります。
(1) の実行結果をまとめると下記の表の通り。
glActiveTexture= 0 1 2 3 4 5 6 ... 31 --------------------------------------------------------------------- GL_TEXTURE_2D obj1 obj1 -- -- -- -- -- -- GL_TEXTURE_CUBE_MAP obj2 -- -- -- -- -- -- -- GL_TEXTURE_3D -- obj3 -- obj4 -- -- -- -- GL_TEXTURE_2D_ARRAY -- -- -- -- -- -- -- --
ただし、実際の描画で Shader が参照できる Texture Object は
各 Unit 1つにつき 1つだけです。
例えば上の状態でシェーダー内の uniform Sampler2D と SamplerCube の両方が、
Unit 0 ( glActiveTexture( GL_TEXTURE0 ) ) の obj1 と obj2 を
それぞれ参照することはできません。
同じ Unit を参照している場合 glValidateProgram() が失敗します。
まとめると、
1. の用途では同時に複数の Texture Object を Bind することがありますが、
2. の用途では、現在の ActiveTexture (Texture Image Unit) に対して複数
Texture を Bind することはありません。
Bind は複数の用途に用いられるので注意が必要です。
2. の用途で Bind している間に、画像サイズを調べようとして 1. の
つもりで Bind してしまうと状態を破壊してしまうことになります。
● Texture Image Unit
GPU には複数の Image Unit が搭載されています。
シェーダーは Unit の数だけ、同時に異なるテクスチャから画像を
読み込むことができます。
各 Unit には Sampler と Texture Object の二つの状態を割り当てます。
・Sampler : データの読み込み方法
・Texture Object : データそのもの
例えば Fragment Shader (PixelShader) の Texture Image Unit 数
(GL_MAX_TEXTURE_IMAGE_UNITS) が 16個なら、FragmentShader は同時に 16種類の
異なるテクスチャマップにアクセスできます。
uv を変更して同じテクスチャを何度も読み込む場合は特に回数制限はありません。
ShaderModel 2.0 (D3D9) 以降は Sampler と Texture の Load 命令の実行が
分離されているからです。(ShaderModel 1.x までは同一でした)
最小 Unit 数 vsh fsh gsh tcsh tesh csh total ---------------------------------------------------------- OpenGL 2.0 0 2 - - - - 2 OpenGL 2.1 0 2 - - - - 2 OpenGLES 2.0 0 8 - - - - 8 OpenGL 3.0 16 16 - - - - 16 OpenGLES 3.0 16 16 - - - - 32 OpenGL 3.1 16 16 - - - - 32 OpenGL 3.2 16 16 16 - - - 48 OpenGL 3.3 16 16 16 - - - 48 OpenGL 4.0 16 16 16 16 16 - 80 OpenGL 4.1 16 16 16 16 16 - 80 OpenGL 4.2 16 16 16 16 16 - 80 OpenGL 4.3 16 16 16 16 16 16 96 OpenGL 4.4 16 16 16 16 16 16 96 ---------------------------------------------------------- ・total = GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
少なくても上記個数は使用可能となっています。
ただ実際に調べたところ、GPU によっては GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
(total) が必ずしも上記表の最低個数を満たしているとは限らないようです。
OpenGL v/ f/ g/tc/te/csh total ------------------------------------------------------------ Adreno 320(Android4.1) ES 2.0 4/16/--/--/--/-- 20 Mali-T604 (Android4.3) ES 3.0 16/16/--/--/--/-- 32 Intel HD 4000 x86 4.0 16/16/16/16/16/-- 80 RADEON HD 6750M x64 4.2 16/16/16/16/16/-- 32 * GeForce GTX 650 x64 4.4 32/32/32/32/32/32 192
描画に使うテクスチャは、glDrawElements() 等の描画命令発行時に Bind された
状態になります。
Fragment Shader だけでなく、Vertex, Geometry 等の各シェーダーがそれぞれ
異なる Texture にアクセスすることが可能です。
例えば vsh, fsh, gsh が 16種類のすべて異なるテクスチャを参照する場合は
48個の Bind 状態が必要になります。
glActiveTexture() に指定する GL_TEXTRE0~ のシンボルは OpenGL 4.4 の
ヘッダを見ても GL_TEXTRE0 ~ GL_TEXTRE31 の 32個しか定義されていません。
OpenGL では GL_TEXTRE0 のオフセット指定が有効だと明記されているので、
32以上も下記の方法でアクセスすることが出来ます。
glActiveTexture( GL_TEXTURE0 + 32 ); ... glActiveTexture( GL_TEXTURE0 + 47 );
ちなみに OpenGL 4.2 (GLSL 4.2) 以降は Shader 内でも直接 Texture Image Unit
の番号を設定できるようになっています。
// GLSL layout(binding=1) uniform sampler2D ColorMap; layout(binding=2) uniform sampler2D NormalMap; layout(binding=0) uniform samplerCube ReflectionMap;
layout の binding は、今まで外部から glUniform1i() で与えていた番号に相当します。
● Sampler Object
Texture の Wrap や Filter mode 等の Sampler State は、Direct3D の場合は
Texture Stage (Sampler) 側の設定値でした。
OpenGL ES 2.0 の場合は Texture Object がこれらのステートを所有します。
例えば下記のように同じオブジェクトを 0,1 の Unit に割り当てます。
// OpenGL ES 2.0 glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glActiveTexture( GL_TEXTURE1 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); // -- (2) glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, tex_obj1 ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
(2) で Unit 0 にバインドされたテクスチャの、Filter mode を変更すると、
Unit 1 の設定値も GL_LINEAR に書き換わります。
OpenGL ES 3.0/OpenGL 3.0 以降は Sampler Object が導入され、D3D のように
Image Unit に直接これらのステートを設定できるようになりました。
各パラメータは Sampler Object 優先です。
Image Unit に Sampler Object が設定されていない場合のみ
Texture Object の値が用いられます。
GLuint sampler_obj= 0; glGenSamplers( 1, &sampler_obj ); glSamplerParameteri( sampler_obj, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glSamplerParameteri( sampler_obj, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ~ glBindSampler( 0, sampler_obj );
Sampler API は、パラメータの設定時は glActiveTexture() の影響を受けません。
また GL_TEXTURE0 ~ ではなく、直接 Unit 番号 0~ を指定しています。
Texture Object と違い、Object 操作のための Bind (1. 相当) が不要です。
Bind するのは実際に Image Unit に割り当てる場合だけ (2. 相当) なので、
わかりやすくてミスしづらい構造となっています。
OpenGL 4.4 ではさらに、複数の Sampler Object をまとめて Bind できる
glBindSamplers() 命令が追加されました。
これは Direct3D 10/11 の SetSamplers() 系命令によく似ています。
GL_SAMPLER_BINDING を用いて現在の Bind 状態を参照する場合は
glActiveTexture() も影響しています。
glActiveTexture( GL_TEXTURE0 + 0 ); GLint sampler= 0; glGetIntegerv( GL_SAMPLER_BINDING, &sampler );
この値は glActiveTexture() で切り替わるのですが、RADEON だけ
値が取れませんでした。
Mali-T604 ES 3.0 取れる Intel HD 4000 4.0 取れるRADEON HD 6750M 4.2 取れない(追記 driver 更新で正常動作を確認) GeForce GTX 650 4.4 取れる
2013/08/11 追記: RADEON HD 6750M でも Catalyst 13.8(beta) OpenGL 4.3 で正しく状態を取れることを確認しました。
関連エントリ
・OpenGL ES 3.0 / OpenGL 4.3 VertexArrayObject と VertexAttribBinding
OpenGL API のオブジェクト操作は、必ずライブラリ内の global な変数を
経由して行います。
例えば Buffer Size を調べる場合も、現在の context のステートに
オブジェクトを bind してから対応するメソッドを呼び出します。
オブジェクトが導入される前の API と互換性を保つことができる反面、
どのオブジェクトを対象としているのかコードを見ただけではわからなく
なっています。
どのオブジェクトが必要なのか、どこまで影響を与えるのかは、それぞれ
命令仕様をマニュアル等で確認する必要があります。
例えば OpenGL ES 2.0 の描画のコードは下記の通りです。
もし仮に D3D API のような書き方ができるとしたらこんな↓感じになるでしょうか。
「擬似コード (2)」を見ると device_context に SetVertexBuffer() 命令が無く、
SetVertexAttrib() の方に vertex_object を渡していることがわかるかと思います。
実際に (1) の glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を
見ておらず、直前に glBindBuffer( GL_ARRAY_BUFFER, 0 ) があっても動作します。
glBindBuffer( GL_ARRAY_BUFFER ) を必要とするのは glVertexAttribPointer()
の方で、各 Vertex Attribute が頂点バッファの情報を保持しています。
Attribute それぞれが異なる Vertex Buffer を参照している可能性があるからです。
やはり (1) のコードを見ただけではこのような内部構造がわからないので、
OpenGL 命令の仕様はきちんと確認した方が良いようです。
● Vertex Array Object
頂点のバインドは複雑で情報も多いので、OpenGL 3.x 以降や OpenGL ES 3.0 では
Vertex Array Object (VAO) が導入されています。
VAO は頂点 Attribute の情報を保存し、描画時に利用することができます。
OpenGL 3.x (core profile) 以降は VAO が必須で、glBindVertexArray() が無いと
glVertexAttribPointer() がエラーになります。
つまり glVertexAttribPointer() は GL2 までは device_context のメソッドでしたが、
GL3 からは Vertex Array Object のメソッドに相当します。
擬似コードで表現してみます。
OpenGL ES 3.0 では OpenGL ES 2.0 互換性のために VAO 無しでも
glVertexAttribPointer() が使えるようになっています。
bind == 0 時にデフォルトの VAO が割り当てられていると考えられます。
OpenGL 3.x でも、RADEON など一部 GPU では default VAO が有効なものが
あるようです。
Vertex Array Object は Direct3D の InputLayout や VertexDeclaration に
相当しますが、上記のように vertex_object や index_object が含まれている点で
異なっています。
D3D では頂点のバインド情報とバッファの割り当ては別の命令でした。
OpenGL でも 4.3 で D3D 同様の仕組みが導入されているようです。
(後述: Vertex Attrib Binding)
● GL_ELEMENT_ARRAY_BUFFER の扱い
VAO の index_object に対する挙動は vertex_object の場合とは異なっています。
glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を参照しないし
VAO も glBindBuffer( GL_ARRAY_BUFFER ) に影響を与えません。
ですが、glDrawElements() は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を
参照し、VAO は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を置き換えます。
よって直前で glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) に 0 が
書き込まれると glDrawElements() は失敗します。
同じ glBindBuffer() 命令でも動作が異なっていることがわかるかと思います。
GL_ELEMENT_ARRAY_BUFFER のバインド情報
・VAO が 0 の時は Default の Bind 情報が保持される。
・VAO が割り当てられている場合は VAO に格納する。
例えば下記 (7) のようなコードがあった場合、(A) は描画やオブジェクトに影響を
与えませんが (B) は VAO (input_layout) の内容を置き換えています。
● Vertex Attribute Binding
(7) にあるように、描画時の (A) glBindBuffer( GL_ARRAY_BUFFER ) は直接
影響を与えません。
もし描画に使用する頂点バッファを置き換えたい場合は、Attribute 毎に
glVertexAttribPointer() を呼び直すことになります。
glVertexAttribPointer() に渡す TYPE などの情報が必要になりますし、
同じバッファを見ている Attribute の数だけ命令を実行しなければなりません。
OpenGL 4.3 では Vertex Buffer と Vertex Attribute を分けて管理できる
仕組みが追加されています。
各 Attribute の頂点フォーマットは、これまでの
glVertexAttribPointer() 同様に glVertexAttribFormat() で定義します。
ただし各 Attribute が参照するバッファの情報は別で、bind_index を用いた
間接参照となります。
bind_index が指しているのは VertexBuffer の情報が入ったテーブルです。
実際のバッファの情報はこの VertexBuffer のテーブルの方に格納されます。
bind_index を通して、Attribute 毎に個別に持っていたバッファ情報を共有する
ことができます。
bind_index の値は glGetVertexAttribiv( GL_VERTEX_ATTRIB_BINDING ) で確認
することができます。
また実装上は Vertex Attribute の Table と Vertex Buffer の Table は
同一のもので、bind_index は自分自身への 2段階間接参照であることもわかります。
(8) を VertexAttribBinding を使って置き換えると下記の通り。
VertexAttribBinding を使うと、下記のように glBindVertexBuffer() 一つで
頂点が参照する Vertex Buffer を置き換えることができます。
試してみると glBindVertexBuffer() は glBindBuffer( GL_ARRAY_BUFFER )
を経由しないし全く変更もしないことがわかります。
これで描画に glBindBuffer( GL_ARRAY_BUFFER ) は必要なくなりました。
関連エントリ
・OpenGL ES 3.0 と Vertex Array Object
経由して行います。
例えば Buffer Size を調べる場合も、現在の context のステートに
オブジェクトを bind してから対応するメソッドを呼び出します。
// GL2 glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); GLint param[1]; glGetBufferParameteriv( GL_ARRAY_BUFFER, GL_BUFFER_SIZE, param ); glBindBuffer( GL_ARRAY_BUFFER, 0 );
オブジェクトが導入される前の API と互換性を保つことができる反面、
どのオブジェクトを対象としているのかコードを見ただけではわからなく
なっています。
どのオブジェクトが必要なのか、どこまで影響を与えるのかは、それぞれ
命令仕様をマニュアル等で確認する必要があります。
例えば OpenGL ES 2.0 の描画のコードは下記の通りです。
// GL2 -- (1) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
もし仮に D3D API のような書き方ができるとしたらこんな↓感じになるでしょうか。
// GL2 -- 擬似コード (2) program->SetConstant( location, 4, matrix ); device_context->SetProgram( program ); device_context->SetIndexBuffer( index_object ); device_context->SetVertexAttrib( 0, 3, GL_FLOAT, GL_FALSE, 24, 0, vertex_object); device_context->SetVertexAttrib( 1, 3, GL_FLOAT, GL_FALSE, 24, 12, vertex_object); device_context->DrawIndexed( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
「擬似コード (2)」を見ると device_context に SetVertexBuffer() 命令が無く、
SetVertexAttrib() の方に vertex_object を渡していることがわかるかと思います。
実際に (1) の glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を
見ておらず、直前に glBindBuffer( GL_ARRAY_BUFFER, 0 ) があっても動作します。
glBindBuffer( GL_ARRAY_BUFFER ) を必要とするのは glVertexAttribPointer()
の方で、各 Vertex Attribute が頂点バッファの情報を保持しています。
Attribute それぞれが異なる Vertex Buffer を参照している可能性があるからです。
やはり (1) のコードを見ただけではこのような内部構造がわからないので、
OpenGL 命令の仕様はきちんと確認した方が良いようです。
● Vertex Array Object
頂点のバインドは複雑で情報も多いので、OpenGL 3.x 以降や OpenGL ES 3.0 では
Vertex Array Object (VAO) が導入されています。
// GL3 -- Vertex Array Object の作成 (3) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
VAO は頂点 Attribute の情報を保存し、描画時に利用することができます。
// GL3 -- VAO を使った Rendering (4) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindVertexArray( input_layout ); glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
OpenGL 3.x (core profile) 以降は VAO が必須で、glBindVertexArray() が無いと
glVertexAttribPointer() がエラーになります。
つまり glVertexAttribPointer() は GL2 までは device_context のメソッドでしたが、
GL3 からは Vertex Array Object のメソッドに相当します。
擬似コードで表現してみます。
// GL3 -- VAO 作成 擬似コード (5) IInputLayout* input_layout= device->CreateInputLayout( 2 ); input_layout->SetIndexBuffer( index_object ); input_layout->SetVertexAttrib( 0, 3, GL_FLOAT, GL_FALSE, 24, 0, vertex_object ); input_layout->SetVertexAttrib( 1, 3, GL_FLOAT, GL_FALSE, 24, 12, vertex_object );
// GL3 -- VAO を使った Rendering 擬似コード (6) program->SetConstant( location, 4, matrix ); device_context->SetProgram( program ); device_context->SetInputLayout( input_layout ); device_context->DrawIndexed( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
OpenGL ES 3.0 では OpenGL ES 2.0 互換性のために VAO 無しでも
glVertexAttribPointer() が使えるようになっています。
bind == 0 時にデフォルトの VAO が割り当てられていると考えられます。
OpenGL 3.x でも、RADEON など一部 GPU では default VAO が有効なものが
あるようです。
Vertex Array Object は Direct3D の InputLayout や VertexDeclaration に
相当しますが、上記のように vertex_object や index_object が含まれている点で
異なっています。
D3D では頂点のバインド情報とバッファの割り当ては別の命令でした。
OpenGL でも 4.3 で D3D 同様の仕組みが導入されているようです。
(後述: Vertex Attrib Binding)
● GL_ELEMENT_ARRAY_BUFFER の扱い
VAO の index_object に対する挙動は vertex_object の場合とは異なっています。
glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を参照しないし
VAO も glBindBuffer( GL_ARRAY_BUFFER ) に影響を与えません。
ですが、glDrawElements() は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を
参照し、VAO は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を置き換えます。
よって直前で glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) に 0 が
書き込まれると glDrawElements() は失敗します。
GLuint input_layout; GLuint vertex_object1; GLuint index_object1; // ** Bind: Vertex=[0] Index=[0] glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object1 ); // ↓ ここでの bind は VAO input_layout に対して行う glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object1 ); // ** Bind: Vertex=[vertex_object1] Index=[index_object1] glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 12, 0 ); glEnableVertexAttribArray( 0 ); glBindVertexArray( 0 ); // ↑VAO の bind を解除したので index_object1 のバインドも消える↓ // ** Bind: Vertex=[vertex_object1] Index=[0] // VAO の変更によって GL_ELEMENT_ARRAY_BUFFER (index) の bind は // 元に戻るが、GL_ARRAY_BUFFER (vertex) には影響がでない。
同じ glBindBuffer() 命令でも動作が異なっていることがわかるかと思います。
GL_ELEMENT_ARRAY_BUFFER のバインド情報
・VAO が 0 の時は Default の Bind 情報が保持される。
・VAO が割り当てられている場合は VAO に格納する。
例えば下記 (7) のようなコードがあった場合、(A) は描画やオブジェクトに影響を
与えませんが (B) は VAO (input_layout) の内容を置き換えています。
// GL3 -- (7) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); // -- (A) glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); // -- (B) glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 ); glBindVertexArray( 0 );
● Vertex Attribute Binding
(7) にあるように、描画時の (A) glBindBuffer( GL_ARRAY_BUFFER ) は直接
影響を与えません。
もし描画に使用する頂点バッファを置き換えたい場合は、Attribute 毎に
glVertexAttribPointer() を呼び直すことになります。
glVertexAttribPointer() に渡す TYPE などの情報が必要になりますし、
同じバッファを見ている Attribute の数だけ命令を実行しなければなりません。
OpenGL 4.3 では Vertex Buffer と Vertex Attribute を分けて管理できる
仕組みが追加されています。
glVertexAttribBinding( attrib_index, bind_index ); glBindVertexBuffer( bind_index, buffer_object, offset, stride ); glVertexAttribFormat( attrib_index, size, type, normalized, offset );
各 Attribute の頂点フォーマットは、これまでの
glVertexAttribPointer() 同様に glVertexAttribFormat() で定義します。
ただし各 Attribute が参照するバッファの情報は別で、bind_index を用いた
間接参照となります。
bind_index が指しているのは VertexBuffer の情報が入ったテーブルです。
実際のバッファの情報はこの VertexBuffer のテーブルの方に格納されます。
◎ GL3.x/4.x ・頂点 Attribute が持つ情報 Type, Size, Offset, NormalizeFlag, VertexBuffer, Stride ◎ GL4.3/4.4 VertexAttribBinding ・頂点 Attribute が持つ情報 Type, Size, Offset, NormalizeFlag, BindIndex (BindVertexBuffer を参照する) ・BindVertexBuffer のテーブルが持つ情報 VertexBuffer, Stride
bind_index を通して、Attribute 毎に個別に持っていたバッファ情報を共有する
ことができます。
bind_index の値は glGetVertexAttribiv( GL_VERTEX_ATTRIB_BINDING ) で確認
することができます。
また実装上は Vertex Attribute の Table と Vertex Buffer の Table は
同一のもので、bind_index は自分自身への 2段階間接参照であることもわかります。
// GL3 --- (8) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
(8) を VertexAttribBinding を使って置き換えると下記の通り。
// GL4.3/4.4 --- (9) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glBindVertexBuffer( 0, vertex_object, 0, 24 ); glVertexAttribBinding( 0, 0 ); glVertexAttribBinding( 1, 0 ); glVertexAttribFormat( 0, 3, GL_FLOAT, GL_FALSE, 0 ); glVertexAttribFormat( 1, 3, GL_FLOAT, GL_FALSE, 12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
VertexAttribBinding を使うと、下記のように glBindVertexBuffer() 一つで
頂点が参照する Vertex Buffer を置き換えることができます。
// GL4.3/4.4 -- (10) glBindVertexArray( input_layout ); glBindVertexBuffer( 0, vertex_object2, 0, 64 ); glBindVertexArray( 0 );
試してみると glBindVertexBuffer() は glBindBuffer( GL_ARRAY_BUFFER )
を経由しないし全く変更もしないことがわかります。
これで描画に glBindBuffer( GL_ARRAY_BUFFER ) は必要なくなりました。
関連エントリ
・OpenGL ES 3.0 と Vertex Array Object