OpenGL + CUDA interopのコードを読む

しばらく前に、CUDAを使ってGPU上に作ったデータをそのままGPU上でpixelの配列にしてそのまま画面に描画しようとしていた。

cuda_practice/ising-show at master · ToruNiina/cuda_practice · GitHub

例えばこれなのだが。これは、isingモデルをシミュレーションしつつ描画するコードだ。シミュレーションしているのでスピンの状態がGPUに乗っている。OpenGLのバッファもGPUの上にあるのなら、CPUに戻してから再度GPUにコピーするのは無駄以外の何物でもない。GPU上で直接RGBの配列を作って、それを描画すれば良い。

というわけでここではcudaGraphicsGLRegisterImageを使ってOpenGLのrender bufferをcudaのgraphics resourceにバインドしている。そこからcudaArray_tを取り出して、 cudaBindSurfaceToArrayを使ってsurface<void, cudaSurfaceType2D>に変換し、そこにsurf2Dwriteで書き込んでいる。具体的なコードは長くなるので上のレポジトリを見てもらうのが早い。モデルが単純なので「画像をGPUで作ってそれを直接ウィンドウに描画する」と言う目的のノイズになる部分はほとんどない。

で、上のコードには問題がある。cudaBindSurfaceToArrayだいぶ前から非推奨なのだ。「動くから良し!」で諦めたい気持ちはあるが、非推奨にするからにはより良い方法が提供されているはずだろう。そっちを使った方がいいに決まってる。特に自分の趣味プロジェクトなら、締め切りも要件もないのだし。

で、さまよっていたらこんなレポジトリを見つけた。

github.com

これを読めばdeprecatedでないやり方がわかるのではないかと思って読んでみた。ちなみにこのレポジトリではdriver APIが叩かれているので、上のレポジトリで私が叩いているruntime APIと名前が違う。だがだいたい対応するものがあり、概ねdriver APICUxxxはruntime APIcudaXxxに対応している。全てがこの規則通りと言うわけでもないが、まあ目安として。

まず、ここでOpenGLのバッファたちをCUgraphicsResourceにbindし、CUarrayを取り出している。ここまでは同じだ。

gl_cuda_interop_pingpong_st/simulateCUDA.cpp at master · nvpro-samples/gl_cuda_interop_pingpong_st · GitHub

実際、非推奨なのはcudaBindSurfaceToArrayなので、問題なのはCUarrayに書き込む方法だけなのだ。このあとCUarrayをどうするのか追いかけていこう。

このCUarrayはここでCUsurfref型のm_surfWriteRefという変数に関連づけられている。

gl_cuda_interop_pingpong_st/simulateCUDA.cpp at master · nvpro-samples/gl_cuda_interop_pingpong_st · GitHub

さらにm_surfWriteRefcuModuleGetSurfRefによってmodule"volumeTexOut"という名前で関連づけられている。

gl_cuda_interop_pingpong_st/simulateCUDA.cpp at master · nvpro-samples/gl_cuda_interop_pingpong_st · GitHub

moduleって何だ? そしてこれ以降m_surfWriteRefは一切使われておらず、糸が途切れてしまった。

どうなってるんだ? と思って書き込んでいそうな方を見てみると、volumeTexOutというグローバル変数が突如現れている。

gl_cuda_interop_pingpong_st/heatEquation.cu at master · nvpro-samples/gl_cuda_interop_pingpong_st · GitHub

レポジトリ内を検索しても、ここと、これをコンパイルしたであろうptx、そして

cuModuleGetSurfRef( &m_surfWriteRef, m_module, "volumeTexOut" )

の呼び出ししかない。

このあたりで、m_module"volumeTexOut"というのがこのグローバル変数(ファイルを跨いでいるので通常アクセスできない)を指しているとしか考えられなくなった。moduleはおそらくGPUで走るプログラム全体か、翻訳単位あたりを指しているのか、と思ってドキュメントを検索してみた。

The driver API provides an additional level of control by exposing lower-level concepts such as CUDA contexts - the analogue of host processes for the device - and CUDA modules - the analogue of dynamically loaded libraries for the device.

Programming Guide :: CUDA Toolkit Documentation

どうやらそういうことらしい。そしてmodule経由でsurfaceを取り出すためにdriver APIの方を叩いているのだということもわかった。

どうなっているのかわかったし、多分これで真似してみる準備は概ね整ったと思うが、力技すぎないか!? driver APIを使って別のファイルにあってスコープも分かれているグローバル変数を文字列で名前指定して取得する? 本当にそれでいいのか、nvidiaよ……。