テクスチャへの代入

ドキュメント内 _openglcl (Page 111-124)

GPUによる描画処理では多数のバーテックスシェーダーおよび多数のフラグメントシェー

2

ダーが並列実行する。しかもシェーダープログラムはプログラミング可能であるから、そ

3

れをうまく活用すれば描画に限らず様々な並列計算ができるのではないかと期待できる。

4

ここで述べるテクスチャとフラグメントシェーダーを用いる並列計算はその自由度が高

5

く、CUDAOpenCLにおける計算方法の先駆けになった。

6

テクスチャは全てのシェーダープログラムから共通に参照可能な配列のように見える。特

7

にテクスチャからRGBA値を取得する(サンプリングする)方法を線形補間(GL LINEAR

8

ではなく、最近傍(GL NEAREST)に設定する場合には、テクスチャ内の個々の生のRGBA

9

値をそのまま読み出すことができるため、配列要素を読み出すイメージに非常に近い。

10

前章のテクスチャデータをは読み出し専用であるが、もしテクスチャへデータを書き込

11

むことができるならば、配列要素データを読み込み、配列要素へデータを書き込む一連の

12

操作ができる訳であるから、2次元配列のデータ並列演算ができるだろうことは想像に難

13

くない。実際、可能である。テクスチャへの書き込みは、元々はより高度な3D CGを実

14

装するため1に導入されたのだが、これを用いれば任意のデータ並列演算ができることに

15

気づいた研究者に利用されるようになっていく。その演算性能が圧倒的であったため、そ

16

の後、CUDAOpenCLなどのGPGPU専用のプログラミング環境が開発された。

17

この章ではGPGPU草創期のOpenGLによるデータ並列計算の方法を振り返る。

18

ホストプログラム

バーテックス シェーダー プログラム

フラグメント シェーダー プログラム

画像出力

CPU

GPU

ラスタ ライザ

テクスチャ attribute 変数

フレームバッファ オブジェクト (FBO) (a)

(b) (c)

図4.1: テクスチャへデータを代入する経路

図4.1はテクスチャへデータを書き込む仕組みの概念図である。

1

OpenGLではフレームバッファを仮想化/抽象化した機能を有するフレームバッファオ

2

ブジェクト(以下、FBO)が利用できる。これを用いてテクスチャをフレームバッファに

3

見せかける。手順は以下の通りである。

4

1. まず、FBOをひとつ生成する。

5

2. FBOをテクスチャと接続する(図4.1の(a)の矢印)。

6

3. フラグメントシェーダーの出力をFBOに接続する(図4.1(b)の矢印)

7

4. 通常の描画処理を行う。これによって、フラグメントシェーダーの計算した各画素

8

のRGBA値は、本来の画像バッファへは格納されず、テクスチャの対応する画素へ

9

格納される。

10

5. テクスチャデータをCPUのメモリへ読み出し(図4.1の(c)の矢印)、ホストプロ

11

グラムでその内容を確認する。

12

4.1.1 GPGPUのためのプログラムの全体構成

1

これ以降は画像描画を行わないため、2章の図2.3で設定したプログラムの全体構成を

2

図4.2のように変更する。ここに、

3

initSystem()initData() は、これまでとほぼ同じ役割の関数である。

4

compute() は、これまでのdisplay()と同様の処理を行うが、描画を行わないため、関

5

数名を変えた。

6

showResults() は、計算結果を表示するために新たに導入する関数である。

7

ここまで用いてきたglutDisplayFunc()glutMainLoop()は描画専用の関数であるた

8

め、これ以降は用いない。

9

なお、いくつかの情報は複数の関数で共有する必要があるため、既に導入済みの大域変

10

数 sp(シェーダー実行可能プログラムへのポインタ)、大域定数NUM POINTS(頂点数)

11

に加え、大域変数width、height(テクスチャの幅、高さ)、texZp(出力用テクスチャ

12

へのポインタ)を導入した(図4.2の上方を参照)。

13

プログラミング作法として大域変数の乱用は避けるべきであることは言うまでもないが、

14

この例題では引数を関数間でだらだらと引き回すよりも大域変数を用いた方がよいと判断

15

した。

16

4.1.2 GPGPUのためのinitSystem()

17

この章で用いるinitSystem()は図4.3の通りである。ここまでのinitSystem()(図

18

3.44)からの変更点を中心に述べる。

19

1. 関数呼び出しglutInit()、glutInitDisplayMode()、1行飛んでglutCreateWindow("Test

20

Window") は変更しない。

21

2. 関数呼び出し

22

glutInitWindowSize(width,height)

23

では、フレームバッファの大きさをテクスチャの大きさと正確に揃える必要がある

24

ため、幅、高さとして大域変数 widthheightを参照している。

25

3. 関数呼び出し

26

#include "All.h"

// 以下の大域変数は複数の関数で共有する。

Shader *sp; // シェーダープログラムオブジェクトへのポインタ

const int NUM_POINTS = 3; // 頂点数

const int width = 5; // テクスチャの幅 const int height = 7; // テクスチャの高さ

RWTexture2D *texZp; // 出力用テクスチャへのポインタ

void initSystem() {

/* ここにシステムパラメータの設定等の初期化処理 */

}

void initData() {

/* ここに入力データなどの初期化処理 */

}

void compute() // display()に相当

{

/* ここにGPU実行処理 */

}

void showResults() // 新たに導入

{

/* ここに結果の出力等の処理 */

}

int main() {

initSystem();

initData();

compute();

showResults();

return 0;

}

図4.2: GPGPUのためのホストプログラムのひな形

void initSystem(int argc, char *argv[])

{

glutInit(&argc,argv);

glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);

glutInitWindowSize(width,height);

glutCreateWindow("Test Window");

glClearColor(0.0, 0.0, 0.0, 0.0);

#if defined(WIN32) glewInit();

#endif

GLuint fb;

glGenFramebuffers(1, &fb);

glBindFramebuffer(GL_FRAMEBUFFER, fb);

sp = new Shader("shader.vert","shader.frag");

sp->use();

}

図4.3: GPGPUのためのinitSystem() glClearColor(0.0, 0.0, 0.0, 0.0)

1

は出力用テクスチャの各要素の初期値を0.0 に設定している。もちろん必要に応じ

2

て0.0 以外に設定してもよい。

3

4. 関数呼び出し

4

glGenFramebuffers(1, &fb)2

5

は、図4.1で紹介したフレームバッファオブジェクトを生成し、OpenGLコンテキ

6

ストに登録する。

7

5. 関数呼び出し

8

glBindFramebuffer(GL FRAMEBUFFER, fb)3

9

は、これ以降のフレームバッファへの設定は、参照番号 fb のフレームバッファオ

10

ブジェクトをターゲットにして行うことを宣言している。参照番号を用いて各種設

11

定を行う方法は既にシェーダー実行可能プログラム、バッファ、テクスチャについ

12

2void glGenFramebuffers(GLsizei n, GLuint *ids);

3void glBindFramebuffer( GLenum target, GLuint framebuffer);

void initData()

{

Position2D pos[NUM_POINTS];

pos[0].x = -0.5; pos[0].y = -0.5;

pos[1].x = +0.5; pos[1].y = +0.5;

pos[2].x = +0.5; pos[2].y = -0.5;

ArrayBuffer ab((float*)pos,2,NUM_POINTS);

sp->bindArrayBuffer("position",&ab); // これより上は前章までと同じ texZp = new RWTexture2D(0,NULL,width,height);//入出力兼用テクスチャ

sp->bindTextureW(texZp); // 出力用に設定

}

図4.4: テクスチャ出力のためのinitData()

て解説したが、フレームバッファオブジェクトについても同様に行う仕様になって

1

いる。

2

6. シェーダープログラムのコンパイル&リンク、およびuse()の呼び出しはこれまで

3

と同じである。

4

4.1.3 テクスチャ出力のためのinitData()

5

この節で用いるinitData()の内容は、図4.4の通りである。以下、解説する。

6

1. 冒頭から関数呼び出し

7

sp->bindArrayBuffer("position",&ab) までの6行は、前章のinitData()(図

8

3.45)と同じである。三角形を描画する設定である。

9

2. 関数呼び出し

10

texZp = new RWTexture2D(0, NULL, width, height)

11

は出力用テクスチャオブジェクトを生成し、そのアドレスをポインタtexZpへ代入す

12

る。RWTexture2Dクラスについては後述する。第1引数はテクスチャの装置番号4

13

第2引数は初期データの格納されているメモリエリアの先頭アドレス、第3、第4

14

数はテクスチャの幅、高さを指定する。装置番号は0以上の整数であり、0から順に

15

struct RWTexture2D {

GLuint texID; // 参照番号 GLint num; // 装置番号

RWTexture2D(int tnum, void* data, int w, int h); // コンストラクタ void readData(void*, int w, int h); // GPUデータをCPUへ転送

};

図 4.5: クラスRWTexture2Dの定義

付番せねばならない。第2引数がNULL の場合には、CPUからGPUへデータ転送

1

を行わない。よって出力用テクスチャでは NULL を用いる。実装の詳細は次項に述

2

べる。

3

3. 関数呼び出し

4

sp->bindTextureW(texZp)

5

はテクスチャをフレームバッファに接続する。詳細は次項に述べる。

6

4.1.4 入出力兼用テクスチャクラス RWTexture2D

7

OpenGLのテクスチャの取り扱いは相当に複雑である。それをできるだけ簡単に利用で

8

きるように、入出力兼用テクスチャのクラスRWTexture2D を設計する。入出力を兼用に

9

したのも複雑さを一度に軽減するためであり、また後にひとつのテクスチャを入力用/

10

力用で次々と切り替えていくために兼用にしている。

11

入力部分は前章の内容そのままであるから、出力部分を追加実装すればよい。ここでは、

12

出力部分の実装について解説する。

13

まず、クラスは図4.5にように設計した。

14

コンストラクタの実装は図4.6の通りである。これは最後の関数呼び出しを除いて図3.24

15

と全く同じである。最後の関数呼び出し:

16

glFramebufferTexture2D(GL_FRAMEBUFFER,

17

GL_COLOR_ATTACHMENT0+num,

18

GL_TEXTURE_2D, texID, 0)

19

5は、参照番号texIDの2次元テクスチャ(GL TEXTUR 2D)を、num番目の装置6として

20

5void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);

6くどいようだが、正確に言えば装置番号ではなく、color attachmentの番号(色接続点番号?)である。

装置番号とcolor attachiment番号は独立に設定できるのだが、煩雑なのでこの講義テキストでは装置番号に まとめている。

RWTexture2D::RWTexture2D(int tnum, void* data, int w, int h){

num = tnum;

glGenTextures (1, &texID);

glBindTexture(GL_TEXTURE_2D,texID);

glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_FLOAT, data);

glFramebufferTexture2D(GL_FRAMEBUFFER,

GL_COLOR_ATTACHMENT0+num, GL_TEXTURE_2D, texID, 0);

}

図 4.6: クラスRWTexture2Dのコンストラクタの実装

void RWTexture2D::readData(void* data, int w, int h){

glReadBuffer(GL_COLOR_ATTACHMENT0+num);

glReadPixels(0, 0, w,h, GL_RGBA,GL_FLOAT,data);

}

図4.7: クラスRWTexture2DのreadData()の実装

当該フレームバッファ7に割り当てる。なお、第5引数にはミップマップのレベルを指定

1

するが、この講義ではミップマップは用いないため、0でよい。

2

次にメンバー関数readData()は、このテクスチャに対応するGPUメモリ上のデータ

3

をCPUメモリ上の第1引数のアドレスへ転送する関数である。readData()の実装は図

4

4.7の通りである。関数呼び出し

5

glReadBuffer(GL COLOR ATTACHMENT0+num)8

6

は、GPUからCPUへデータを読み出す際の読み出し元を指定する。この場合は num番

7

目の装置である。numの値はコンストラクタで初期設定する(図4.6参照)。関数呼び出し

8

glReadPixels(0, 0, w,h, GL RGBA,GL FLOAT,data)9

9

は、読み出し元から配列 data へ実際にデータを読み出す。

10

7直近のglBindFrameBuffer()で宣言されたフレームバッファのことで、この例題ではinitSystem() 作られたフレームバッファオブジェクトのこと。

void Shader::bindTextureR(const char* vname, RWTexture2D* tp){

glActiveTexture(GL_TEXTURE0+(tp->num));

glBindTexture(GL_TEXTURE_2D,tp->texID);

GLint p = glGetUniformLocation(program, vname);

if(p < 0) {

cerr << "texture2d name error: "<< vname << endl;

exit(1);

}

glUniform1i(p, tp->num);

}

void Shader::bindTextureW(RWTexture2D* tp){

glDrawBuffer(GL_COLOR_ATTACHMENT0+(tp->num));

}

図4.8: クラスShaderbindTextureR/W()の実装

void compute(void) {

glClear(GL_COLOR_BUFFER_BIT);

sp->run(GL_TRIANGLES,NUM_POINTS);

}

図4.9: 三角形領域を計算するcompute()の実装

さらに、RWTexture2Dの関連する関数として図4.8の二つのメンバー関数をShaderク

1

ラスへ追加実装しておく。

2

関数bindTextureR()はテクスチャを入力用として宣言するものであり、前章の図3.4

3

のbindTexture()と全く同じ内容である。

4

関数bindTextureW()はテクスチャを入力用として宣言するものである。関数呼び出し:

5

glDrawBuffer(GL COLOR ATTACHMENT0+tp->num)10

6

は、フラグメントシェーダーの出力先をフレームバッファオブジェクトの tp->num 番目

7

の装置へ接続する関数である。tp->num番目の装置は、既に特定のテクスチャと接続され

8

ているから、結果、この関数呼び出しをもってフラグメントシェーダーの出力先とテクス

9

チャが接続されたことになる。

10

ドキュメント内 _openglcl (Page 111-124)