2009年11月24日火曜日

fsincosでプチ高速化

Check
ここ最近、透視投影行列についてお勉強していました。透視投影行列を求めるために、D3DXMatrixPerspectiveFovLH()関数と同じ機能を持つ関数を自前でこしらえるとなると、どうしても速度的に問題が出てきます。

MSDNによるとD3DXMatrixPerspectiveFovLH()関数の中身は以下の通り。
w       0       0               0
0       h       0               0
0       0       zf/(zf-zn)      1
0       0       -zn*zf/(zf-zn)  0
where:
h is the view space height. It is calculated from 
h = cot(fovY/2);

w is the view space width. It is calculated from
w = h / Aspect.
ここで問題になるのがcot(fovY/2)の部分。コタンジェントは tanθの逆数らしいので、つまるところcosθ/sinθ。

毎フレーム呼びたい関数にsin()とcos()関数が両方とも入っちゃってるわけです。実際、素直に書き起こしたコードとD3DXMatrixPerspectiveFovLH()関数の速度を比較してみたら、1000万回のループでおおよそ1.5~2倍くらいの時間差が! ボトルネックは予想通り、sin()とcos()でした。

そこでsin()とcos()の部分だけインラインアセンブラで高速化に挑戦してみることにしました。 使った命令は8087命令セット?なので、まあ今日のx86プロセッサなら問題なく使えると思います。

ただ、この8087命令は使用するレジスタがちと特殊なようで、汎用レジスタからではfsincos命令の引数も戻り値も受け取ることはできないっぽい。長々と書くのもアレなんで、ズバリ答えが載ってるサイトを発見しました。ありがたく利用させていただくことに。

float angle, cosx, sinx;//引用元はdoubleでしたがfloatに変更させていただきました
_asm
{
 fld angle ;レジスタスタックに角度をプッシュ
 fsincos  ;fsincos命令で一気にsin(angle)とcos(angle)を計算。答えはST(0)にcos,ST(1)にsinが入っている。
 fstp cosx ;ST(0)スタックトップからcosをポップ。ST(1)がST(0)にスライドする。
 fstp sinx ;同じくスタックトップからsinをポップ。
}

たったこれだけのコードで、sinθとcosθの値が同時に求められるんですね。これは便利だ。速度もd3dxをぶっちぎりで上回る200%以上の効果。ここまで速いと、エラーチェックとか要るのかなぁとかえって不安になってしまいますが…。まぁ気にしない気にしない…。

 とっても参考になったサイト
SSE2を用いたPOV-Rayの最適化に関する考察
Intel x86 Instruction Reference
SIMPLY FPU

2009年11月17日火曜日

operator<<のオーバーロード

Check
またハマってしまった…。環境はVC++2008です。

ある名前空間内で宣言されたクラスにおいて、operator<<をオーバーロードして標準出力にクラス内の情報をを出力したいと思ったわけですよ。

コードにするとこんな感じ。

namespace hoge
{
class aho
{
public:
  friend std::ostream &operator<<(std::ostream &os, const aho &a);
};
}//!hoge

//以下の記述(というか記述場所)が誤り
std::ostream &operator<<(std::ostream &os, const hoge::aho &a)
{
    return os << "aに関係した何か";
}


ここでなにを勘違いしたのか、私は上記の関数の実装をグローバル空間で行ってしまった。結果は当然、

namespace.cpp(20) : error C2593: 'operator <<' があいまいです。
namespace.cpp(12): 'std::ostream &operator <<(std::ostream &,const hoge:
:aho &)' の可能性があります。
namespace.cpp(6): または 'std::ostream &hoge::operator <<(std::ostream &,const hoge::aho &)' [引数依存の照合を使用して検出しました] の可能性があります
引数リスト '(std::ostream, hoge::aho)' を一致させようとしているとき
 そんな関数ありませんよ、と怒られてしまった。

こんなアホな間違いをする人間はなかなかいないようで、ググってもそれらしき解決策は見当たらない。が、世界レベルだと同じことをやらかす方もいらっしゃるようで、ようやく解決。

早い話が、hogeという名前空間内で宣言したクラスにおいてfriend関数を宣言したならば、その実装もhogeの中でやれってことだそうです。以下ちゃんとコンパイルできたコード。

namespace hoge
{
class aho
{
public:
    friend std::ostream& operator<<(std::ostream &os, const aho& a);
};

//hogeの中で実装するのが正解
std::ostream &operator<<(std::ostream &os, const hoge::aho &a)
{
    return os << "('A`)";
}
}//!hoge
言い訳ではないですが、operator<<ってなんかグローバルな演算子な気がしてですね…。そのくせ、普段operator=とかoperator*とかは平気でメンバ関数として実装してるわけですが…。自分のアホさに辟易させられた小一時間でした。こんなミスする人は滅多にいないでしょうが、何かの役に立てば幸いです…。

2009年11月15日日曜日

VC++でインラインアセンブラ

Check
おもろそうな記事を見つけたので試してみた。
生でアセンブル言語を使用するのはちと敷居が高いので、インラインアセンブラから入ってみようという企画。ゆとりPGの自分にはちょうどいい。

用語の整理
  • ロード メモリからレジスタに値を読み込むこと
  • ストア レジスタの値をメモリに書き込むこと
  • 汎用レジスタ いろんな計算に使える便利なやつ。EAX~EDXまであるようだ

とりあえず実践。出力は普通にcoutを使うよ。
#include <iostream>
using namespace std;
// iに100を代入して表示するだけのプログラム
int main()
{
    int i;
    __asm
    {
        mov i, 100;
    }
    cout << i << endl;
}
次にレジスタを使ってみる。
#include <iostream>
using namespace std;
// レジスタに0xFFFF0000をロードしてiにストアするプログラム
int main()
{
    int i;
    __asm
    {
        mov eax, 0xFFFF0000;
        mov i, eax;
    }
    cout << hex << i << endl;// ffff0000
}
配列を扱うときは、インデクスの値に注意。インデクスを1つ増やしてもアセンブラでは1バイトしか移動しないようだ。そこで、Cでいうところの[sizeof(int)]と同じ役割を持つ[type 変数名]をインデクス値にかけてやる必要がある。
#include <iostream>
using namespace std;
//配列arrayに値を代入するプログラム
int main()
{
    int array[4];

    __asm
    {
        mov array[0], 100;
        mov array[1*type array], 200;//1*sizeof(int)と同じ。これでポインタが4バイト移動する
        mov array[2*type array], 300;
        mov array[3*type array], 400;
    }
 
    for(int i=0;i<4; ++i)
    {
        cout << "array[" << i << "] = " << array[i] << endl;
    }
    return 0;
}

みんな大好きポインタ篇
#include <iostream>
using namespace std;
int main()
{
    int i;
    int *p = &i;//pにiのアドレスを格納
    __asm
    {
        mov ebx, p;ポインタ用のレジスタEBXにpをロード
        mov [ebx], 100;*pに100をストア
    }
    cout << i << endl;//100
    return 0;
}

ポインタが指す実体を参照したい場合は、レジスタ名を[]で囲むといいようだ。ポインタ変数pに対する*pと同じことが、レジスタEBXに対する[EBX]で行えるというわけだ。

なお、__asmブロック内ではC/C++のコメント//や/**/が使えるほか、;の後ろも1行コメントとして認識されるらしい。

構造体も扱えちゃう
#include <iostream>
using namespace std;
//適当に構造体を作成
struct FOO
{
    int val1, val2;
};

int main()
{
    FOO a;//実体を作る
    FOO *p = &a;//aへのポインタ
    __asm
    {
        mov ebx, p//aのアドレスをレジスタEBXにロード
        mov [ebx]a.val1, 100/*ポインタを参照してa.val1に100をストア*/
        mov [ebx]a.val2, 200;同様にa.val2に200をストア
    }
    cout << "a.val1 = " << a.val1 << endl;// a.val1 = 100
    cout << "a.val2 = " << a.val2 << endl;// a.val2 = 200
    return 0;
}
次回は演算を勉強しよう…

2009年11月14日土曜日

templateクラスでoperator<<をオーバーロード

Check
templateクラスHogeのインスタンスfugaを、
Hoge<int> fuga;
std::cout << fuga << std::endl;
みたいに出力しようと思えばoperator<<をオーバーロードする必要があります。
んで、
#include <iostream>
using namespace std;

template<class T>
class Hoge{
public:
    friend ostream &operator<<(ostream &os, const Hoge &h);//decl※実はここが間違い
   T elem;
};
//impl 
template<class T> ostream &operator<<(ostream &os, const Hoge<t> &h){
    return os << h.elem;
}
とか書いてみたところ見事にリンクエラーを食らってしまった。
error LNK2019: 未解決の外部シンボル "class std::basic_ostream<char,struct std::char_traits><char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits><char>> &,class Hoge<int> const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABV?$Hoge@H@@@Z) が関数 _main で参照されました。
要するにヘッダに宣言したoperator<<(declのところ)と、実装したoperator<<(impl)が別物だと解釈されているようだ。

色々試してみたところ宣言部に間違いがあったようで、正しくは以下のようになります。
template<class T>
class Hoge{
  //関係ないところは省略
  friend ostream &operator<<<T>(ostream &os, const Hoge &h);
};
ほとんどの部分が一緒なんですが、よーく見てみると、operator<<の後ろに<T>なる文字が入っています。マジわかりづらい。

テンプレートは便利なんですが、私のような素人にとっては大変な危険も伴う諸刃の剣のように思えてなりません。てか素直にtoStringメソッドでも作ったほうがよかったのかもしれん…。

どうでもいいがこの記事書くのに異様に時間がかかってしまった。理由はエスケープ文字。<やら>やらがたくさん出てくるおかげで&lt;なんかをあちこちに書かなきゃいかんかった。今度からここを使おう
HTMLエスケープ(HTMLエンコード)ツール

2009年11月12日木曜日

Pythonでソースのエンコードを指定する方法

Check
Pythonのコードを書いているとき、日本語でコメントをつけた部分にエラーが出て怒られた。
おそらくエンコードの指定が不正、もしくは不明なのが原因だと思われる。

こいつに対処する方法はソースコードの文頭に、以下の記述を行えば良いようだ。

# -*- coding: mbcs -*-
Windowsならたいていはこれで大丈夫?

参考

2009年11月9日月曜日

Syntax Highlighter

Check
ブログなどで見かけるソースコードが綺麗に表示されてるあれです。

色々やってみたのに最終的にウィジェット追加ボタンをクリック一発ポンだったとは…。

テストも兼ねてトライ
/**
 * test
 */
char *test()
{
    return "aho";
}
こりゃ便利だ。晒す価値のあるコードが書けるかどうかは別として…。

さらに詳しい情報 

2010/06/02追記
上記のSyntaxHighlighterのバージョンは1.5だったのですが、2.0をBloggerで使用する方法が下記のサイトに掲載してありました。
Awesome code syntax highlighting made easy | Carter Cole's Blog

タグ付けの方法がちょっとだけ変わった
class = "cpp"  -----------> class = "brush: cpp"
ので注意!
結局ブログの全記事のHTMLソースを書き換えるはめになった。

2009年11月7日土曜日

チュートリアル動画

Check
ニコ動にBlenderのチュートリアル動画 が上がっていた。これは…ありがたい…。
基本的なことから解説してくれているので、まさに今の自分にぴったり。

Blender導入

Check
我が家のマシンにBlender2.49bが入った。オランダ発のフリー3DCG開発環境だそうで、優れた機能と変態的なUIを持つことで有名、だそうだ。

以下にWindowsXP32bitでの導入手順を示す。

1.Python2.6を入手する(任意)


これがないと動かないので、Python2.6以降を入手しよう。
プラグインが動かなくなるだけで、必須ではないようです。まあでも、あった方が色々便利だと思う。
http://www.python.jp/Zope/download/pythoncore
現在最新版は2.6.2のようなので、インストーラ付きのpython-2.6.2.msiを選択。

2. Blender2.49bを入手する


http://www.blender.org/download/get-blender/
windows使ってるなら、windows32bitsを選択すればおk。

Python、Blenderの順にインストールしていく。とりあえずこれでインスコは完了。あとはBlenderの日本語化と簡単な設定変更。

3.Blenderの日本語化

Blenderを立ち上げたら、画像の部分を引っ張って設定パネルを表示する。

1.Lauguage&Fontのタブを選択。
2.SelectFontでフォントを選択する。
デフォルトだとC:\WINDOWS\Fonts\あたりにフォントは入ってるんじゃなかろうか。ディレクトリ操作がWindowsとはかけ離れているので注意。上の階層に上がるときはPボタンを押し、決定はダブルクリックではなく、ENTERキー押下だということに気をつけよう。

3.Language:Englishとなっている部分をクリックして、Language:Japaneseを選択。下にある3つのボタンを押せば日本語化は完了。最後にCtrl+Uを押して設定の変更を保存しておこう。これを忘れると次回起動したときにまた同じことをやるハメになる。

以上、導入と簡単な設定について書いてみた。

備忘録がわりに

Check
主にプログラミング・3DCGのことでも書いていこうか。