LoginSignup
9
11

More than 5 years have passed since last update.

BlenderのC++アドオンで高速にデータを受け渡す (LuxBlendにおける手法)

Last updated at Posted at 2018-01-02

LuxRenderのBlender版アドオンであるLuxBlendにはダイナミックライブラリ版のLuxCoreが存在し、高速なデータの受け渡しに対応しています。この仕組みをちょっと解説します。
なお、ソースコードのライセンスはLuxBlendがGPL、LuxRaysがApache Licenseであるため、コードの引用は最小限に留めておきます。

まず、BlenderのPython構造体のベースクラスであるbpy_structにはas_pointer関数があり、多くのデータのポインターを取得することができます。
https://docs.blender.org/api/2.79/bpy.types.bpy_struct.html

as_pointer()

Returns the memory address which holds a pointer to blenders internal data

Returns: int (memory address).
Return type: int

Note This is intended only for advanced script writers who need to pass blender data to their own C/Python modules.

例えば、Blenderのコンソールでbpy.context.active_object.as_pointer()と打つと、アクティブオブジェクトのポインタを取って来れます。

>>> bpy.context.active_object.as_pointer()
140128802162184

LuxBlendでは、このas_pointer関数を使ってメッシュや頂点データのポインタを取得して、C++で実装されたLuxRays側のメンバー関数に渡しています。
https://bitbucket.org/luxrender/luxblend25/src/10dd12c045d79530acf763642af28e1180903e11/src/luxrender/export/luxcore/meshes.py?at=default&fileviewer=file-view-default

meshes.py
[...]

    def __export_mesh_to_shape(self, name, mesh, luxcore_scene):
        faces = mesh.tessfaces[0].as_pointer()
[...]

        mesh_definitions = luxcore_scene.DefineBlenderMesh(name, len(mesh.tessfaces), faces, len(mesh.vertices),
                                                                 vertices, texCoords, vertexColors, transformation)
[...]

LuxRaysのC++側では、Boost.Pythonを使ってPythonのメンバー関数を定義しています。
https://bitbucket.org/luxrender/luxrays/src/ceb10f7963250be95af709f98633907c13da7830/src/luxcore/pyluxcore.cpp?at=default&fileviewer=file-view-default

pyluxcore.cpp
[...]
#include <boost/python.hpp>
[...]
BOOST_PYTHON_MODULE(pyluxcore) {
[...]
    class_<luxcore::detail::SceneImpl>("Scene", init<optional<float> >())
[...]
        .def("DefineBlenderMesh", &blender::Scene_DefineBlenderMesh1)
        .def("DefineBlenderMesh", &blender::Scene_DefineBlenderMesh2)
[...]
}
[...]

関数の実体では、ポインタをsize_tで受けて、それを実際の構造体のポインタ型にキャストしています。構造体の定義はBlender側のヘッダを使わず、独自に定義していますね。
https://bitbucket.org/luxrender/luxrays/src/ceb10f7963250be95af709f98633907c13da7830/src/luxcore/pyluxcoreforblender.cpp?at=default&fileviewer=file-view-default

pyluxcoreforblender.cpp
[...]

struct MFace {
    u_int v[4];
    short mat_nr;
    char edcode, flag;
};

[...]

boost::python::list Scene_DefineBlenderMesh1(luxcore::detail::SceneImpl *scene, const string &name,
        const size_t blenderFaceCount, const size_t blenderFacesPtr,
        const size_t blenderVertCount, const size_t blenderVerticesPtr,
        const size_t blenderUVsPtr, const size_t blenderColsPtr,
        const boost::python::object &transformation) {
[...]
    MFace *blenderFaces = reinterpret_cast<MFace *>(blenderFacesPtr);
[...]
}

この構造体の定義がBlender側ではどこにあるかというと、makesdnaディレクトリの下にあるようです。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/tree/HEAD:/source/blender/makesdna/

DNA_meshdata_types.h
 /*tessellation face, see MLoop/MPoly for the real face data*/
typedef struct MFace {
        unsigned int v1, v2, v3, v4;
        short mat_nr;
        char edcode, flag;  /* we keep edcode, for conversion to edges draw flags in old files */
} MFace; 

TODO: bpy_prop_collectionについて纏める?

追記
この記事はMeshSync for Blender開発中のi_saintさんの下記ツイートを見て即席で書いたものですが、
https://twitter.com/i_saint/status/947955269375377408
https://twitter.com/i_saint/status/947955773841145856

既に別の解法を見つけたとのこと。
https://twitter.com/i_saint/status/948227910380294145
https://github.com/unity3d-jp/MeshSync/blob/e255f9c/Plugin/MeshSyncClientBlender/msbContext.cpp#L252

msbContext.cpp
void msbContext::extractMeshData(ms::MeshPtr dst_, py::object src_)
{
    auto rna = (BPy_StructRNA*)src_.ptr();
    auto& obj = *(Object*)rna->ptr.id.data;
    auto& src = *(Mesh*)obj.data;
[...]
}

unity_mesh_sync.py
def msb_add_mesh(ctx, obj):
[...]
        ctx.extractMeshData(dst, obj)
[...]

C++11とPythonのやりとりにはpybind11を使用しており、C++11側はPythonオブジェクトをpy::objectで直接受け取って、そこからポインタを得ている模様。namespace py = pybind11;されているので、py::objectはpybind11::objectクラスのことで、この辺に定義されている。
https://github.com/pybind/pybind11/blob/086d53e8c66a84d0ec723d5435918c76edd878e8/include/pybind11/pytypes.h

派生先→派生元の関係は、pybind11::object→pybind11::handle→pybind11::detail::object_api<handle>→pybind11::detail::pyobject_tag。src_.ptr()はPyObjectのポインタを返していて、それをBPy_StructRNAのポインタにキャストしている。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/python/intern/bpy_rna.h

bpy_rna.h
 typedef struct {
        PyObject_HEAD /* required python macro   */
#ifdef USE_WEAKREFS
        PyObject *in_weakreflist;
#endif
        PointerRNA ptr;
#ifdef USE_PYRNA_STRUCT_REFERENCE
        /* generic PyObject we hold a reference to, example use:
         * hold onto the collection iterator to prevent it from freeing allocated data we may use */
        PyObject *reference;
#endif /* !USE_PYRNA_STRUCT_REFERENCE */

#ifdef PYRNA_FREE_SUPPORT
        bool freeptr;  /* needed in some cases if ptr.data is created on the fly, free when deallocing */
#endif /* PYRNA_FREE_SUPPORT */
} BPy_StructRNA;

BPy_StructRNAはPyObjectの上位互換で、RNAへのポインタ構造体であるPointerRNAを持っている。

RNA_types.h
/** Pointer
 *
 * RNA pointers are not a single C pointer but include the type,
 * and a pointer to the ID struct that owns the struct, since
 * in some cases this information is needed to correctly get/set
 * the properties and validate them. */

typedef struct PointerRNA {
        struct {
                void *data;
        } id;

        struct StructRNA *type;
        void *data;
} PointerRNA;

https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/intern/rna_rna.c
PointerRNA.typeは&RNA_Property (値型) や&RNA_Struct (構造体型) などが入ってる模様。
PointerRNA.dataは、typeが前者ならPropertyRNA*型で、typeが後者ならStructRNA*型な模様。
PointerRNA.id.dataは、PointerRNA.dataがID structの時に、その参照先のデータのポインタが入る模様。ID structというのはオブジェクトやマテリアルなどのBlender内部で参照として扱う型のこと…だと思う。ID structの一覧は下にある。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/intern/rna_ID.c

…RNA周りは、やりたいことは分かるけど若干怪しげ (コンテキスト周りからしてアレだと思う)。

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11