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 | このブログの読者になる | 更新情報をチェックする
×

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