2017-04-24

Siphon memo 3

Siphon を開発するにあたって Blender における Freestyle の処理フローを調査している。Blender のソースコードを引用しつつ、Freestyle の関数がどのように呼び出されているのかざっくり追ってみよう。

1. bpy.ops.render.render()

基本的に Freestyle はレンダリング処理中に動作する。そこで bpy.ops.render.render() の処理を見てみよう。bpy.ops.render は一見 Python の submodule のように見える。

>>> import bpy
>>> bpy.ops.render
<module like class 'bpy.ops.render'>

module like class という見慣れないクラスのようだ。help() で調べてみる。

>>> help(bpy.ops.render)
Help on BPyOpsSubMod in module bpy.ops object:

class BPyOpsSubMod(builtins.object)
 |  Utility class to fake submodules.
 |
 |  eg. bpy.ops.object

 ...

すると fake submodules という興味深い文字列が出力される。どうやらただのクラスではないようだ。実際のコード片については /release/scripts/modules/bpy/ops.py にある。読んでみると _bpy という C-extension の中身を Python submodule に見せかけてアクセスできるようにしたものらしい。この C-extension は /source/blender/python/intern/bpy_operator.c に内容が記述されている。

render.render のような処理はどうやら OperatorType (OT) と呼ばれているらしく、bpy_operator.cL.169 のように、

ot = WM_operatortype_find(opname, true);

という関数で呼び出されるようだ。おそらく opname には render.render という文字列が入りそうである。WM_operatortype_find 関数は /blender/windowmanager/intern/wm_operators.c に実態がある。

wmOperatorType *WM_operatortype_find(const char *idname, bool quiet)
{
    if (idname[0]) {
        wmOperatorType *ot;

        /* needed to support python style names without the _OT_ syntax */
        char idname_bl[OP_MAX_TYPENAME];
        WM_operator_bl_idname(idname_bl, idname);

        ot = BLI_ghash_lookup(global_ops_hash, idname_bl);
        if (ot) {
            return ot;
        }

さて、ここで WM_operator_bl_idname 関数は同じファイルに、

/* some.op -> SOME_OT_op */
void WM_operator_bl_idname(char *to, const char *from)

とある。つまり render.render はおそらく RENDER_OT_render に変換され WM_operatortype_find 関数内部の BLI_ghash_lookup で検索が行われるようだ。予想通り /editors/render/render_internal.cRENDER_OT_render という関数を見つけた。

/* contextual render, using current scene, view3d? */
void RENDER_OT_render(wmOperatorType *ot)
{
    PropertyRNA *prop;

    /* identifiers */
    ot->name = "Render";
    ot->description = "Render active scene";
    ot->idname = "RENDER_OT_render";

    /* api callbacks */
    ot->invoke = screen_render_invoke;
    ot->modal = screen_render_modal;
    ot->cancel = screen_render_cancel;
    ot->exec = screen_render_exec;

さて、上のコード片を読んだ限りでは /* api callbacks */ 以下の行が処理の中心になりそうだ。試しに同じファイルに含まれている ot->execscreen_render_exec 関数を見てみる。

/* executes blocking render */
static int screen_render_exec(bContext *C, wmOperator *op)
{
    Scene *scene = CTX_data_scene(C);
    SceneRenderLayer *srl = NULL;
    Render *re;
    Image *ima;
    View3D *v3d = CTX_wm_view3d(C);
    Main *mainp = CTX_data_main(C);
    unsigned int lay_override;
    const bool is_animation = RNA_boolean_get(op->ptr, "animation");
    const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");

    // ...

    re = RE_NewRender(scene->id.name);

    // ...

    BLI_begin_threaded_malloc();
    if (is_animation)
        RE_BlenderAnim(re, mainp, scene, camera_override, lay_override, scene->r.sfra, scene->r.efra, scene->r.frame_step);
    else
        RE_BlenderFrame(re, mainp, scene, srl, camera_override, lay_override, scene->r.cfra, is_write_still);
    BLI_end_threaded_malloc();

    // ...

    return OPERATOR_FINISHED;
}

実際のレンダリングは1フレームを描画する場合 RE_BlenderFrame、アニメーションの場合 RE_BlenderAnim で行われるみたいだ。RE_BlenderFrame/render/intern/source/pipeline.c に実装がある。この pipeline.c でようやく Freestyle の文字が出現し始める。

2. /render/intern/source/pipeline.c

pipeline.c はレンダリング処理が主に記述された 4,000 行ほどのファイルである。ここでは /freestyle/FRS_freestyle.h がインクルードされており pipeline.c のいくつかの関数で様々な Freestyle の関数が呼び出されている。FRS_freestyle.h の関数名には FRS_ という prefix が基本的に付けられており、この prefix で Blender のソースコード全体を検索すると Freestyle が外部から呼び出されている場面も見つけることができるので便利である。

pipeline.c の中で実際に呼び出されているのは以下の7つのようだ。

FRS_init_stroke_renderer(re);
FRS_begin_stroke_rendering(re);
FRS_do_stroke_rendering(re, srl, render);
FRS_end_stroke_rendering(re);

FRS_composite_result(re, srl, freestyle_render);

FRS_is_freestyle_enabled(srl);
FRS_exit();

すべての関数は /freestyle/intern/blender_interface/FRS_freestyle.cpp に実装がある。ちなみに /freestyle/ 内で全ソースコードで _OT_ とつく関数を検索したところヒットしなかったため OperatorType 自体は存在しないようだ。

呼び出されている関数の中で stroke が名前に含まれるものについて実装を少し見てみよう。

void FRS_init_stroke_renderer(Render *re)
{
    if (G.debug & G_DEBUG_FREESTYLE) {
        cout << endl;
        cout << "#===============================================================" << endl;
        cout << "#  Freestyle" << endl;
        cout << "#===============================================================" << endl;
    }

    init_view(re);

    controller->ResetRenderCount();
}
void FRS_begin_stroke_rendering(Render *re)
{
    init_camera(re);
}
Render *FRS_do_stroke_rendering(Render *re, SceneRenderLayer *srl, int render)
{
    Render *freestyle_render = NULL;

    if (!render)
        return controller->RenderStrokes(re, false);

    RenderMonitor monitor(re);
    controller->setRenderMonitor(&monitor);
    controller->setViewMapCache((srl->freestyleConfig.flags & FREESTYLE_VIEW_MAP_CACHE) ? true : false);

    if (G.debug & G_DEBUG_FREESTYLE) {
        cout << endl;
        cout << "----------------------------------------------------------" << endl;
        cout << "|  " << (re->scene->id.name + 2) << "|" << srl->name << endl;
        cout << "----------------------------------------------------------" << endl;
    }

    // prepare Freestyle:
    //   - load mesh
    //   - add style modules
    //   - set parameters
    //   - compute view map
    prepare(re, srl);

    if (re->test_break(re->tbh)) {
        controller->CloseFile();
        if (G.debug & G_DEBUG_FREESTYLE) {
            cout << "Break" << endl;
        }
    }
    else {
        // render and composite Freestyle result
        if (controller->_ViewMap) {
            // render strokes
            re->i.infostr = IFACE_("Freestyle: Stroke rendering");
            re->stats_draw(re->sdh, &re->i);
            re->i.infostr = NULL;
            g_freestyle.scene = re->scene;
            int strokeCount = controller->DrawStrokes();
            if (strokeCount > 0) {
                freestyle_render = controller->RenderStrokes(re, true);
            }
            controller->CloseFile();
            g_freestyle.scene = NULL;

            // composite result
            if (freestyle_render) {
                FRS_composite_result(re, srl, freestyle_render);
                RE_FreeRenderResult(freestyle_render->result);
                freestyle_render->result = NULL;
            }
        }
    }

    return freestyle_render;
}
void FRS_end_stroke_rendering(Render * /*re*/)
{
    // clear canvas
    controller->Clear();
}

よく見かける controller というのは /freestyle/intern/application/Controller.h で定義されている。この controller には FEdgeXDetector (FEdgefeature edge を指す)や ViewMapBuilder などの処理機構が定義されている。FRS_freestyle.cppFRS_initialize()controller の初期化処理が記述されている。

void FRS_initialize()
{
    if (freestyle_is_initialized)
        return;

    pathconfig = new Config::Path;
    controller = new Controller();
    view = new AppView;
    controller->setView(view);
    controller->Clear();
    g_freestyle.scene = NULL;
    lineset_copied = false;

    BLI_callback_add(&load_post_callback_funcstore, BLI_CB_EVT_LOAD_POST);

    freestyle_is_initialized = 1;
}

中心的な処理を行うのは FRS_do_stroke_rendering のようだ。特に prepare(re, srl)

    // prepare Freestyle:
    //   - load mesh
    //   - add style modules
    //   - set parameters
    //   - compute view map

というコメントの通り ViewMap の計算を行ったり重要な役割を果たしている。

3. Blender アドオンからの利用

さて、ここまで Blender における Freestyle の処理を内部処理までは踏み込まないでざっくりと見てきた。Blender アドオンからどれくらいの粒度で利用可能だろうか。Python スクリプトで記述することを考えると、細かく制御するのはちょっと厳しい気がしている。

これから確認しなければならないことは、いまのところ以下である:

  • ViewMap cache の詳細 (現行の Blender では ViewMap cache の機能が搭載されている。どのようなデータがキャッシュされているのか、インスタンスが create されたり free されるタイミングを調べる)
  • Predicates の処理フロー

たとえば Freestyle の論文にある Calligraphic shader は方向をベクトルの形で指定することができるが、これを現在用意されている Python スタイルモジュールではなく NodeTree の GUI でマウス等を用い方向を指定して、さらにリアルタイムで結果を反映させられないかと考えている。以前 Freestyle SVG Exporter を調べたとき Python のオブジェクトから ViewShape, ViewEdge, ViewMap などにアクセスできた記憶があるので、それらを deepcopy して使うという手もあるのかもしれない。