Browse Source

python: expose gl.Context.

It's a bit involved as we need to ensure that gl.Context.current doesn't
outline the Application instance, so we need to:

 - remember the Application object when it gets constructed (and clear
   it again when it gets destructed)
 - in gl.Context.current check if there's an active Application (which
   means sharing data across two different Python modules, and even
   though pybind11 docs suggest to "simply export a symbol", this
   *cannot* possibly work in practice; instead we share data using a
   Python capsule), and increase its refcount when returning the Context
   instance
 - decrease the Application refcount again when the Context gets
   destructed
pull/11/head
Vladimír Vondruš 5 years ago
parent
commit
c51928c07e
  1. 136
      src/python/magnum/gl.cpp
  2. 2
      src/python/magnum/platform/application.h
  3. 3
      src/python/magnum/platform/egl.cpp
  4. 3
      src/python/magnum/platform/glfw.cpp
  5. 3
      src/python/magnum/platform/glx.cpp
  6. 72
      src/python/magnum/platform/holder.h
  7. 3
      src/python/magnum/platform/sdl2.cpp
  8. 3
      src/python/magnum/platform/wgl.cpp
  9. 2
      src/python/magnum/platform/windowlessapplication.h
  10. 7
      src/python/magnum/test/test_gl.py
  11. 23
      src/python/magnum/test/test_gl_gl.py

136
src/python/magnum/gl.cpp

@ -31,6 +31,7 @@
#include <Magnum/GL/AbstractShaderProgram.h>
#include <Magnum/GL/Attribute.h>
#include <Magnum/GL/Buffer.h>
#include <Magnum/GL/Context.h>
#include <Magnum/GL/DefaultFramebuffer.h>
#include <Magnum/GL/Framebuffer.h>
#include <Magnum/GL/Mesh.h>
@ -52,6 +53,27 @@
namespace magnum { namespace {
/* Stores additional stuff needed for proper refcounting of applications that
own current context. The context itself isn't deleted, that's a
responsibility of the applications instead. For some reason it *has to be*
templated, otherwise PYBIND11_DECLARE_HOLDER_TYPE doesn't work. */
template<class T> struct ContextHolder: std::unique_ptr<T, pybind11::nodelete> {
static_assert(std::is_same<T, GL::Context>::value, "context holder has to hold a context");
/* Used when instantiating a context directly */
explicit ContextHolder(T* object): std::unique_ptr<T, pybind11::nodelete>{object} {}
/* Used by Context.current() */
explicit ContextHolder(T* object, pybind11::object owner) noexcept: std::unique_ptr<T, pybind11::nodelete>{object}, owner{std::move(owner)} {}
ContextHolder(ContextHolder<T>&&) noexcept = default;
ContextHolder(const ContextHolder<T>&) = delete;
ContextHolder<T>& operator=(ContextHolder<T>&&) noexcept = default;
ContextHolder<T>& operator=(const ContextHolder<T>&) = default;
pybind11::object owner;
};
/* Otherwise pybind yells that `generic_type: type "Framebuffer" has a
non-default holder type while its base "Magnum::GL::AbstractFramebuffer"
does not` -- we're using PyFramebufferHolder for it */
@ -61,6 +83,7 @@ template<class T> struct NonDefaultFramebufferHolder: std::unique_ptr<T, PyNonDe
}}
PYBIND11_DECLARE_HOLDER_TYPE(T, magnum::ContextHolder<T>)
PYBIND11_DECLARE_HOLDER_TYPE(T, magnum::NonDefaultFramebufferHolder<T>)
namespace magnum {
@ -278,6 +301,119 @@ void gl(py::module_& m) {
.def("version", static_cast<std::pair<Int, Int>(*)(GL::Version)>(GL::version), "Major and minor version number from enum value", py::arg("version"))
.def("is_version_es", GL::isVersionES, "Whether given version is OpenGL ES or WebGL");
/* Context */
{
py::class_<GL::Context, ContextHolder<GL::Context>> context{m, "Context", "Magnum OpenGL context"};
py::enum_<GL::Context::Flag> contextFlag{context, "Flag", "Context flag"};
contextFlag
.value("DEBUG", GL::Context::Flag::Debug)
#ifndef MAGNUM_TARGET_GLES
.value("FORWARD_COMPATIBLE", GL::Context::Flag::ForwardCompatible)
#endif
.value("NO_ERROR", GL::Context::Flag::NoError)
#ifndef MAGNUM_TARGET_GLES2
.value("ROBUST_ACCESS", GL::Context::Flag::RobustAccess)
#endif
.value("NONE", GL::Context::Flag{});
corrade::enumOperators(contextFlag);
py::enum_<GL::Context::State> contextState{context, "State", "State to reset"};
contextState
.value("BUFFERS", GL::Context::State::Buffers)
#ifndef MAGNUM_TARGET_GLES2
.value("UNBIND_PIXEL_BUFFER", GL::Context::State::UnbindPixelBuffer)
#endif
.value("FRAMEBUFFERS", GL::Context::State::Framebuffers)
.value("MESHES", GL::Context::State::Meshes)
.value("MESH_VAO", GL::Context::State::MeshVao)
.value("BIND_SCRATCH_VAO", GL::Context::State::BindScratchVao)
.value("PIXEL_STORAGE", GL::Context::State::PixelStorage)
.value("RENDERER", GL::Context::State::Renderer)
.value("SHADERS", GL::Context::State::Shaders)
.value("TEXTURES", GL::Context::State::Textures)
#ifndef MAGNUM_TARGET_GLES2
.value("TRANSFORM_FEEDBACK", GL::Context::State::TransformFeedback)
#endif
.value("ENTER_EXTERNAL", GL::Context::State::EnterExternal)
.value("EXIT_EXTERNAL", GL::Context::State::ExitExternal);
corrade::enumOperators(contextState);
py::enum_<GL::Context::DetectedDriver> contextDetectedDriver{context, "DetectedDriver", "Detected driver"};
contextDetectedDriver
#ifndef MAGNUM_TARGET_WEBGL
.value("AMD", GL::Context::DetectedDriver::Amd)
#endif
#ifdef MAGNUM_TARGET_GLES
.value("ANGLE", GL::Context::DetectedDriver::Angle)
#endif
#ifndef MAGNUM_TARGET_WEBGL
.value("INTEL_WINDOWS", GL::Context::DetectedDriver::IntelWindows)
.value("MESA", GL::Context::DetectedDriver::Mesa)
.value("NVIDIA", GL::Context::DetectedDriver::NVidia)
.value("SVGA3D", GL::Context::DetectedDriver::Svga3D)
#ifdef MAGNUM_TARGET_GLES
.value("SWIFTSHADER", GL::Context::DetectedDriver::SwiftShader)
#endif
#endif
#ifdef CORRADE_TARGET_ANDROID
.value("ARM_MALI", GL::Context::DetectedDriver::ArmMali)
#endif
;
corrade::enumOperators(contextDetectedDriver);
context
.def_property_readonly_static("has_current", [](py::object) {
return GL::Context::hasCurrent();
}, "Whether there is any current context")
.def_property_readonly_static("current", [](py::object) {
if(!GL::Context::hasCurrent()) {
PyErr_SetString(PyExc_RuntimeError, "no current context");
throw py::error_already_set{};
}
py::object owner = py::none{};
auto* glContextOwner = reinterpret_cast<std::pair<const void*, const std::type_info*>*>(py::get_shared_data("magnumGLContextOwner"));
if(glContextOwner && glContextOwner->first) {
CORRADE_INTERNAL_ASSERT(glContextOwner->second);
owner = Corrade::pyObjectFromInstance(glContextOwner->first, *glContextOwner->second);
}
return ContextHolder<GL::Context>{&GL::Context::current(), owner};
}, "Current context")
/** @todo context switching (needs additions to the "who owns
current context instance" variable -- a map?) */
.def_property_readonly("version", &GL::Context::version, "OpenGL version")
.def_property_readonly("vendor_string", &GL::Context::vendorString, "Vendor string")
.def_property_readonly("renderer_string", &GL::Context::rendererString, "Renderer string")
.def_property_readonly("version_string", &GL::Context::versionString, "Version string")
.def_property_readonly("shading_language_version_string", &GL::Context::shadingLanguageVersionString, "Shading language version string")
.def_property_readonly("shading_language_version_strings", &GL::Context::shadingLanguageVersionStrings, "Shading language version strings")
.def_property_readonly("extension_strings", &GL::Context::extensionStrings, "Extension strings")
#ifndef MAGNUM_TARGET_WEBGL
.def_property_readonly("flags", &GL::Context::flags, "Context flags")
#endif
/** @todo supportedExtensions() (needs Extension exposed) */
#ifndef MAGNUM_TARGET_GLES
.def_property_readonly("is_core_profile", &GL::Context::isCoreProfile, "Detect if current OpenGL context is a core profile")
#endif
.def("is_version_supported", &GL::Context::isVersionSupported, "Get supported OpenGL version", py::arg("version"))
/** @todo supportedVersion() (takes an initializer list, add an
arrayview overload */
/** @todo isExtensionSupported(), isExtensionDisabled() (needs
Extension exposed) */
.def("reset_state", [](GL::Context& self, GL::Context::State states) {
self.resetState(states);
}, "Reset internal state tracker", py::arg("states") = GL::Context::State{})
.def_property_readonly("detected_driver", [](GL::Context& self) {
return GL::Context::DetectedDriver(UnsignedShort(self.detectedDriver()));
}, "Detected driver")
.def_property_readonly("owner", [](GL::Context& self) {
return pyObjectHolderFor<ContextHolder>(self).owner;
}, "Magnum Application owning the context");
}
/* Shader (used by AbstractShaderProgram, so needs to be before) */
{
py::class_<GL::Shader> shader{m, "Shader", "Shader"};

2
src/python/magnum/platform/application.h

@ -32,7 +32,7 @@
namespace magnum { namespace platform {
template<class T, class Trampoline> void application(py::class_<T, Trampoline>& c) {
template<class T, class Trampoline, class Holder> void application(py::class_<T, Trampoline, Holder>& c) {
py::class_<typename T::Configuration> configuration{c, "Configuration", "Configuration"};
configuration
.def(py::init())

3
src/python/magnum/platform/egl.cpp

@ -28,6 +28,7 @@
#include "magnum/bootstrap.h"
#include "magnum/platform/windowlessapplication.h"
#include "magnum/platform/holder.h"
namespace magnum { namespace platform {
@ -64,7 +65,7 @@ void egl(py::module_& m) {
virtual ~PyWindowlessApplication() {}
};
py::class_<PyWindowlessApplication> windowlessEglApplication{m, "WindowlessApplication", "Windowless EGL application"};
py::class_<PyWindowlessApplication, ApplicationHolder<PyWindowlessApplication>> windowlessEglApplication{m, "WindowlessApplication", "Windowless EGL application"};
windowlessapplication(windowlessEglApplication);
}

3
src/python/magnum/platform/glfw.cpp

@ -30,6 +30,7 @@
#include "magnum/bootstrap.h"
#include "magnum/platform/application.h"
#include "magnum/platform/holder.h"
namespace magnum { namespace platform {
@ -155,7 +156,7 @@ void glfw(py::module_& m) {
}
};
py::class_<PublicizedApplication, PyApplication> glfwApplication{m, "Application", "GLFW application"};
py::class_<PublicizedApplication, PyApplication, ApplicationHolder<PublicizedApplication>> glfwApplication{m, "Application", "GLFW application"};
/** @todo def_property_writeonly for swap_interval */
PyNonDestructibleClass<PublicizedApplication::InputEvent> inputEvent_{glfwApplication, "InputEvent", "Base for input events"};

3
src/python/magnum/platform/glx.cpp

@ -28,6 +28,7 @@
#include "magnum/bootstrap.h"
#include "magnum/platform/windowlessapplication.h"
#include "magnum/platform/holder.h"
namespace magnum { namespace platform {
@ -64,7 +65,7 @@ void glx(py::module_& m) {
virtual ~PyWindowlessApplication() {}
};
py::class_<PyWindowlessApplication> windowlessGlxApplication{m, "WindowlessApplication", "Windowless GLX application"};
py::class_<PyWindowlessApplication, ApplicationHolder<PyWindowlessApplication>> windowlessGlxApplication{m, "WindowlessApplication", "Windowless GLX application"};
windowlessapplication(windowlessGlxApplication);
}

72
src/python/magnum/platform/holder.h

@ -0,0 +1,72 @@
#ifndef magnum_platform_holder_h
#define magnum_platform_holder_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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 <pybind11/pybind11.h>
namespace magnum { namespace platform { namespace {
/* Takes care of updating magnum::glContextOwner so it doesn't need to be
duplicated in every application implementation */
template<class T> struct ApplicationHolder: std::unique_ptr<T> {
explicit ApplicationHolder(T* object): std::unique_ptr<T>{object} {
/* There's no real possibility to export a symbol from magnum.gl and
access it from here (because there's no real possibility for a
module to ensure another module is loaded before it in order to make
the symbols resolve correctly; Corrade's PluginManager does that by
having dependency info *external* to the module, that's the only
way), so we're sharing the data using a bunch of very ugly
allocations instead. Fortunately construction/destruction of an
application happens *very seldom*, and gl.Context.current()
hopefully also not that often. Yes, the parameter is a std::string.
JOY. */
auto* glContextOwner = static_cast<std::pair<const void*, const std::type_info*>*>(py::get_shared_data("magnumGLContextOwner"));
if(!glContextOwner)
py::set_shared_data("magnumGLContextOwner", glContextOwner = new std::pair<const void*, const std::type_info*>{});
CORRADE_ASSERT(!glContextOwner->first, "Sorry, just one magnum.*.Application instance can exist at a time", );
*glContextOwner = {object, &typeid(T)};
}
ApplicationHolder(ApplicationHolder<T>&&) noexcept = default;
ApplicationHolder(const ApplicationHolder<T>&) = delete;
~ApplicationHolder() {
auto* glContextOwner = static_cast<std::pair<const void*, const std::type_info*>*>(py::get_shared_data("magnumGLContextOwner"));
CORRADE_INTERNAL_ASSERT(glContextOwner && glContextOwner->first == this->get());
delete glContextOwner;
py::set_shared_data("magnumGLContextOwner", nullptr);
}
ApplicationHolder<T>& operator=(ApplicationHolder<T>&&) noexcept = default;
ApplicationHolder<T>& operator=(const ApplicationHolder<T>&) = default;
};
}}}
PYBIND11_DECLARE_HOLDER_TYPE(T, magnum::platform::ApplicationHolder<T>)
#endif

3
src/python/magnum/platform/sdl2.cpp

@ -30,6 +30,7 @@
#include "magnum/bootstrap.h"
#include "magnum/platform/application.h"
#include "magnum/platform/holder.h"
namespace magnum { namespace platform {
@ -155,7 +156,7 @@ void sdl2(py::module_& m) {
}
};
py::class_<PublicizedApplication, PyApplication> sdl2application{m, "Application", "SDL2 application"};
py::class_<PublicizedApplication, PyApplication, ApplicationHolder<PublicizedApplication>> sdl2application{m, "Application", "SDL2 application"};
sdl2application
.def_property("swap_interval", &PyApplication::swapInterval,
[](PublicizedApplication& self, Int interval) {

3
src/python/magnum/platform/wgl.cpp

@ -28,6 +28,7 @@
#include "magnum/bootstrap.h"
#include "magnum/platform/windowlessapplication.h"
#include "magnum/platform/holder.h"
namespace magnum { namespace platform {
@ -64,7 +65,7 @@ void wgl(py::module_& m) {
virtual ~PyWindowlessApplication() {}
};
py::class_<PyWindowlessApplication> windowlessWglApplication{m, "WindowlessApplication", "Windowless WGL application"};
py::class_<PyWindowlessApplication, ApplicationHolder<PyWindowlessApplication>> windowlessWglApplication{m, "WindowlessApplication", "Windowless WGL application"};
windowlessapplication(windowlessWglApplication);
}

2
src/python/magnum/platform/windowlessapplication.h

@ -29,7 +29,7 @@
namespace magnum { namespace platform {
template<class T> void windowlessapplication(py::class_<T>& c) {
template<class T, class Holder> void windowlessapplication(py::class_<T, Holder>& c) {
py::class_<typename T::Configuration> configuration{c, "Configuration", "Configuration"};
configuration
.def(py::init());

7
src/python/magnum/test/test_gl.py

@ -36,6 +36,13 @@ class Attribute(unittest.TestCase):
self.assertEqual(a.components, gl.Attribute.Components.TWO)
self.assertEqual(a.data_type, gl.Attribute.DataType.FLOAT)
class Context(unittest.TestCase):
def test_no_current(self):
self.assertFalse(gl.Context.has_current)
with self.assertRaisesRegex(RuntimeError, "no current context"):
gl.Context.current
class FramebufferClear(unittest.TestCase):
def test_ops(self):
self.assertEqual(gl.FramebufferClear.COLOR|gl.FramebufferClear.COLOR, gl.FramebufferClear.COLOR)

23
src/python/magnum/test/test_gl_gl.py

@ -132,6 +132,29 @@ class Buffer(GLTestCase):
a = gl.Buffer()
a.set_data(array.array('f', [0.5, 1.2]))
class Context(GLTestCase):
def test(self):
self.assertTrue(gl.Context.has_current)
# Retrieving the context should hold down the app with one more
# reference
app_refcount = sys.getrefcount(self.app)
current = gl.Context.current
self.assertEqual(sys.getrefcount(self.app), app_refcount + 1)
self.assertEqual(current.owner, self.app)
# Some properties
self.assertGreater(len(current.renderer_string), 0)
self.assertGreater(len(current.extension_strings), 0)
# Interestingly enough, this "just works". I thought I would need to do
# something extra but apparently not
current2 = gl.Context.current
self.assertTrue(id(current) == id(current2))
del current, current2
self.assertEqual(sys.getrefcount(self.app), app_refcount)
class DefaultFramebuffer(GLTestCase):
def test(self):
# Using it should not crash, leak or cause double-free issues

Loading…
Cancel
Save