2009年07月09日

vectexのバックエンドレンダラをQtSVGに変更する

SVG描画ライブラリをQtSVGに変更したvectex内臓版のBlederを作成しました。

実行ファイルはこちらです。
Linux 32bit(Blender_Qt_vectex_Linux32090709.tar.gz)
Linux 64bit(Blender_Qt_vectex_Linux64090709.tar.gz)
Windows 32bit(Blender_Qt_vectex_win32090709.zip)

ソースコードはこちら(Blender_Qt_vectex_src090709.zip)です。

Windows版はPython2.5、Linux版はPython2.6を使用しています。
以前公開したものはBlender2.49をベースとしていましたが、その後バグフィックスされたBlender2.49aがリリースされたので、今回はそちらの2.49aをベースとしています。
ついでにvectexのパネルにSVGファイルを選択する際にファイルブラウザを使用するためのボタンを付けました。
pic090709_01.jpg pic090709_02.jpg

QtSVGを使用しているので、通常のSVGファイルならほとんどが読み込めるようになっているのではないかと思います。
描画の速度もAGGのものよりは若干遅くなるようですが、それほど気にならない範囲だと思います。

6/12の記事で公開したオリジナルのAGG、Expatを使用して作成したvectex内臓Blenderの方も、Blender2.49aをベースとしたものに更新しました。
ファイルサイズがかなり大きいので、古いものは破棄して新しいものに差し替えています。

ダウンロード可能なソースコードのファイルは、これまではフォルダ構成を維持して変更のあったファイルをまるごとコピーしていました。
ファイル数が増えてくるとこれを用意するのはかなり面倒なので、今回からはdiffで作成した差分ファイル(patchファイル)のみにさせていただきます。

Blender2.49aのソースコードのフォルダを「/home/mato/Desktop/blender」に置いているとすると、
cd /home/mato/Desktop
patch -p0 < changes.text
のようにコマンドを実行すると、修正版のソースコードを作成できると思います。
Blenderの公式のパッチとは若干適用の仕方(patchコマンドを実行する場所)が違っていますのでご注意ください。(というか、同じように適用できなくてすみません...)
pic090709_03.jpg

ということで、ここからソースコードの修正内容について説明していきたいと思います。

AGG、ExpatのときはLinux版は両方、Windows版はAGGのみを静的リンクライブラリとして使用していましたが、QtSVGについてはLinux、Windowsともに動的リンクのライブラリで使用しています。
このため、Windows版にはBlender実行ファイルと同じディレクトリにQtのDLLファイルを入れてあります。
Linux版の場合は、使用しているOSのパッケージ管理ソフト等でQtをインストールする必要があります。使用しているのはQtのうち、QtGUI、QtSVG、QtCOERの3つです。

○ライブラリの設定
Blenderにvectexを組み込む際に、まずライブラリを組み込みました。
以前組み込んだAGG、Expatは不要になり、また、動的リンクによってライブラリを使用するため、必要な作業はScons用の設定ファイルの修正だけとなります。

・config/linux2-config.py
...
#for vectex SVG texture
WITH_BF_VECTEX = True

BF_QT = '/usr'
BF_QT_INC = '${BF_QT}/share/qt4/mkspecs/linux-g++ ${BF_QT}/include/qt4/QtCore ${BF_QT}/include/qt4/QtGui ${BF_QT}//include/qt4/QtSvg ${BF_QT}/include/qt4'
BF_QT_LIB = 'QtSvg QtGui QtCore'
BF_QT_LIBPATH = '${BF_QT}/lib'

...

・config/win32-mingw-config.py
...
#for vectex SVG texture
WITH_BF_VECTEX = True

BF_QT = LIBDIR + '/qt'
BF_QT_INC = '${BF_QT}/mkspecs/win32-g++ ${BF_QT}/include/QtCore ${BF_QT}/include/QtGui ${BF_QT}/include/QtSvg ${BF_QT}/include\ActiveQt ${BF_QT}/include'
BF_QT_LIB = 'QtSvg4 QtGui4 QtCore4'
BF_QT_LIBPATH = '${BF_QT}/lib'

...

QtはWindowsとLinuxとでライブラリの名前や場所が微妙に違っているようです。
それに合わせて若干細部が変わっていますが、基本的にはほとんど書いている内容は同じです。

・tools/Blender.py
...
    if lenv['WITH_BF_VECTEX']:
        libincs += Split(lenv['BF_QT_LIBPATH'])

...
...
    if lenv['WITH_BF_VECTEX']:
        syslibs += Split(lenv['BF_QT_LIB'])

...
・tools/btools.py
...
            'WITH_BF_VECTEX',
            'BF_QT', 'BF_QT_INC', 'BF_QT_LIB', 'BF_QT_LIBPATH',

...
...
        (BoolVariable('WITH_BF_VECTEX', 'Use vectex if tue', True)),

        ('BF_QT', 'Qt base path', ''),
        ('BF_QT_INC', 'Qt include path', ''),
        ('BF_QT_LIB', 'Qt library', ''),
        ('BF_QT_LIBPATH', 'Qt library path', ''),

...
どちらのファイルもlinux2/win32-mingw-config.pyで設定したQtの設定内容を追加しています。
WITH_BF_VECTEXというオプションをオンにしているときにだけQtライブラリを使用するようにしていますが、実はまだオフにしたときにvectexの機能を無効にするようにC、C++のソースコードを修正していません。
ただ、このソースコードはvectexを使用する場合以外には意味がまったくないので、オフにしたときにvectexのソースがコンパイルされないようにする必要はあまりなさそうです。

・source/blender/vectex/SConscript
以前の記事でAGG、Expatを組み込んだときは、AGGとExpatの他にAGGのSVG描画を行うファイルの部分もexternディレクトリに置いて、通常のライブラリと同じ扱いにしてしまいましたが、その後考え直してsource/blenderの中にvectexディレクトリを作成してそこに置くことにしました。
今回はAGG、Expatは使用しないのでこのディレクトリに置くのはSConscript、vectex.h、agg_vectex.cppの3つのファイルだけになります。
#!/usr/bin/python
import sys
import os

Import('env')

sources = env.Glob('*.cpp')
incs = '. #/intern/guardedalloc ../blenlib/ ../blenkernel ../makesdna'
incs += ' ' + env['BF_QT_INC']

defs = '-DQT_NO_DEBUG -DQT_SVG_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED'
cflags = '-pipe -O2 -Wall -W -D_REENTRANT'

if env['OURPLATFORM']=='linux2':
    cflags='-pipe -O2 -Wall -W -D_REENTRANT -pthread'

env.BlenderLib ( libname='bf_vectex',
        sources=sources, includes=Split(incs),
        defines=Split(defs),
        libtype='core', priority=45, compileflags = Split(cflags))
中身は、通常のライブラリとしてexternディレクトリに置いていたときとそれほど違いませんが、Qtのコンパイル用にコンパイルオプションを指定してあります。
QMakeを使用して作成したMakefileを調べてみると、コンパイルオプションの内容はLinuxとWindowsとでかなり違っているようですが、Windowsのものは中身がかなり複雑になっていてよくわからなかったので、とりあえずLinuxとほとんど同じままで済ませてしまいました。
とりあえず普通に動いているようですが、もしかすると後で修正することになるかもしれません。

○vectexのSVG描画ライブラリに関連するソースファイル

・source/blender/vectex/vectex.h
Qtを使用することによる修正ではありませんが、デバッグ用メッセージの表示に関連する部分をBlenderの仕様に合わせるために少し修正を加えました。
...
#include "BKE_global.h"
...
...
#define MSG0(F)         if(G.f & G_DEBUG) printf(F);
#define MSG1(F,A)       if(G.f & G_DEBUG) printf(F, (A));
#define MSG2(F,A,B)     if(G.f & G_DEBUG) printf(F, (A), (B));
#define MSG3(F,A,B,C)   if(G.f & G_DEBUG) printf(F, (A), (B), (C));
#define MSG4(F,A,B,C,D) if(G.f & G_DEBUG) printf(F, (A), (B), (C), (D));
...
Blenderの実行時にコマンドラインの引数としてデバッグオプション「-d」を指定することができます。
このソースコードでは、グローバル変数を調べることでこのオプションが有効かどうかを判定して、有効ならprintf()関数を実行するようなマクロの記述を行っています。

・source/blender/vectex/vectex_agg.cpp
この部分が今回のハイライトともいうべき部分です。
このファイルにはvectexのソースコードのうち、外部のSVG描画ライブラリを使用するコードがすべてまとめられています。
といっても、中身は一つの構造体と3つの関数の記述があるだけです。
...
#include <QtGui>
#include <QtSvg>
...
#include "BKE_global.h"
...
QtSVGを使用するためにヘッダファイルをインクルードします。
ここでは、2つのライブラリ用のヘッダファイルだけが必要になります。
その他に、vectex.hで修正したデバッグ用のコードを使用するためグローバル変数のヘッダをインクルードしています。

これもQtを使用することとは無関係ですが、以前vectexをBlenderに組み込んだとき、source/blender/render/intern/source/texture.cの中の関数は名前の重複を避けるために頭に「vtex_」を付けるという修正をしていましたが、こちらのvectex_agg.cppの中の関数は修正するのを忘れていました。
この状態で、Blenderに内臓したvectexの他にテクスチャプラグインのvectexを同じシーンファイル上で使用すると、プラグインのvectexから内臓vectexのコードが呼ばれてしまうという恐ろしい状況になっていました。
これを避けるため、vectex_agg.cppの中の構造体、関数の名前をオリジナルのvectexとは違う名前に変更しています。
typedef struct VtexBackend
{
    QSvgRenderer svg_render;
    double xmin;
    double ymin;
    double xmax;
    double ymax;
} VtexBackend;
AGGを使用した元のvectexでは、SVGファイルをXMLパーサで読み込んだSVGデータの本体をagg::svg::path_rendererという形で保持していました。同じものはQtSVGではQSvgRendererというクラスが対応しています。
EXT void *vtex_create_backend(char *filename)
{
    MSG0("[vtex_create_backend]\n");
   
    VtexBackend *backend = new VtexBackend();

    QString svgfname = filename;
    if (backend->svg_render.load(svgfname) != true)

    {
        fprintf (stderr, "Cannot open input file %s\n", filename);

        delete backend;
        return (NULL);
    };
   
    QSize doc_size = backend->svg_render.defaultSize();

   
    MSG2("- dimension x:%d y:%d\n", doc_size.width(), doc_size.height());

    backend->xmin = 0;
    backend->ymin = 0;
    backend->xmax = doc_size.width();
    backend->ymax = doc_size.height();

   
    return (backend);
}

この関数は、先ほど修正した構造体を作成し、引数で受け取ったSVGファイル名を使ってQSvgRendererのインスタンスを作成します。
Qtのサンプルファイルを見ると、try{}、catch{}などが見当たらないので、とりあえず戻り値を使って成否の判定をしました。
そして、読み込んだSVGファイルの中で指定されているドキュメントサイズを調べ、その値をVtexBackendの中に保存しています。
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border)
{
    char buff1[256];
    int n;

    VtexBackend *backend = (VtexBackend *) backend_data;

    int width  = tile_size + border;
    int height = tile_size + border;
   
    unsigned char          *buf = (unsigned char *)MEM_mallocN(width * height * 4, "vectex:tile");

    double size = MAX2(backend->xmax - backend->xmin, backend->ymax - backend->ymin);

     QImage image(buf, width, height, QImage::Format_ARGB32);
    
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);

   
    QColor col;
    col.setRgbF(base_r, base_g, base_b, 1.0);
    painter.fillRect(image.rect(), col);
   
    if (resolution > width) {
        QTransform transform;
        transform.reset();
        transform.translate(- tile_size * tile_col, - tile_size * tile_row);
        transform.scale(resolution / tile_size, resolution / tile_size);
        painter.setTransform(transform);
    }
   
    backend->svg_render.render(&painter);

       
    if(G.f & G_DEBUG) {
        n = sprintf(buff1, "/tmp/test%d_%d_%d.png", resolution, tile_col, tile_row);
        QString fname = buff1;
         image.save(fname, "PNG", -1);
     }

    
    return (buf);
}
元のファイルには古いコードをコメントアウトしたものがそのままになっていたりしていますが、ここではそれらは省いています。
ここではまず、MEM_mallocN()というBlender内部の関数を使用して、キャッシュに保存されるテクスチャ画像用のメモリを作成しています。
オリジナルのvectexでは1ピクセルあたりRGBの3Bytのみを確保するようになっていますが、今回はRGBAというようにアルファ値の分を加えた4Byteを確保しています。
この変更のため、texture.cの中でこのメモリ領域から作成されたテクスチャの色を読み取る処理の部分にも、同じように変更が必要になります。

Qtでは、画像処理を行う機能をQPainterというクラスが行います。
QPainterクラスを作成する際には、どの画像データに対して処理を行うのかを指定する必要があります。
そして、画像データ本体を保持するクラスがQImageです。
QImageのインスタンスを作成するには既存のJpeg、PNGなどの画像ファイルを指定したり、単純にRGBA32などのフォーマットと画像サイズのみを指定して作成することもできますが、今回は作成済みのメモリ領域を指定することで*bufというポインタからも同じメモリ領域にアクセスできるようにしています。

その後の処理は、アンチエイリアスの指定、引数で受け取った色での画像のクリア、変換行列を使用したスケール、オフセットの変更と続きます。
変換行列の部分は、AGGの同等の処理とは仕組みがかなり違うため引数をどう指定するべきなのか、かなり戸惑いました。
具体的には、AGGではSVG画像が描画される描画先の方を変換する方式なのですが、Qtでは描画元の方を変換するようになっています。

最後に、VtexBackend構造体に保持しているQSvgRendererに作成したQImageへとSVG描画を行って、画像イメージのメモリ領域を指すポインタbufを関数の戻り値として返しています。

一番最後の部分にデバッグ用のコードとして、作成したタイル画像をPNG画像として保存する機能を付けてみました。
ただし、現在のところ、出力先を/tmp/に固定しているためLinux上でしか動作しないと思います。
pic090709_04.jpg

vectex_destroy_backend()関数内には変更はありません(名前が変更されていますが、ソースコードは省略します)。

○Blender本体のソースコードの修正
vectexを組み込んだときに加えた変更を前提として、それに対してさらに修正を行った内容を説明します。

・source/blender/render/intern/source/texture.c
int vtex_get_tile(VtexInstance *instance, int x, int y, int power)
{
...
    BLI_lock_thread(LOCK_IMAGE);
...
    }
    BLI_unlock_thread(LOCK_IMAGE);

    return (index);
}
Qtの使用には直接関係ありませんが、マルチスレッドレンダリングに対応するために追加したスレッドロック関数の位置を若干変更しています。
以前は、vtex_get_tile()を呼び出しているvtex_sample()関数の中に置いていましたが、スレッドロックの影響を受ける部分はなるべく少ない方がいいと考え直して範囲を狭めてみました。
さらに突き詰めるとvtex_backend_render_tile()関数の中のメモリ確保の部分をロックすれば、レンダリング時にフリーズすることが回避できるようになるようですが、その場合同じ部分のテクスチャを複数のスレッドが重複して作成してしまうという別の問題が起こってしまいました。
重複を避けるための処理を記述すればいいわけですが、それはもう少しvectexのソースコードの仕組みを理解してからの方が良さそうに思います。
void vtex_sample(VtexInstance *instance, float *texvec, int power, int interp,
            double *pr, double *pg, double *pb)
{
...

            /* Calculate pixel offset taking the border into account */
            int offset = (y * (tile_size + VTEX_TILE_BORDER) + x) * 4;
               *pb = (double)(tile[offset]     / 255.0);
            *pg = (double)(tile[offset + 1] / 255.0);
            *pr = (double)(tile[offset + 2] / 255.0);
...
   
            /* Calculate pixel offset taking the border into account */
            int tl = (y      * (tile_size + VTEX_TILE_BORDER) + x)      * 4; /* top left */
            int tr = (y      * (tile_size + VTEX_TILE_BORDER) + x_next) * 4; /* top right */
            int bl = (y_next * (tile_size + VTEX_TILE_BORDER) + x)      * 4; /* bottom left */
            int br = (y_next * (tile_size + VTEX_TILE_BORDER) + x_next) * 4; /* bottom right */
            *pb = ((tile[tl]     * u_opposite + tile[tr]     * u_ratio) * v_opposite +
                   (tile[bl]     * u_opposite + tile[br]     * u_ratio) * v_ratio) / 255.0;   
            *pg = ((tile[tl + 1] * u_opposite + tile[tr + 1] * u_ratio) * v_opposite +
                   (tile[bl + 1] * u_opposite + tile[br + 1] * u_ratio) * v_ratio) / 255.0;   
            *pr = ((tile[tl + 2] * u_opposite + tile[tr + 2] * u_ratio) * v_opposite +
                   (tile[bl + 2] * u_opposite + tile[br + 2] * u_ratio) * v_ratio) / 255.0;
        }
    }
}

キャッシュされるタイルテクスチャの画像用メモリ領域は、AGGを使用している元のvectexではRGBの3Byteですが、それをRGBAの4Byteに拡張しています。
現在はまだ何も処理を追加していませんが、後でアルファ値を使用できるように機能を拡張したいと思っています。
そして、この変更とは直接関係ないのかもしれませんが、AGGではメモリ上の色の並びがRGBの順になっていますが、QtではBGRの順番になっています。
書き込みはQtの関数が勝手に行っているので、読み出しを行うこの部分で整合性が取れるように調整しています。

その他に、vectex.h内の構造体、関数名を変更していますので、それらを呼び出している部分で名前を修正していますが省略します。

・source/creator/creator.c -> creator.cpp
...
#include <QtGui>
...
#ifdef __cplusplus
#define EXT extern "C"
#else
#define EXT
#endif

...
extern "C" void    winlay_get_screensize(int *width_r, int *height_r);

/* for the callbacks: */

EXT int pluginapi_force_ref(void);  /* from blenpluginapi:pluginapi.c */
...
int main(int argc, char **argv)
{
    int a, i, stax, stay, sizx, sizy, scr_init = 0;
    SYS_SystemHandle syshandle;
   
    QApplication app(argc, argv, QApplication::Tty);
...
}
Qtを使用するために拡張子を「.c」から「.cpp」に修正しています。
ソースコードの中では、まず、QtGuiのヘッダファイルのインクルードを追加しています。
最後の部分は、Qtを使用するプログラムに必要な処理です。
実際に何をどうするために必要なのか詳しいことは調べていないのですが、Qtで使用している参照カウンタの管理などのために必要なのかもしれません。
中間にある部分は、creator.cの拡張子を「.cpp」に変更したことの副作用という感じのものです。
creator.cppの中で使用している別のファイルの関数のプロトタイプ宣言すべてに対して、C++のファイルから利用される際に「extern "C"」のマクロが展開されるように、このような記述を多数のファイルに追加しなければならなくなってしまいました。
このような変更を以下のファイルに対して行っています。

source/blender/blendkernel/BKE_bad_lebel_calls.h, BKE_node.h, BKE_scene.h
source/blender/imbuf/IMB_imbuf.h
source/include/BIF_drawscene.h, BIF_editsound.h, BIF_toolbox.h, BIF_usilender.h, playanim_ext.h
source/blender/render/extern/include/RE_pipeline.h
source/blender/src/winlay.h

このようなことをしてしまうと、Blenderのソースコードを保守する上で非常に不都合となりますので、もし可能であればなんとかして回避したいところですが、今のところどうすればいいのかよくわかりません。

・source/creator/SConscript
creator.cの変更に合わせてソースファイル名の修正、Qtのライブラリを使用するためにヘッダファイルの位置の指定を追加しています。
...
sources = 'creator.cpp'
...
incs += ' ' + env['BF_QT_INC']
...
これ以外に、今回、vectexパネルにファイルセレクタを使用するためのボタンを追加している関係で、修正を行っているファイルがいくつかあります。
SVGファイルの指定に関しては、現在相対パスによるファイルの指定ができないという問題がありますので、それを修正するときにまとめてとりあげたいと思います。
posted by mato at 03:25| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年07月05日

オープンソースのSVGライブラリ

前回、Blenderにvectexの機能を組み込んで、ようやくvectexの機能自体を改造する準備ができました。
ここで、使用するSVGファイルを指定する際にファイルセレクタを使用するようにしたり、UVマッピング以外のマッピングが使えない制限を改善したりと色々やりたいことがあるのですが、なによりもまず普通のSVGファイルならどんなものでも使用できるようにSVG描画機能そのものを改善したいと思います。

現在のvectexではフォントを使った文字、グラデーションやぼかしなど色々とSVGの仕様に対応していないため、あらかじめ文字をパスに変換しておくなどの対応をしておく必要があります。
これではかなり使いにくいので、できればInkscapeなどを使って作成したイラストのデータをそのまま利用できるようにしたいところです。

そこで次の目標として、vectexのSVG描画エンジンをAGG、Expatを使ったものからオープンソースの別のSVGライブラリに変更する、という改造を進めていきたいと思います。

使用するSVGライブラリには、
・vectexが使用しているメモリ上の画像領域にSVG画像を直接描画できる
・SVG画像の描画の際にオフセット、スケーリングをAGGと同じように変換行列で指定できる
というようなことが必要になりそうです。

調べてみたところ、オープンソースのSVGライブラリとしては、
・GNOMEプロジェクトのlibrsvg
・KDEのKSVG(KHTML)
・QtのQtSVG
・WXWindowのwxSVG
などが見つかりました。
これ以外に、Inkscapeの内部機能を調べて描画する部分だけを使わせてもらうということも可能ではあると思いますが、他のプログラムから利用されることを前提にして作られているライブラリと比べると、色々と大変そうです。

私自身が普段GNOME DESKTOPを使用していることもあり、とりあえずlibrsvgを第一候補として選びました。

○librsvg
調べてみると、librsvgはCairoという2Dグラフィックスライブラリと組み合わせて使うことで、vectexがAGGを使っているのと同じように独自に確保したメモリ領域を使用することができるようです。
また、Cairoの機能として、描画領域のオフセットやスケーリングを指定するような変換行列を使うこともできるようです。

librsvgをvectexに組み込む前に、テストとして簡単なサンプルプログラムを作ってみます。
SVGファイルを開いて、PNGファイルとして保存するというものです。

vectexのコードとして使用することを考えて、mallocを使って自前で確保した画像用メモリを使用したり、変換行列を使ってスケールの変更をしたりしています。

librsvg_test.c
#include <librsvg/rsvg.h>
#include <librsvg/rsvg-cairo.h>
#include <cairo-svg.h>
#include <stdio.h>
#include <stdlib.h>

int
main (int argc, char *argv[])
{
    cairo_t *cr3;
    cairo_surface_t *image;
   
    RsvgHandle *r_svg;
    RsvgError rs_err;
    RsvgDimensionData svgdim;
   
    cairo_matrix_t matrix;
   
    int width = 256;
    int height = 256;
   
    unsigned char          *buf = (unsigned char *)malloc(width * height * 4);
   
    rsvg_init();
    r_svg = rsvg_handle_new_from_file("/home/mato/Desktop/sample/tiger.svg", NULL);
    rsvg_handle_get_dimensions(r_svg,&svgdim);

    image = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, width, height, width * 4);
    cr3 = cairo_create (image);
   
    cairo_set_source_rgb (cr3, 1.0, 0.5, 0.0);
    cairo_paint (cr3);
   
    cairo_matrix_init_identity(&matrix);
    cairo_matrix_scale(&matrix, 0.5, 0.5);
    cairo_transform(cr3, &matrix);
   
    rsvg_handle_render_cairo(r_svg, cr3);
   
    cairo_surface_write_to_png (image, "hello.png");

    rsvg_handle_free(r_svg);
    rsvg_term();

    cairo_destroy (cr3);
    cairo_surface_destroy (image);

    free(buf);
   
    return 0;
}

SConscript
env = Environment()
env.MergeFlags('!pkg-config --cflags --libs librsvg-2.0')
env.Program('librsvg_test.c')

このサンプルプログラムをコンパイルするためのScons用のSConscriptファイルでは、librsvgのコンパイルオプションを指定するためにpkg-configという機能を使っています。
librsvgはライブラリの依存関係がかなり面倒なのですが、このツールを使うとMakefileやSConsvriptがかなりすっきりとしたものになります。

実際にコンパイルするときには必要な様々なライブラリのインクルードファイルの場所、ライブラリの場所、ライブラリ名などが展開されます。

Blenderにライブラリを組み込む際には、このサンプルファイルのコンパイル時に表示されるメッセージやライブラリ自体のMakefileなどを参考にしてSconsの設定ファイルを修正していくことになります。

ちなみに、このサンプルプログラムはソースコード中でSVGファイルの名前を指定していますので、実際にコンパイル、実行する場合にはこの部分を適当に書き換える必要があります。
また、librsvgは当然として、それ以外にもcairo、GLIB、GKT+などいくつかのライブラリの開発用パッケージをインストールしておく必要があります。

サンプルプログラムが正常に動作することが確認できて、librsvgがvectexのバックエンドとして使えそうな感触をつかむことができました。
pic090704_01.jpg

さっそく、Blenderに組み込んでみました...が、色々あってこのlibrsvgを使ってvectexを改造する計画は保留することにしました。
実は、librsvgは絵の再現性については文句なしなのですが、vectexで使うにはかなり致命的な問題があることに、後から気が付きました。

一応、AGG、Expatを置き換える形でlibrsvgをBlenderに組み込んで動作させるところまで改造を進めてあります。
現状でも、元のvectexでは読み込めなかったテキストやグラデーションなどを使ったSVGファイルを読み込んで、Blenderのレンダリング画像の中に表示させることができるようになっています。
pic090704_07.jpg

問題というのは、SVG画像の細部をある程度以上に拡大する状況で、拡大率に応じてテクスチャの作成に時間がかかるようになり、ある限界以上の解像度になるとプログラムが停止してしまうこともあるというものです。
レンダリングのテストでAGG、Expatのバージョンで1分以内にレンダリングできたものが、librsvgを使った場合20分以上かかるということもありました。

また、ライブラリの依存関係が複雑なこともあって、Blenderのビルドシステムへの組み込みが今ひとつうまくいっていないらしく、動作が非常に不安定な感じになっています。

AGG、Expatを使った元のvectexでは、SVG画像の一部を拡大表示する際に、どこをどれだけ拡大しているかという描画領域の違いによって、描画時間が極端に長くなるようなことは感じられません。
しかし、librsvgのサンプルSVGビュワーやMozilla FirefoxなどでSVGファイルを表示して、画像の一部をどんどんと拡大していくと、拡大率が増えるにしたがって処理速度が低下し、ある限界を超えるとSVGビュワーでは描画不能となり、Firefoxでは拡大ができなくなります。
pic090704_02.jpg pic090704_03.jpg

Adobeの作成したInternet Explore用のSVGプラグインでも同じようなことが起こるようですので、これはlibrsvgに何か問題があるということではなくて、描画品質を最大限に高めるようなSVG描画エンジンを目指すと、こういう形にならざるを得ないという仕様のようなものなのだと思います。

結局、このような仕様のライブラリは、vectexに組み込んでレンダリングの状況に応じて最適な解像度のテクスチャを作成するという用途には、そもそも初めから向いていなかったということが分かりました。

○QtSVG
ということで、次の候補を探しているうちに、QtSVGというものがあることを知りました。
QtというのはLinuxのデスクトップ環境KDEで使われているプログラミングツールキットです。
このQtのサンプルの中にSVGビュワーが含まれています。Qt4のExampleの中のPaintingというカテゴリに入っています。
pic090704_04.jpg

このサンプルプログラムを動かしてみて、拡大表示の動作が非常に高速なことに驚きました。
起動時に既定で読み込まれるSVGファイルがいきなりアニメーションファイルになっています。
そして、Renderingメニューを開くとOpenGLという項目があって、2D描画を行う際にOpenGLを使ったハードウェアレンダリングが可能なようです。
pic090704_05.jpg

マウスホイールで拡大縮小操作ができますが、細部を拡大表示してもほとんどスピードは落ちません。
このSVG描画エンジンをvectexで使用できるとしたら、テクスチャ作成の時間はAGGを使っているオリジナルと同じか、もっと早くなるかもしれません。

対応しているSVGの仕様はSVG 1.2 tinyというものでECMA scriptやDOMなどには対応していないということですが、vectexで使用する場合はあまり関係なさそうです。

librsvgの時と同じようにサンプルプログラムを作ってみました。
qt_test.cpp
#include <QtGui>
#include <QtSvg>
#include <stdlib.h>

int main(int argc, char **argv)
{   
    QApplication app(argc, argv, QApplication::Tty);
    
    QString svgfname = "/home/mato/Desktop/sample/tiger.svg";
    QSvgRenderer svg_render;
    svg_render.load(svgfname);
   
    QSize size = svg_render.defaultSize();
    unsigned char          *buf = (unsigned char *)malloc(size.width() * size.height() * 4);

     QImage image(buf, size.width(), size.height(), QImage::Format_ARGB32);
    
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
   
    QColor col;
    col.setRgbF(1.0, 0.5, 0.0, 1.0);
    painter.fillRect(image.rect(), col);
       
    QTransform transform;
    transform.reset();
    transform.translate(-128, -128);
    transform.scale(2.0, 2.0);
    painter.setTransform(transform);
   
    svg_render.render(&painter);

    QString fname = "Hello.png";
     image.save(fname, "PNG", -1);
    
    free(buf);
 
    return 0;
}

qt_test.pro
######################################################################
# Automatically generated by qmake (2.01a) ? 6? 26 23:46:27 2009
######################################################################

TEMPLATE = app
TARGET =
DEPENDPATH += .
INCLUDEPATH += .

# Input
SOURCES += qt_test.cpp

QT += svg

Qtを使ったプログラムはコンパイルの際に、普通とはちょっと違ったQMakeというコマンドを使うようです。
qt_test.proというのは、そのQMakeで使用するコンパイルの設定ファイルです。
設定する内容はSconsのSConscriptと似たような感じのものですが、QMakeというコマンドはこのファイルを使って直接コンパイルするのではなくて、GCCなどの別のコンパイラで使えるMakefileを作成することになります。
pic090704_06.jpg

vectexで使用することを想定して、mallocで確保したメモリの使用、変換行列でのスケールなどを行っています。
QMakeという独自のツールを使っているので、Blenderのビルドシステムとの相性がちょっと気になりますが、普通のMakefileが作成されているのでインクルードファイルやライブラリ名などを調べるのは問題なくできそうです。

本来は、QtはGUIを使ったプログラミングを行うためのものですが、QApplicationのコンストラクタで指定することでコンソールアプリケーションとして使用できるようです。

ただし、Qtを使ったプログラムでは、main()関数の中でQApplicationのインスタンスを必ず作成しなければならない、ということが気になります。
はたして、これをBlenderに組み込むことができるのでしょうか...。

かなり無理があるような気もしますが、main()関数のあるファイルcreator.cの拡張子を「.c」から「.cpp」に変えることで無理やり組み込むことができました。

次回の記事で、詳しい内容について書きたいと思います。
posted by mato at 01:10| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年06月12日

Blenderにvectexを組み込む(2)

前回サーバにアップロードできなかったvectex機能内臓の改造版Blenderの実行ファイルです。
Linux32bit(Blender_modified_Linux32090611.tar.gz)、Linux64bit(Blender_modified_Linux64090611.tar.gz)、Windows32bit(Blender_modified_win32090611.zip)版の3種類です。

Linux版、Windwos版ともにPythonは2.5を使用しています。Windows版はMinGWでのコンパイルのため、FFMpeg、Quicktimeの機能が入っていません。
ソースコードが前回のものと少しだけ変わっていますので、実行ファイルを作成した際に使用したものをこちら(Blender_modified_src090611.zip)に置いておきます。

※追記(2009/07/09) 実行ファイルを修正版と差し替えました。
修正内容は、ベースのBlenderのバージョンを2.49aに変更、ファイルセレクタの追加、Linux版のPythonのバージョンを2.6に変更、などです。
ソースコードは、古い方もそのまま残します。修正版のソースコードはこちら(Blender_modified_src090709.zip)です。

※追記(2009/07/17) 実行ファイルを修正版と差し替えました。
修正内容は、バンプマッピングのサポート、SVGファイル名の相対パスへの対応、その他、若干のバグ修正、です。
修正版のパッチファイルはこちら(Blender_agg_vectex_patch090717.zip)です。

今回の記事の内容は、前回の記事でBlenderに組み込んだvectexのソースコードを修正して、plugin版とほぼ同じように動くようにするまでの流れを書いてみます。

○CLAMPマクロ
前回の記事でBlenderにvectexのソースコードを組み込んでみましたが、この状態でSconsを使ってコンパイルすると以下のようなエラーが出ます。
pic090611_01.jpg

1071行目付近で構文エラーが起こっているようです。
ソースコードを見てみるととくに問題はなさそうで、どこが悪いのかしばらく悩みました。  
pic090611_02.jpg

1071行目のコードにあるCLAMP()という命令文は、すべて大文字になっていることからも推測できますが、関数ではなくてマクロです。
ここで使用されているマクロはutil.hでテクスチャプラグイン用に用意されているものです。
#define CLAMP(val, low, high) ((val>high)?high:((val<low)?low:val))
そして、Blenderのソースコードを調べてみると、source/blender/blenkernel/BKE_utildefines.hというファイルに同じ名前のマクロが見つかりました。
pic090611_03.jpg

マクロの内容を見てみると、if文を使っていてutil.hに書かれているものとは少し展開のされ方が違っているようです。
若干違ってはいますが、すぐ下のCLAMPIS(a,b,c)というものの方が展開のされ方が近いようですので、コンパイルエラーが起こっている部分のCLAMPマクロをCLAMPISに置き換えてみました。
void vtex_sample(VtexInstance *instance, float *texvec, int power, int interp, double *pr, double *pg, double *pb)
{
        ...

        /* extend: copy the color of edge pixels */
        x = CLAMPIS(x, 0, resolution - 1);
        y = CLAMPIS(y, 0, resolution - 1);
        /* clip: alpha=0 outside edges */
        ...
}
○dxt、dyt
これで、とりあえず最後までSconsのビルドが進むようになりました。
早速、作成されたBlender実行ファイルを動かしてみると、Textureパネルのテクスチャ選択メニューにvectexの項目が追加されていて、vectexのパネルを表示することができます。
しかし、テキスト入力の部分でSVGファイルを指定して読み込もうとすると、フリーズして動かなくなりました。
pic090611_04.jpg

ここから先は、ソースコードを眺めているだけだとなかなか問題の原因を突き止めるのは難しくなります。
ソースコードのあちこちにprintf()などを組み込んで、どこまで正常に動作しているのか確認することでもできますが、デバッガを使うとより簡単にプログラムの動作を調べることができます。
(私が実際にこの部分のソースコードを修正したときには、vectex.hにあるMSGマクロをあちこちに書き込んでコンソールに出力される内容で動作確認しています。その後、さらにややこしい問題に直面し、この方法でのデバッグには限界を感じてデバッガを使用することにしました。)

BlenderをSconsでビルドする場合、
scons BF_DEBUG=1
というようにBF_DEBUGオプションを指定することで、デバッグ用のシンボルを埋め込んだ実行ファイルを出力できます。
pic090611_05.jpg

私の使っているUbuntuはGnomeデスクトップのものでKDE環境ではないのですが、KDbvgというデバッガを使ってみました。
デバッガを起動するとファイルメニューから実行ファイルを読み込むことができますので、デバッグオプションを付けてビルドしたBlenderの実行ファイルを読み込みます。自動的にBlenderの最初に起動する部分のソースコードが表示されますが、必要に応じて他のソースコードも読み込むことができます。
pic090611_06.jpg pic090611_07.jpg

デバッガを使用すると無限ループなどでフリーズするような場合でも、ソースコードのどの部分で処理が止まっているのかを調べることができます。
デバッガからBlenderを起動して、vectexを使用するまで通常通りに操作します。フリーズしたら、デバッガの「ブレーク」という機能を使用してプログラムを一時停止します。このとき表示される部分がフリーズを起こしている部分になります。
pic090611_08.jpg

vectex()関数の中のfor()文で変数powerの値を変更している部分でフリーズが起こっていることが分かりました。
そして、その原因はvectex()関数の引数として読み込んでいるdxt[]、dyt[]の値が正しく得られていないことにあるようです。

ソースコード、texture.cの中でvectex()関数のすぐ下にはplugintex()という関数があります。
テクスチャプラグインのvectexを実行しているときには、このplugintex()という関数が呼ばれているはずなので、この関数の中でdxt[]、dyt[]がどのように処理されているのかを調べると、原因について何か分かるかもしれません。
pic090611_09.jpg

plugintex()のソースコードを見てみると、この部分でosatexという変数の値に応じてpluginTexDoit()関数にdxt、dytの値をそのまま渡すか、代わりに0を渡すかを切り替えているようです。
dxy、dytはfloat値の配列(ポインタと同じようなもの)ですから、osatexが真(!=0)のときアドレスを渡し、偽(=0)のときはアドレスが無効であることを伝えるために0を渡しているようです。
今回作成したvectex()関数でも、同じようにosatexの値に応じてdxt、dytの値を変更するコードを追加することで、テクスチャプラグインのvectexと同じように「if (dxt && dyt)」の部分を動作させることができるはずです。
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
    ...
    if (osatex)
    {
    }
    else
    {
        dxt = 0;
        dyt = 0;
    }

    ...
}
ということで、こんな感じのコードを追加しました。もう少し違う書き方もできるはずですが、とりあえずなるべく元々あるソースコードには変更を加えずに、分かりやすくすることを第一に考えていますので、これはこれでいいことにします。
このようにosatexという変数が必要になったため、vectex()関数の引数に「int osatex」を加えてあります。
pic090611_10.jpg pic090611_11.jpg

○osatex
これで、テクスチャプレビューにSVG画像が表示され、普通にレンダリングもできるようになりました。
ところが、色々とカメラ設定を変えてレンダリングしてみると、ちょっと問題があることに気が付きます。
vectex付属のサンプルファイルtest-animated.blendをレンダリングしてみると、アニメーションの最後の方で本来なら虎の目の部分にカメラが近づいたときに「Blender Vector Texture!」という文字がはっきり表示されるはずですが、画像がぼけたようになり文字がまったく読めません。
pic090611_19.jpg pic090611_20.jpg

デバッガで調べてみると、vectex()関数の中ではosatexの値が常に0でそれ以外の値になることがありません。
plugintex()関数の中でのosatexの値の変化を見てみると、テクスチャプラグインを読み込んでテクスチャプレビューを表示するときにはosatex=0となりますが、レンダリングを実行するとosatex=512などになり、0以外の値になっています。
pic090611_14.jpg

ところが、vecetx()関数の中でのosatexの値はレンダリング時でも0のままです。
grepコマンドを使っていろいろと調べた結果、TEX_PLUGINとOSAが関係していそうなコードが/source/blender/blenkernel/intern/material.cに見つかりました。
pic090611_15.jpg

ELEM3()というのはマクロのようです。
grepコマンドで調べると、/source/blender/blenkernel/BKE_utildefines.hに定義が見つかります。
pic090611_16.jpg

似たようなマクロがたくさんありますが、一番最初の引数が2番目以降のもののいずれかと同じかどうかを調べるためのもののようです。
先ほどのmaterial.cの中のコードは、テクスチャのタイプがイメージかプラグインか環境マップのときにma->texcoに512とのビットマスクのORを設定するというような内容で、テクスチャプラグインでosatex=512となっているのはこのコードが行っているようです。
もしそうであれば、このコードのテクスチャタイプにTEX_VECTEXを追加することで、vectex()関数にosatexが渡されるときにテクスチャプラグインを使っているときと同じ値が入るようになりそうです。
static void do_init_render_material(Material *ma, int r_mode, float *amb)
{
            ...
            if(r_mode & R_OSA) {

                if ELEM4(mtex->tex->type, TEX_IMAGE, TEX_PLUGIN, TEX_ENVMAP, TEX_VECTEX) ma->texco |= TEXCO_OSA;
            }
            ...
}

この修正を行ってビルドしたBlenderでは、レンダリング時にosatex=512という値が得られるようになりました。
test-animated.blendをレンダリングしてみると、虎の目の中に置かれているテキストがはっきりとレンダリングされるようになっています。
pic090611_17.jpg pic090611_18.jpg

○.blendファイルの読み込み
元のvectex.cのソースコードには、plugin_instance_init()という関数が使われています。
これは、Blenderのテクスチャプラグインが用意している関数のうちの一つで、vectexではこの関数の中に.blendファイルを開いたときに無効なInstance構造体へのポインタがメモリアクセス違反を起こすのを防ぐコードが書かれています。
pic090611_21.jpg
これがないとvectexを使って作成された.blendファイルを開いた時、すぐにBlenderが落ちるという状況になります。
plugin_instance_init()に相当する機能はBlenderの内部テクスチャには用意されていませんので、自力で同じような処理を作成する必要があります。

Blenderで.blendファイルを開くときの処理が書かれているのは、
/source/blender/blenloader/intern/readfile.c
というファイルです。このファイルにdirect_link_texture()という関数があり、.blendファイルを開いたときにその中でテクスチャが使われていた場合に、必要な処理を記述できるようになっています。
テクスチャプラグインのplugin_instance_init()関数も、この部分で実行されるようになっています。
static void direct_link_texture(FileData *fd, Tex *tex)
{
    ...
    if(tex->vtex_instance_data){
        tex->vtex_instance_data= NULL;

    }
    ...
}
このような感じでInstance構造体へのポインタをクリアするコードを追加しました。

○デフォルト値の設定
Textureパネルで新規に作成するテクスチャタイプを選択すると、作成されるパネルの数値データにはあらかじめ設定されたデフォルト値が入っています。
pic090611_22.jpg pic090611_23.jpg

テクスチャプラグインにはボタンの設定を行うVarStructという構造体にデフォルト値を指定できるようになっています。しかし、Blenderの内部のボタンを作成するuiDefBut()という関数にはデフォルト値を指定する機能はありません。

Blenderのソースコードの中でTex構造体のメンバ変数にデフォルト値を設定している場所を探してみると、default_tex()という関数として用意されています。この部分にvectexのためのコードを以下の用に追加しました。
/source/blender/blenkernel/intern/texture.c
void default_tex(Tex *tex)
{
    ...
    tex->vn_distm = 0;
    tex->vn_coltype = 0;
    /* vectex */
    tex->vtex_enable = 1;
    tex->vtex_mipmap = 1;
    tex->vtex_interpol = 1;
    tex->vtex_tex_level = 0;
    tex->vtex_mem_max = 20;
    tex->vtex_mem = 0;

    ...
}
ただ、ここにvectex用の変数の設定を書いても、Textureパネルからvectexを選択したときに表示されるパネルには結果が反映されません。
この関数に加えた変更は、テクスチャプレビューのパネルにある「Default Vars」というボタンを押すと、vectexパネルの状態をデフォルト状態に変わることで確認できます。
pic090611_24.jpg

Blenderにもとから用意されているプロシージャルテクスチャのパネルは、あらかじめ設定してあるデフォルト値がセットされた状態で開きますが、その設定がどこで行われているのかちょっと悩みました。
grepコマンドで調べると、上記のdefault_tex()関数と、/source/blender/blenloader/intern/readfile.cのdo_versions()という関数の中でTex構造体のメンバ変数に値を設定しているコードが見つかります。

今回Tex構造体に追加したvectex用の変数を、do_version()関数で最新のバージョン2.49よりも読み込んだデータのバージョンが小さい場合にデフォルト値をセットするように記述しました。
static void do_versions(FileData *fd, Library *lib, Main *main)
{
    ...
    if (main->versionfile < 249) {
        Scene *sce;
        Tex *tex;
        for (sce= main->scene.first; sce; sce= sce->id.next)
            sce->r.renderer= 0;

        for(tex= main->tex.first; tex; tex= tex->id.next) {
            /* vectex */
            tex->vtex_enable = 1;
            tex->vtex_mipmap = 1;
            tex->vtex_interpol = 1;
            tex->vtex_tex_level = 0;
            tex->vtex_mem_max = 20;
            tex->vtex_mem = 0;
        }       
    }
    ...
}
これで、Blender249より前に作成された.blendファイルを読み込んだとき、あるいはまったくファイルを読み込まずにBlender実行ファイルに組み込まれたデータを読み込む場合に、デフォルト値が設定された状態でvectexのパネルが開くようになります。

もし、今後2.49よりも新しいバージョンにvectexを組み込む場合は、このdo_version()の記述はそのままにして、代わりにBlender実行ファイルに内臓されているB.blendファイルのデータ(/source/blender/src/B.blend.c)をvectexのデフォルト値が設定されたものと入れ替えることで対応するようにします。
たとえばVer2.50でvectexを組み込む場合、上記の変更をさらに修正して(main->versionfile < 250)のときにvectexのデフォルト値を設定するというコードを書いてしまうと、バージョン2.49としてvectexを使用して保存したファイルを読み込んだときに、保存されているデータの設定値をすべてデフォルト値で上書きしてしまうことになり、正しくデータを読み込めなくなります。
posted by mato at 02:53| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
×

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