From 76d7ee0dc780e5b11296abe6df8bc99612ba4723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 27 Jul 2019 19:15:21 +0200 Subject: [PATCH] python: store memory owner references in the holder type instead. Compared to having to subclass every type that can reference external data, this has several advantages for 3rd party binding code: * it doesn't need to worry about the additional type when binding function arguments (currently it had to provide lambdas that accept the PyFoo subtype instead of just Foo) * and it can now easily bind those types also for function return values and properties -- the return type doesn't need to be subclassed (which in case of move-only types is practically impossible) but instead just wrapped in a holder along with the memory owner object reference The new holders also assert that memory owner is always specified unless the data is empty. --- src/Corrade/Containers/CMakeLists.txt | 30 ++++ src/Corrade/Containers/Python.h | 56 +++++++ src/Corrade/Python.h | 39 ++++- src/Magnum/Python.h | 22 ++- src/python/corrade/CMakeLists.txt | 8 +- src/python/corrade/PyArrayView.h | 67 --------- src/python/corrade/containers.cpp | 208 +++++++++++++------------- src/python/magnum/gl.cpp | 8 +- src/python/magnum/magnum.cpp | 50 ++++--- 9 files changed, 284 insertions(+), 204 deletions(-) create mode 100644 src/Corrade/Containers/CMakeLists.txt create mode 100644 src/Corrade/Containers/Python.h delete mode 100644 src/python/corrade/PyArrayView.h diff --git a/src/Corrade/Containers/CMakeLists.txt b/src/Corrade/Containers/CMakeLists.txt new file mode 100644 index 0000000..283d0b4 --- /dev/null +++ b/src/Corrade/Containers/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 +# Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +if(WITH_PYTHON) + add_custom_target(CorradeContainersPython SOURCES Python.h) + set_target_properties(CorradeContainersPython PROPERTIES FOLDER "Corrade/Python") + install(FILES Python.h DESTINATION ${CORRADE_INCLUDE_INSTALL_DIR}/Containers) +endif() diff --git a/src/Corrade/Containers/Python.h b/src/Corrade/Containers/Python.h new file mode 100644 index 0000000..a524994 --- /dev/null +++ b/src/Corrade/Containers/Python.h @@ -0,0 +1,56 @@ +#ifndef Corrade_Containers_Python_h +#define Corrade_Containers_Python_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include /* :( */ +#include + +namespace Corrade { namespace Containers { + +/* Stores additional stuff needed for proper refcounting of array views. Better + than subclassing ArrayView because then we would need to wrap it every time + it's exposed to Python, making 3rd party bindings unnecessarily complex. */ +template struct PyArrayViewHolder: std::unique_ptr { + explicit PyArrayViewHolder(T* object): PyArrayViewHolder{object, pybind11::none{}} { + /* Array view without an owner can only be empty */ + CORRADE_INTERNAL_ASSERT(!object->data()); + } + + explicit PyArrayViewHolder(T* object, pybind11::object owner): std::unique_ptr{object}, owner{std::move(owner)} {} + + pybind11::object owner; +}; + +template PyArrayViewHolder pyArrayViewHolder(const T& view, pybind11::object owner) { + return PyArrayViewHolder{new T{view}, owner}; +} + +}} + +PYBIND11_DECLARE_HOLDER_TYPE(T, Corrade::Containers::PyArrayViewHolder) + +#endif + diff --git a/src/Corrade/Python.h b/src/Corrade/Python.h index d29a048..aac8f8e 100644 --- a/src/Corrade/Python.h +++ b/src/Corrade/Python.h @@ -26,7 +26,7 @@ */ #include -#include +#include namespace Corrade { @@ -51,6 +51,43 @@ template inline T& pyInstanceFromHandle(pybind11::handle handle) { return caster; } +/* py::cast() doesn't work on holder types because it takes const T&. Fuck + that. The casting "just works" for function return types, so instead reuse + the stuff that's done inside py::class_::def(). */ +template class Holder, class T> pybind11::object pyCastButNotShitty(Holder&& holder) { + static_assert(std::is_base_of, Holder>::value, + "holder should be a subclass of std::unique_ptr"); + /* Extracted out of cpp_function::initialize(), the cast_out alias. Not + *exactly* sure about the return value policy or parent. Stealing the + reference because cpp_function::dispatcher() seems to do that too (and + using reinterpret_borrow makes tests fail with too high refcount) */ + return pybind11::reinterpret_steal(pybind11::detail::make_caster>::cast(std::move(holder), pybind11::return_value_policy::move, {})); +} + +/* Extracted the simplest case from py::type_caster_generic::load_impl() */ +template T& pyObjectHolderFor(pybind11::handle obj, pybind11::detail::type_info* typeinfo) { + /* So we don't need to bother with + copyable_holder_caster::check_holder_compat() */ + static_assert(!std::is_copy_constructible::value, + "holder should be a move-only type"); + /* Assume the type is not subclassed on Python side */ + CORRADE_INTERNAL_ASSERT(Py_TYPE(obj.ptr()) == typeinfo->type); + const pybind11::detail::value_and_holder vh = reinterpret_cast(obj.ptr())->get_value_and_holder(); + /* And its already created and everything (i.e., we're not calling it in + its own constructor) */ + CORRADE_INTERNAL_ASSERT(vh.holder_constructed()); + CORRADE_INTERNAL_ASSERT(vh.instance_registered()); + return vh.holder(); +} + +template class T, class U> T& pyObjectHolderFor(U& obj) { + /* Not using pyHandleFromInstance in order to avoid calling get_type_info + more than once. I bet it involves some std::unordered_map access and + that's like the slowest stuff ever. */ + pybind11::detail::type_info* typeinfo = pybind11::detail::get_type_info(typeid(U)); + return pyObjectHolderFor>(pybind11::detail::get_object_handle(&obj, typeinfo), typeinfo); +} + } #endif diff --git a/src/Magnum/Python.h b/src/Magnum/Python.h index f72a3fe..7f685ad 100644 --- a/src/Magnum/Python.h +++ b/src/Magnum/Python.h @@ -25,19 +25,31 @@ DEALINGS IN THE SOFTWARE. */ +#include /* :( */ #include -#include namespace Magnum { -/* Wrapper for ImageView holding a reference to the memory owner */ -template struct PyImageView: ImageView { - /*implicit*/ PyImageView() noexcept: owner{pybind11::none{}} {} - explicit PyImageView(const ImageView& view, pybind11::object owner) noexcept: ImageView{view}, owner{std::move(owner)} {} +/* Stores additional stuff needed for proper refcounting of image views. Better + than subclassing ImageView because then we would need to wrap it every time + it's exposed to Python, making 3rd party bindings unnecessarily complex */ +template struct PyImageViewHolder: std::unique_ptr { + explicit PyImageViewHolder(T* object): PyImageViewHolder{object, pybind11::none{}} { + /* Image view without an owner can only be empty */ + CORRADE_INTERNAL_ASSERT(!object->data()); + } + + explicit PyImageViewHolder(T* object, pybind11::object owner): std::unique_ptr{object}, owner{std::move(owner)} {} pybind11::object owner; }; +template PyImageViewHolder pyImageViewHolder(const T& view, pybind11::object owner) { + return PyImageViewHolder{new T{view}, owner}; +} + } +PYBIND11_DECLARE_HOLDER_TYPE(T, Magnum::PyImageViewHolder) + #endif diff --git a/src/python/corrade/CMakeLists.txt b/src/python/corrade/CMakeLists.txt index 703e43b..fac1187 100644 --- a/src/python/corrade/CMakeLists.txt +++ b/src/python/corrade/CMakeLists.txt @@ -30,7 +30,9 @@ set(corrade_containers_SRCS # modules if(NOT CORRADE_BUILD_STATIC) pybind11_add_module(corrade_containers SYSTEM ${corrade_containers_SRCS}) - target_include_directories(corrade_containers PRIVATE ${PROJECT_SOURCE_DIR}/src/python) + target_include_directories(corrade_containers PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/python) target_link_libraries(corrade_containers PRIVATE Corrade::Containers Corrade::Utility) @@ -48,7 +50,9 @@ else() ${corrade_containers_SRCS}) pybind11_add_module(corrade SYSTEM ${corrade_SRCS}) - target_include_directories(corrade PRIVATE ${PROJECT_SOURCE_DIR}/src/python) + target_include_directories(corrade PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/python) target_link_libraries(corrade PRIVATE Corrade::Utility ${corrade_LIBS}) set_target_properties(corrade PROPERTIES FOLDER "python" diff --git a/src/python/corrade/PyArrayView.h b/src/python/corrade/PyArrayView.h deleted file mode 100644 index 24f6c4f..0000000 --- a/src/python/corrade/PyArrayView.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef corrade_PyArrayView_h -#define corrade_PyArrayView_h -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 - Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#include -#include -#include - -#include "corrade/bootstrap.h" - -namespace corrade { - -/* Wrapper for Containers::ArrayView holding a reference to the memory owner */ -template struct PyArrayView: Containers::ArrayView { - /*implicit*/PyArrayView() noexcept: obj{py::none{}} {} - explicit PyArrayView(Containers::ArrayView view, py::object obj) noexcept: Containers::ArrayView{view}, obj{std::move(obj)} {} - - /* Turning this into a reference so buffer protocol can point to it instead - of needing to allocate */ - std::size_t& sizeRef() { return Containers::ArrayView::_size; } - - py::object obj; -}; - -/* Wrapper for Containers::StridedArrayView holding a reference to the memory owner */ -template struct PyStridedArrayView: Containers::StridedArrayView { - /*implicit*/ PyStridedArrayView() noexcept: obj{py::none{}} {} - explicit PyStridedArrayView(Containers::StridedArrayView view, py::object obj) noexcept: Containers::StridedArrayView{view}, obj{std::move(obj)} {} - - /* Turning this into a reference so buffer protocol can point to it instead - of needing to allocate */ - Containers::StridedDimensions& sizeRef() { - return Containers::StridedArrayView::_size; - } - Containers::StridedDimensions& strideRef() { - return Containers::StridedArrayView::_stride; - } - - py::object obj; -}; - -} - -#endif diff --git a/src/python/corrade/containers.cpp b/src/python/corrade/containers.cpp index 83b1f62..6892b39 100644 --- a/src/python/corrade/containers.cpp +++ b/src/python/corrade/containers.cpp @@ -29,8 +29,9 @@ #include #include +#include "Corrade/Containers/Python.h" + #include "corrade/bootstrap.h" -#include "corrade/PyArrayView.h" #include "corrade/PyBuffer.h" namespace corrade { @@ -88,7 +89,7 @@ template bool arrayViewBufferProtocol(T& self, Py_buffer& buffer, int f if(flags != PyBUF_SIMPLE) { /* The view is immutable (can't change its size after it has been constructed), so referencing the size directly is okay */ - buffer.shape = reinterpret_cast(&self.sizeRef()); + buffer.shape = reinterpret_cast(&Containers::Implementation::sizeRef(self)); if((flags & PyBUF_STRIDES) == PyBUF_STRIDES) buffer.strides = &buffer.itemsize; } @@ -96,11 +97,11 @@ template bool arrayViewBufferProtocol(T& self, Py_buffer& buffer, int f return true; } -template void arrayView(py::class_>& c) { +template void arrayView(py::class_, Containers::PyArrayViewHolder>>& c) { /* Implicitly convertible from a buffer */ - py::implicitly_convertible>(); + py::implicitly_convertible>(); /* This is needed for implicit conversion from np.array */ - py::implicitly_convertible>(); + py::implicitly_convertible>(); c /* Constructor */ @@ -125,45 +126,46 @@ template void arrayView(py::class_>& c) { the buffer because we no longer care about the buffer descriptor -- that could allow the GC to haul away a bit more garbage */ - return PyArrayView{{static_cast(buffer.buf), std::size_t(buffer.len)}, py::reinterpret_borrow(buffer.obj)}; + return Containers::pyArrayViewHolder(Containers::ArrayView{static_cast(buffer.buf), std::size_t(buffer.len)}, py::reinterpret_borrow(buffer.obj)); }), "Construct from a buffer") /* Length and memory owning object */ - .def("__len__", &PyArrayView::size, "View size") - .def_readonly("obj", &PyArrayView::obj, "Memory owner object") + .def("__len__", &Containers::ArrayView::size, "View size") + .def_property_readonly("obj", [](const Containers::ArrayView& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner object") /* Conversion to bytes */ - .def("__bytes__", [](const PyArrayView& self) { + .def("__bytes__", [](const Containers::ArrayView& self) { return py::bytes(self.data(), self.size()); }, "Convert to bytes") /* Single item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const PyArrayView& self, std::size_t i) { + .def("__getitem__", [](const Containers::ArrayView& self, std::size_t i) { if(i >= self.size()) throw pybind11::index_error{}; return self[i]; }, "Value at given position") /* Slicing */ - .def("__getitem__", [](const PyArrayView& self, py::slice slice) -> py::object { + .def("__getitem__", [](const Containers::ArrayView& self, py::slice slice) -> py::object { const Slice calculated = calculateSlice(slice, self.size()); /* Non-trivial stride, return a different type */ if(calculated.step != 1) { - return py::cast(PyStridedArrayView<1, T>( - Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step), self.obj)); + return pyCastButNotShitty(Containers::pyArrayViewHolder(Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner)); } /* Usual business */ - return py::cast(PyArrayView{self.slice(calculated.start, calculated.stop), self.obj}); + return pyCastButNotShitty(Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop), pyObjectHolderFor(self).owner)); }, "Slice the view"); - enableBetterBufferProtocol, arrayViewBufferProtocol>(c); + enableBetterBufferProtocol, arrayViewBufferProtocol>(c); } -template void mutableArrayView(py::class_>& c) { +template void mutableArrayView(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const PyArrayView& self, std::size_t i, const T& value) { + .def("__setitem__", [](const Containers::ArrayView& self, std::size_t i, const T& value) { if(i >= self.size()) throw pybind11::index_error{}; self[i] = value; }, "Set a value at given position"); @@ -281,15 +283,15 @@ template bool stridedArrayViewBufferProtocol(T& self, Py_buffer& buffer buffer.itemsize = sizeof(typename T::Type); buffer.len = sizeof(typename T::Type); for(std::size_t i = 0; i != T::Dimensions; ++i) - buffer.len *= self.sizeRef()[i]; + buffer.len *= Containers::Implementation::sizeRef(self)[i]; buffer.buf = const_cast::type*>(self.data()); buffer.readonly = std::is_const::value; if((flags & PyBUF_FORMAT) == PyBUF_FORMAT) buffer.format = const_cast(FormatStrings[formatIndex::type>()]); /* The view is immutable (can't change its size after it has been constructed), so referencing the size/stride directly is okay */ - buffer.shape = const_cast(reinterpret_cast(self.sizeRef().begin())); - buffer.strides = const_cast(reinterpret_cast(self.strideRef().begin())); + buffer.shape = const_cast(reinterpret_cast(Containers::Implementation::sizeRef(self).begin())); + buffer.strides = const_cast(reinterpret_cast(Containers::Implementation::strideRef(self).begin())); return true; } @@ -298,11 +300,11 @@ inline std::size_t largerStride(std::size_t a, std::size_t b) { return a < b ? b : a; /* max(), but named like this to avoid clashes */ } -template void stridedArrayView(py::class_>& c) { +template void stridedArrayView(py::class_, Containers::PyArrayViewHolder>>& c) { /* Implicitly convertible from a buffer */ - py::implicitly_convertible>(); + py::implicitly_convertible>(); /* This is needed for implicit conversion from np.array */ - py::implicitly_convertible>(); + py::implicitly_convertible>(); c /* Constructor */ @@ -332,66 +334,68 @@ template void stridedArrayView(py::class_{{ + return Containers::pyArrayViewHolder(Containers::StridedArrayView{ {static_cast(buffer.buf), size}, Containers::StaticArrayView{reinterpret_cast(buffer.shape)}, Containers::StaticArrayView{reinterpret_cast(buffer.strides)}}, - py::reinterpret_borrow(buffer.obj)}; + py::reinterpret_borrow(buffer.obj)); }), "Construct from a buffer") /* Length, size/stride tuple, dimension count and memory owning object */ - .def("__len__", [](const PyStridedArrayView& self) { + .def("__len__", [](const Containers::StridedArrayView& self) { return Containers::StridedDimensions(self.size())[0]; }, "View size in the top-level dimension") - .def_property_readonly("size", [](const PyStridedArrayView& self) { + .def_property_readonly("size", [](const Containers::StridedArrayView& self) { return size(self.size()); }, "View size in each dimension") - .def_property_readonly("stride", [](const PyStridedArrayView& self) { + .def_property_readonly("stride", [](const Containers::StridedArrayView& self) { return stride(self.stride()); }, "View stride in each dimension") - .def_property_readonly("dimensions", [](const PyStridedArrayView&) { return dimensions; }, "Dimension count") - .def_readonly("obj", &PyStridedArrayView::obj, "Memory owner object") + .def_property_readonly("dimensions", [](const Containers::StridedArrayView&) { return dimensions; }, "Dimension count") + .def_property_readonly("obj", [](const Containers::StridedArrayView& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner object") /* Conversion to bytes */ - .def("__bytes__", [](const PyStridedArrayView& self) { + .def("__bytes__", [](const Containers::StridedArrayView& self) { /* TODO: use _PyBytes_Resize() to avoid the double copy */ const Containers::Array out = bytes(Containers::arrayCast(self)); return py::bytes(out.data(), out.size()); }, "Convert to bytes") /* Slicing of the top dimension */ - .def("__getitem__", [](const PyStridedArrayView& self, py::slice slice) -> py::object { + .def("__getitem__", [](const Containers::StridedArrayView& self, py::slice slice) { const Slice calculated = calculateSlice(slice, Containers::StridedDimensions{self.size()}[0]); - return py::cast(PyStridedArrayView(self.slice(calculated.start, calculated.stop).every(calculated.step), self.obj)); + return Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner); }, "Slice the view"); - enableBetterBufferProtocol, stridedArrayViewBufferProtocol>(c); + enableBetterBufferProtocol, stridedArrayViewBufferProtocol>(c); } -template void stridedArrayView1D(py::class_>& c) { +template void stridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { c /* Single item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const PyStridedArrayView<1, T>& self, std::size_t i) { + .def("__getitem__", [](const Containers::StridedArrayView<1, T>& self, std::size_t i) { if(i >= self.size()) throw pybind11::index_error{}; return self[i]; }, "Value at given position"); } -template void stridedArrayViewND(py::class_>& c) { +template void stridedArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { c /* Sub-view retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const PyStridedArrayView& self, std::size_t i) { + .def("__getitem__", [](const Containers::StridedArrayView& self, std::size_t i) { if(i >= Containers::StridedDimensions{self.size()}[0]) throw pybind11::index_error{}; - return PyStridedArrayView{self[i], self.obj}; + return Containers::pyArrayViewHolder(self[i], pyObjectHolderFor(self).owner); }, "Sub-view at given position") /* Single-item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ /* Multi-dimensional slicing */ - .def("__getitem__", [](const PyStridedArrayView& self, const typename DimensionsTuple::Type& slice) -> py::object { + .def("__getitem__", [](const Containers::StridedArrayView& self, const typename DimensionsTuple::Type& slice) { Containers::StridedDimensions starts; Containers::StridedDimensions stops; Containers::StridedDimensions steps; @@ -403,153 +407,153 @@ template void stridedArrayViewND(py::class_(self.slice(starts, stops).every(steps), self.obj)); + return Containers::pyArrayViewHolder(self.slice(starts, stops).every(steps), pyObjectHolderFor(self).owner); }, "Slice the view"); } -template void stridedArrayView2D(py::class_>& c) { +template void stridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__getitem__", [](const PyStridedArrayView<2, T>& self, const std::tuple& i) { + .def("__getitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1]) throw py::index_error{}; return self[std::get<0>(i)][std::get<1>(i)]; }, "Value at given position") - .def("transposed", [](const PyStridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { + .def("transposed", [](const Containers::StridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) - return PyStridedArrayView<2, T>{self.template transposed<0, 1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 2)}; }, "Transpose two dimensions") - .def("flipped", [](const PyStridedArrayView<2, T>& self, const std::size_t dimension) { + .def("flipped", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension) { if(dimension == 0) - return PyStridedArrayView<2, T>{self.template flipped<0>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<2, T>{self.template flipped<1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; }, "Flip a dimension") - .def("broadcasted", [](const PyStridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { + .def("broadcasted", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) - return PyStridedArrayView<2, T>{self.template broadcasted<0>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<2, T>{self.template broadcasted<1>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; }, "Broadcast a dimension"); } -template void stridedArrayView3D(py::class_>& c) { +template void stridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__getitem__", [](const PyStridedArrayView<3, T>& self, const std::tuple& i) { + .def("__getitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]; }, "Value at given position") - .def("transposed", [](const PyStridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { + .def("transposed", [](const Containers::StridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) - return PyStridedArrayView<3, T>{self.template transposed<0, 1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 2) || (a == 2 && b == 0)) - return PyStridedArrayView<3, T>{self.template transposed<0, 2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 2) || (a == 2 && b == 1)) - return PyStridedArrayView<3, T>{self.template transposed<1, 2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 3)}; }, "Transpose two dimensions") - .def("flipped", [](const PyStridedArrayView<3, T>& self, const std::size_t dimension) { + .def("flipped", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension) { if(dimension == 0) - return PyStridedArrayView<3, T>{self.template flipped<0>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<3, T>{self.template flipped<1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); if(dimension == 2) - return PyStridedArrayView<3, T>{self.template flipped<2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; }, "Flip a dimension") - .def("broadcasted", [](const PyStridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { + .def("broadcasted", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) - return PyStridedArrayView<3, T>{self.template broadcasted<0>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<3, T>{self.template broadcasted<1>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); if(dimension == 2) - return PyStridedArrayView<3, T>{self.template broadcasted<2>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; }, "Broadcast a dimension"); } -template void stridedArrayView4D(py::class_>& c) { +template void stridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__getitem__", [](const PyStridedArrayView<4, T>& self, const std::tuple& i) { + .def("__getitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2] || std::get<3>(i) >= self.size()[3]) throw pybind11::index_error{}; return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)]; }, "Value at given position") - .def("transposed", [](const PyStridedArrayView<4, T>& self, const std::size_t a, std::size_t b) { + .def("transposed", [](const Containers::StridedArrayView<4, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) - return PyStridedArrayView<4, T>{self.template transposed<0, 1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 2) || (a == 2 && b == 0)) - return PyStridedArrayView<4, T>{self.template transposed<0, 2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 3) || (a == 3 && b == 0)) - return PyStridedArrayView<4, T>{self.template transposed<0, 3>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<0, 3>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 2) || (a == 2 && b == 1)) - return PyStridedArrayView<4, T>{self.template transposed<1, 2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 3) || (a == 3 && b == 1)) - return PyStridedArrayView<4, T>{self.template transposed<1, 3>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<1, 3>(), pyObjectHolderFor(self).owner); if((a == 2 && b == 3) || (a == 3 && b == 2)) - return PyStridedArrayView<4, T>{self.template transposed<2, 3>(), self.obj}; + return Containers::pyArrayViewHolder(self.template transposed<2, 3>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 4)}; }, "Transpose two dimensions") - .def("flipped", [](const PyStridedArrayView<4, T>& self, const std::size_t dimension) { + .def("flipped", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension) { if(dimension == 0) - return PyStridedArrayView<4, T>{self.template flipped<0>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<4, T>{self.template flipped<1>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); if(dimension == 2) - return PyStridedArrayView<4, T>{self.template flipped<2>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); if(dimension == 3) - return PyStridedArrayView<4, T>{self.template flipped<3>(), self.obj}; + return Containers::pyArrayViewHolder(self.template flipped<3>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 4)}; }, "Flip a dimension") - .def("broadcasted", [](const PyStridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) { + .def("broadcasted", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) - return PyStridedArrayView<4, T>{self.template broadcasted<0>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) - return PyStridedArrayView<4, T>{self.template broadcasted<1>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); if(dimension == 2) - return PyStridedArrayView<4, T>{self.template broadcasted<2>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); if(dimension == 3) - return PyStridedArrayView<4, T>{self.template broadcasted<3>(size), self.obj}; + return Containers::pyArrayViewHolder(self.template broadcasted<3>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 4)}; }, "Broadcast a dimension"); } -template void mutableStridedArrayView1D(py::class_>& c) { +template void mutableStridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const PyStridedArrayView<1, T>& self, const std::size_t i, const T& value) { + .def("__setitem__", [](const Containers::StridedArrayView<1, T>& self, const std::size_t i, const T& value) { if(i >= self.size()) throw pybind11::index_error{}; self[i] = value; }, "Set a value at given position"); } -template void mutableStridedArrayView2D(py::class_>& c) { +template void mutableStridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const PyStridedArrayView<2, T>& self, const std::tuple& i, const T& value) { + .def("__setitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1]) throw pybind11::index_error{}; self[std::get<0>(i)][std::get<1>(i)] = value; }, "Set a value at given position"); } -template void mutableStridedArrayView3D(py::class_>& c) { +template void mutableStridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const PyStridedArrayView<3, T>& self, const std::tuple& i, const T& value) { + .def("__setitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; @@ -557,9 +561,9 @@ template void mutableStridedArrayView3D(py::class_ void mutableStridedArrayView4D(py::class_>& c) { +template void mutableStridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const PyStridedArrayView<4, T>& self, const std::tuple& i, const T& value) { + .def("__setitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2] || @@ -573,22 +577,22 @@ template void mutableStridedArrayView4D(py::class_> arrayView_{m, + py::class_, Containers::PyArrayViewHolder>> arrayView_{m, "ArrayView", "Array view", py::buffer_protocol{}}; arrayView(arrayView_); - py::class_> mutableArrayView_{m, + py::class_, Containers::PyArrayViewHolder>> mutableArrayView_{m, "MutableArrayView", "Mutable array view", py::buffer_protocol{}}; arrayView(mutableArrayView_); mutableArrayView(mutableArrayView_); - py::class_> stridedArrayView1D_{m, + py::class_, Containers::PyArrayViewHolder>> stridedArrayView1D_{m, "StridedArrayView1D", "One-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> stridedArrayView2D_{m, + py::class_, Containers::PyArrayViewHolder>> stridedArrayView2D_{m, "StridedArrayView2D", "Two-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> stridedArrayView3D_{m, + py::class_, Containers::PyArrayViewHolder>> stridedArrayView3D_{m, "StridedArrayView3D", "Three-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> stridedArrayView4D_{m, + py::class_, Containers::PyArrayViewHolder>> stridedArrayView4D_{m, "StridedArrayView4D", "Four-dimensional array view with stride information", py::buffer_protocol{}}; stridedArrayView(stridedArrayView1D_); stridedArrayView1D(stridedArrayView1D_); @@ -602,13 +606,13 @@ void containers(py::module& m) { stridedArrayViewND(stridedArrayView4D_); stridedArrayView4D(stridedArrayView4D_); - py::class_> mutableStridedArrayView1D_{m, + py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView1D_{m, "MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> mutableStridedArrayView2D_{m, + py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView2D_{m, "MutableStridedArrayView2D", "Mutable two-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> mutableStridedArrayView3D_{m, + py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView3D_{m, "MutableStridedArrayView3D", "Mutable three-dimensional array view with stride information", py::buffer_protocol{}}; - py::class_> mutableStridedArrayView4D_{m, + py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView4D_{m, "MutableStridedArrayView4D", "Mutable four-dimensional array view with stride information", py::buffer_protocol{}}; stridedArrayView(mutableStridedArrayView1D_); stridedArrayView1D(mutableStridedArrayView1D_); diff --git a/src/python/magnum/gl.cpp b/src/python/magnum/gl.cpp index 1b9b7e1..55cded7 100644 --- a/src/python/magnum/gl.cpp +++ b/src/python/magnum/gl.cpp @@ -42,7 +42,6 @@ #include "Corrade/Python.h" #include "Magnum/Python.h" -#include "corrade/PyArrayView.h" #include "corrade/EnumOperators.h" #include "magnum/bootstrap.h" #include "magnum/NonDestructible.h" @@ -177,7 +176,7 @@ void gl(py::module& m) { .def_property_readonly("id", &GL::Buffer::id, "OpenGL buffer ID") .def_property("target_hint", &GL::Buffer::targetHint, &GL::Buffer::setTargetHint, "Target hint") /* Using lambdas to avoid method chaining getting into signatures */ - .def("set_data", [](GL::Buffer& self, const corrade::PyArrayView& data, GL::BufferUsage usage) { + .def("set_data", [](GL::Buffer& self, const Containers::ArrayView& data, GL::BufferUsage usage) { self.setData(data, usage); }, "Set buffer data", py::arg("data"), py::arg("usage") = GL::BufferUsage::StaticDraw) /** @todo more */; @@ -300,9 +299,8 @@ void gl(py::module& m) { .def("clear", [](GL::AbstractFramebuffer& self, GL::FramebufferClear mask) { self.clear(mask); }, "Clear specified buffers in the framebuffer") - .def("read", [](GL::AbstractFramebuffer& self, const Range2Di& rectangle, PyImageView<2, char>& image) { - self.read(rectangle, image); - }, "Read block of pixels from the framebuffer to an image view"); + .def("read", static_cast(&GL::AbstractFramebuffer::read), + "Read block of pixels from the framebuffer to an image view"); NonDestructibleBase defaultFramebuffer{m, "DefaultFramebuffer", "Default framebuffer"}; diff --git a/src/python/magnum/magnum.cpp b/src/python/magnum/magnum.cpp index e319db9..002529d 100644 --- a/src/python/magnum/magnum.cpp +++ b/src/python/magnum/magnum.cpp @@ -25,13 +25,16 @@ #include #include +#include +#include #include #include #include +#include "Corrade/Python.h" +#include "Corrade/Containers/Python.h" #include "Magnum/Python.h" -#include "corrade/PyArrayView.h" #include "magnum/bootstrap.h" #ifdef MAGNUM_BUILD_STATIC @@ -56,7 +59,7 @@ template struct PyDimensionTraits<3, T> { static VectorType from(const Math::Vector<3, T>& vec) { return vec; } }; -template void imageView(py::class_& c) { +template void imageView(py::class_>& c) { /* Missing APIs: @@ -65,17 +68,17 @@ template void imageView(py::class_& c) { c /* Constructors */ - .def(py::init([](const PixelStorage& storage, PixelFormat format, const typename PyDimensionTraits::VectorType& size, const corrade::PyArrayView& data) { - return T{ImageView{storage, format, size, data}, data.obj}; + .def(py::init([](const PixelStorage& storage, PixelFormat format, const typename PyDimensionTraits::VectorType& size, const Containers::ArrayView& data) { + return pyImageViewHolder(T{storage, format, size, data}, pyObjectHolderFor(data).owner); }), "Constructor") - .def(py::init([](PixelFormat format, const typename PyDimensionTraits::VectorType& size, const corrade::PyArrayView& data) { - return T{ImageView{format, size, data}, data.obj}; + .def(py::init([](PixelFormat format, const typename PyDimensionTraits::VectorType& size, const Containers::ArrayView& data) { + return pyImageViewHolder(T{format, size, data}, pyObjectHolderFor(data).owner); }), "Constructor") .def(py::init([](const PixelStorage& storage, PixelFormat format, const typename PyDimensionTraits::VectorType& size) { - return T{ImageView{storage, format, size}, py::none{}}; + return T{storage, format, size}; }), "Construct an empty view") .def(py::init([](PixelFormat format, const typename PyDimensionTraits::VectorType& size) { - return T{ImageView{format, size}, py::none{}}; + return T{format, size}; }), "Construct an empty view") /* Properties */ @@ -86,22 +89,25 @@ template void imageView(py::class_& c) { return PyDimensionTraits::from(self.size()); }, "Image size") .def_property("data", [](T& self) { - return corrade::PyArrayView{{static_cast(self.data().data()), self.data().size()}, self.owner}; - }, [](T& self, const corrade::PyArrayView& data) { + return Containers::pyArrayViewHolder(self.data(), pyObjectHolderFor(self).owner); + }, [](T& self, const Containers::ArrayView& data) { self.setData(data); - self.owner = data.obj; + pyObjectHolderFor(self).owner = + pyObjectHolderFor(data).owner; }, "Image data") .def_property_readonly("pixels", [](T& self) { - return corrade::PyStridedArrayView{self.pixels(), self.owner}; + return Containers::pyArrayViewHolder(self.pixels(), pyObjectHolderFor(self).owner); }, "View on pixel data") - .def_readonly("owner", &T::owner, "Memory owner"); + .def_property_readonly("owner", [](T& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner"); } -template void imageViewFromMutable(py::class_& c) { +template void imageViewFromMutable(py::class_>& c) { c - .def(py::init([](const PyImageView& other) { - return T{ImageView{other}, other.owner}; + .def(py::init([](const BasicMutableImageView& other) { + return pyImageViewHolder(BasicImageView(other), pyObjectHolderFor(other).owner); }), "Constructor"); } @@ -187,12 +193,12 @@ void magnum(py::module& m) { .def_property("skip", &PixelStorage::skip, &PixelStorage::setSkip, "Pixel, row and image skip"); - py::class_> imageView1D{m, "ImageView1D", "One-dimensional image view"}; - py::class_> imageView2D{m, "ImageView2D", "Two-dimensional image view"}; - py::class_> imageView3D{m, "ImageView3D", "Three-dimensional image view"}; - py::class_> mutableImageView1D{m, "MutableImageView1D", "One-dimensional mutable image view"}; - py::class_> mutableImageView2D{m, "MutableImageView2D", "Two-dimensional mutable image view"}; - py::class_> mutableImageView3D{m, "MutableImageView3D", "Three-dimensional mutable image view"}; + py::class_> imageView1D{m, "ImageView1D", "One-dimensional image view"}; + py::class_> imageView2D{m, "ImageView2D", "Two-dimensional image view"}; + py::class_> imageView3D{m, "ImageView3D", "Three-dimensional image view"}; + py::class_> mutableImageView1D{m, "MutableImageView1D", "One-dimensional mutable image view"}; + py::class_> mutableImageView2D{m, "MutableImageView2D", "Two-dimensional mutable image view"}; + py::class_> mutableImageView3D{m, "MutableImageView3D", "Three-dimensional mutable image view"}; imageView(imageView1D); imageView(imageView2D);