新しいPhysXが出るらしい

PC watchの記事にCellFactor: RevolutionのフルバージョンがAGEIAのサイトから無料で落とせるとのことが書いてあったので、久々にAGEIAのサイトに行ってみた。
日本ではPhysXを持っている人は少ないようで、さらにPhysX SDKで何か開発している人は結構少ないと思う。けどAGEIAの開発者フォーラムにはそこそこ人がいるようである。
DiscussionのDual PhysXのスレにはPhysXは複数枚差しで動作することができるとのことが書いてあった。しかしまだ十分にテストされていないらしい。
さらにはQ3あたりに新しいPhysXのチップをリリースするというようなことが書いてあった。新しいチップは処理は速いが機能は今あるPhysXのチップと同じとのこと。基本的にはPhysXは新しい機能はソフトウェア、ファームウェアの更新で追加する方針となっている。

PC watchの記事には株式会社エルザジャパンからPhysXを搭載したボードが発売されるとあるが、そのチップはきっと一年ほど前にリリースされたPhysX
チップと同じものだと思うのだが。今PhysXカードを一枚持ってるので、それも買って二枚差しをやってみたいなという気もするけど3万円はちょっと高くないかと。

環境にやさしいプログラミング言語

プログラマーとして地球環境のことを考えると、C++アセンブリ言語のような実行効率指向な言語がいいかもしれない。
例えば、Javaで書いたプログラムだと計算に1時間掛かるのが、C++で30分で計算できるなら電気代が半分になる。
PHPで書いたサーバプログラムを走らせるのに10台の鯖が動いていたとする。それをC++アセンブリ言語を使って、メモリやCPUの使用量を減らすような最適化を行う。そして1台の鯖で動かすことができるようになれば電気を使う量を1/10に減らすことができる。


こんなことをふと考えたけど、本気でこんな理由の元にC++アセンブラをあえて選んでいる人はいるだろうか。

GPUPPURのソースコードを公開

Sourceforge.jpにGPUPPURプロジェクトを登録し、GPUPPURのソースコードを公開しました。


GPUPPURプロジェクトページ:
http://sourceforge.jp/projects/gpuppur/


GPUPPURのソースコードの特徴は

  • C++で書かれている
  • 今のところVisual C++ 2005でしかコンパイルできない
  • 変数の等の命名規則がBoostライブラリとほぼ同じ
  • STL、Boost等を使っている
  • templateがちょっと多いかも

GPUPPUR自体はまだまだ実用的なライブラリにはなってませんがGPUPPURに含まれるいくつかの部品はそこそこ使えるんじゃないかと思います。


gpuppurut

  • ウィンドウの初期化、管理、破棄やマウス、キーボードの入力処理等を楽にしてくれるライブラリ
  • OpenGLUTやDXUTをOpenGLDirect3D両方を使えるようにしたもの
  • 複数ウィンドウ、フルスクリーンに対応
  • 一応GPUPPURの開発、テスト用に作ったので機能があまり多くない


include/gpuppur/3dmath/vectorNd.hpp
template
struct VectorNd

  • N次元ベクトルテンプレートクラス
  • ベクトル計算に必要な関数は一通りあると思う
  • ベクトル同士の掛け算、割算(加減算と同様に各要素同士の乗除)とか4次元ベクトルのx,y成分を2次元ベクトルとして扱える等の機能もある
  • boost::arrayにベクトル計算関数をくっつけただけのクラスなので、reinterpret_cast(VectorNd())のようにreinterpret_castするだけでOpenGL,Direct3D,PhysX SDKの関数の引数に渡すことができる


include/gpuppur/3dmath/matrixRxC.hpp
template
class matrixRxC

  • R行C列の行列テンプレートクラス
  • 3Dで必要になる行列関連の関数は一通りあると思う
  • 行列の要素をメモリに格納する方法としてrow-major orderとcolumn-major orderがありますが、両方をサポート
  • 行列の各行や列をベクトルとして扱うことができる
  • reinterpret_castでOpenGL,Direct3D,PhysX SDKの関数の引数に渡せる


sourceforgecvsソースコードをアップロードしようと思ったのですが、ssh関連がうまくいってないのでまだアップロードしてないです。orz

特定条件下ではレイトレはGPUラスタよりも高速?

gpuppur2007-03-06

GPUPPURはGPUとPPUを活用して高速化を狙ってたのですが、PPUに必要なAPIがまだ公開されていないために結局GPU+CPUでレンダリングを行なっています。
それで実際には、多くの場合OpenGLDirect3Dを使ったほうが高速にレンダリングできるのですが、特定の条件下ではGPUPPURのGPUPPURayはそれらよりも高速にレンダリングできます。
レイトレーシングには大き過ぎる512x512サイズの画像へ少な過ぎる70kトライアングルのStanford bunnyをレンダリングする場合では、もちろんGPUによるラスタライズ法には敵いません。しかし、縦横が数十ピクセルの画面へ100k~1000kトライアングルのモデルをレンダリングする場合ではどうでしょうか。実際に実験してみました。

実験に用いたモデル:
sphere.obj
頂点数 98306
三角形数 196608

leg6.obj
頂点数 528753
三角形数 1057344

GPUPPURayではレイトレーシングPhysX SDKに用意されている関数(raycastClosestShape)を使っていて、その関数でポリゴンを扱うにはCooking APIで前処理してから使わないといけません。
本当はThe Stanford 3D Scanning RepositoryのArmadilloを使おうと思ってたのですがCooking APIがうまく処理できず、結局自分でモデルを作りました。
ハイポリなモデルでは滑らかな形じゃないと駄目っぽいですね。

実験PC:
Core2Duo T5500 (1.66GHz)
Geforce Go 7600 256MB
memory 1GB
windows vista

実験方法:
sphere.objとleg6.objをそれぞれ解像度を変化させながらレイトレーシング法、ラスタライズ法でレンダリングし、1frameの平均レンダリング時間を測定します。
レイトレーシングにはGPUPPURayを使い、ラスタライズにはGPUPPURasを改造したもの(単純にメッシュをレンダリングするだけ)を使います。
ラスタライズするときは頂点単位でライティングの計算を行ないます。(今回の場合、ピクセル単位でライティングしたほうが高速かも)

実験結果
数値は1frameをレンダリングするのに掛かった時間(㍉秒)です。

		sphere.obj	leg6.obj
		Raytracing	Raytracing
8x8		0.54		0.45
16x16		1		1.3
32x32		3.4		4.7
64x64		14		17
128x128	48		63
256x256	190		240

		sphere.obj	leg6.obj
		Rasterizing	Rasterizing
8x8		1.9		13
16x16		1.9		13
32x32		1.9		13
64x64		1.9		13
128x128	1.9		13
256x256	1.9		13

結果:
実験結果をグラフにしたものが右上の図です。(小さくて見難いですが)
ちなみに両対数グラフになっていて、横軸が画像サイズ、縦軸が時間となっています。
右上斜め(/)になっているのがレイトレーシング法でレンダリングしたときの時間変化で、2本の線の上のほうがsphere.obj、下のほうがleg6.objをレンダリングしたときのものです。
水平な2本の線がラスタライズでレンダリングしたときのものです。頂点処理が多くてピクセル単位の処理が少ないので画像サイズが変わっても処理時間はほとんど変化無いです。

ラスタライズではトライアングル数が増えると大きく処理時間が変化していますが、レイトレーシングではそれほど大きく変化していません。
画像サイズが小さいとき(グラフの左側)では処理時間がレイトレーシングのほうが短くなっています。
それにトライアングル数が多い方では32x32までのサイズでラスタライズよりも高速です。


今回の実験では実用的でないような小さい画像、ハイポリでの実験だったのですが、トライアングル数と処理効率がもっとあがればレイトレーシングが実用的なサイズでラスタライズを上回ることができるかもしれません。ですが、GPUもどんどん高速になっているのでラスタライズが高速という状況がずっと続くかもしれません。
でも、特定の条件ではレイトレーシングがラスタライズより高速だということが実験で示すことができてよかったです。

g++のエラーメッセージを読み易くする

c++言語でテンプレート関連のコンパイルエラーは非常に読みにくいです。
ですがインデントすれば結構読み易くなります。
例えば以下のようg++からのコンパイル エラーメッセージ

src/gpuppur/cpu/cpu_raytracer.hpp:479: error: `void gpuppur::cpu_raytracer::erase(const typename gpuppur::cpu_raytracers::detail::instance3d::iterator&) [with UserDataType = gpuppur::material]' is private

をインデントすると

src/gpuppur/cpu/cpu_raytracer.hpp:479: error: `void gpuppur::cpu_raytracer
<
	UserDataType
>::erase
(
	const typename gpuppur::cpu_raytracers::detail::instance3d
	<
		UserDataType
	>::iterator&
) 
[
	with UserDataType = gpuppur::material
]' is private

と読み易くなります。

そこでこのようなインデントを自動的にやってくれるc++のプログラムを作りました。
標準入力から読み込んだ内容を自動的にインデントして標準出力に出します。<>()[],
の文字に関してだけ整形を行ないます。
少し面倒なのでインデントはtabで行なうようにしか作ってません。
g++ ver3.4.4でコンパイル、動作確認です。
こういうのはRubyとかPerlで書けば楽なのかもしれませんが、全然使ったことがないorz

#include <algorithm>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

using namespace std;

class indenter
{
private:

	int get_lev() const
	{
		return accumulate(this->levels.begin(), this->levels.end(), 0);
	}

	string level_up(const char c, const int index)
	{
		string ret;

		ret.push_back('\n');
		ret.append(this->get_lev(), '\t');
		this->levels[index]++;
		ret = (ret+c)+'\n';
		ret.append(this->get_lev(), '\t');

		return ret;
	}

	string level_down(const char c, const int index)
	{
		string ret;

		ret.push_back('\n');
		this->levels[index]--;
		ret.append(this->get_lev(), '\t');
		ret.push_back(c);

		return ret;
	}

public:

	indenter(const string& target_list):
		target_list(target_list), levels(target_list.size()/2)
	{
	}

	string convert(const string& src)
	{
		fill(this->levels.begin(), this->levels.end(), 0);

		string ret;

		string::const_iterator i=src.begin();
		for(;i!=src.end(); ++i)
		{
			for(int j=0; j<target_list.size(); j+=2)
			{
				if(*i==this->target_list[j])
				{
					ret+=this->level_up(this->target_list[j], j/2);
					goto contiunue;
				}
				if(*i==this->target_list[j+1])
				{
					ret+=this->level_down(this->target_list[j+1], j/2);
					goto contiunue;
				}
			}

			switch(*i)
			{
			case ',':
				ret.append(string(",\n"));
				ret.append(this->get_lev(), '\t');
				break;

			default:
				ret.push_back((*i));
				break;
			}

		contiunue:;
		}

		return ret;
	}

private:

	string target_list;
	vector<int> levels;
};

int main()
{
	string line;

	indenter conv("<>()[]");

	while(getline(cin, line))
	{
		cout <<
		conv.convert(line)
		<< endl;
	}
}

NVIDIAとATIのGLSL対応の差

今までGPUPPURをNVIDIAのグラフィックスカードだけで開発していたので、テストのためにATIのRadeonX1650Proを取り付けてテストしてみました。
GPUPPURではあまり複雑なシェーダを使ってなかったので問題無く動きましたが他のプログラムでGLSL関連のエラーがでました。


NVIDIAATIでのGLSL対応具合の違いを以下に書いておきます。
NVIDIAはGeforce6600GT ドライバがNVPerfKit2.2のInstrumented Driver
ATIはX1650Pro ドライバがCatalyst Software Suite ver7.1
で確かめたので他のハード、ドライバだと下記と違うことがあるかもしれません。

  • ATIではconst変数を非const変数から初期化できない。
  • ATIでは配列を{}を使って初期化できない。

nvidiaでは配列を{}を使って、vec3 v[2] = {vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0)};と初期化できる。
ATIではArray constructorを使って、
vec3 v[2] = vec3[2](vec3(...), vec3(...));
と初期化できる。
nvidiaではArray constructorを使うとsyntax errorになる。
GLSLangSpecのver1.10では配列を初期化する構文が決まってないっぽい。
GLSLangSpecのver1.20ではArray constructorによって初期化できるとある。

  • ATIではGLSLでsamplerRECTが使えない。
  • ATIでは%を使えない。

%を使うと
'%' : supported in pack/unpack shaders only
とエラーがでる。


ATIのほうだとPixel Buffer Object(PBO)を使ったとき、glUnmapBufferARBを呼び出した後にglBindTexture(),glTexSubImage2D()を呼び出すとドライバ内でクラッシュします。
今使ってるテクスチャのフォーマットだとPBOを使わなくてもあんまり転送速度に差がでないのでしばらくは普通にglTexSubImage2Dでデータを転送することにしました。


そういえば、NVIDIAのグラボを使っているとき、GPUPPURasのDirect3D実装とOpenGL実装ではOpenGL実装のほうが速かったのですがATIのグラボではDirect3D実装のほうが速かった。

GPUPPURayでレイトレーシングで環境マップ

gpuppur2007-01-16

GPUPPURayを使ってレンダリングした画像のキャプチャです。
GPUPPURayではレイトレーシング法でレンダリングしているわけですが、キャプチャ画像ではレイトレーシング法によって反射を計算しているわけではなく環境マップを使って擬似的に反射を表現しています。
もし、反射レイをキャストして真面目に反射を計算していれば、CPUの計算負荷が増えて処理がかなり遅くなります。
しかし、環境マップの場合では環境マップに関連する処理のほとんどはGPUで行なわれています。GPUPPURayはCPUでの計算がボトルネックになっているのでGPUの処理だけを増やすことによって、全体の処理速度をあまり落とさずに環境マップを実装することができます。

環境マップの場合、テクスチャのUV座標は法線ベクトルから計算するのでGPU内で求めることができます。ですのでGPUPPURayでは頂点毎にUV座標を指定するようなテクスチャマッピングはまだ実装していないのですが、環境マップは実装することができます。