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

2009年03月28日

テクスチャプラグインのLCCでのコンパイル

前回、プラグインリポジトリのソースファイルを修正して、マルチスレッドレンダリングに対応するようにしてみました。
Ubuntu 8.10 64bit上ではすべてそのままコンパイルできていますが、Windows Vista 32bit上と、Ubuntu 8.10 32bitではいくつかのプラグインで問題が起こりました。今回はこれらの問題のあったプラグインをどのようにコンパイルしたかについて見ていこうと思います。

○Windows Vista 32bitでの問題
まず、Windowsの方です。ほとんどのプラグインは少し前の記事で書いたVisual C++ 2008 Express Editionでの手順で問題なくコンパイルできるのですが、circdots_rgb、fixnoiseという2つのプラグインではコンパイルエラーが起こります。
pic090327_01.jpg

circdots_rgb.c
   ライブラリ circdots_rgb.lib とオブジェクト circdots_rgb.exp を作成中
circdots_rgb.obj : error LNK2019: 未解決の外部シンボル _roundf が関数 _punkt_abstand で参照されました。
circdots_rgb.dll : fatal error LNK1120: 外部参照 1 が未解決です。

fixnoise.c
   ライブラリ fixnoise.lib とオブジェクト fixnoise.exp を作成中
fixnoise.obj : error LNK2019: 未解決の外部シンボル _srandom が関数 _plugin_initで参照されました。
fixnoise.dll : fatal error LNK1120: 外部参照 1 が未解決です。

エラーの内容はこんな感じです。それぞれソースコードの中で使われている「roundf()」「srandom()」という関数が問題になっているようです。これらのソースファイルは、roundf()またはsrand()が「math.h」の中で定義されていることが前提で書かれていますが、Visual C++ 2008でインクルードされるmath.hにはこれらの関数が見当たりません。

このような場合、他にも対応する方法があると思いますが、私は別のコンパイラでコンパイルすることで、とりあえずこの問題を回避しました。もともと、Blender.orgのプラグインの解説ページでは、LCCというコンパイラを使うようにすすめています。

このLCCというコンパイラを使ってテクスチャプラグインのコンパイルを行う場合、Blenderが用意しているhnoise()などの関数を使っていると、プラグインがうまく動作しません。(追記:以下の手順でコンパイルする場合、についての話です。リンクの仕方とかを工夫することでなんとかできるのかもしれません。)
また、古いBlenderに付属する「plugins」フォルダにあるインクルードファイルであればそのままでコンパイルできるのですが、最新版の「plugins」フォルダのものを使うとコンパイル時にエラーが出ます。

ちなみに、LCCコンパイラを使うためには、インストールを行った後でLCCの実行ファイルのある場所「C:\lcc\bin」をOSの環境変数「path」に追加する必要があります。
また、プラグインのDLLファイルがエクスポートするシンボル定義ファイル「tex.def」というファイルを用意する必要があります。こちらは上記のプラグインの解説ページにも書かれていますが、
EXPORTS
LibMain@12
plugin_but_changed
plugin_getinfo
plugin_init
plugin_tex_doit
plugin_tex_getversion
これだけの内容の普通のテキストファイルとなります。

ここから先は、以下のような状況を想定して話を進めていきます。
  1. Blender2.48aの「plugins」フォルダを「C:\blender」というフォルダにコピーします。
  2. 前回用意した修正済みの「circdots_rgb.c」「fixnoise.c」をその中の「texture」フォルダに入れます。
  3. 上記のtex.defファイルを「include」フォルダに置きます。
pic090327_06.jpg pic090327_04.jpg

この状態で、LCCを使用してプラグインをコンパイルするには、コマンドプロンプトから以下のようにコマンドを実行します。

cd \blender\plugins\texture
lcc -Ic:\blender\plugins\include circdots_rgb.c
lcclnk -dll -nounderscores circdots_rgb.obj c:\blender\plugins\include\tex.def

そうすると、いくつかのエラーが表示されます。
Specified export _callocN is not defined
Specified export _callocT is not defined
Specified export _freeN is not defined
Specified export _freeT is not defined
Specified export _mallocN is not defined
Specified export _mallocT is not defined
Specified export _plugin_seq_getversion is not defined

pic090327_05.jpg

この状態でも「circdots_rgb.dll」が作成されますが、Blenderにロードしようとするとエラーが出ます。
pic090327_07.jpg pic090327_08.jpg

同じことを古いバージョンのBlenderの「plugins」フォルダで行えば、エラーが出ずにDLLファイルができます。Blenderにも当然のように正常にロードできます。
pic090327_09.jpg pic090327_10.jpg

ここで、ちょっとBlenderのプラグインのバージョンについての補足です。「plugins」-「include」フォルダの中の「plugin.h」の39行目を見ると、
#define B_PLUGIN_VERSION    6
と書かれています。
pic090327_13.jpg

この部分の数字がプラグインをコンパイルするときにバイナリファイルの「.so」「.dll」ファイルの中に保持されることで、Blenderはプラグインのバージョンを識別しているようです。
Blenderの過去のバージョンのplugin.hを調べてみると、プラグインのバージョンとBlenderのバージョンとの対応は、以下のようになります。

ver.3    2.40 - 2.42
ver.5    2.43 - 2.45
ver.6    2.46 - 2.48

エラーが出るようになったのは「ver.6」以降からのようで、Blender 2.46以降では「plugins」-「include」フォルダの中のヘッダファイルに、どこか問題がありそうな感じです。
エラーの内容を見ると、callcN()、freeN()など、プラグインから利用できるようにBlenderが用意している関数名が並んでいますが、hnoise()、turbulence()などの関数の名前はありません。「include」フォルダの中で、これらのエラーの出ている関数名が記述されているのは「util.h」、一方のhnoise()、turbulence()については「plugin.h」です。

plugin.hの中の記述は、こうなっています。
ver.5
LIBEXPORT float hnoise(float noisesize, float x, float y, float z);
LIBEXPORT float hnoisep(float noisesize, float x, float y, float z);
LIBEXPORT float turbulence(float noisesize, float x, float y, float z, int depth);
LIBEXPORT float turbulence1(float noisesize, float x, float y, float z, int depth);
ver.6
LIBIMPORT float hnoise(float noisesize, float x, float y, float z);
LIBIMPORT float hnoisep(float noisesize, float x, float y, float z);
LIBIMPORT float turbulence(float noisesize, float x, float y, float z, int depth);
LIBIMPORT float turbulence1(float noisesize, float x, float y, float z, int depth);
util.hの方は、こうなっています。
ver.5
LIBEXPORT          void *mallocN(int len, char *str);
LIBEXPORT          void *callocN(int len, char *str);
LIBEXPORT          short freeN(void *vmemh);  
LIBEXPORT          void *mallocT(int len, char *str);
LIBEXPORT          void *callocT(int len, char *str);
LIBEXPORT          void freeT(void *vmemh);  
ver.6
LIBEXPORT          void *mallocN(int len, char *str);
LIBEXPORT          void *callocN(int len, char *str);
LIBEXPORT          short freeN(void *vmemh);  
LIBEXPORT          void *mallocT(int len, char *str);
LIBEXPORT          void *callocT(int len, char *str);
LIBEXPORT          void freeT(void *vmemh);  
コメント、スペースなどを省略した上、行頭のマクロの部分を色分けして強調してみました。
LIBEXPORT、LIBIMPORTというのは「externdef.h」で定義されているマクロで、プラグインのver.5とver.6とで内容は以下のように変わっています。
ver.5
#ifdef WIN32
 #ifdef PLUGIN_INTERN
  #define LIBEXPORT    __declspec(dllexport)
 #else
  #define LIBEXPORT    extern __declspec(dllimport)
 #endif
#elif !defined(WIN32)
    #define LIBEXPORT extern
#endif
ver.6
#ifdef WIN32
 #ifdef PLUGIN_INTERN
  #define LIBEXPORT    __declspec(dllexport)
  #define LIBIMPORT    __declspec(dllexport)

 #else
  #define LIBEXPORT    __declspec(dllexport)
  #define LIBIMPORT    extern __declspec(dllimport)
 #endif
#elif !defined(WIN32)
    #define LIBEXPORT extern
    #define LIBIMPORT extern

#endif
ifdef文では、まずWindowsとそれ以外で処理内容を分けていて、さらに「PLUGIN_INTERN」という定数で処理内容を分けています。
「plugins」-「include」にあるヘッダファイルは、プラグインの作成時に使用していますが、Blender本体の方でプラグインを処理する部分でも同じ内容のものが使われています。
「PLUGIN_INTERN」はそのどちらかを区別するのに使われていますが、プラグイン作成時には「else」以下の方が有効となるようです。

これを見ると、ver.5ではLIBEXPORTのみだったものがLIBIMPORT、LIBEXPORTの2種類に分けて処理されるようになったようです。そして、マクロがどのように展開されるのかを見ると、

Blenderが公開している関数をプラグインが使うための記述:LIBIMPORT
プラグインが公開している関数をBlenderが使うための記述:LIBEXPORT

ということだと思います。
以上のことから、「util.h」のLIBEXPORTをLIBIMPORTに修正してみます。
util.h line:94-101
LIBIMPORT          void *mallocN(int len, char *str);
LIBIMPORT          void *callocN(int len, char *str);
LIBIMPORT          short freeN(void *vmemh);  

LIBIMPORT          void *mallocT(int len, char *str);
LIBIMPORT          void *callocT(int len, char *str);
LIBIMPORT          void freeT(void *vmemh);  

コンパイル時に表示されたエラーの最後には、plugin_seq_getversion()関数についてのエラーも表示されていました。この関数については、plugin.hの70行目あたりに記述があります。
ver.5
int plugin_tex_getversion(void);
int plugin_seq_getversion(void);
void plugin_getinfo(PluginInfo *);
ver.6
LIBEXPORT int plugin_tex_getversion(void);
LIBEXPORT int plugin_seq_getversion(void);
LIBEXPORT void plugin_getinfo(PluginInfo *);
ここで、plugin_tex_get_version()、plugin_getinfo()は実際にプラグインで使用されていますが、使われていないplugin_seq_getversion()にまでLIBEXPORTを加えているのがエラーの原因のようです。

ということで、この部分はこんな感じに修正してみます。
plugin.h line:70-72
LIBEXPORT int plugin_tex_getversion(void);
int plugin_seq_getversion(void);
LIBEXPORT void plugin_getinfo(PluginInfo *);
これでエラーが出ずに、コンパイルが完了しました。
Blenderにロードすることもできて、スレッド数:4でレンダリングしてもノイズが出ていないようです。
pic090327_11.jpg pic090327_12.jpg


○Ubuntu 8.10 32bitでの問題

Ubuntu 8.10 32bitで問題があったプラグインですが、こちらはsarah0というプラグインです。普通にコンパイルできたのですが、Blenderにロードすることができませんでした。マルチスレッドレンダリング対応に修正していないものでも同じようにBlenderにロードできない「.so」ファイルができてしまいます。
ldコマンドで調べてみると、
mato@ubuntu:~/programing/pr_245/texture$ ld sarah0.so
ld: warning: cannot find entry symbol _start; not setting start address
sarah0.so: undefined reference to `__stack_chk_fail_local'
sarah0.so: undefined reference to `sqrt'
sarah0.so: undefined reference to `__fprintf_chk'
sarah0.so: undefined reference to `fflush'
sarah0.so: undefined reference to `turbulence'
sarah0.so: undefined reference to `__strncat_chk'
sarah0.so: undefined reference to `turbulence1'
sarah0.so: undefined reference to `hnoise'
sarah0.so: undefined reference to `fopen'
sarah0.so: undefined reference to `fclose'
sarah0.so: undefined reference to `fputc'
sarah0.so: undefined reference to `fwrite'
sarah0.so: undefined reference to `floorf'
sarah0.so: undefined reference to `hnoisep'

というように表示されました。
プラグインリポジトリからダウンロードしたバイナリファイルでは、
mato@ubuntu:~/Download/blender/plugin$ ld sarah0.Linux.so
ld: warning: cannot find entry symbol _start; not setting start address
sarah0.Linux.so: undefined reference to `sqrt'
sarah0.Linux.so: undefined reference to `floor'
sarah0.Linux.so: undefined reference to `fflush'
sarah0.Linux.so: undefined reference to `turbulence'
sarah0.Linux.so: undefined reference to `turbulence1'
sarah0.Linux.so: undefined reference to `fprintf'
sarah0.Linux.so: undefined reference to `hnoise'
sarah0.Linux.so: undefined reference to `strncat'
sarah0.Linux.so: undefined reference to `fopen'
sarah0.Linux.so: undefined reference to `memset'
sarah0.Linux.so: undefined reference to `fclose'
sarah0.Linux.so: undefined reference to `fputc'
sarah0.Linux.so: undefined reference to `fwrite'
sarah0.Linux.so: undefined reference to `hnoisep'

このように表示されます。
fprintf()、strncat()などの関数で問題が起こっているようですが、そもそもテクスチャプラグインではテキスト処理の関数は必要なさそうです。sarah0プラグインではsarah0.c以外に、2つのヘッダファイルsarahplugins.h、sarahutil.hが使われています。このうち、sarahutil.hの中にプラグインの動作をデバッグするための関数debug_log_inputs()があり、fprintf()、strncat()はそこでだけ使われているようです。
デバッグ用の関数はとりあえずなくてもOKということで、debug_log_inputs()関数をまるごとコメントアウトした状態でコンパイルすると、正常に動作するようになりました。
posted by mato at 00:37| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする

2009年03月25日

プラグインリポジトリのテクスチャプラグインをマルチスレッド対応に

前回でテクスチャプラグインをマルチスレッドレンダリングに対応させるために必要なことが大体分かりましたので、プラグインリポジトリにあるテクスチャプラグインをマルチスレッドに対応できるかを試してみました。結果を先に言うと、ほとんどのものが前回の記事で書いた修正内容だけでマルチスレッドレンダリングできるようになりました。

プラグインリポジトリにあるプラグインの多くは GPL、Public Domainなどの再配布、改変を許可するライセンスで公開されていますが、一部そうなっていないものもあります。ライセンス上問題ないものについて、修正済みのソースコード、コンパイル済みのバイナリファイル(Windows 32bit、Linux 32/64bit)をまとめてみました。

texture_plugins_modified.zip

この中に含まれるのは、brick、brick1、circdots_rgb、dots2、led、pattern、pie、r_weave、refract、rings2、rtilings、sarah0、scales、sinus、spirals、t_bricks、t_clouds、t_marble、t_marble_terrain、t_terrain、t_wood、trellis、water、wbricks の24のテクスチャプラグインになります。
各プラグインのソースファイルが入っているフォルダに、「changes.txt」という名前のファイルを入れてあります。これは、オリジナルのソースコードと今回修正したソースコードの内容の違いを記述した差分ファイルで、前回使ったMeldというツールで作成しています。このファイルを見ていただくと、どこをどう修正したのかが一目でわかると思います。
pic090324_01.jpg pic090324_02.jpg

ほとんどのものは、
  1. plugin_tex_doit()関数の引数にresult[]配列へのポインタを追加し、プロトタイプ宣言もそれに合わせて変更
  2. plugin_instance_init()関数とそのプロトタイプ宣言を追加し、plugin_getinfo()関数の中に関連するコードを追加
という修正内容だけで済んでいますが、一部にはそれだけではうまくいかないものがありました。ここから先は、それら問題の起こったプラグインにどのような修正をしているかを見ていきたいと思います。

実際に問題が起こったプラグインは、dots2、led、trellis、ceramictilesの4つですが、このうちceramictilesはライセンスの関係で今回は外してあります。ちなみに修正後に起こった問題の具体的な内容ですが、dots2、ledの2つのプラグインでは修正後にプレビュー画面が真っ黒になり、プラグインが正常に動作しなくなりました。trellis、 ceramictilesの2つのプラグインのでは、上記の修正を行った後もノイズがなくならないという感じになります。もしかすると、私が気づいていないだけで、これ以外にもまだノイズが発生するものがあるかもしれません。

まず、問題が起こったプラグインのうちのdots2、ledについてです。
このdots2のソースファイル「dots2.c」を見ると、plugin_tex_doit()関数の中から別の関数calc_dot_value()を呼び出して、計算処理をそちらで行っています。
void calc_dot_value(Cast *cast,float x,float y)
{
  ....
}

int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt)
{
  ...
  calc_dot_value(cast,x,y);
  ...
}

プラグインリポジトリにあるプラグインでは、plugin_tex_doit()関数から別の関数を呼び出すということはごく普通に行われています。問題が起こったプラグインではすべてこのような構造になっていて、さらにdots2、ledの2つのプラグインでは、この呼ばれている関数の中でresult[]配列が使われているという点が共通しています。ただし、ledの方では関数内に直接result[]配列の記述があるのではなく、呼ばれている関数内で使われているマクロの中にresult[]配列の記述があります。
pic090324_03.jpg pic090324_04.jpg

plugin_tex_doit()関数の引数にresult[]配列へのポインタを追加する修正をした場合、そのままだとplugin_tex_doit()関数内のresult[]配列と、呼び出されている関数内のresult[]配列は名前は同じでもまったく別のものとなっています。
このため、本来の計算処理が行われずにプレビュー画面が真っ黒になってしまいます。

void calc_dot_value(Cast *cast,float x,float y)
{
  ....
 result[0]=0.;
  ....
}

int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt, float *result)
{
  ...
  calc_dot_value(cast,x,y);
  ...
}
plugin_tex_doit()関数以外の関数についても、result[]配列を使用している関数すべてにresult[]配列へのポインタを引数で渡すようにすればこの問題は解決します。
void calc_dot_value(Cast *cast,float x,float y, float *result)
{
  ....
 result[0]=0.;
  ....
}

int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt, float *result)
{
  ...
  calc_dot_value(cast,x,y, result);
  ...
}

次にtrellis、ceramictilesについてです。
この2つのプラグインで問題になるのはグローバル変数を使っていることです。 trellisのソースコード「trellis.c」を見てみると、126行目あたりに次のような記述があります。
/* We use globals to avoid passing lots of float args around every call */
float x, y, z;            /* Normalised coordinates */
float t;            /* thickness */
float t2;            /* 1/2 thickness */
int multi;            /* Use multi-colours? */
これらのグローバル変数は、plugin_tex_doit()関数から呼ばれるcalc_trellis()とcalc_cube()という2つの関数の中で何度も使われています。これらの変数は、result[]配列のようにBlenderに渡される強度、色などのデータそのものを保持するわけではありませんが、複数のスレッドがこれらのデータを上書きし合う状況では計算結果が本来のものと全く違ったものになってしまい、結果としてノイズのようなものが起こることになります。
int plugin_tex_doit(int stype, Cast * cast, float *texvec, float *dxt, float *dyt) {
    ...
        result[0] = calc_trellis(cast->pattern);
    ...
        result[0] = calc_cube((float) rand() / (float) RAND_MAX);
    ...
}
今回は、これらのグローバル変数をplugin_tex_doit()関数内のローカル変数に修正し、calc_trellis()とcalc_cube()の両関数の引数としてこれらの全ての値を渡すようにしてみました。
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt, float *result)
 {
    float x, y, z;            /* Normalised coordinates */
    float t;            /* thickness */
    float t2;            /* 1/2 thickness */
    int multi;            /* Use multi-colours? */


    ...
        result[0] = calc_trellis(cast->pattern, x, y, z, t, t2, multi);
    ...
       
        result[0] = calc_cube((float) rand() / (float) RAND_MAX, x, y, z, t, t2, multi);
    ...
}
私がソースコードに対して行った修正は、以上のような感じです。
ところが、実はソースコード自体に問題がなくても、コンパイル時に問題が起こったものがいくつかあります。そのため、今回修正したソースファイルを使ってコンパイルを行う場合、いくつかのプラグインでコンパイルに失敗する可能性があります。
次回は、それらのコンパイル時の問題への対応方法について書くつもりです。
posted by mato at 02:09| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする

2009年03月22日

テクスチャプラグインのマルチスレッドレンダリング

Blenderの公式マニュアルでこちらのページを見ると、テクスチャプラグインはBlender2.46からマルチスレッドレンダリングに対応していることがわかります。そして、以前のバージョンのプラグインをマルチスレッドでレンダリングすると、レンダリング結果にノイズのようなものが発生するので、その場合はシングルスレッドでレンダリングするようにと書かれています。

プラグインリポジトリで公開されているテクスチャプラグインを調べてみると、プラグインの種類によっては目立たないものもありますが、ほとんどのものでノイズのようなものが確認できます。
pic090321_01.jpg pic090321_02.jpg

前回の記事で作成したテスト用のプラグインは、実はマルチスレッドに対応したものになっています。スレッド数を「4」とかにしてもノイズがでたりすることはありません。Blender2.46以降の「plugins」フォルダのサンプルをコンパイルすると、その結果できあがるプラグインはマルチスレッドレンダリングに対応したものになります。
プラグインリポジトリで公開されているファイルの中には、Blenderの「plugins」フォルダに付属しているサンプルと同じ「clouds2」「tiles」もあります。そちらは古いバージョンとしてコンパイルされているので、マルチスレッドでレンダリングするとノイズが出ます。この違いはどこにあるのでしょうか。
pic090321_03.jpg pic090321_05.jpg

プラグインリポジトリにあるソースコード「clouds2.c」「tiles.c」と最新版の「plugins」フォルダの中のソースコードとを比べることで、古いバージョンのテクスチャプラグインをマルチスレッドレンダリングに対応させるための方法がわかりそうです。
このくらいの行数のファイルであれば、両方をテキストエディタで見比べることで違いを確認することもできますが、Linuxのツール「Diff」というものを使うとより確実に調べることができます。「Diff」自体はコマンドラインから使用するものですが、Gnome、KDEなどのデスクトップ上で使えるような派生ツールがいくつも作られていて、Windowsでも同じようなものはたくさんみつかります。Ubuntuの「アプリケーションの追加と削除」を見てみると、以前Vectexのソースを調べるのに使ったMeldの他にTkDiff、Kompareなどがありました。今回は「Meld差分ビュワー」を使って「clouds2.c」の新旧のバージョンの違いを調べてみたいと思います。
pic090321_06.jpg

どちらを比較元にするかで若干表示が変わってくると思いますが、とりあえず普通に古い方をオリジナル(画面左)として、新しいバージョン(画面右)との違いを表示させてみます。
pic090321_07.jpg

まず、冒頭のコメント部分が違っています。とりあえず動作には関係ないので無視していいでしょう。
pic090321_08.jpg

この「Meld」というツールではツールバーの「下へ」「上へ」というボタンを押すと、ファイルの中の内容が違っている部分に画面を移動させることができます。次に見つかったのはPlugin_tex_doit()、plugin_instance_init()の2つの関数のプロトタイプ宣言の部分です。
pic090321_09.jpg

plugin_tex_doit()関数には、新しいバージョンでは引数が一つ追加されています。結果から言いますと、テクスチャプラグインをマルチスレッドに対応させるために変更されたのは、まさにこの部分となります。
前回の記事を見ていただくとわかりますが、この引数はresult[]配列へのポインタになります。複数のスレッドがplugin_tex_doit()関数を呼び出している場合、古いバージョンではresult配列に書き込まれた計算結果を別のスレッドが上書きしてしまうことがあります。本来渡されるべき値が変更されてしまったピクセルは、周辺のピクセルとは全然別の色として表示され、点状、線状のノイズのようになります。
新しいバージョンでは各スレッドにそれぞれ自分用のresult[]配列を引数として与えることで、この問題が解決されています。
プロトタイプ宣言が変更されているので、当然それと対応する関数本体のコードも同じように変更されています。この関数の本体はソースコードの最後の方にあります。
もう一方のplugin_instance_init()関数は古いバージョンにはなかったものが、新しいバージョンで追加されたようです。

次の変更箇所です。
pic090321_10.jpg

まず、plugin_instance_init()関数の本体が追加されています。
そして、plugin_getinfo()関数の中身で最後の方が2ヶ所ほど変更されています。一つ目は、"info->tex_doit="と"(TexDoit) plugin_tex_doit;"の間のスペースが一つ余分だったのが修正されています。これは無視していいような内容ですね。
二つ目はplugin_instande_init()関数が追加されたことに関連して、Blender本体からこの関数にアクセスするための情報がここに追加されているようです。

次に、plugin_tex_doit()関数の本体の部分です。
pic090321_11.jpg pic090321_12.jpg

関数の前の部分のコメントが削除されていますが、これは無視していいでしょう。
そして、先ほどのプロトタイプ宣言の変更に対応して、関数名に続く引数の記述部分に「float *result」が追加されています。
元々、result[]配列はグローバルなデータとして同じ名前でplugin_tex_doit()関数の中で使われていたものです。そのため、result[]配列に関しては、この関数内部のコードはまったく変更されていません。

最後の変更部分です。
pic090321_13.jpg

これは関数の戻り値を「0」「1」「2」というような数値ではなく、「TEX_INT」「TES_RGB」「TES_NOR」というマクロを使った定数の表記に変えているようです。「plugins」フォルダの「include」にあるplugin.hの86行目あたりを見てみると、以下のように記述があります。

/* return values (bitfield like) for textures (DNA_texture_types.h) */
#define TEX_INT         0
#define TEX_RGB         1
#define TEX_NOR         2

前回のサンプルプラグインでは直接「0」を返してしまいましたが、本来はこのように「TEX_INT」という定数を使うべきだったようです。(とりあえず、そのままでも動作には問題なさそうなので放置しておきます。まずいかな...。)

ということで、ざっと新旧のバージョンの変更内容を見てきました。
plugin_tex_doit()関数の引数にresult[]配列を渡すようにすること、plugin_instance_init()関数が新しく追加されたこと、基本的にはこの2点が変わっただけで、プラグインの動作に直接関わる部分には変更はないようです。これらの部分を修正してコンパイルし直せば、プラグインリポジトリにあるたくさんのプラグインもマルチスレッドでレンダリングできるようになりそうな感じがします。
次回は、プラグインリポジトリのテクスチャプラグインのソースコードを修正して、実際に動作するか試してみるつもりです。
posted by mato at 00:14| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする

2009年03月18日

テクスチャプラグインを作ってみる

今回はこちらのページを参考にして、テクスチャプラグインを作ってみたいと思います。

一応、Blenderの公式ユーザーマニュアルの中のページなのですが、一番上の部分を見ると「Blender Version 2.31」となっていて、内容がBlenderの最新バージョンに対応していないようです。このページ以外にBlenderプラグインリポジトリなどにもBlenderのテクスチャプラグインのサンプルコードが掲載されていますが、いずれも現在のBlenderに付属するプラグイン作成用のヘッダファイルとバージョンが違っていて、そのままではコンパイルしてもきちんと動作しないようです。

そのため、今回はBlender2.48aの「plugins」フォルダに入っているサンプルファイル「clouds2.c」のソースファイルを元にして、オリジナルのプラグインを作るにはどこを修正すればいいのか、という形で話を進めたいと思います。ただし、上記の参考ページの解説内容については、ほとんどの部分が現在も有効です。今回は最低限の内容に話を絞っていますので、もしこの内容に興味を持たれた場合は、そちらのページの方をご参照ください。
pic090318_01.jpg

まず、「clouds2.c」を複製して「test.c」と名前を変更してください。そして、以下の手順でこのファイルの内容を作り替えていきます。
一応、サンプルデータsample_data090318.zipにソースファイルとWindows(32bit)、Linux(32/64bit)のバイナリファイルを用意してあります。私は現在のところ、Windows64bit、Mac OSなどのバイナリファイルを作ることができません(将来的にもできなさそうです)。ご了承ください。

○コメント
サンプルファイルには冒頭にライセンスについての記述があります。
本来は、きちんとライセンスの内容を確認した上でコードの管理者などを明記する必要があると思いますが、今回はとりあえず冒頭のファイル名の部分を変更してあります。
/**
 * $Id: test.c $
 *

○インクルードファイル
#include "plugin.h"

テクスチャプラグインを作成するには、必ずこの「plugin.h」をインクルードする必要があります。サンプルファイルではこの他に「math.h」をインクルードしていますが、今回は使用しませんので削除してあります。

○プラグイン名の設定
char name[24]= "Test";

とりあえずダブルクォートで囲まれた部分(" ")を変更しています。
参考のページでは、「Texture Buttons Windowでtexture's titleとして表示される」と書かれていますが、現在のバージョンではプラグインファイルのパスが表示されていて、ここで設定した名前は表示されていないようです。

○サブタイプの設定
#define NR_TYPES    1
char stnames[NR_TYPES][16]= {"Default"};

サブタイプというのは、プラグインの使われ方を切り替えるスイッチのようなものです。今回は簡単にするために一つだけに修正しています。「cluds2.c」では、「強度」「カラー」「バンプ」の3種類の使い方ができるようになっていて、「NR_TYPES」が「3」に、「stnames」が「{"Intens","Col","Bump"}」となっています。
pic090318_02.jpg pic090318_03.jpg

○VarStruct構造体 ボタンの設定
VarStruct varstr[]= {
{    NUM|FLO,    "value",    0.0,    -1.0,    1.0,    "test value"},
};

「plugin」パネルに表示するボタン類をここで定義します。ここでは「value」という数値を変更できる数値ボタンを、デフォルト値「0.0」、最小値「-1.0」、最大値「1.0」として設定しています。
VarStructという構造体には「type」「name」「def」「min」「max」「tip」を指定します。
  1. type ここで数値設定用のボタン(数値ボタン、数値スライダ、トグルボタン)、テキストラベルなどのタイプを指定します。数値ボタンの場合数値のフォーマット(INT,FLO)とボタンタイプ(NUM,NUMSLI,TOG)を「|(OR)」で組み合わせて指定します。
  2. name ボタンに表示される名前です。15文字までです。
  3. 数値の範囲 デフォルト、最小、最大の値を指定します。トグルボタンでは「min」が押された状態、「max」が押されていない状態に対応します。
  4. tip ボタン上にマウスポインタを置いたときに表示されるテキストです。80文字までです。

pic090318_04.jpg pic090318_05.jpg

○Cast構造体 ボタンで設定する変数の本体
typedef struct Cast {
    float val;
} Cast;

VarStructでは、ボタンの表示に関する設定だけを行っていて、実際に描画のための処理を行う関数(plugin_tex_doit)で使用するための変数については何も書かれていません。Cast構造体には、VarStructで指定した順番通りに、それぞれのボタンに対応する変数を記述します。今回はボタンが一つだけなので、それに合わせてfloat型の「val」という名前の変数を一つだけ記述してあります。

○変更の必要ない部分
以下、実際のソースコードではplugin_tex_doit()関数まで、基本的には内容を変更する必要のないコードが書かれています。変更していないのでブログの記事上では内容を省略しますが、コンパイルするために必要なコードが書かれていますので、実際のソースコードでは省略しないでください。

○プラグインの描画処理
int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, float *dyt, float *result)
{
    if (texvec[0] <= cast->val) {
        result[0] = 0;
    } else {
        result[0] = 1;
    }
   
    return 0;
}

この部分がレンダリングの際に、各ピクセルに対して毎回Blenderから呼ばれるプラグインの本体となる部分です。参考のページのもの、pluginsフォルダのサンプル、どちらもそれなりに複雑な計算をする処理が書かれているので、ちょっと見ただけでは内容がわかりにくいように思いました。そこで、このサンプルでは思いっきり簡単にしてみました。

plugin_tex_doit()関数に渡される引数

  1. stype どのサブタイプが有効になっているかが渡されます。今回はサブタイプが一つなので関係ありませんが、 2つ以上設定している場合はこの値に応じて(強度、カラー、バンプなど)処理を分けることになります。
  2. *cast ボタンに応じた変数を格納したCast構造体のポインタです。
  3. *texvec レンダリングされているピクセルで使用されているテクスチャ座標を「float」型の3つの数値で渡しています。texvec[0]がX座標、texvec[1]がY座標、texvec[2]がZ座標となります。
  4.  *dxt,*dyt OSAがオンで、アンチエイリアスが正しく計算されているとき(それ以外はNULLとなりますので確認が必要です)3つのfloat値の配列のポインタが渡されます。アンチエイリアスの計算で使用されるようなのですが、正直なところ私にはまだよく理解できていません。
  5. *result この配列を使って計算結果をBlenderに返します。つまり、このresult[]をなんらかの値に設定することが、この plugin_tex_doit()関数の目的になります。result配列は、float型のresult[0]からresult[7]までの8つの値が渡されます。その中に入れる内容はあらかじめ決まっていて、result[0]は強度、result[1]からresult[4]まではRGBA値、 result[5]からresult[7]までがnormalのXYZ値となります。result[0]の強度は必ず返す必要があります。それ以外はオプションとなります。RGBA、normalの値を返す場合は、plugin_tex_doit()関数の戻り値でそれぞれ「1(RGB)」「2(Normal)」「3(全て)」を返すように設定する必要があります。強度のみの場合は「0」です。
プラグインの描画処理の内容についてです。
このプラグインでは「plugin」パネルで数値ボタン「value」の値を変えることができ、テクスチャ座標のX座標がそれより小さければテクスチャ強度「0」、大きければテクスチャ強度「1」になります。

テクスチャプラグインの描画処理は、基本的に「texvec」からテクスチャ座標(X,Y,Z)を取り出して、その場所でのテクスチャの強度を計算し、結果を「result[0]」に格納する、という感じになります。このサンプルでは「texvec[0]」でテクスチャ座標のX座標についてだけ調べています。

プラグインのパネルに設定したボタンからは、Cast構造体の変数を通して値を受け取ることができます。このサンプルでは「cast->val」の部分で、プラグインパネルの数値ボタンの現在の設定値を調べています。

if文の条件式の部分は、(texvec[0] <= cast->val)となっています。「val」変数にはデフォルト値で「0.0」を指定してあり、テクスチャ座標は、「-1.0」から「1.0」の範囲の値が渡されるようです。
例として、あるピクセルが描画される際、plugin_tex_doit()関数に渡されるテクスチャ座標Xの値が「-0.5」であったとします。このとき「val」の値がデフォルトのままであればif文の条件判定は「-0.5 <= 0.0」でTRUEとなりますから、result[0] = 0がレンダラに渡されます。結果として、そのピクセルはテクスチャ強度「0」となり、テクスチャプレビュー表示では黒になります。
実際にこのプラグインをロードした状態(「Val」が0.0になっている)でテクスチャの「Preview」パネルを見ると、左半分が黒で右半分が白になっています。
pic090318_06.jpg

プラグインパネルの「Value」の値を変更すると、それに応じてプレビューの表示の白い部分と黒い部分の境目が左右に動くのが確認できると思います。
pic090318_08.jpg pic090318_09.jpg

最後の部分で、plugin_tex_doit()関数の戻り値は、result[0]以外を使用していないので「return 0;」としています。
はっきりいって、実用性ゼロのプラグインができてしまいましたが、他のサンプルと比べてかなり内容が分かりやすくなったと思いますが...どうでしょうか。

このブログ、ソースコードのインデントが表示できないことに、今頃気づきました。プログラミングの記事を書くのには向いてなかったようです...。
※追記
スタイルシートとかを変えれば表示できそうなので、少し調べてみたいと思います。 ->スタイルシートは変更できなかったのですが、記事の本文を書く際に「リッチテキストエディタを使用する」というのを選択したら、とりあえずインデントが反映されるようになりました。
posted by mato at 22:50| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする
×

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