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

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

> <dl><dt>as_pointer()</dt>
> <dd><p>Returns the memory address which holds a pointer to blenders internal data</p>
>
> <p>Returns:	int (memory address).<br>
> Return type:	int</p>
>
> <p>Note This is intended only for advanced script writers who need to pass blender data to their own C/Python modules.</p></dd></dl>

例えば、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

```python: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

```cpp: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

```cpp: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/

```cpp: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

```cpp: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;
[...]
}
```

https://github.com/unity3d-jp/MeshSync/blob/e255f9c/Plugin/MeshSyncClientBlender/python/unity_mesh_sync.py

```py: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

```cpp: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を持っている。

https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/RNA_types.h

```cpp: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周りは、やりたいことは分かるけど若干怪しげ (コンテキスト周りからしてアレだと思う)。