2009年10月07日

メモリリーク

今回の更新はバグフィックスのみです。
修正したバグはメモリリークが起こっていたことです。

実行ファイルはこちらになります。

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
{
    unsigned char *image;
    unsigned int   age;
} Tile;
そしてそのTile構造体にはInstance構造体から*tile_setというポインタでアクセスします。
typedef struct Instance
{
    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;
Instance構造体へのアクセスのために、PluginのCast構造体に*instance_dataというポインタを登録しています。
typedef struct Cast
{
    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のプラグインの仕組みを調べていたときには気づかなかったのですが、通常Cast構造体に登録されるのはPluginパネルに表示されるボタンの状態を保持するためのデータであって、大量の画像へのポインタを保持するInstance構造体へのポインタのようなものを登録するのはかなり特殊な使い方と考えていいようです。

BlenderのプラグインAPIはこのような使い方を想定して作られていないので、Blenderの終了時などにプラグインを使用しているテクスチャが削除される処理が行われるとき、Cast構造体自体が削除されてもそこに登録されたポインタが指す実体については削除されないことになります。

このような理由からオリジナルのvectexではBlenderの終了またはプロジェクトを閉じる処理が行われたとき、作成したタイル画像のデータ、そしてそれを管理するInstance構造体などのデータが使用しているメモリ領域がきちんと解放されずにメモリリークを起こしています。

現在のOSではアプリケーションの終了時にはそのプロセスに割り当てられたメモリ領域全体が破棄されるような仕組みになっていて、実際にはこのメモリリークが大きな問題になることはなさそうです。
ただし、アプリケーションを終了せずにプロジェクトを閉じる操作を何度も続けると問題がおこるかもしれません。
具体的には、vectexを使用してレンダリングを実行し、Blenderを終了しないまま新規プロジェクトを作成し、新しいプロジェクトでvectexを使用してレンダリングを実行、新規プロジェクトを作成...というように操作を続けていくとメモリが不足する可能性があります。

では、vectex内臓版のBlenderではこのあたりのことがどうなっているのかというと、
作成されるタイル画像は、VtexTile構造体の*imageポインタでアクセスされます。
typedef struct VtexTile
{
    unsigned char *image;
    unsigned int   age;
    float pad;
} VtexTile;
VtexTile構造体にはVtexInstance構造体の*tile_setポインタでアクセスされます。
typedef struct VtexInstance
{
    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;
さらにVtexInstance構造体にはBlenderのテクスチャのデータ全般を管理しているTex構造体に追加した*vtex_instance_dataからアクセスできるようになっています。
typedef struct Tex {
...   
    /* 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;
Blenderの終了時には、テクスチャのデータを保持するTex構造体は、blender/source/blender/blenkernel/intern/texuture.c (line:417)にあるfree_texture()という関数でメモリの解放を行っています。

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)
{
    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()という関数はこれまでvectex()関数と同じblender/source/render/intern/source/texuture.cに置いていましたが、blender/source/blender/blenkernelという全く別のディレクトリにあるファイルから参照できるようにする必要があります。
しかし、そもそも現状では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
...
struct VtexInstance;
...
...
int vtex_get_base_index(int power);
void vtex_destroy_instance(struct Tex *tex);
void vtex_destroy_tile_set(struct VtexInstance *instance);

...
移動した関数をコンパイル時に正しく認識できるように、SConsの設定ファイルSConscriptファイルの内容を修正します。
blender/source/blender/blenkernel/SConscript
...
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] )
このように一つの関数を複数の場所のファイルから参照できるようにすると、インクルードファイルの位置だけでなく、コンパイル後に作成されるライブラリの有線順位priorityについても変更が必要になることがあります。
今回は、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のメモリリークが表示されました。
...
==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)
...

今回の修正後、vectexのメモリリークの表示はなくなったようです。
(起動後の操作によって出力の内容も変わるので、操作内容によっては別の結果が出る可能性があります。)
posted by mato at 00:09| Comment(2) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
vectexを2.49bで使う方法を探しているうちにこちらに辿りつき、windows版をダウンロードさせていただきました。公開ありがとうございます
Posted by at 2009年10月17日 18:51
コメントをありがとうございます。
半分は自分自身がBlenderのプログラミングを学習するのが目的で開発を進めていますが、もし何かお役に立つようでしたら嬉しいです。
Posted by mato at 2009年10月17日 19:20
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

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