ATIでのOpenGLの罠

NVIDIAカード(Geforce)ではうまく動いていたのに、
ATIカード(RADEON)でうまく動かなくなったというネタです。今回の使用APIはOpenGLです。

症状

ミップマップありのテクスチャで、ミップマップが使用されない。
ベースレベルの画像しか使われない(ことがある)、という現象が発生

原因および対処法

glTexImage2DとglTexParameteriの呼び出し順序が関係する模様です。

glTexParameteriでミップマップを使わない設定(GL_NEAREST)とかに設定して、
ミップマップありのテクスチャを作成&転送した場合、
それはミップマップなしのテクスチャとして作成されてしまう。
ミップマップありのテクスチャを作成したい場合にはミップマップを使用する設定のパラメータを設定しておき、
glTexImage2Dでテクスチャデータを転送する必要がある。

GL_NEAREST設定後、glTexImage2Dでのミップマップ設定は無視されるのか、
その後 GL_NEAREST_MIPMAP_NEAREST に設定を変更しても
テクスチャ自身がミップなし状態でリソース生成されてしまっているため、有効にならない。

まとめ

まさかそんな罠が潜んでいるとは…という感じでした。
確かに設定から考えてみると、ミップマップなしの設定された後テクスチャ転送なので、
最適化の観点から「使わないから余分なものを生成しない」というのは
合っているような気はします。
ちょっと強引な最適化ではありますが。

先日のDirectXの挙動といい、ATIはドライバ内で強引な最適化を施す傾向にあるのかもしれません。


DirectX9のRenderStateからGL読替

DirectX9のRenderState設定がGLのどれにつながるかわからないって話を聞いた。
知っている範囲で書いてみます。

DirectX9 OpenGL
D3DFILL_WIREFRAME glPolygonMode( GL_FRONT_AND_BACK, GL_LINE )
D3DCULL_NONE glDisable( GL_CULL_FACE )
D3DCULL_CW,CCW GL_CULL_FACE有効化してGL_CW,GL_CCWらも設定
D3DRS_SCISSORTESTENABLE GL_SCISSOR_TEST
D3DRS_STENCILENABLE GL_STENCIL_TEST
D3DRS_ZENABLE GL_DEPTH_TEST
D3DRS_ZWRITEENABLE glDepthMaskで設定
D3DRS_STENCILREF glStencilFuncで設定

各種比較関数、ブレンド設定の列挙値は大変なので除外で。
調べてみるとすぐにわかりそうな感じですし。

感想

OpenGL側はDirectXとちがって、必要要素を一括に設定する気配を感じる。
このほうがデバイスに値を流すのには適しているんだろう。

○○さんへ

他にこれは?というのがあれば、コメントにでも~。
知ってる範囲で追記しようかと思います。


DirectX9でATIカードに振り回される

どうもATIのグラフィックボードおよびドライバは
DirectXを正しく使わないと描画が不正になる模様。DrawIndexedPrimitiveでnMinVertex, nMaxVertexを正しく設定しないと、
それだけでポリゴンが壊れて描画されたりすることを確認できた。
ちなみにNVIDIAではこれらの情報を参考にしていないのか、
適当な値を放り込んでもきちんと動いてくれる。

症状としては、全く描画されないか、変な部分とくっついたりしたポリゴンなどが確認できたので、
動作としては未定義動作に近いのかもしれない。

環境

Core i7 870, 4GB
RADEON 5450 1GB(Catalyst 10.7)
Windows7 Ultimate(x64)

まとめ

開発環境では NVIDIAのカードがおすすめ。
ドライバで振り回されることも比較的少なめ。
でもテスト環境にATIのカードを準備しておくとよさそう。
なぜならATIの環境では、正しく命令(API)を使えているかの確認に使えそうだからである。

むしろ、NVIDIAが緩いせいか、NVIDIAのカードでしか動作確認していないと、
ATIの環境に持って行ったときにうまく動かないということが発覚しそうな気がする。

いやはや、勉強になった。


ATIのドライバ

catalyst 10.7の挙動が不満だったので、以前の状態にしようとしてかなり大変だった。
結論として、バージョンをあげてしまったらもう後戻りできないということみたい。DriverSweeperとか使ってみてもだめだったのでどうしようもない。

この結論に至る前にはいろいろとありました。
発生した症状を書いてみます。

  • glTexImage2Dで、アクセス違反により落ちることがある。
    • デバッグで止めながら実行していると発生しにくい。
  • コンパネからのアンインストール中でBSoD発生
    • この時点ですでにいろいろと破壊された可能性は高い。
  • ChoosePixelFormat呼び出しでドライバ内部でアクセス違反発生。

まとめ

やっとまだマシになったのでそのときのメモを。
ドライババージョンをあげるときでもDriverSweeperできれいにしてからあげる。

これによりCatalyst10.7使っても、glTexImage2Dで落ちないし*1
ChoosePixelFormatで落ちることも無くなりました。

しかし、こんな挙動示すなんて…ATIのボード&ドライバは使いにくくて仕方ないです。
NVIDIAのほうがこの部分に関しては優れている気がします。

*1 : ダメでした。動かしていると落ちることが出てきた…


シェーダーのコンパイル失敗時挙動

どうもglGetShaderInfoLogの挙動がおかしい。
シェーダーのコンパイル失敗という挙動を示すようになったのも問題だったりするけど、
この関数の挙動が赤本のサンプルプログラムでは正常動作しない。

原因の推測

このおかしな挙動を始めた前後で変えたことはCatalystのバージョンを変えたこと。
現在は 10.7 を使っている。

そもそも以前の環境ではGLSLのリンクまでうまく動作していた。
ドライバが持つコンパイラ&リンカの挙動が変わったとしか思えない。

症状

あるプログラムのGLSLのコンパイルが失敗するようになった。
そしてエラー処理に突入し、赤本に書いてあるようなコードでエラーログを出しているが、
関数を抜けるとアクセス違反で終了する。

デバッグ

  • NVIDIA環境では旧コードで問題なく動いていた。
  • ATIのドライバ更新する前までは問題なく動いていた。

これらの点から考えると、プログラムの大筋は間違ってはいない。
今回のドライバによるバグが原因と考えられる。

エラー発生ポイントから、glGetShaderInfoLogのログを取得して、解放する部分で何かあるとしか考えられない。

  1. glGetShaderivでログ長取得
  2. ログ格納バッファ確保
  3. glGetShaderInfoLogでログを取得
  4. ログ表示
  5. 格納バッファ解放
  6. 呼び出し元に失敗コードを返却し、処理抜ける

という感じにくんでいるが、ログ格納バッファを解放する段階でアクセス違反していることが判明した。

CRTの中を追いかけていってわかったのは、格納バッファにログを取得した時点で、
メモリ領域を一部破壊しているということだった。
どうもログ長で返された値よりも大きなサイズを書き込みしている模様。
また、glGetShaderInfoLogで取得できるログバッファ書き込みサイズもまたglGetShaderivで返されるログ長と同じなので当てにならない。

末尾にヌル文字でも余計に書き込んでいるのでは?と考え、
取得できるログ長にその分を加算して確保するように変更した。
その結果、このアクセス違反問題は解決した。

結論

OpenGLはドライバ依存になる部分がおおい。
値を信用せず、若干の余裕を持たせてバッファは確保しておく方が
多くの環境で動かすポイントになりそう。

また、NVIDIAだけでなくATIのカードでも動作を試してみることが
OpenGLに至っては必須なのかもしれない。
DirectXだったら両者でそこまで深刻な違いは出ないのかもしれないが。


OpenGLで座標系をいじる

よくある右手系、左手系の変換の話ではありません。DirectXとOpenGLではビューポートの原点位置が異なるのですが、
これがOpenGL拡張を使うと共通化できるのでは!と。

その対応をやってくれそうな拡張が

  • GL_ARB_fragment_coord_conventions

というやつです。
ドキュメントを読むと座標系の原点をどこに設定するか、
ピクセルのサンプリング位置をどうするか、という部分を変更できるようです。

この内容だけ見るとDirectX9の半テクセルずらすという部分とも対応をとることができそうです。
あと、この拡張はOpenGL 3.2に標準として取り込まれたらしいです。

問題はこれはGLSLのシェーダー内で記述があって有効となるようです。

左上を原点としてみる、のテスト

ピクセル位置のほうはひとまず置いておいて、
ビューポートでの原点を変更することを試してみます。

DirectX9では、左上が原点だったので、
OpenGLでもこれにあわせることができるかを確認してみます。

GLSLのプログラム内に記述を書くも、挙動かわらず。。。

まとめ

そもそも変換するという点で間違っていました。
よく見ると”fragment_coord_conventions”であり、”convertion”じゃありませんでした。

というわけでOpenGLのピクセル座標系をいじるものではありませんでした。
この機能は、ピクセルに関する取り決めをするようなそんな程度のものっぽいです。

でもこれきちんと使えば、DirectXの半テクセルずれている系を
OpenGLで再現させることもできるため、エミュレーション的動作には役立たせることが可能かもしれません。


GL拡張のリスト(2)

ドライバを更新して取得してみた。

  • catalyst 10.7

感想

transfrom_feedback2,transform_feedback3がサポートに入っている!
GL_ARB_sampler_objectsもサポート範囲に入っているし、
OpenGL4.0には対応しているってことだろうか。

気になった点

dxt6,7の拡張が消えた!
その代わり、BC6,7用拡張であるGL_EXT_texture_compression_bptcが含まれた模様。
こちらについては、OpenGL Registryに登録があるし、
きっと正しいのだと思う。


続・CubeMapGS

以前の7/29日記で、CubeMapGSの動作を書いたので、
NVIDIAのGeforce9800GTではどうだったんだろうと思ってチェックしてみた。

球体モデルの結果

カード Instancing fps値
RADEON 5450 TRUE 28.2
RADEON 5450 FALSE 38.2
GeForce9800GT TRUE 119.2
GeForce9800GT FALSE 61.2

これだけ見ると世代的には前の9800GTのほうが性能がいい。
またインスタンシングフラグの影響がそれぞれ逆転しているのも不思議。

車体モデルの結果

カード Instancing fps値
RADEON 5450 TRUE 8.8
RADEON 5450 FALSE 9.7
GeForce9800GT TRUE 44.8
GeForce9800GT FALSE 32.6

製品のレンジが違うとはいえ、ここまで差があるのか。
GSはNVIDIAの圧勝です。

まとめ

この状況を見るとGSで6面のキューブマップについては
リアルタイムに毎フレーム処理できるものではなさそうです。
せいぜいシーン初回で描いてしまうとかその程度かなと思います。
他のメリットは、レンダーパスの処理がシンプルになるというのもあるけど、
ローエンドでもこれくらいの形状ならある程度(60fps維持)の性能が出せないと、
安心して使えないですね。


DirectX10を始めました

はじめに

今はもうDirectX11が出て使われ始めていますが、
今更DirectX10の勉強を始めてみました。

今回初ということで、画面のクリアをするプログラムを作ってみました。
これをベースにしていろいろと機能を触ってみようかと思います。

DXGIのセットアップ

DXGIとはスワップチェインとか管理しているレイヤーになります。
ハードウェアデバイス列挙やフルスクリーンへの切り替えなど担当してくれます。
まずはこの初期化を行います。
初期化すると、スワップチェインとD3D10デバイスが取得できます。

やり方は以下の通りです。

初期化に必要なパラメータを構造体に詰めて、D3D10CreateDeviceAndSwapChainを呼び出します。
D3D9の頃と比べるとこの部分は楽になったような気がします。

バックバッファの取得とメインとなるカラーバッファの取得

デバイス生成するとバックバッファが1つ付いてきます。
これを通常の描画先とするために、ビュー(RenderTargetView)を取得します。
その取得している部分は以下のようになります。

バックバッファは2次元テクスチャの扱いで、
この中身にアクセスするためのビューと作成しています。
普段のクリアで使うのは、ビューを用いて行うので、
作成が終わったらpBackBufferは使わないため解放しています。

デプスバッファとステンシルバッファ

カラーのバッファについては標準で付いてきたのですが、
デプスは自分で作らないといけないようです。

デプスとステンシルについては扱い的に2Dテクスチャとなっているようで
まずはこれの作成から開始します。
このテクスチャを作った後はバックバッファと同じようにビューを作成します。

ビューポートの設定と標準の描画先の設定

若干構造体が変わっていますがD3D9のころと中身は変わりません。
ここで先ほど作成した2つのビューをデバイスに設定しておきます。

画面のクリア処理

クリア処理は、デバイスに対して行うという感じではなく、
デバイスに対して、クリアするビューを設定して行う、という感じになっています。

メインのループでこの記述をすれば画面のクリアが行われ、
今回赤い色が表示されます。

後始末

今まで取得した&作成したインターフェースに対して、
Releaseを呼び出していきます。

感想

画面のクリアまでならば、D3D9の頃と比べて遙かに楽でした。
若干概念が変わっている部分については最初こそ戸惑いましたが、
そういうものだと理解してしまえば引っかかるような場所でもありません。

ビューとは、バイナリデータを解釈するものである、と自分は理解しました。
このバイナリデータというものがテクスチャであり、リソース群を示します。
よって、解釈対象であるビューに対してクリアなどの操作指示を行うのだと。

この2段構えな感じがD3D10の特徴なのかもしれないと思います。
そしてこのせいでD3D9ほど素直な感じがしないのだとも感じます。

ソースコード

適当に書いた初回D3D10のソースコードです。
自動生成されるコードに今まで説明した部分付け加えて、
そして、描画ループがまわるようにした程度の簡単なものです。

HelloDX10ソースコード

何かの役に立つかな・・・


テクスチャオブジェクトとサンプラ設定

いつのまにか、OpenGLのテクスチャ挙動もDirectXに合わせる方向になっているようだ。OpenGL 3.0では、glTexParameteriで GL_TEXTURE_BORDER_COLOR を設定することが非推奨となっている。
他にも テクスチャラッピングモードで GL_CLAMP_TO_BORDER を指定することは推奨されていないとなっている。
さらにglTexImage2Dでボーダー指定で 0 を設定することを要求しているようです。

従来のボーダーの処理に関して、実はドライバ内部のソフトウェア処理にて実現し、
ハードウェアとしてはサポートしていない!ということになっているのでは?と疑いを持ったのですが、
そういうわけではないようです。

DirectX10のAPIを調べてみると、ボーダーの色設定はできるのでボーダー自身のサポートがなくなるわけではないようです。
では、OpenGLではどうなるのか調べてみたところ、サンプラーオブジェクトなるものが存在していました。
そのエクステンションの名前は、”GL_ARB_sampler_objects”。
ただし、OpenGL 3.3での拡張となるようです。

サンプラオブジェクトを作成し、テクスチャのバインド前にセットすることで、
拡大縮小フィルタ設定、テクスチャラッピングモード、ボーダーカラーの設定らが可能になるとのこと。
まさにこの挙動はDirectX10のサンプラーオブジェクトと同じだといえます。

残念ながら手元のRADEON環境ではOpenGL3.3対応していないのか、
GL_ARB_sampler_objects の文字列が見当たらなかったので試せていません。
Catalystの更新((10.5で含まれるらしい。また以前のtransform_feedback2,3もここで入るらしい)でサポートされるとのことなので、そのうち試してみようと思ってはいます。