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 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

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


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

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