Skip to content
Permalink
Browse files

python/magum/math: switch to our own buffer protocol implementation.

Also don't needlessly define buffer protocol again for subclasses (such
as Matrix3 or Color4) as the base can handle that just fine.
  • Loading branch information...
mosra committed Jul 25, 2019
1 parent f9a2146 commit d1d6cb9ec046e824ca6c50fdcf9c41fe005a67f7
@@ -37,6 +37,56 @@

namespace magnum {

/* Keep in sync with math.h */

const char* const FormatStrings[]{
/* 0. Representing bytes as unsigned. Not using 'c' because then it behaves
differently from bytes/bytearray, where you can do `a[0] = ord('A')`. */
"B",

"b", /* 1 -- std::int8_t */
"B", /* 2 -- std::uint8_t */
"i", /* 3 -- std::int32_t */
"I", /* 4 -- std::uint32_t */
"f", /* 5 -- float */
"d" /* 6 -- double */
};

/* Flipped as numpy expects row-major */
const Py_ssize_t MatrixShapes[][2]{
{2, 2}, /* 0 -- 2 cols, 2 rows */
{3, 2}, /* 1 -- 2 cols, 3 rows */
{4, 2}, /* 2 -- 2 cols, 4 rows */
{2, 3}, /* 3 -- 3 cols, 2 rows */
{3, 3}, /* 4 -- 3 cols, 3 rows */
{4, 3}, /* 5 -- 3 cols, 4 rows */
{2, 4}, /* 6 -- 4 cols, 2 rows */
{3, 4}, /* 7 -- 4 cols, 3 rows */
{4, 4} /* 8 -- 4 cols, 4 rows */
};
const Py_ssize_t MatrixStridesFloat[][2]{
{4, 4*2}, /* 0 -- 2 cols, 2 rows */
{4, 4*3}, /* 1 -- 2 cols, 3 rows */
{4, 4*4}, /* 2 -- 2 cols, 4 rows */
{4, 4*2}, /* 3 -- 3 cols, 2 rows */
{4, 4*3}, /* 4 -- 3 cols, 3 rows */
{4, 4*4}, /* 5 -- 3 cols, 4 rows */
{4, 4*2}, /* 6 -- 4 cols, 2 rows */
{4, 4*3}, /* 7 -- 4 cols, 3 rows */
{4, 4*4} /* 8 -- 4 cols, 4 rows */
};
const Py_ssize_t MatrixStridesDouble[][2]{
{8, 8*2}, /* 0 -- 2 cols, 2 rows */
{8, 8*3}, /* 1 -- 2 cols, 3 rows */
{8, 8*4}, /* 2 -- 2 cols, 4 rows */
{8, 8*2}, /* 3 -- 3 cols, 2 rows */
{8, 8*3}, /* 4 -- 3 cols, 3 rows */
{8, 8*4}, /* 5 -- 3 cols, 4 rows */
{8, 8*2}, /* 6 -- 4 cols, 2 rows */
{8, 8*3}, /* 7 -- 4 cols, 3 rows */
{8, 8*4} /* 8 -- 4 cols, 4 rows */
};

namespace {

template<class T> void angle(py::class_<T>& c) {
@@ -26,13 +26,48 @@
*/

#include <sstream>
#include <Python.h>
#include <Corrade/Utility/Debug.h>
#include <Magnum/Magnum.h>

#include "magnum/bootstrap.h"

namespace magnum {

/* Keep in sync with math.cpp */

extern const char* const FormatStrings[];
template<class> constexpr std::size_t formatIndex();
template<> constexpr std::size_t formatIndex<char>() { return 0; }
template<> constexpr std::size_t formatIndex<Byte>() { return 1; }
template<> constexpr std::size_t formatIndex<UnsignedByte>() { return 2; }
template<> constexpr std::size_t formatIndex<Int>() { return 3; }
template<> constexpr std::size_t formatIndex<UnsignedInt>() { return 4; }
template<> constexpr std::size_t formatIndex<Float>() { return 5; }
template<> constexpr std::size_t formatIndex<Double>() { return 6; }

extern const Py_ssize_t MatrixShapes[][2];
template<UnsignedInt cols, UnsignedInt rows> constexpr std::size_t matrixShapeStrideIndex();
template<> constexpr std::size_t matrixShapeStrideIndex<2, 2>() { return 0; }
template<> constexpr std::size_t matrixShapeStrideIndex<2, 3>() { return 1; }
template<> constexpr std::size_t matrixShapeStrideIndex<2, 4>() { return 2; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 2>() { return 3; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 3>() { return 4; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 4>() { return 5; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 2>() { return 6; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 3>() { return 7; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 4>() { return 8; }

extern const Py_ssize_t MatrixStridesFloat[][2];
extern const Py_ssize_t MatrixStridesDouble[][2];
template<class> constexpr const Py_ssize_t* matrixStridesFor(std::size_t i);
template<> constexpr const Py_ssize_t* matrixStridesFor<Float>(std::size_t i) {
return MatrixStridesFloat[i];
}
template<> constexpr const Py_ssize_t* matrixStridesFor<Double>(std::size_t i) {
return MatrixStridesDouble[i];
}

template<class T> std::string repr(const T& value) {
std::ostringstream out;
Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << value;
@@ -27,11 +27,13 @@

#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/FormatStl.h>
#include <Magnum/Math/Matrix3.h>
#include <Magnum/Math/Matrix4.h>

#include "corrade/PybindExtras.h"
#include "corrade/PyBuffer.h"

#include "magnum/math.h"

@@ -44,10 +46,10 @@ template<class T> struct VectorTraits<2, T> { typedef Math::Vector2<T> Type; };
template<class T> struct VectorTraits<3, T> { typedef Math::Vector3<T> Type; };
template<class T> struct VectorTraits<4, T> { typedef Math::Vector4<T> Type; };

template<class U, class T> void initFromBuffer(T& out, const py::buffer_info& info) {
template<class U, class T> void initFromBuffer(T& out, const Py_buffer& buffer) {
for(std::size_t i = 0; i != T::Cols; ++i)
for(std::size_t j = 0; j != T::Rows; ++j)
out[i][j] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(info.ptr) + i*info.strides[1] + j*info.strides[0]));
out[i][j] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(buffer.buf) + i*buffer.strides[1] + j*buffer.strides[0]));
}

/* Called for both Matrix3x3 and Matrix3 in order to return a proper type /
@@ -67,24 +69,30 @@ template<class T, class ...Args> void everyRectangularMatrix(py::class_<T, Args.
.def(py::init(), "Default constructor")
.def(py::init<typename T::Type>(), "Construct a matrix with one value for all components")

/* Buffer protocol, needed in order to make numpy treat the matric
correctly as column-major. Has to be defined *before* the from-tuple
constructor so it gets precedence for types that implement the
buffer protocol. */
.def(py::init([](py::buffer buffer) {
py::buffer_info info = buffer.request();
/* Buffer protocol, needed in order to properly detect row-major
layouts. Has to be defined *before* the from-tuple constructor so it
gets precedence for types that implement the buffer protocol. */
.def(py::init([](py::buffer other) {
Py_buffer buffer{};
if(PyObject_GetBuffer(other.ptr(), &buffer, PyBUF_FORMAT|PyBUF_STRIDES) != 0)
throw py::error_already_set{};

if(info.ndim != 2)
throw py::buffer_error{Utility::formatString("expected 2 dimensions but got {}", info.ndim)};
Containers::ScopeGuard e{&buffer, PyBuffer_Release};

if(info.shape[0] != T::Rows ||info.shape[1] != T::Cols)
throw py::buffer_error{Utility::formatString("expected {}x{} elements but got {}x{}", T::Cols, T::Rows, info.shape[1], info.shape[0])};
if(buffer.ndim != 2)
throw py::buffer_error{Utility::formatString("expected 2 dimensions but got {}", buffer.ndim)};

if(buffer.shape[0] != T::Rows || buffer.shape[1] != T::Cols)
throw py::buffer_error{Utility::formatString("expected {}x{} elements but got {}x{}", T::Cols, T::Rows, buffer.shape[1], buffer.shape[0])};

T out{Math::NoInit};

if(info.format == "f") initFromBuffer<Float>(out, info);
else if(info.format == "d") initFromBuffer<Double>(out, info);
else throw py::buffer_error{Utility::formatString("expected format f or d but got {}", info.format)};
/* Expecting just an one-letter format */
if(buffer.format[0] == 'f' && !buffer.format[1])
initFromBuffer<Float>(out, buffer);
else if(buffer.format[0] == 'd' && !buffer.format[1])
initFromBuffer<Double>(out, buffer);
else throw py::buffer_error{Utility::formatString("expected format f or d but got {}", buffer.format)};

return out;
}), "Construct from a buffer")
@@ -113,6 +121,31 @@ template<class T, class ...Args> void everyRectangularMatrix(py::class_<T, Args.
}, "Values on diagonal");
}

template<class T> bool rectangularMatrixBufferProtocol(T& self, Py_buffer& buffer, int flags) {
/* I hate the const_casts but I assume this is to make editing easier, NOT
to make it possible for users to stomp on these values. */
buffer.ndim = 2;
buffer.itemsize = sizeof(typename T::Type);
buffer.len = sizeof(T);
buffer.buf = self.data();
buffer.readonly = false;
if((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
buffer.format = const_cast<char*>(FormatStrings[formatIndex<typename T::Type>()]);
if(flags != PyBUF_SIMPLE) {
/* Reusing shape definitions from matrices because I don't want to
create another useless array for that and reinterpret_cast on the
buffer.internal is UGLY. It's flipped from column-major to
row-major, so adjusting the row instead. */
buffer.shape = const_cast<Py_ssize_t*>(MatrixShapes[matrixShapeStrideIndex<T::Cols, T::Rows>()]);
CORRADE_INTERNAL_ASSERT(buffer.shape[0] == T::Rows);
CORRADE_INTERNAL_ASSERT(buffer.shape[1] == T::Cols);
if((flags & PyBUF_STRIDES) == PyBUF_STRIDES)
buffer.strides = const_cast<Py_ssize_t*>(matrixStridesFor<typename T::Type>(matrixShapeStrideIndex<T::Cols, T::Rows>()));
}

return true;
}

template<class T> void rectangularMatrix(py::class_<T>& c) {
/*
Missing APIs:
@@ -128,21 +161,6 @@ template<class T> void rectangularMatrix(py::class_<T>& c) {
*/

c
/* Buffer protocol, needed in order to make numpy treat the matrix
correctly as column-major. The constructor is defined in
everyRectangularMatrix(). */
.def_buffer([](const T& self) -> py::buffer_info {
// TODO: ownership?
return py::buffer_info{
const_cast<typename T::Type*>(self.data()),
sizeof(typename T::Type),
py::format_descriptor<typename T::Type>::format(),
2,
{T::Rows, T::Cols},
{sizeof(typename T::Type), sizeof(typename T::Type)*T::Rows}
};
})

/* Comparison */
.def(py::self == py::self, "Equality comparison")
.def(py::self != py::self, "Non-equality comparison")
@@ -170,6 +188,11 @@ template<class T> void rectangularMatrix(py::class_<T>& c) {

.def("__repr__", repr<T>, "Object representation");

/* Buffer protocol, needed in order to make numpy treat the matrix
correctly as column-major. The constructor is defined in
everyRectangularMatrix(). */
corrade::enableBetterBufferProtocol<T, rectangularMatrixBufferProtocol>(c);

/* Matrix column count */
char lenDocstring[] = "Matrix column count. Returns _.";
lenDocstring[sizeof(lenDocstring) - 3] = '0' + T::Cols;
@@ -40,8 +40,13 @@ void mathMatrixDouble(py::module& root) {
py::class_<Matrix4x3d> matrix4x3d{root, "Matrix4x3d", "4x3 double matrix", py::buffer_protocol{}};
py::class_<Matrix4x4d> matrix4x4d{root, "Matrix4x4d", "4x4 double matrix", py::buffer_protocol{}};

py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix", py::buffer_protocol{}};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix", py::buffer_protocol{}};
/* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{}
would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */

py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix"};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix"};

matrices<Double>(
matrix2x2d, matrix2x3d, matrix2x4d,
@@ -40,8 +40,13 @@ void mathMatrixFloat(py::module& root) {
py::class_<Matrix4x3> matrix4x3{root, "Matrix4x3", "4x3 float matrix", py::buffer_protocol{}};
py::class_<Matrix4x4> matrix4x4{root, "Matrix4x4", "4x4 float matrix", py::buffer_protocol{}};

py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix", py::buffer_protocol{}};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix", py::buffer_protocol{}};
/* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{}
would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */

py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix"};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix"};

matrices<Float>(
matrix2x2, matrix2x3, matrix2x4,

0 comments on commit d1d6cb9

Please sign in to comment.
You can’t perform that action at this time.