2017-05-02

Siphon memo 4

前回に引き続き Blender における Freestyle の処理フローを調査している。今回は ViewMap の構築とキャッシュあたりについて調べてみよう。

1. Controller::Controller()

まずは Controller::Controller() の実装を見てみよう。

namespace Freestyle {

Controller::Controller()
{

    // ...

    _ViewMap = 0;

    _Canvas = 0;

    _VisibilityAlgo = ViewMapBuilder::ray_casting_adaptive_traditional;
    //_VisibilityAlgo = ViewMapBuilder::ray_casting;

    _Canvas = new AppCanvas;

    _inter = new PythonInterpreter();
    _EnableViewMapCache = false;
    _EnableQI = true;
    _EnableFaceSmoothness = false;
    _ComputeRidges = true;
    _ComputeSteerableViewMap = false;
    _ComputeSuggestive = true;
    _ComputeMaterialBoundaries = true;
    _sphereRadius = 1.0;
    _creaseAngle = 134.43;
    prevSceneHash = -1.0;

    init_options();
}

_EnableViewMapCache = false; となっている。これは同じファイルに

void Controller::setViewMapCache(bool iBool)
{
    _EnableViewMapCache = iBool;
}

とあるようにここで指定されるようである。

実際は /freestyle/intern/blender_interface/FRS_freestyle.cppFRS_do_stroke_rendering 関数

Render *FRS_do_stroke_rendering(Render *re, SceneRenderLayer *srl, int render)
{
    // ...

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

で実行されているようだ。

2. View map cache

まずは ViewMap のキャッシュを削除している箇所を見てみよう。

/freestyle/intern/blender_interface/FRS_freestyle.cpp を見てみる。名前からおそらくキャッシュ削除処理は FRS_free_view_map_cache 関数で行われているのではないかと考える。

void FRS_free_view_map_cache(void)
{
    // free cache
    controller->DeleteViewMap(true);
#if 0
    if (G.debug & G_DEBUG_FREESTYLE) {
        printf("View map cache freed\n");
    }
#endif
}

controller->DeleteViewMap を見てみる。/freestyle/intern/application/Controller.cpp L.419 に実装が存在する。

void Controller::DeleteViewMap(bool freeCache)
{

    // ...

    if (NULL != _ViewMap) {
        if (freeCache || !_EnableViewMapCache) {
            delete _ViewMap;
            _ViewMap = NULL;
            prevSceneHash = -1.0;
        }
        else {
            _ViewMap->Clean();
        }
    }
}

freeCache || _EnableViewMapCache から推測して freeCache はハードコードされたタイミングでのキャッシュ消去を意図しているのだろう。DeleteViewMap で検索すると、

./freestyle/intern/application/Controller.cpp:  DeleteViewMap();
./freestyle/intern/application/Controller.cpp:void Controller::DeleteViewMap(bool freeCache)
./freestyle/intern/application/Controller.cpp:  DeleteViewMap(true);
./freestyle/intern/application/Controller.cpp:  DeleteViewMap();
./freestyle/intern/application/Controller.h:    void DeleteViewMap(bool freeCache = false);
./freestyle/intern/blender_interface/FRS_freestyle.cpp: controller->DeleteViewMap(true);

とある。実際に呼び出されている箇所を見てみると、

呼び出し元関数 命令
Controller::CloseFile() DeleteViewMap()
Controller::ComputeViewMap() DeleteViewMap(true) 強制削除
Controller::DrawStrokes() DeleteViewMap()
FRS_free_view_map_cache(void) DeleteViewMap(true) 強制削除

となる。

キャッシュを削除する場合は、delete _ViewMap; とあるように完全に Controller_ViewMap 自体を削除するようだ。SceneHash というのは ViewMap を生成する際 scene graph から生成されるハッシュのようである。

ではキャッシュを削除しない場合はどうなるかというと _ViewMap->Clean() が実行されるようだ。この実装は /freestyle/intern/view_map/ViewMap.cpp L.66 付近にある。

void ViewMap::Clean()
{
    vector<FEdge*> tmpEdges;

    for (vector<ViewShape*>::iterator vs = _VShapes.begin(), vsend = _VShapes.end(); vs != vsend; vs++) {
        vector<FEdge*>& edges = (*vs)->sshape()->getEdgeList();
        for (vector<FEdge*>::iterator it = edges.begin(), itend = edges.end(); it != itend; it++) {
            if ((*it)->isTemporary()) {
                (*it)->setTemporary(false); // avoid being counted multiple times
                tmpEdges.push_back(*it);
            }
        }
    }

    for (vector<FEdge*>::iterator it = tmpEdges.begin(), itend = tmpEdges.end(); it != itend; it++) {
        for (vector<ViewShape*>::iterator vs = _VShapes.begin(), vsend = _VShapes.end(); vs != vsend; vs++) {
            (*vs)->sshape()->RemoveEdge(*it);
        }
        (*it)->vertexA()->RemoveFEdge(*it);
        (*it)->vertexB()->RemoveFEdge(*it);
        delete (*it);
    }
}

これを見る感じだと feature edges のうち一時的に使用したものについて削除しているようである。

さて、この FRS_free_view_map_cache 関数は /freestyle/intern/blender_interface/FRS_freestyle.cpp 以外に /makesrna/intern/rna_scene.c という箇所でも使用されている。

L.1604-1609

static void rna_Scene_use_view_map_cache_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
#ifdef WITH_FREESTYLE
    FRS_free_view_map_cache();
#endif
}

とある。さて、これは同じファイルの L.4141-4145 に 以下のような実装がある。

    prop = RNA_def_property(srna, "use_view_map_cache", PROP_BOOLEAN, PROP_NONE);
    RNA_def_property_boolean_sdna(prop, NULL, "flags", FREESTYLE_VIEW_MAP_CACHE);
    RNA_def_property_ui_text(prop, "View Map Cache",
                             "Keep the computed view map and avoid re-calculating it if mesh geometry is unchanged");
    RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_use_view_map_cache_update");

"Keep the computed view map..." の文字列は、

ViewMapCache

と同じようだ。なのでチェックが入れば _EnableViewMapCache = true となり、

  • Controller::CloseFile()
  • Controller::DrawStrokes()

の場合であれば _ViewMap は削除されず clean() だけ実行される仕組みのようだ。しかし RNADNA が Blender でのどんなデータを意味しているのかまだ良くわかっていない。後で調べてみよう。

3. ViewMapBuilder, Controller::ComputeViewMap()

ViewMap の計算は /freestyle/intern/view_map/ViewMapBuilder.cpp L.995 に内容が示されている。

ViewMap *ViewMapBuilder::BuildViewMap(WingedEdge& we, visibility_algo iAlgo, real epsilon,
                                      const BBox<Vec3r>& bbox, unsigned int sceneNumFaces)
{
    _ViewMap = new ViewMap;
    _currentId = 1;
    _currentFId = 0;
    _currentSVertexId = 0;

    // Builds initial view edges
    computeInitialViewEdges(we);

    // Detects cusps
    computeCusps(_ViewMap);

    // Compute intersections
    ComputeIntersections(_ViewMap, sweep_line, epsilon);

    // Compute visibility
    ComputeEdgesVisibility(_ViewMap, we, bbox, sceneNumFaces, iAlgo, epsilon);

    return _ViewMap;
}

ViewMapBuilder は以下の /freestyle/intern/application/Controller.cppComputeViewMap() を見てみると vmBuilder としてインスタンス化されている。WXEdgeBuilderedgeDetector 等により処理された拡張 winged edge オブジェクト _winged_edge と quantitative invisibility (QI) などのいくつかの設定を用いて ViewMap を構築しているようだ。winged edge は後の Blender から Freestyle へメッシュを読み込む Controller::LoadMesh でも触れる。

void Controller::ComputeViewMap()
{
    if (!_ListOfModels.size())
        return;

    DeleteViewMap(true);

    // ...

    edgeDetector.processShapes(*_winged_edge);

    real duration = _Chrono.stop();
    if (G.debug & G_DEBUG_FREESTYLE) {
        printf("Feature lines    : %lf\n", duration);
    }

    if (_pRenderMonitor->testBreak())
        return;

    // Builds the view map structure from the flagged WSEdge structure:
    //----------------------------------------------------------
    ViewMapBuilder vmBuilder;
    vmBuilder.setEnableQI(_EnableQI);
    vmBuilder.setViewpoint(vp);
    vmBuilder.setTransform(mv, proj, viewport, _pView->GetFocalLength(), _pView->GetAspect(), _pView->GetFovyRadian());
    vmBuilder.setFrustum(_pView->znear(), _pView->zfar());
    vmBuilder.setGrid(&_Grid);
    vmBuilder.setRenderMonitor(_pRenderMonitor);

#if 0
    // Builds a tesselated form of the silhouette for display purpose:
    //---------------------------------------------------------------
    ViewMapTesselator3D sTesselator3d;
    ViewMapTesselator2D sTesselator2d;
    sTesselator2d.setNature(_edgeTesselationNature);
    sTesselator3d.setNature(_edgeTesselationNature);
#endif

    if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "\n===  Building the view map  ===" << endl;
    }
    _Chrono.start();
    // Build View Map
    _ViewMap = vmBuilder.BuildViewMap(*_winged_edge, _VisibilityAlgo, _EPSILON, _Scene3dBBox, _SceneNumFaces);
    _ViewMap->setScene3dBBox(_Scene3dBBox);

    if (G.debug & G_DEBUG_FREESTYLE) {
        printf("ViewMap edge count : %i\n", _ViewMap->viewedges_size());
    }

    // ...

    duration = _Chrono.stop();
    if (G.debug & G_DEBUG_FREESTYLE) {
        printf("ViewMap building : %lf\n", duration);
    }

    // ...

    // Draw the steerable density map:
    //--------------------------------
    if (_ComputeSteerableViewMap) {
        ComputeSteerableViewMap();
    }
    // Reset Style modules modification flags
    resetModified(true);

    DeleteWingedEdge();
}

SteerableViewMap はまだよく分からない。内部で GrayImage というのが使用されているようなので、もしかしたらストロークの密度を逐次監視しつつ描き足していく causal density と関係しているのかもしれない。後で調べる必要がある。また frustum とは円錐台のことらしい。

ComputeViewMap() は主に /freestyle/intern/blender_interface/FRS_freestyle.cppprepare 関数で呼び出されるようだ。/freestyle/intern/application/Controller.cpp L.1017 付近にも

void Controller::toggleEdgeTesselationNature(Nature::EdgeNature iNature)
{
    _edgeTesselationNature ^= (iNature);
    ComputeViewMap();
}

と呼び出しているところがある。ただこの toggleEdgeTesselationNature という関数はソースコードを grep した限りヘッダーファイル以外では他にヒットしないので、これは何かの名残りではないかと考えている。

4. prepare(Render *re, SceneRenderLayer *srl)

前回の記事で見た /freestyle/intern/blender_interface/FRS_freestyle.cpp にある prepare 関数をもう一度見てみよう。

static void prepare(Render *re, SceneRenderLayer *srl)
{
    // load mesh
    re->i.infostr = IFACE_("Freestyle: Mesh loading");
    re->stats_draw(re->sdh, &re->i);
    re->i.infostr = NULL;
    if (controller->LoadMesh(re, srl)) // returns if scene cannot be loaded or if empty
        return;
    if (re->test_break(re->tbh))
        return;

    // add style modules
    FreestyleConfig *config = &srl->freestyleConfig;

    if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "\n===  Rendering options  ===" << endl;
    }
    int layer_count = 0;

    switch (config->mode) {
    case FREESTYLE_CONTROL_SCRIPT_MODE:
        // ...
        break;
    case FREESTYLE_CONTROL_EDITOR_MODE:
        // ...
        break;
    }

    // set parameters

    // ...

    if (controller->hitViewMapCache())
        return;

    // compute view map
    re->i.infostr = IFACE_("Freestyle: View map creation");
    re->stats_draw(re->sdh, &re->i);
    re->i.infostr = NULL;
    controller->ComputeViewMap();
}

FREESTYLE_CONTROL_SCRIPT_MODEFREESTYLE_CONTROL_EDITOR_MODE という config->mode によって処理が分岐しているが、基本的には描画処理で必要な設定をそれぞれ準備しているだけなので省略した。

注目すべきは関数後半の // compute view map のあたり、

    if (controller->hitViewMapCache())
        return;

    // compute view map
    re->i.infostr = IFACE_("Freestyle: View map creation");
    re->stats_draw(re->sdh, &re->i);
    re->i.infostr = NULL;
    controller->ComputeViewMap();

である。つまり hitViewMapCache()true であれば ViewMap を再計算せずキャッシュを使用するということなのだろう。hitViewMapCache() は、

bool Controller::hitViewMapCache()
{
    if (!_EnableViewMapCache) {
        return false;
    }
    if (sceneHashFunc.match()) {
        return (NULL != _ViewMap);
    }
    sceneHashFunc.store();
    return false;
}

となっている。つまりシーンが変更されなければハッシュ値は一致するので再計算は回避されるようだ。

5. Controller::LoadMesh(Render *re, SceneRenderLayer *srl)

最後に Blender のメッシュを読み込む Controller::LoadMesh を見てみよう。

int Controller::LoadMesh(Render *re, SceneRenderLayer *srl)
{
    BlenderFileLoader loader(re, srl);

    loader.setRenderMonitor(_pRenderMonitor);

    // ...

    if (_EnableViewMapCache) {

        NodeCamera *cam;
        if (g_freestyle.proj[3][3] != 0.0)
            cam = new NodeOrthographicCamera;
        else
            cam = new NodePerspectiveCamera;
        double proj[16];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                proj[i * 4 + j] = g_freestyle.proj[i][j];
            }
        }
        cam->setProjectionMatrix(proj);
        _RootNode->AddChild(cam);
        _RootNode->AddChild(new NodeSceneRenderLayer(*re->scene, *srl));

        sceneHashFunc.reset();
        //blenderScene->accept(sceneHashFunc);
        _RootNode->accept(sceneHashFunc);
        if (G.debug & G_DEBUG_FREESTYLE) {
            cout << "Scene hash       : " << sceneHashFunc.toString() << endl;
        }
        if (hitViewMapCache()) {
            ClearRootNode();
            return 0;
        }
        else {
            delete _ViewMap;
            _ViewMap = NULL;
        }
    }


    _Chrono.start();

    WXEdgeBuilder wx_builder;
    wx_builder.setRenderMonitor(_pRenderMonitor);
    blenderScene->accept(wx_builder);
    _winged_edge = wx_builder.getWingedEdge();

    duration = _Chrono.stop();
    if (G.debug & G_DEBUG_FREESTYLE) {
        printf("WEdge building   : %lf\n", duration);
    }

    // ...

    _ListOfModels.push_back("Blender_models");

    _Scene3dBBox = _RootNode->bbox();

    _bboxDiag = (_RootNode->bbox().getMax() - _RootNode->bbox().getMin()).norm();
    if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "Triangles nb     : " << _SceneNumFaces << " imported, " <<
                _winged_edge->getNumFaces() << " retained" << endl;
        cout << "Bounding Box     : " << _bboxDiag << endl;
    }

    ClearRootNode();

    _SceneNumFaces = _winged_edge->getNumFaces();
    if (_SceneNumFaces == 0) {
        DeleteWingedEdge();
        return 1;
    }

    return 0;
}

WXEdgeBuilder から _winged_edge が生成されている。WXEdgeX というのは何かというと /freestyle/intern/winged_edge/WXEdgeBuilder.cpp に、

/** \file blender/freestyle/intern/winged_edge/WXEdgeBuilder.cpp
 *  \ingroup freestyle
 *  \brief Class inherited from WingedEdgeBuilder and designed to build a WX (WingedEdge + extended info
 *         (silhouette etc...)) structure from a polygonal model

というコメントがある。この extended info というのは前に見た edgeDetector により検知した様々な feature edges の種類、たとえば silhouettevalleys などを保持しているのだと予想している。

さて ViewMap の構築やキャッシュの処理について、ざっくりと流れを見てみた。キャッシュされた ViewMap 自体は Controller() にアクセスするのが難しい以上 Python からアクセスするのは無理そうだ。なので render 後のポスト処理から処理を実装していくのをメインに進めていけば良さそうである。