修正したバグはメモリリークが起こっていたことです。
実行ファイルはこちらになります。
Linux 64bit
Linux 32bit
Windows 32bit
ソースファイルのパッチ(Blender2.49.2_Vectex_QtSVG_patch.zip)
AGGバージョンも更新しています。
実行ファイルは以前の記事からダウンロード可能です。
AGGバージョンのソースファイルのパッチ(Blender_modified_patch091005.zip)
以下、今回の修正内容について詳しく説明します。
元々のプラグインのvectexでは、メモリの管理について以下のように行っています。
vectexは大量のタイル画像を作成しますが、それらのタイル画像はTile構造体の*imageというポインタでアクセスできるようになっています。
typedef struct TileそしてそのTile構造体にはInstance構造体から*tile_setというポインタでアクセスします。
{
unsigned char *image;
unsigned int age;
} Tile;
typedef struct InstanceInstance構造体へのアクセスのために、PluginのCast構造体に*instance_dataというポインタを登録しています。
{
void *backend_data;
int tile_set_max_power;
Tile *tile_set;
int tile_memory;
unsigned int timestamp;
unsigned char current_base_color[4];
char current_file_name[64];
} Instance;
typedef struct Castこのブログを始めてすぐのころにBlenderのプラグインの仕組みを調べていたときには気づかなかったのですが、通常Cast構造体に登録されるのはPluginパネルに表示されるボタンの状態を保持するためのデータであって、大量の画像へのポインタを保持するInstance構造体へのポインタのようなものを登録するのはかなり特殊な使い方と考えていいようです。
{
int enable;
int mipmap;
int interpol;
/*int usealpha;*/
int label_dummy0;
int tex_level;
int mem_max;
int mem;
int label_dummy1;
unsigned char base_color[4];
int label_dummy2;
char file_name[64];
int label_dummy3;
void *instance_data;
} Cast;
BlenderのプラグインAPIはこのような使い方を想定して作られていないので、Blenderの終了時などにプラグインを使用しているテクスチャが削除される処理が行われるとき、Cast構造体自体が削除されてもそこに登録されたポインタが指す実体については削除されないことになります。
このような理由からオリジナルのvectexではBlenderの終了またはプロジェクトを閉じる処理が行われたとき、作成したタイル画像のデータ、そしてそれを管理するInstance構造体などのデータが使用しているメモリ領域がきちんと解放されずにメモリリークを起こしています。
現在のOSではアプリケーションの終了時にはそのプロセスに割り当てられたメモリ領域全体が破棄されるような仕組みになっていて、実際にはこのメモリリークが大きな問題になることはなさそうです。
ただし、アプリケーションを終了せずにプロジェクトを閉じる操作を何度も続けると問題がおこるかもしれません。
具体的には、vectexを使用してレンダリングを実行し、Blenderを終了しないまま新規プロジェクトを作成し、新しいプロジェクトでvectexを使用してレンダリングを実行、新規プロジェクトを作成...というように操作を続けていくとメモリが不足する可能性があります。
では、vectex内臓版のBlenderではこのあたりのことがどうなっているのかというと、
作成されるタイル画像は、VtexTile構造体の*imageポインタでアクセスされます。
typedef struct VtexTileVtexTile構造体にはVtexInstance構造体の*tile_setポインタでアクセスされます。
{
unsigned char *image;
unsigned int age;
float pad;
} VtexTile;
typedef struct VtexInstanceさらにVtexInstance構造体にはBlenderのテクスチャのデータ全般を管理しているTex構造体に追加した*vtex_instance_dataからアクセスできるようになっています。
{
void *backend_data;
int tile_set_max_power;
int tile_memory;
unsigned int timestamp;
unsigned char current_base_color[4];
char current_file_name[160];
short imaflag;
short vtex_imaflag;
float pad;
struct VtexTile *tile_set;
} VtexInstance;
typedef struct Tex {Blenderの終了時には、テクスチャのデータを保持するTex構造体は、blender/source/blender/blenkernel/intern/texuture.c (line:417)にあるfree_texture()という関数でメモリの解放を行っています。
...
/* vectex */
float vtex_resolution_scale;
int vtex_enable, vtex_mipmap, vtex_interpol, vtex_tex_level, vtex_mem_max, vtex_mem;
unsigned char vtex_base_color[4];
char vtex_file_name[160];
short vtex_imaflag;
short vtex_version;
char pad1[4];
struct PackedFile * vtex_packedfile;
struct VtexInstance *vtex_instance_data;
...
} Tex;
Tex構造体に追加したポインタ*vtex_instance_dataの実体もここでメモリの解放を行う必要があります...が、これまでメモリの解放処理を書かないまま放置してありました。
このため、vectex内臓版Blenderでもアプリケーション終了時やプロジェクトを閉じた時に、オリジナルのプラグインとまったく同じようにメモリリークを起こしていました。
本来ならTex構造体にポインタ*vtex_instance_dataを追加した時点で気づいていなければいけなかったのですが、今頃になって気づきました。
プラグインと違ってBlender自体のソースコードを自由に変更できる状況にありながら、数ヶ月もの間メモリリークを放置していたとは、なんとも情けない話です...。
今回の更新でのソースコードの修正内容です。
前回からの修正内容(changes091006.zip)
まず、free_texture()関数にメモリの解放の処理を書く必要があります。
blender/source/blender/blenkernel/intern/texuture.c (line:417)
void free_texture(Tex *tex)ここで使用しているvtex_destroy_instance()という関数はこれまでvectex()関数と同じblender/source/render/intern/source/texuture.cに置いていましたが、blender/source/blender/blenkernelという全く別のディレクトリにあるファイルから参照できるようにする必要があります。
{
free_plugin_tex(tex->plugin);
if(tex->coba) MEM_freeN(tex->coba);
if(tex->env) BKE_free_envmap(tex->env);
if(tex->vtex_instance_data) vtex_destroy_instance(tex);
BKE_previewimg_free(&tex->preview);
BKE_icon_delete((struct ID*)tex);
tex->id.icon_id = 0;
if(tex->nodetree) {
ntreeFreeTree(tex->nodetree);
MEM_freeN(tex->nodetree);
}
}
しかし、そもそも現状ではvtex_destroy_instance()関数は、外部のファイルから使用できるようにするためにプロトタイプ宣言をヘッダファイルで公開していません。
blender/source/blender/render/intern/include/texture.hというヘッダファイルにこの関数のプロトタイプ宣言を書いて、blender/source/blender/blenkernel/texuture.cから参照できるようにすることもできますが、関数の用途と配置について考えるとむしろvtex_destroy_instance()関数そのものを.../blenkernel/texture.cの方に移してそれを.../render/intern/source/texture.cの方から使用するというようにした方がいいようです。
実際にこの関数を移動することを考えた場合、この関数で使用しているいくつかの関数も同じようにファイルを移動させる必要がでてきます。
vtex_destroy_tile_set()関数と、その関数で使われているvtex_get_base_index()関数です。
vtex_destroy_instance()関数では、これ以外にもvtex_destroy_backend()関数も使われていますが、こちらはもともとblender/source/vectex/vectex.hで公開されているので、そのヘッダファイルをインクルードするだけで使用できます。
.../blendkernel/texture.cに移動したので、.../render/intern/source/texture.cからはこれらの関数は削除しました。
これらの修正は移動のみの修正のため、ソースコードの引用については省略します。
移動した関数のプロトタイプ宣言をヘッダファイルに追加します。
blender/source/blender/blenkernel/BKE_texture.h
...移動した関数をコンパイル時に正しく認識できるように、SConsの設定ファイルSConscriptファイルの内容を修正します。
struct VtexInstance;
...
...
int vtex_get_base_index(int power);
void vtex_destroy_instance(struct Tex *tex);
void vtex_destroy_tile_set(struct VtexInstance *instance);
...
blender/source/blender/blenkernel/SConscript
...このように一つの関数を複数の場所のファイルから参照できるようにすると、インクルードファイルの位置だけでなく、コンパイル後に作成されるライブラリの有線順位priorityについても変更が必要になることがあります。
if env['WITH_BF_VECTEX']:
incs += ' ../vectex'
env.BlenderLib ( libname = 'bf_blenkernel', sources = sources, includes = Split(incs), defines = defs, libtype=['core','player'], priority = [65, 20] )
今回は、blender/source/blender/vectexディレクトリのSConscriptファイルのpriorityの数値をblender/source/blender/blenkernelのSConscriptと同じになるように修正しました。
もともとはblender/source/blender/renderのSConscriptに合わせていましたが、そのままではWindowsのmingwでのコンパイルでエラーが出てしまったため、より有線順位が高いと思われるblenkernelの方に合わせることにしました。
blender/source/blender/vectex/SConscript
...
env.BlenderLib ( libname='bf_vectex',
sources=sources, includes=Split(incs),
defines=Split(defs),
libtype=['core','player'], priority = [65, 20], compileflags = Split(cflags))
今回の修正内容はこれだけです。
この修正を行うにあたってLinuxでメモリリークの有無をしらべることのできるツールを探し、Valgrindというものを使ってみました。
このツールはGUIのないコマンドラインで使用するタイプのものですが、メモリリークを調べるだけという簡単な用途で使うなら、使い方はそれほど難しくありません。
通常の操作で
myprog arg1 arg2
というようにコマンドラインで使用するプログラムを
valgrind --leak-check=yes myprog arg1 arg2
というようにするだけで、端末画面にエラーメッセージが表示されます。
動作スピードがかなり遅くなりますが、BlenderのようなGUIプログラムでも通常と同じようにプログラムが起動し、普通に操作することができます。
ただし、Blederを調べようとすると大量のエラーメッセージが表示されて、これ以上メッセージを表示しないというように言われてしまいます。
valgrind --leak-check=yes --error-limit=no ./blender
というように--error-limit=noというオプションを付けることで、この問題を回避できるようです。
今回の修正前の状態では、valgrindの出力に以下の様にvectexのメモリリークが表示されました。
...今回の修正後、vectexのメモリリークの表示はなくなったようです。
==8900== 348,284 bytes in 5 blocks are possibly lost in loss record 369 of 372
==8900== at 0x4C278AE: malloc (vg_replace_malloc.c:207)
==8900== by 0x14DCD8A: MEM_mallocN (mallocn.c:258)
==8900== by 0xAF43F9: vtex_create_instance (texture.c:920)
==8900== by 0xAF5D2E: vtex_check_instance (texture.c:1286)
==8900== by 0xAF5FEB: vectex (texture.c:1344)
==8900== by 0xAFE27D: multitex (texture.c:3155)
==8900== by 0xB00D35: do_material_tex (texture.c:3686)
==8900== by 0xB41188: shade_lamp_loop (shadeoutput.c:1566)
==8900== by 0xB6F42F: shade_material_loop (shadeinput.c:93)
==8900== by 0xB6FAE6: shade_input_do_shade (shadeinput.c:154)
==8900== by 0xB6B9C4: shade_tra_samples (zbuf.c:3889)
==8900== by 0xB6CC5F: zbuffer_transp_shade (zbuf.c:4191)
...
(起動後の操作によって出力の内容も変わるので、操作内容によっては別の結果が出る可能性があります。)