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

OpenGL 3.x/4.x のシェーダー GLSL とメモリアクセス命令

OpenGL のシェーダーはバージョンごとに機能が拡張されています。
バッファやメモリ周りの命令は似たようなものが複数存在しており、
複雑なのでまとめてみました。

● 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