1

please consider this little example.

mcve.cpp

#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

class mat4 {
  public:
    mat4(float v = 1.0f) {
        data[0] = v; data[4] = 0; data[8] = 0; data[12] = 0;
        data[1] = 0; data[5] = v; data[9] = 0; data[13] = 0;
        data[2] = 0; data[6] = 0; data[10] = v; data[14] = 0;
        data[3] = 0; data[7] = 0; data[11] = 0; data[15] = v;
    }

    mat4(
        float m00, float m01, float m02, float m03,
        float m10, float m11, float m12, float m13,
        float m20, float m21, float m22, float m23,
        float m30, float m31, float m32, float m33
    ) {
        data[0] = m00; data[4] = m01; data[8] = m02; data[12] = m03;
        data[1] = m10; data[5] = m11; data[9] = m12; data[13] = m13;
        data[2] = m20; data[6] = m21; data[10] = m22; data[14] = m23;
        data[3] = m30; data[7] = m31; data[11] = m32; data[15] = m33;
    }

    float data[16];
};

PYBIND11_MODULE(bindings, m) {
    py::class_<::mat4>(m, "mat4")
        .def(py::init<float>(), py::arg("v") = 1.0f)
        .def("__getitem__", [](mat4& m, py::ssize_t i) {
            return m.data[i];
        })
        .def("__setitem__", [](mat4& m, py::ssize_t i, float v) {
            m.data[i] = v;
        })
        .def("__str__", [](mat4& m) {
            #define str_row(a,b,c,d) std::to_string(a) + "," + std::to_string(b) + "," + std::to_string(c) + "," + std::to_string(d) + ","
            return "mat4(\n"
                   "    "+str_row(m.data[0], m.data[4], m.data[8], m.data[12])+"\n"+
                   "    "+str_row(m.data[1], m.data[5], m.data[9], m.data[13])+"\n"+
                   "    "+str_row(m.data[2], m.data[6], m.data[10], m.data[14])+"\n"+
                   "    "+str_row(m.data[3], m.data[7], m.data[11], m.data[15])+"\n"+
                   ")";
        })
        .def("value_ptr", [](mat4& m) {
            return py::array_t<float>({16}, m.data);
        })
    ;
};

CMakelists.txt

cmake_minimum_required(VERSION 3.4)

project(mcve)
add_subdirectory(pybind11)


pybind11_add_module(bindings "mcve.cpp")

test.py

from build.bindings import mat4

if __name__ == "__main__":
    m = mat4(1)
    ptr = m.value_ptr()

    print("----case1----")
    print(m)
    print(ptr)

    print("----case2----")
    m[5] = 2
    m[10] = 3
    m[15] = 4
    print(m)
    print("wrong content!!! ", ptr)
    print("valid content!!! ", m.value_ptr())

If you try to compile & run the above content by doing something similar to:

mkdir build && cd build && cmake .. -G Ninja && cmake --build . && cd ..
python test.py

And everything went well... you should be getting below output:

----case1----
mat4(
    1.000000,0.000000,0.000000,0.000000,
    0.000000,1.000000,0.000000,0.000000,
    0.000000,0.000000,1.000000,0.000000,
    0.000000,0.000000,0.000000,1.000000,
)
[1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1.]
----case2----
mat4(
    1.000000,0.000000,0.000000,0.000000,
    0.000000,2.000000,0.000000,0.000000,
    0.000000,0.000000,3.000000,0.000000,
    0.000000,0.000000,0.000000,4.000000,
)
wrong content!!!  [1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1.]
valid content!!!  [1. 0. 0. 0. 0. 2. 0. 0. 0. 0. 3. 0. 0. 0. 0. 4.]

As you can see, in case2 the original matrix content has been modified by you can see ptr is still showing the original content... So i need to run again m.value_ptr() in order to get the updated data.

So my question is, how can I effectively create a zero-copy view from a c++ raw pointer using pybind11?

I've been researching for hours and tried different ways but I'm stuck. First of all, I'd like to understand what's wrong with the above code... It feels like py::array is copying the content from the array rather than creating a view out of it, so... is there any way to avoid this copy by using py::array and just have a "view" from the pointer? What'd be the proper way to achieve this? In the real case I'll be dealing with large ammounts of memory, so copying around unnecesarily data is not an option at all.

References:

BPL
  • 9,632
  • 9
  • 59
  • 117

1 Answers1

1

Possible solution:

    .def("value_ptr", [](mat4& m) {
        return py::array_t<float>{16, m.data, py::cast(m)};
    })

Original reference taken from https://github.com/pybind/pybind11/issues/2271#issuecomment-650569067

BPL
  • 9,632
  • 9
  • 59
  • 117