Edited at

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

More than 1 year has passed since last update.

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

https://github.com/unity3d-jp/MeshSync/blob/e255f9c/Plugin/MeshSyncClientBlender/python/unity_mesh_sync.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


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


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