くまりゅう日記

もっと過去の日記
[.NET | BeOS | Blender | COLLADA | fossil | mono | monotone | NPR | OpenGL | PeerCastStation | Riko | Ruby | Silverlight | TRPG | XNA | ゲーム | ゲーム作り | ]

2012-04-05

日記

新しいGeForceが出たけどなんか新しいGL拡張追加されてないんかなーと思ったら早速公開されてたので読んでみた。まああんまり派手な機能は無かったんだけれども。あとついでに最近追加されてた拡張も読んでみた。言う程最近じゃないけど気になったものもちゃんと読んでみた。

[OpenGL] NV_shader_atomic_float

NV_shader_atomic_float

最近はシェーダ内でテクスチャやバッファオブジェクトを直接読み書きしちゃったりできるわけですが、この拡張はその際に浮動小数点数に対して不可分操作をできる拡張です。今までは不可分操作は整数しかなかったんだね。そんなん使うかよとか思ったけど加算とかあればあったで使うか。

[OpenGL] NV_bindless_texture

NV_bindless_texture

描画時にテクスチャを読む場合は

  1. コンテキストのテクスチャユニットにテクスチャ(とサンプラ)を設定
  2. 設定したテクスチャユニットの番号をシェーダに渡す
  3. シェーダからテクスチャを読む

という流れになってたんだけど、テクスチャユニットはコンテキストグローバルなので沢山の描画を流したいときも一気にGPUに流せずいちいち途中でテクスチャのバインドが挟まるのが残念だった。そこでシェーダパラメータとしてテクスチャ(とサンプラ)を渡してテクスチャユニット関係なく読み込めるようにするぜ!という拡張です。

シェーダで使うテクスチャ設定するときにどこのテクスチャユニットに割り当てようかなぁとか、その番号をあらためてシェーダに渡したりとかいまいちめんどうだと思ってたので便利そうな拡張なんだが、思ったよりはめんどうだった。

NV_bindless_textureを使ったときの流れは

  1. テクスチャオブジェクト(とサンプラオブジェクト)のハンドルを取得する
  2. テクスチャハンドルを常駐化する
  3. ハンドルをシェーダに渡す
  4. いつも通りシェーダでテクスチャの読み(書き)をする
  5. いらなくなったテクスチャハンドルを非常駐化する

という感じ。テクスチャオブジェクトとかサンプラオブジェクトの番号をそのままUniformとして渡せれば簡単だったんだがなぁ。一度ハンドルを取得するようになっているのはハンドルが64bitだからのようだ。テクスチャオブジェクトとかサンプラオブジェクトは基本32bitだし、最近のOpenGLでないと好き勝手な番号使えることになっちゃってるからなぁ。あとサンプラとテクスチャを関連付けないといけないというのも理由かもね。

ハンドルを取得する必要があるのはともかく、テクスチャハンドル常駐化ってのがいまいちわからん。 テクスチャを常駐化する(ビデオメモリに載っける)っぽいんだが、失敗しないんだよなこいつ。 ビデオメモリが足りなければ他のテクスチャが非常駐化するから常に成功でもいいのかな。 そもそも1枚でもビデオメモリに載らないテクスチャなんか作れないし。 関係ないけど非常駐化って日本語としておかしいかも。

これであとはsampler2Dとかのuniform変数にハンドルを渡してやるとシェーダでいつもどおりサンプルできるってわけだ。

ついでにこの拡張ではsampler変数を64bit符号無し整数としても扱えるようになる。今までできなかったsampler変数をuniformブロックにつっこんだり、頂点属性として渡したりとかもやりほうだいのようだ。値としてはテクスチャハンドルを指定しておく。

そのうちARB拡張やGL 4.xに入ってくれるとよさそうな拡張だけど、ハンドルとかどうだろうな。

[OpenGL] AMD_vertex_shader_layer / AMD_vertex_shader_viewport_index

AMD_vertex_shader_layer / AMD_vertex_shader_viewport_index

プリミティブをレイヤードテクスチャのレイヤーやキューブマップの面、はたまた複数定義したビューポートのどれかにシェーダ中で振り分けて描画する機能ってのが最近はあるんですが、こいつがジオメトリシェーダ必須なわけですよ。しかし振り分けるだけのジオメトリシェーダを挟むのはめんどくさーいということで頂点シェーダだけで振り分けができるようにするのがこの拡張。

特筆することはなにもないですねー、と思ったがよく考えたらおかしい。ジオメトリシェーダはプリミティブ毎に実行なんで振り分けもわかるんだが、頂点シェーダで三角形のプリミティブを頂点単位で別なレイヤーに振り分けちゃったらどうなるんですか!よく読んだら、どうなるかは実装依存なのでひどいことになりたくなかったらちゃんとプリミティブ単位で同じところに振り分けろや、って書いてあった。インスタンス化描画してインスタンス毎に振り分けみたいなのを想定してるようだ。なるほどたしかにそうするし、ジオメトリシェーダでプリミティブ複製するより楽でもしかしたら速いかもな。ジオメトリシェーダさん涙目。

[OpenGL] WGL_EXT_swap_control_tear / GLX_EXT_swap_control_tear

WGL_EXT_swap_control_tear / GLX_EXT_swap_control_tear

WGL_EXT_swap_control拡張にあるwglSwapIntervalEXT関数では何VSync待ってバッファのスワップをするか決められた。たいていは垂直同期を普通に待つ1か、ティアリング気にせずスワップしちゃう0を指定するわけですな。

常に待つとティアリングが発生しないのが嬉しいんだけど、60fpsをちょっと下回ったときに急に30fpsになってしまい(垂直同期周波数が60Hzだとして)、さらに次の垂直同期までしばらーく無駄に待ってしまう。

全く待たないようにすると一時的に処理負荷が上がっても垂直同期を無駄に待ったりしないので時間を有効に使えるんだけどティアリングが発生してしまう。処理落ちしてるときにティアリングが発生するのはフレーム数が急に1/2になるよりかはマシだろうが、困ったことに60fpsを越えてる場合も垂直同期に関係なくスワップするおかげでティアリングが発生してしまう。

さてそこでこの拡張、両者のいいとこ取りをしてくれちゃう。wglSwapIntervalEXTに負の値を渡せるようになるんだけど、たとえば-1を指定すると処理落ちしてる場合は即スワップ、処理落ちしてない場合は1VSync待ってくれる。処理落ちってのは前のスワップから指定した値の絶対値回VSyncを通過してるかどうかで判断ね。なので処理落ちしてる場合はティアリングは発生するけど急にフレームレートが下がりまくったり無駄な時間を待ったりはせず、間に合ってる場合は同期を取ってくれるのでティアリングが発生しなくて嬉しい。

なんかNVIDIAがGeForce GTX 680の発表でさも新機能のように発表してたけど、Rageが出たころにドライバにそういう機能入れてもらったよーとか言ってたし、RadeonはもちろんIntel GPUですら対応しちゃった普通の機能です。NVIDIAの場合はコントロールパネルから垂直同期強制上書きでこの設定できるみたいだけどね。

単純だけど嬉しい機能。使い方も簡単なので是非使ってほしい。

[OpenGL] AMD_pinned_memory

AMD_pinned_memory

普通にmallocとかで割り当てたメモリ領域をそのままバッファオブジェクトとして使えるようにする拡張。 どうもGPUが直接システムメモリにアクセスできる場合に素敵なことになるよ!というのを想定してるようなんだが、仕様としてそれを要求してるわけではない。Radeonの5000系でも使えちゃうのでやっぱりGPUがシステムメモリにアクセスするなんてのは必須ではないようだ。

使い方は簡単でBufferData時にターゲットとしてEXTERNAL_VIRTUAL_MEMORY_BUFFER_AMDを指定すると、以降そのバッファオブジェクトはBufferDataに渡したメモリ領域をそのまま使うようになる。もちろんバッファオブジェクトとして使ってる間はメモリ領域解放しちゃだめだよー。

なんつーか普通にバッファオブジェクトを割り当ててMapBufferするのと大差なくて微妙かなぁと思ったんだけど、よく考えるとそうでもなかった。 OpenGLでシステムメモリ上にあるデータをシステムメモリ上への無駄なコピーをせずに非同期でGPUメモリにコピりたいなと思うと意外にできないんだが、AMD_pinned_memoryを使うとやっと実現できる。

ファイルから読み込んできた頂点データがメモリ上にある時に、これを描画のために頂点バッファオブジェクトにつっこみたい。 いくらか方法はあるが、

  • BufferDataにメモリ上の頂点データを渡してバッファ作成&コピー
  • BufferDataではバッファ作成だけしておいてMapBufferでマップして手動でコピー
  • BufferDataではバッファ作成だけしておいてMapBufferでマップした領域に直接ファイルから読み込みをかける

このどれかだろう。

BufferDataでバッファ確保と同時にコピーするのは単純だが、この方法はでは非同期にコピーができない。 というのもBufferDataを呼んだ直後にメモリ上の頂点データは解放してかまわないことになっているので、ドライバはBufferData内で同期的にGPUにデータ転送する(まあありえないだろう)か、ドライバ内部でシステムメモリ上の領域に同期コピーしてあとでゆっくりGPUに転送しておくかだ。後者は非同期にGPUへ転送してるかもしれないけど、システムメモリ上へのコピーが一回余計に入ってるしやっぱり同期コピーだ。メモリコピーもでかいデータだと結構馬鹿にならん時間がかかったりする。

MapBufferはもう明らかに同期コピーだわな。手動でコピーだし。まあ別スレッドでコピーしておけばいいじゃないと言う意見もあるが、結局マップされた領域ってのはシステムメモリ上にあるわけでUnmapされるとそこからドライバ内部でGPUにゆっくり転送ということになるので、やっぱりシステムメモリ上に一回無駄にコピーしてるなぁ。

ファイルからメモリに読んであるデータをバッファオブジェクトにコピるんじゃなくて、MapBufferした領域を対象に直接ファイル読み込みをすると無駄なコピーは無くていい感じではある。ただこれはめんどい作りである。普通やらんだろう。

といった感じでなかなか悩ましいところなんだが、AMD_pinned_memoryを使うと解決してしまうわけだ。

  1. 頂点データのある領域をglBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, ~)でバッファオブジェクトにする
  2. glBufferData(GL_ELEMENT_ARRAY, ~)で普通に頂点バッファオブジェクトを作っておく
  3. 1のバッファオブジェクトから2のバッファオブジェクトへglCopyBufferでコピる

CopyBufferは非同期でやってくれるので、はい、これでアプリで読み込んだデータをシステムメモリからGPUメモリに無駄なく非同期コピー完了です。まあ気をつけなきゃいけないのは頂点データのある領域はコピーが終わるまで解放したらまずいってことですかね。

つかシステムメモリ上の領域をバッファオブジェクトにした時点で頂点バッファとして描画でもなんでもできるじゃないですか!という意見もあると思いますが、たぶん速度的にあまり嬉しいことにならないんじゃないかと思うのでどうなんだろうね。本当にGPUがシステムメモリを直接見れるシステムなら問題ないだろうけど、今はまだ出来ないよね?Radeonの7970とかならある程度できるんだっけ?

あとこれは思いついただけで試してもないので本当に出来るかどうかわからんが、メモリマップドファイルのメモリ領域を直接バッファオブジェクトにできるとちょっと面白いかも。2GBとかの巨大頂点配列をDrawArraysするだけでファイルからストリーミング描画していけるかもしんない。頂点バッファに限らずテクスチャバッファとかUniformバッファでもいいので出来れば使い出はいくらでもありそうだね。

この拡張はNVIDIAにも是非実装してもらいたいところ。つかGPUから直アクセスである必然は無さそうだし、標準に入んねーかな。


ページのトップへ | トップ «前の日記(2012-03-29) 最新 次の日記(2012-04-09)» | 編集 | kumaryu.net by kumaryu