OpenGLのTextureについて

OpenGLのTexture周りの機能について、改めて勉強したいと思います。

TextureObjectとSamplerObjectについて

OpenGLのTextureObjectには、Textureのフォーマット情報とデータの実体に加えて、サンプリング時に使用される、Samplerの設定が格納されます。これに対して、SamplerObjectに格納されるのは、Samplerの情報のみが格納されます。
TextureObjectとSamplerObjectは、同時に、同じTextureUnitにBindすることが出来ます。この場合は、SamplerObjectのSamplerの設定が使用されます。
SamplerObjectに0をBindすることで、SamplerObjectをUnBindできます。この場合は、TextureObjectに設定されたSamplerの設定が使用されます。

SmaplerObjectに格納されるのは、

GL_TEXTURE_WRAP_S
GL_TEXTURE_WRAP_T
GL_TEXTURE_WRAP_R
GL_TEXTURE_MIN_FILTER
GL_TEXTURE_MAG_FILTER
GL_TEXTURE_BORDER_COLOR
GL_TEXTURE_MIN_LOD
GL_TEXTURE_MAX_LOD
GL_TEXTURE_LOD_BIAS
GL_TEXTURE_COMPARE_MODE
GL_TEXTURE_COMPARE_FUNC

に関する情報です。

また、TextureObjectでは、同様のパラメーターに関しては

glTexParameter*();

を用いて設定していましたが、SampleObjectでは、

glSamplerParameter*()

を用いて設定します。APIが異なるので、注意が必要です。

ARB_bindless_texture拡張について

従来のOpenGLでは、ShaderからTextureを参照するには、TextureObjectをTextureUnitにBindする必要がありました。TextureUnitの数には限りがあり、一般的なGPUでは16個程度のTextureUnitにTextureをBindして利用していました。
一方で、BindlessTextureの機能を用いた場合、ShaderへTextureObjectのHandleを、Uniformや頂点Attributeなどの何らかの方法で伝え、Shader側はそのHandleよりTextureを参照します。従って、BindlessTextureの場合は、同時参照できるTextureの数に明示的な上限は存在しないのが大きな特徴です。

Bindless用のHandleの取得

Bindless用のHandleは、従来のTextureObjectのIDとして使用されていた、GLuint型ではなく、GLuint64型となります。取得には下記のAPIを使います。

GLuint64 glGetTextureHandleARB(GLuint texture);
GLuint64 glGetTextureSamplerHandleARB(GLuint texture, GLuint sampler);

glGetTextureHandleARB()は、単に指定されたTextureObjectのHandleを返します。
glGetTextureSamplerHandleARB()は、Samplerに関する指定に、引数samplerで指定されたSamplerObjectを利用します。
glGetTextureSamplerHandleARB()で参照されたSamplerObjectは、各ステートの変更が出来なくなるので、注意が必要です。

Residentの指定

Handleを取得後、実際に、Shader内でHandleを通じてTextureにアクセスするには、そのTextureがResident(GPUのシェーダーユニットよりアクセス可能なメモリに配置されている状態)である必要があります。
これを指定するAPIが以下になります。

GLvoid glMakeTextureHandleResidentARB(GLuint64 handle);
GLvoid glMakeTextureHandleNonResidentARB(GLuint64 handle);
GLboolean glIsTextureHandleResidentARB(GLuint64 handle);

glMakeTextureHandleResidentARB()でTextureObjectをResidentに指定し、glMakeTextureHandleNonResidentARB()で、その指定を解除します。
glIsTextureHandleResidentARB()で、現在のResident指定の状態を取得できます。
glMakeTextureHandleResidentARB()のAPIは、現在のところ、メモリ不足によるResident指定の失敗を定義していません。しかし、Resident指定は、実質的にTextureObjectをGPU側のメモリに固定することになると思います。従って、GPUメモリの使用量に関しては注意が必要と思われます。

また、

GLboolean glAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences );

というAPIがOpenGLのCompatibility Profileに存在しますが、BindlessTextureと直接の関係はありません。

Shaderからの参照方法

ShaderからBindlessTextureを参照するには、とにかく64bit型のHandleの値を入手すれば良い訳です。その手段は問われません。
ただし、今のところ最も一般的な手法としては、GL_SHADER_STORAGE_BUFFERに、Handleの配列を格納しておく手法があげられると思います。
Handleの配列を用意しておくと、Textureのインデックス値を何らかの方法でShaderに渡すだけで、Shaderは、この配列を通じでTextureを参照することが出来ます。
UniformBufferObjectでも同様の手法を取ることができます。しかし、GL_SHADER_STORAGE_BUFFERの方が、ランダムアクセスを想定されており、サイズの上限も大きいです。
ひとつ、注意点として挙げられるのが、BindlessTextureへアクセスする際にはHandleの値が必要で、これを格納するために、Shader内で通常の演算などにも利用されるレジスターが消費されるということです。しかも64Bitの値なので、1枚のTextureへのHandleを保持するだけで、2つのレジスターを消費することになります。レジスター消費に関しては常に意識しながらShaderを書く必要があります。

glGenBuffers(1, &buf);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buf);
glBufferData(GL_SHADER_STORAGE_BUFFER, TEX_COUNT * sizeof(GLuint64), handleArray, GL_STATIC_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buf);
#extension GL_ARB_bindless_texture : require

layout (std430, binding = 0) readonly
buffer CB0
{
uint64_t texHandles[];
};

void main()
{
Out.v4Color = texture(sampler2D(texHandles[In.DrawID]), In.v2TexCoord));
}

考察

SamplerObjectとBindlessTextureの機能について見てみました。
OepnGLのSamplerObjectには、現在のところ、Shader内で明示的にアクセスして、TextureとSamplerをそれぞれ組み合わせて使う機能が提供されていません。
また、現状のSamplerObjectは、一度BindlessTextureのHandleに参照されると、値の変更が出来なくなります。これは、実際には、TextureObjectにSamplerの状態が管理されていることを意味するのかも知れません。
この点はDX11と異なる点です。OpenGLではTextureObjectがSamplerに関する情報を保持している限り、この制限は無くならないかもしれません。