2009年04月09日

スレッドロック関数のエクスポート

前回、Blenderのマルチスレッドレンダリングで排他制御をするために必要な関数を探しました。
Blenderのソースコードのディレクトリ中の「source/blender/blenlib/intern/threads.c」にあるBLI_lock_thread()、BLI_unlock_thread()という2つの関数です。今回は、これらの関数をBlenderの外部のプログラム、テクスチャプラグインで使用できるようにエクスポートしてみたいと思います。

しかし、このような変更を加えるというのは、勝手にプラグインの仕様を変えてしまうようなものですが大丈夫なのでしょうか。結果から言うと全然大丈夫ではなくて、このような変更を加えたBlender本体とそれに合わせて作成されたテクスチャプラグインは、その組み合わせでのみ正しく動作します。修正版のBlenderで普通のテクスチャプラグインを使うことはできると思いますが、修正版Blender用のテクスチャプラグインを通常のBlenderから使用することはできません。
これについては、最終的にはvectexの機能をBlenderの本体の中に組み込むというようなことも考えてはいますが、とりあえずはこの方法でvectexをマルチスレッドレンダリング対応で使用できるようにしたいと思います。

Blenderのソースコードの中でテクスチャプラグインに関連する部分は、「/source/blender/blenpluginapi」というディレクトリの中に入っています。
pic090408_01.jpg pic090408_02.jpg

ここには、Blenderの実行ファイルのある場所に置かれている「plugins/include」ディレクトリの中にあるヘッダファイルと同じものがあります。その他に、「intern」というディレクトリがあって、そこにはpluginapi.cというファイルが入っています。
pic090408_03.jpg

pluginapi.cには、plugin.h、util.h、iff.hなどでエクスポートされている関数の本体が書かれています。スレッドをロックするための関数をエクスポートするためには、このファイルの適当な場所にエクスポートする関数を追加すればいいようです。他のエクスポートされている関数の記述をみると、実際にエクスポートされている関数名はBlenderの内部で使用している関数名とは微妙に違った名前に変更されているようです。今回は、lock_thread()、unlock_thread()というようにしてみました。Blenderの他の場所で同じ名前の関数が使われていないことを確認してあります。
pic090408_05.jpg
LIBEXPORT void lock_thread (int type)
{
    BLI_lock_thread(type);
}

LIBEXPORT void unlock_thread (int type)
{
    BLI_unlock_thread(type);
}
こんな風に関数の中でBLI_lock_thread()関数を使用するためには、この関数のプロトタイプ宣言の書かれているBLI_threads.hをインクルードする必要があるのですが、今回はたまたま他の部分でスレッドに関連する機能が使われていたらしく、すでにインクルードされていました。
pic090408_04.jpg

この他に、pluginapi.cというファイルの最後には、エクスポートされている全ての関数名が記述されているpluginapi_force_ref()という関数がありますので、この部分にも修正を加えます。
pic090408_10.jpg
        ...
        GET_INT_FROM_POINTER( gamwarp ) +
        GET_INT_FROM_POINTER( lock_thread ) +
        GET_INT_FROM_POINTER( unlock_thread );
}

pluginapi.cに行う修正はここまでです。
次に、ヘッダファイルにプロトタイプ宣言を追加します。エクスポートする関数の内容によって、plugin.h、util.h、iff.hのどのファイルに記述するべきかが違ってくるようですが、今回はmallocN()、callocN、freeN()などと同じ場所に置いておくのがよさそうに思いましたのでutil.hに書くことにしました。こちらにはBLI_lock_thread()関数に渡す引数「LOCK_IMAGE」と、ついでにもう一つ「LOCK_CUSTOM1」の定数のマクロ定義も追加しておきました。
pic090408_07.jpg
...
#define LOCK_IMAGE    0

#define LOCK_CUSTOM1    1
...
LIBIMPORT          void lock_thread (int type);
LIBIMPORT          void unlock_thread (int type);
...

そして、Linuxでは使用しませんが、Windowsでプラグインを作成する際に使用するplugin.DEFファイルにも修正が必要です。ファイルの最後に今回エクスポートした関数名を追加しました。
pic090408_08.jpg
...
lock_thread
unlock_thread
以上で、ソースファイルの修正は終了です。あとは、これらの修正が反映された状態でソースファイルからBlenderをビルドするだけです。Blenderをソースコードからビルドする方法については、以前書いたこちらの記事に参考になるページへのリンクがあります。
ということで、ビルド済みの修正版Blenderをこちらに用意しました。

Blender_modified_Linux64_090408.tar.gz
Blender_modified_Linux32_090408.tar.gz
Blender_modified_win32_090408.zip

今回は、SVNリポジトリからではなく、TARファイルとして配布されているBlender2.48aのソースコードを使用してビルドしています。
Linuxでのビルドはとくに問題なくできましたが、現在のところWindowsではFFMpeg関連のライブラリで問題が起こるようなので、コンパイルの設定でFFMpegを外しています。また、Windows版ではVisual C++ 2008 SP1の再頒布可能パッケージが必要なようです。Linuxでは、OpenAL、OpenEXRなどいくつかのライブラリをインストールしていないと動かない可能性があります。その場合、blenderの実行ファイルに対して「ldd」コマンドを実行すると、問題のあるライブラリを調べることができます。
pic090408_09.jpg

この修正版のBlenderは機能的には普通のものと何も変わらないので、現在のところはまったく意味がないです。次回は、この修正版のBlenderを使用してvectexに排他制御の組み込みを行う予定ですので、それまでもうしばらくお待ちください。
posted by mato at 00:07| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする

2009年04月05日

Blenderのマルチスレッドレンダリングでの排他制御

前回はSVGテクスチャプラグインのvectexを、プラグインバージョン5から6へとバージョンアップして、マルチスレッドレンダリング時にノイズが出る問題を回避できるようにしました。しかし、vectexではそれ以外にもマルチスレッドレンダリング時にレンダリングの途中でフリーズしてしまうという問題があります。解像度別に用意されるテクスチャマップを作成する際にスレッド間での競合が起こっていると想像できますが、なんらかの方法で競合部分に排他制御を組み込む必要がありそうです。

しかし、排他制御というのはスレッド本体の記述と対になって動作するものですので、Blender本体のソースコードを調べないで勝手に作ってしまうということはできません。しかし、逆に言えばBlender本体にはすでにマルチスレッドレンダリングに対応する排他制御が組み込まれているはずですので、それをそのまま真似するだけで済む可能性が高いということでもあります。

vectexではテクスチャマップを作成する際に、解像度別にいくつものテクスチャを用意するミップマップという機能を使用しています。BlenderのButtons Windowで「Shading」「Texture buttons」を選択し、「Map Image」パネルを表示させると、左上に「MipMap」というボタンがあります。(Texture Typeで「Image」を選択している場合)
pic090404_01.jpg

Blenderの標準機能の中にミップマップがあり、マルチスレッドレンダリングを行った場合でも問題なく動作しているということを考えると、このミップマップを処理している部分で何か参考になるようなコードが見つかりそうな気がします。ここからは、Blender2.48aのソースコードを使用して、Blenderの内部でマルチスレッドレンダリングの排他制御がどのように行われているのか調べていきたいと思います。

Blenderのソースコードは、展開するとサイズが86MBにもなる大規模なものです。
pic090404_03.jpg

しかし、実際にはコンパイル時にリンクされるライブラリまで含まれた状態で配布されています。Blender本体のソースコードはこのうちの一部分だけで、トップディレクトリの「source」の中にあります。(このsourceディレクトリだけでも28MBもありますので、大規模なものであることに変わりはないですが...。)
pic090404_02.jpg pic090404_05.jpg

このsourceディレクトリの下にはさらにいくつかのディレクトリがありますが、とりあえず細かいことは考えずにsourceディレクトリ以下の全てを対象として、grepコマンドでミップマップに関係しているソースファイルを探してみましょう。

ターミナルウィンドウを開き、blender-2.48aのトップディレクトリに移動して以下のようにコマンドを入力してみます。

grep -r mipmap *

grepのすぐ後の「-r」はディレクトリがあったらその中に対して再帰的に処理するというオプション、次の「mipmap」が検索したいキーワード、最後の「*」は検索の対象となるファイルで、この場合はすべてのファイルを指定しています。
pic090404_06.jpg

こうすると結果がずらっと表示されます。今回は250行程度なのでこの状態で端末画面をスクロールして探すこともできますが、下のようにしてテキストファイルにコマンドの結果を書き出しておけば、テキストエディタの検索コマンドなどを使うことができて便利かもしれません。(実際にこのコマンドで出力したテキストファイルtext090404_01.txt)

grep -r mipmap * > tmp.txt


このコマンドの出力を見るときには、まず、そのファイルがどこにあるのかというファイルのパスが、一つの重要な手がかりになります。
出力の最初の方の「extern/ffmpeg」「extern/glew」などexternディレクトリの下にはBlenderが使用している外部のライブラリが入っているので、今回は関係ありません。
「po/zh_CN」「po/ko」などのディレクトリには他国語対応のためのファイル、「source/gameengine」はゲームエンジン関連のファイル...というような感じで、あまり関係なさそうな部分は無視します。
pic090404_07.jpg

ずっと進んでいくと「source/blender/render」というディレクトリの下に「intern/sourece/imagetexture.c」というファイルがあります。「source/blender/render」というディレクトリには、レンダラのソースコードがまとめられているようです。現在探しているミップマップという機能は、Blenderのレンダラのテクスチャマップの機能の一部ですので、このファイルは探しているコードに関連している可能性が高そうです。
pic090404_08.jpg

実際に、テキストエディタで開いてみます。
テキストエディタには文字列の検索機能があると思いますので、「mipmap」を検索すると先ほどのgrepコマンドの検索で表示された行を探すのは簡単です。
pic090404_09.jpg pic090404_10.jpg

このファイル中には9ヶ所ほどmipmapという文字列が含まれていますが、imagewraposa()という関数中の655行目からの数行には、探していたミップマップのメモリ確保に関するコードらしきものが含まれています。
    /* mipmap test */
    if(tex->imaflag & TEX_MIPMAP) {
        if(ibuf->flags & IB_fields);
        else if(ibuf->mipmap[0]==NULL) {
            BLI_lock_thread(LOCK_IMAGE);
           
            if(ibuf->mipmap[0]==NULL)
                IMB_makemipmap(ibuf, tex->imaflag & TEX_GAUSS_MIP);

            BLI_unlock_thread(LOCK_IMAGE);
        }
    }
IMB_makemipmap()という関数がそれです。
そして、その前後をはさむようにして「BLI_lock_thread(LOCK_IMAGE)」「BLI_unlock_thread((LOCK_IMAGE)」という関数が使われています。
「thread」「lock」「unlock」という単語が使われていることから考えて、Blenderのマルチスレッドで排他制御を行っている関数である可能性が高いです。

ミップマップのメモリ確保らしき関数の中身については、関数名に「mipmap」の文字列が含まれていますので、grepでの検索結果の中に入っています。「source/blender/imbuf/intern/filter.c」というファイルの中に書かれているようですので、一応こちらも確認しておいた方がいいかもしれません。
pic090404_11.jpg pic090404_12.jpg

ちょっと見ただけでは詳しいことはわかりませんが、IMB_allocImBuf()という関数でメモリの確保をしていると思われます。
pic090404_13.jpg

メモリ確保の処理はとりあえず今回は必要ありませんので、スレッドの排他制御のコマンドの方に戻ります。
この関数を使うには、引数の「LOCK_IMAGE」がどのようなものなのかを調べる必要がありそうですし、関数の定義がどうなっているのか見ておいた方がよさそうです。先ほどと同じようにして、grepコマンドで「BLI_lock_thread」をキーワードとしてソースファイルを検索してみましょう。この関数の定義が書かれている場所以外にも、この関数を使っている別のレンダリング処理なども見ておくと、何かの参考になるかもしれません。

grep -r BLI_lock_thread *

今度はかなり検索結果が絞られているようです。19行だけが表示されました(text090404_02.txt)。
pic090404_16.jpg

BLI_lock_thread()の定義は「source/blender/blenlib/intern/threads.c」に書かれているようです。そして、「source/blender/render」ディレクトリの中でシャドーバッファ、SSS、AOなどのレンダリング処理の際に、この関数が使われていることが分かります。それ以外にもフルイドシミュレーションの処理に使われたりもしているようです。

BLI_lock_thread()の定義が書かれているthreads.cを開いてみます。
pic090404_14.jpg pic090404_15.jpg

BLI_lock_thread()とBLI_unlock_thread()の定義部分(line:220-234)です。
void BLI_lock_thread(int type)
{
    if (type==LOCK_IMAGE)
        pthread_mutex_lock(&_image_lock);
    else if (type==LOCK_CUSTOM1)
        pthread_mutex_lock(&_custom1_lock);
}

void BLI_unlock_thread(int type)
{
    if (type==LOCK_IMAGE)
        pthread_mutex_unlock(&_image_lock);
    else if(type==LOCK_CUSTOM1)
        pthread_mutex_unlock(&_custom1_lock);
}
pthreadのミューテックスを使っています。pthread_mutex_lock()関数は、引数としてpthread_mutex_t型のポインタを取るようです。(pthread_mutex_lock()のmanページ)
同じファイルの「_image_lock」「_custom1_lock」の定義部分(line:95-98)です。
static pthread_mutex_t _malloc_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _image_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _custom1_lock = PTHREAD_MUTEX_INITIALIZER;
static int thread_levels= 0;    /* threads can be invoked inside threads */

詳しいことはわかりませんが、BLI_lock_thread()関数の引数は、先ほどのgrepの検索結果などから考えて、テクスチャ関係の処理では「LOCK_IMAGE」を使い、それ以外には「LOCK_CUSTOM1」を使っているようです。

とりあえず、ここまででマルチスレッドレンダリングの排他制御に必要な記述が大体分かりました。vectexのソースコードの中で、マルチスレッドレンダリング時にメモリ管理で問題が起こっている部分を探し、その前後を
  1. BLI_lock_thread(LOCK_IMAGE);
  2. BLI_unlock_thread(LOCK_IMAGE);
この2つの関数で囲めばいいようです。

そして、問題はこの関数をvectexのソースコードの中で使えるのかということです。
テクスチャプラグインのサンプルclouds2.cは、ソースコードの中でhnoise()という関数を使っています。hnoise()関数はBlenderの本体の方で定義が書かれていて、「plugins」「include」フォルダの中にあるplugin.hの中でエクスポートされることで、Blender本体とはまったく別にコンパイルされるテクスチャプラグインからでも使用できるようになっています。
このようにBlenderからエクスポートされている関数はhnoise()以外にもいくつかあり、util.hにはメモリ確保に関する*mallocN()、*callocN()、freeN()などの関数、iff.hにはイメージバッファ関連の多数の関数があります。

次回はそれらを参考にしBLI_lock_thread()、BLI_unlock_thread()という2つの関数をBlenderからエクスポートして、テクスチャプラグインから利用できるようにしてみたいと思います。
posted by mato at 04:33| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする

2009年03月31日

vectexをマルチスレッドレンダリング対応にする(1)

Blenderのテクスチャプラグインをマルチスレッドレンダリングに対応させる方法が大体つかめてきましたので、今回からvectexをマルチスレッドレンダリングに対応させていきたいと思います。

ところで、実際にBlenderを起動してvectexに付属しているサンプルデータをスレッド数4などでレンダリングした場合にどうなるのかを確認してみると、vectexでのマルチスレッドレンダリングの問題はこれまで見てきたプラグインほど簡単ではないことに気づきます。

プラグインリポジトリにあるテクスチャプラグインでは、マルチスレッドでレンダリングした場合にノイズが発生することはあっても、レンダリングそのものは最後まで終了します。一方、vectexでは、マルチスレッドでレンダリングすると、ノイズがどうとかいう以前にレンダリングのプレビュー画面が真っ黒のままでフリーズしてしまいます。
このフリーズするという問題は、これまでのテクスチャプラグインでは見られなかった、全く別の原因で起こっていると考えるべきでしょう。
pic090331_01.jpg

vectexを使ってスレッド数を色々と変えてレンダリングをテストしていると、マルチスレッドでレンダリングしてもフリーズしないこともあるのが分かります。
一度スレッド数1でレンダリングをした後、カメラやレンダリングサイズなどの設定を変更せずにスレッド数を増やした場合、マルチスレッドでのレンダリング中にフリーズしないようです。

なぜ、こんなことが起こるのでしょうか。
vectexは内部的にミップマップという仕組みを使っているようです。ミップマップはオリジナルのマッピング画像より小さい解像度の画像を複数用意しておき、レンダリングの際に描画されるポリゴンの面積に合わせて最適なマッピング画像を使用することでレンダリング品質を高めるというものです。

スレッド数を1にしてレンダリングを行うと、必要な解像度のテクスチャ画像が作成されてキャッシュに保存されます。保存されたテクスチャ画像は、メモリが足りなくなると使用頻度の低いものから削除されていきますが、それまではずっと保存され続けます。
おそらく、vectexで初回のレンダリング時にマルチスレッドレンダリングを行うと、複数のスレッドがそれぞれに解像度別のテクスチャ画像を作成しようとするため、メモリ管理になんらかの問題が起こってレンダリングの途中でフリーズしてしまうようです。

あらかじめスレッド数を1にした状態でキャッシュにテクスチャ画像を用意しておけば、その後はマルチスレッドでレンダリングを行うことができるということであれば、
  1. AO(アンビエントオクルージョン)やレイトレースシャドー、SSSなど、比較的処理が重く、さらにvectexのキャッシュに影響がでないような設定をオフにした状態でスレッド数を1にしてレンダリングを行い、必要なテクスチャ画像を作成しておく。
  2. その後で必要な設定をすべてオンにし、マルチスレッドレンダリングを行う。
というようにすれば、レンダリングの重いデータの場合にレンダリング時間を少しは短縮できそうです。

ただし、現在の状態ではマルチスレッドにしてレンダリングを行うとノイズが出てしまいます。プラグインリポジトリのテクスチャプラグインに行ったのと同じ対処をしておけば、vectexでもノイズは出なくなるはずです。
pic090331_02.jpg

複数スレッドが解像度別のテクスチャを作成しようとしてフリーズが起こるという問題は、Blender本体の方でマルチスレッドでのレンダリングがどのように実装されているのかを調べないと、対応することは無理そうです。

通常、マルチスレッドプログラミングではこのような問題に対応するための様々な手段が用意されています。例えばmutexというものを使うと、一つのスレッドが処理している間、他のスレッドが干渉しないように処理が終わるまで待たせるというようなことができます。
Blenderの本体のソースコードの中でも、Jpegなどのビットマップ画像のテクスチャにミップマップ処理を行っている部分では何らかの対策を行っているはずですので、vectexでもそれを参考にして対応することができると思います。これについては次回以降で行う予定です。


ということで、ここからはマルチスレッドレンダリングでのノイズに対する対応を行っていきます。
vectexはagg、expatという2つの外部のライブラリを使用していますのでコンパイルに必要なソースファイルの数はかなり多いのですが、実際にテクスチャプラグインのためのソースコードとして書かれているのは「vectex.h」「vectex.c」「cectex_agg.cpp」の3つのファイルだけです。

Blenderのテクスチャプラグインで必要になるデータやplugin_tex_doit()などの関数は、vectex.cの中に書かれています。また、「blender」フォルダの中に、Blenderの「plugins」フォルダに入っているplugin.hなどのヘッダファイルが置かれています。今回、変更を行うのはこの2ヶ所となります。
pic090331_04.jpg pic090331_05.jpg

「blender」フォルダの中のplugin.hの39行目を見てみると、
#define B_PLUGIN_VERSION    5
となっていて、プラグインのバージョンは5として作成されていることが分かります。
pic090331_06.jpg

プラグインのバージョンが5となっているので、plugin_instance_init()関数はすでに使用されています。プラグインリポジトリのテクスチャプラグインはほとんどがバージョン3として作成されていたため、バージョン3からバージョン5への修正、バージョン5からバージョン6への修正の2つ分のバージョンアップを行う必要がありました。
vectexは約1年前に作成されていて比較的新しいこともあって、バージョンアップ自体の作業はむしろ他のテクスチャプラグインより簡単で、plugin_tex_doit()関数の引数にresult[]配列を追加するという作業だけでよさそうです。

まず、plugin_tex_doit()関数のプロトタイプ宣言の部分です。これは、vectex.cの111行目にあります。
...
/* Current frame number */
float cfra;

void plugin_init(void);
void plugin_callback(int);
void plugin_instance_init(Cast *);
int  plugin_tex_doit(int, Cast *, float *, float *, float *);
...
...
/* Current frame number */
float cfra;

void plugin_init(void);
void plugin_callback(int);
void plugin_instance_init(Cast *);
int plugin_tex_doit(int, Cast *, float *, float *, float *, float *);
...
次に、関数の本体の方です。こちらは579行目になります。
...
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt)
{
    stype = 0; /* prevent unused variable warning */

    if (! cast->enable)
    {
...
...
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt, float *result)
{
    stype = 0; /* prevent unused variable warning */

    if (! cast->enable)
    {
...
ソースコードの修正はこれだけです。
あとは、vectexのソースコードの「blender」フォルダの中身をまるごとBlender2.48aの「plugins」フォルダのものと入れ替えれば、コンパイルの準備が完了します。

ということで、とりあえずプラグインバージョン5からバージョン6へと更新したvectexができました。この時点での、修正したソースコード(vectex.c)とバイナリファイル(Windows 32bit、Linux 32/64bit)をここにまとめておきます。

vectex_modified090331.zip

(WindowsのバイナリファイルはVisusal C++ 2008 Expressで作成していますが、オリジナルのものとはバージョンの異なるexpatライブラリを使っています。オリジナルは1.95.8ですが、今回は2.0.1のwin32用のものを使っています。コンパイルは正常にできていますが、もしかすると実行時にバージョンの違いによる問題が起こる可能性があります。)

これで、初回のレンダリングでスレッド数1でレンダリングを行っておけば、キャッシュに残っているテクスチャ画像を使って、マルチスレッドレンダリングができるようになりました。
しかし、これではやはり不便なので、次回以降で初回のレンダリング時にフリーズする問題を解決したいと思います。
posted by mato at 22:00| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。