Intel HD Graphics 4000 とは何だったのか

この記事はGPGPU Advent Calendar23日目の記事です。遅れてますが23日目です。

さて、IvyBridgeのGPUであるIntel HD Graphics 4000(以下HD4000)からはOpenCLがサポートされました。


が、Windowsでしか使えないうえに、理論性能がえーと、http://en.wikipedia.org/wiki/Comparison_of_Intel_graphics_processing_units によると、EU数*4*2*clockと書いてあるので、166.4[GFlops] と、CPUの217.6[GFlops]よりも低くてわざわざめんどくさいOpenCL書くメリットあんの?感がすごいです。



なのですが、このHD4000、他のGPUと違って素晴らしい点が一個あって、
http://intellinuxgraphics.org/documentation.html
ドキュメントが公開されてます。OpenCLが動くようなGPUが実際どうなっているのか、ちゃんと書いてある公開ドキュメントは多分これしか無いと思います。


OpenCLプログラマは是非とも読むべき…とは思わないですが、まあ、そういうのが好きな人は読むとよいと思いますね。


以上。


と、いいたいところですが、実際、数百ページのドキュメントが、17冊とかあって、これ読むのすごい大変で、おそらく読もうと思った大半の人が「どっから読んだらいいかわからん」という状況になって投げ出すのではないかと思います。(僕も3年くらい前から読もうと思ってたがずっと投げ出していた)

今回は、DirectX/OpenGL/CUDA/OpenCLを使わないでGPGPUしましょう、というような感じで、このドキュメントの読みかたみたいなのを書いていこうと思います。


概要的なものを先に読んでおいたり、あわせて読んだりすると理解が早くなるかもしれませんので、以下もあわせて読むとよいかもしれません。(Sandy用ですが多分あんま変わらんかと)

http://www.realworldtech.com/sandy-bridge-gpu/ (日本語訳: http://microboy.seesaa.net/article/302280196.html)

GPUメモリ (GTT/GART)

さて、まずはメモリについて知りましょう。
http://phd.mupuf.org/publication/2012/11/25/a-deeper-look-into-gpus-and-the-linux-graphics-stack/
多分これの図がわかりやすいので、これのP18を見てもらうとして、


GARTは…なんだろう…物理メモリをGPUアドレス空間にマップする物体。GTTは、GPU用のMMUみたいなもんで、アドレス変換をします。
GTTIntel用語ですが、多分AMD/NVIDIAにも似たようなのあるんじゃないかと思います。GTTの詳細は、Vol1 Part2の4. あたりかな…あんまちゃんと読んでないので知らない。

ここで重要なことは、ユーザー空間で見えるアドレスや物理アドレスGPUに渡したところでGPUには意味が無いという点です。
アドレス変換をうまくなんとかするために、GPUにアドレスを渡す場合は、OS/ドライバの力を借りなければなりません。


Linuxでは、この部分をGEMというのがやってくれます。Linux以外は知りません。


GEMは…allocしてmapして…とか見れば大体わかると思うのですが、リロケーションだけ若干ややこしいので解説しておくと…
GEMは、(多分)GPUメモリが足りないとメインメモリに移動するとかを頑張っていて、メモリのアドレスが固定されてません。実際にバッファオブジェクトがどのアドレスに置かれるかは、実行する瞬間のカーネルしか知りません。


これをなんとかするために、GEMは独自のリロケーションの仕組みを持っていて、「バッファ内のこの部分がboへのアドレスを入れる位置だよー」というのを入れられます。
それがdrm_i915_gem_relocation_entryとかですね。

これ結構使うのめんどくさいので、このリロケーションを管理してくれるlibdrmというのがいて、intel_bufmgr.hを使えば、多少は楽になります。それでも面倒ですが。


まあ、メモリ使うだけで面倒な仕組みがあるということです。GEMの詳細は本題ではないので、あとはサンプルコードの空気を読んで納得してください。

バッチバッファ

さて、メモリの使いかたも極めたところで、GPUを動かしてみましょう。


動かして…



みま…しょう…?



どうしたらいいんでしょうね。


答えは、多分、Vol1 Part2 の 「5. Device Programming Environment」に書いてあって、まあ、ちゃんと読んでないので知らないのですが、僕は

あたりを参考にしてます。


今のGPUにはバッチバッファというのが付いてて(昔見たnouveauのも似たようなことやってたので多分どのGPUも一緒)、このバッチバッファ上にコマンドを書き込むと、そのとおりに動いてくれます。

GEMを使う場合は、GPUメモリにコマンドを書いて、drm_intel_bo_exec してあげると、まあなんかバッチ的なのを動かしてくれます。

http://cgit.freedesktop.org/xorg/app/intel-gpu-tools/tree/benchmarks/intel_upload_blit_small.c

あたりがわかりやすいですかねー。これは、メモリからメモリへコピーする処理で、

	/* Render the junk to the dst. */
	BEGIN_BATCH(8);
	OUT_BATCH(XY_SRC_COPY_BLT_CMD |
		  XY_SRC_COPY_BLT_WRITE_ALPHA |
		  XY_SRC_COPY_BLT_WRITE_RGB);
	OUT_BATCH((3 << 24) | /* 32 bits */
		  (0xcc << 16) | /* copy ROP */
		  (width * 4) /* dst pitch */);
	OUT_BATCH(0); /* dst x1,y1 */
	OUT_BATCH((height << 16) | width); /* dst x2,y2 */
	OUT_RELOC(dst_bo, I915_GEM_DOMAIN_RENDER, I915_GEM_DOMAIN_RENDER, 0);
	OUT_BATCH(0); /* src x1,y1 */
	OUT_BATCH(width * 4); /* src pitch */
	OUT_RELOC(src_bo, I915_GEM_DOMAIN_RENDER, 0, 0);
	ADVANCE_BATCH();

この部分が、バッチバッファに書いてる部分です。で、

	intel_batchbuffer_flush(batch);

これで実行ですね。(中でdrm_intel_bo_execしてます)


XY_SRC_COPY_BLT_CMD が、コマンドで、これの詳細は、Vol1 Part4の「1.9.14 XY_SRC_COPY_BLT」です。詳細は…まあいいか各自読んでください。


こんな感じで、バッチバッファにコマンドを投入すると、そのとおり動いてくれます。

EU

さてコマンドの送りかたはわかりました。次はシェーダーを起動したいところですね。


IntelGPUは、EUという汎用プロセッサみたいなのが付いてて、これがシェーダやらOpenCLやらを動かしてくれます。

使いかたは以下のようになります

  1. まず、gen4asm(http://cgit.freedesktop.org/xorg/app/intel-gen4asm/) というのを使って、EU用のバイナリを作ります。
  2. そのバイナリをGPU側のメモリにアップロードします。
  3. 適切な設定をして、3D PRIMITIVEを描画します
  4. 設定したシェーダがEU上で動きます

という感じです。

どうやって設定するかは、
http://cgit.freedesktop.org/~keithp/drm/tree/intel/tests/gen7-3d.batch-ref.txt
あたりを見ると多分雰囲気がつかめて(僕も詳細は理解してない)、途中のkernel pointerとかいうところに、シェーダのアドレスが埋まってて、最後の3DPRIMITIVEで何か描画するんだろーなー、とか、想像できます。


まあ、これだと問題があって、3DPRIMITIVEでシェーダを起動すると、OpenCLのbarrierみたいなのができないので、そのままでは使えないのですが、それは次に置いとくとして、じゃあ、シェーダ用のプログラムって何書けばいいの?というのを先にやりましょう。


EUの詳細は、Vol4に書いてあって、まあ、EUというコアと、そのまわりにshared functionがくっついて、Subsystemというのになってます、的なことが書いてあります。


EUは、shared functionにメッセージを送る機能を持っていて、これを使って、テクスチャ拾ったり、メモリアクセスしたり、別のスレッドと通信したりできますね、的なことがVol4 Part1 1.5 Shared Functionsに、書いてあります。


さて、このEUは、(多分)4clockかけて16要素を同時に計算するという、CUDA的な挙動をするわけなのですが、AoS的なデータに対して効率よく計算できるようになっていて、以下二点が、あんまり他では見かけない構造になってますね。

  • 演算のSIMD幅、レジスタ16要素のうちどの要素を使うかを命令ごとに決められる
  • レジスタを間接参照できる

と、いうようなことが、Vol4 Part3 の 「3. Execution Environment」に書いてあります。


そんなこんなで、EU用のプログラムを書きます。

GPGPU Pipeline

これでOpenCLの実装に必要なものは大体揃いました。あとはbarrierだけです。
barrierは、コア内で同期とかだと、効率良い実装ができなくて、もっと柔軟にスレッド数に応じて同期というのが必要になります。


GEN(IntelGPUの名前)の固定機能は、3D/Media/GPGPUというのを切り換えることで、使える機能が変わって、3Dの場合は、GS/VS/PSをうまいことなんとかしてくれるように、Mediaの場合は、Codecとして使いやすいように、GPGPUを選ぶと、OpenCL用のGPGPUとして使いやすいようになります。
これを切り換えるのが、3DSTATE_PIPELINE_SELECTですね。


ここで、GPGPUパイプラインを選ぶと、以下のことができるようになります。

  • 柔軟な同期
  • 適切なthread id割り当て

多分、詳細は、Vol2 Part2の「1.3.3 Programming the GPGPU Pipeline」とか「1.4.2.4 GPGPU Mode」あたりですね。なんか、説明を見てると、OpenCL用にわざわざ作った感がすごいですね。GPGPUとはGPUを活用するためのものだったはずが、GPGPU用にGPUが拡張されるとき、それは一体何と呼ぶべきなのか…


MEDIA_INTERFACE_DESCRIPTOR_LOADで、カーネルの設定をして、GPGPU_WALKERをバッチバッファにつっこもう!多分これで起動するはずだ。まあ、まだ動作確認してないですけどね!


というわけで、次号、実践/パフォーマンス計測編に続…く…のか?