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

OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object

Buffer と同じように Texture を扱う場合も Bind 操作が必要です。
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