From 652d19c604d40d065784c3caf54bd3f95a037783 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 11 Jul 2017 02:37:25 -0700 Subject: [PATCH 1/3] WIP: gl. --- src/_mpl_cairo.cpp | 82 ++++++++++++++++++++++++++++++++++++---------- src/_mpl_cairo.h | 4 +++ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/_mpl_cairo.cpp b/src/_mpl_cairo.cpp index e4608292..89904c37 100644 --- a/src/_mpl_cairo.cpp +++ b/src/_mpl_cairo.cpp @@ -91,14 +91,7 @@ GraphicsContextRenderer::~GraphicsContextRenderer() { } } -void GraphicsContextRenderer::set_ctx_from_image_args( - cairo_format_t format, int width, int height) { - if (cr_) { - cairo_destroy(cr_); - } - auto surface = cairo_image_surface_create(format, width, height); - cr_ = cairo_create(surface); - cairo_surface_destroy(surface); +void GraphicsContextRenderer::init_ctx() { set_ctx_defaults(cr_); auto stack = new std::stack({AdditionalState{}}); stack->top().clip_path = {nullptr, &cairo_path_destroy}; @@ -109,6 +102,17 @@ void GraphicsContextRenderer::set_ctx_from_image_args( cairo_set_user_data(cr_, &STATE_KEY, stack, operator delete); } +void GraphicsContextRenderer::set_ctx_from_image_args( + cairo_format_t format, int width, int height) { + if (cr_) { + cairo_destroy(cr_); + } + auto surface = cairo_image_surface_create(format, width, height); + cr_ = cairo_create(surface); + cairo_surface_destroy(surface); + init_ctx(); +} + void GraphicsContextRenderer::set_ctx_from_pycairo_ctx(py::object ctx) { if (cr_) { cairo_destroy(cr_); @@ -130,13 +134,55 @@ void GraphicsContextRenderer::set_ctx_from_pycairo_ctx(py::object ctx) { } cr_ = ptr->ctx; set_ctx_defaults(cr_); - auto stack = new std::stack({AdditionalState{}}); - stack->top().clip_path = {nullptr, &cairo_path_destroy}; - stack->top().hatch = {}; - stack->top().hatch_color = to_rgba(rc_param("hatch.color")); - stack->top().hatch_linewidth = rc_param("hatch.linewidth").cast(); - stack->top().sketch = py::none(); - cairo_set_user_data(cr_, &STATE_KEY, stack, operator delete); + init_ctx(); +} + +void GraphicsContextRenderer::set_ctx_from_current_gl() { + if (cr_) { + cairo_destroy(cr_); + } + + cairo_surface_t* surface; + // This is set up by Qt before paintGL() is called. + // On Linux, Qt uses GLX by default, but uses EGL if QT_XCB_GL_INTEGRATION + // is set to xcb_egl. + if (auto [draw, dpy, ctx] = + std::tuple{ + glXGetCurrentDrawable(), + glXGetCurrentDisplay(), + glXGetCurrentContext()}; + draw && dpy && ctx) { + unsigned int width, height; + glXQueryDrawable(dpy, draw, GLX_WIDTH, &width); + glXQueryDrawable(dpy, draw, GLX_HEIGHT, &height); + py::print(width, height); // FIXME + auto device = cairo_glx_device_create(dpy, ctx); + surface = + cairo_gl_surface_create_for_window(device, draw, width, height); + cairo_device_destroy(device); + goto create_surface; + } + if (auto [draw, dpy, ctx] = + std::tuple{ + eglGetCurrentSurface(1), // FIXME I guess 1 is draw? + eglGetCurrentDisplay(), + eglGetCurrentContext()}; + draw && dpy && ctx) { + EGLint width, height; + eglQuerySurface(dpy, draw, EGL_WIDTH, &width); + eglQuerySurface(dpy, draw, EGL_HEIGHT, &height); + py::print(width, height); // FIXME + auto device = cairo_egl_device_create(dpy, ctx); + surface = cairo_gl_surface_create_for_egl(device, draw, width, height); + goto create_surface; + } + throw std::runtime_error("Neither GLX nor EGL contexts are current"); +create_surface: + return; + cr_ = cairo_create(surface); + cairo_surface_destroy(surface); + init_ctx(); + // cairo_gl_surface_swapbuffers(surface); } uintptr_t GraphicsContextRenderer::get_data_address() { @@ -1037,10 +1083,12 @@ PYBIND11_PLUGIN(_mpl_cairo) { .def(py::init()) // Backend-specific API. - .def("set_ctx_from_pycairo_ctx", - &GraphicsContextRenderer::set_ctx_from_pycairo_ctx) .def("set_ctx_from_image_args", &GraphicsContextRenderer::set_ctx_from_image_args) + .def("set_ctx_from_pycairo_ctx", + &GraphicsContextRenderer::set_ctx_from_pycairo_ctx) + .def("set_ctx_from_current_gl", + &GraphicsContextRenderer::set_ctx_from_current_gl) .def("get_data_address", &GraphicsContextRenderer::get_data_address) // GraphicsContext API. diff --git a/src/_mpl_cairo.h b/src/_mpl_cairo.h index d2e5c789..7854dec3 100644 --- a/src/_mpl_cairo.h +++ b/src/_mpl_cairo.h @@ -5,6 +5,7 @@ #include #include +#include #if CAIRO_HAS_XLIB_SURFACE && __has_include() #include #define MPLCAIRO_HAS_X11 @@ -76,8 +77,11 @@ class GraphicsContextRenderer { GraphicsContextRenderer(double width, double height, double dpi); ~GraphicsContextRenderer(); + void init_ctx(); void set_ctx_from_image_args(cairo_format_t format, int width, int height); void set_ctx_from_pycairo_ctx(py::object surface); + void set_ctx_from_current_gl(); + uintptr_t get_data_address(); void set_alpha(std::optional alpha); From 111e4c5fdff5a21355216af8b086b53ba2080ce3 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 11 Jul 2017 21:02:10 -0700 Subject: [PATCH 2/3] More WIP. --- mpl_cairo/base.py | 2 +- mpl_cairo/qtopengl.py | 44 ++++++++++++++++++++++++++++++++ src/_mpl_cairo.cpp | 59 ++++++++++++++++++++++++------------------- src/_mpl_cairo.h | 1 + 4 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 mpl_cairo/qtopengl.py diff --git a/mpl_cairo/base.py b/mpl_cairo/base.py index 2b632ec1..97ef8b51 100644 --- a/mpl_cairo/base.py +++ b/mpl_cairo/base.py @@ -73,7 +73,7 @@ def __init__(self, figure): self._renderer = None self._last_renderer_args = None - # NOTE: Not documented, but needed for tight_layout. + # NOTE: Should be documented upstream, as it is needed by tight_layout. def get_renderer(self): renderer_args = self.get_width_height(), self.figure.dpi if renderer_args != self._last_renderer_args: diff --git a/mpl_cairo/qtopengl.py b/mpl_cairo/qtopengl.py new file mode 100644 index 00000000..d8383f4a --- /dev/null +++ b/mpl_cairo/qtopengl.py @@ -0,0 +1,44 @@ +from matplotlib import rcsetup +from matplotlib.backends.backend_qt5 import ( + QtGui, QtWidgets, _BackendQT5, FigureCanvasQT) + +from .base import FigureCanvasCairo + + +rcsetup.interactive_bk += ["module://mpl_cairo.qtopengl"] # NOTE: Should be fixed in Mpl. + + +class FigureCanvasQTOpenGLCairo( + FigureCanvasCairo, QtWidgets.QOpenGLWidget, FigureCanvasQT): + + def initializeGL(self): + print("igl") + + def paintGL(self): + print("pgl") + renderer = self.get_renderer() + renderer.set_ctx_from_current_gl() + self.figure.draw(renderer) + self._renderer.flush() + # self.context().swapBuffers(self.context().surface()) + + # Currently, FigureCanvasQT.resizeEvent manually forwards the event to + # FigureCanvasBase and QWidget, which mean that QOpenGLWidget.resizeGL + # doen't get triggered. For now, just completely reimplement it here. + + def resizeEvent(self, event): + FigureCanvasQT.resizeEvent(self, event) + QtWidgets.QOpenGLWidget.resizeEvent(self, event) # Forward to resizeGL. + + def resizeGL(self, w, h): + print("rgl", w, h) + renderer = self.get_renderer() + renderer.set_ctx_from_current_gl() + self.figure.draw(renderer) + self._renderer.flush() + # self.context().swapBuffers(self.context().surface()) + + +@_BackendQT5.export +class _BackendQT5Cairo(_BackendQT5): + FigureCanvas = FigureCanvasQTOpenGLCairo diff --git a/src/_mpl_cairo.cpp b/src/_mpl_cairo.cpp index 89904c37..513e6418 100644 --- a/src/_mpl_cairo.cpp +++ b/src/_mpl_cairo.cpp @@ -144,41 +144,30 @@ void GraphicsContextRenderer::set_ctx_from_current_gl() { cairo_surface_t* surface; // This is set up by Qt before paintGL() is called. - // On Linux, Qt uses GLX by default, but uses EGL if QT_XCB_GL_INTEGRATION - // is set to xcb_egl. if (auto [draw, dpy, ctx] = std::tuple{ glXGetCurrentDrawable(), glXGetCurrentDisplay(), glXGetCurrentContext()}; draw && dpy && ctx) { - unsigned int width, height; - glXQueryDrawable(dpy, draw, GLX_WIDTH, &width); - glXQueryDrawable(dpy, draw, GLX_HEIGHT, &height); - py::print(width, height); // FIXME + GLint dims[4]; // x, y, w, h. + glGetIntegerv(GL_VIEWPORT, dims); + GLint x{dims[0]}, y{dims[1]}, width{dims[2]}, height{dims[3]}; + // NOTE: glXQueryDrawable(dpy, draw, GLX_{WIDTH,HEIGHT}, &out) sets out + // to 1? + if (x || y) { + throw std::runtime_error("Expected framebuffer with origin at (0, 0)"); + } auto device = cairo_glx_device_create(dpy, ctx); surface = cairo_gl_surface_create_for_window(device, draw, width, height); cairo_device_destroy(device); goto create_surface; } - if (auto [draw, dpy, ctx] = - std::tuple{ - eglGetCurrentSurface(1), // FIXME I guess 1 is draw? - eglGetCurrentDisplay(), - eglGetCurrentContext()}; - draw && dpy && ctx) { - EGLint width, height; - eglQuerySurface(dpy, draw, EGL_WIDTH, &width); - eglQuerySurface(dpy, draw, EGL_HEIGHT, &height); - py::print(width, height); // FIXME - auto device = cairo_egl_device_create(dpy, ctx); - surface = cairo_gl_surface_create_for_egl(device, draw, width, height); - goto create_surface; - } - throw std::runtime_error("Neither GLX nor EGL contexts are current"); + // (Other GL interfaces, e.g. EGL and WGL, could go here.) + throw std::runtime_error( + "No current GLX context; other OpenGL interfaces not implemented"); create_surface: - return; cr_ = cairo_create(surface); cairo_surface_destroy(surface); init_ctx(); @@ -196,6 +185,18 @@ uintptr_t GraphicsContextRenderer::get_data_address() { return reinterpret_cast(buf); } +void GraphicsContextRenderer::flush(void) { + py::print("cr", cairo_status_to_string(cairo_status(cr_))); + py::print("surface", cairo_status_to_string( + cairo_surface_status(cairo_get_target(cr_)))); + py::print("device", cairo_status_to_string( + cairo_device_status(cairo_surface_get_device(cairo_get_target(cr_))))); + cairo_surface_flush(cairo_get_target(cr_)); + cairo_device_flush(cairo_surface_get_device(cairo_get_target(cr_))); + cairo_gl_surface_swapbuffers(cairo_get_target(cr_)); + py::print("gl", glGetError()); +} + void GraphicsContextRenderer::set_alpha(std::optional alpha) { if (!cr_) { return; @@ -377,11 +378,13 @@ int GraphicsContextRenderer::get_width() { auto surface = cairo_get_target(cr_); switch (cairo_surface_get_type(surface)) { case CAIRO_SURFACE_TYPE_IMAGE: - return cairo_image_surface_get_width(cairo_get_target(cr_)); + return cairo_image_surface_get_width(surface); #ifdef MPLCAIRO_HAS_X11 case CAIRO_SURFACE_TYPE_XLIB: // For Gtk3. - return cairo_xlib_surface_get_width(cairo_get_target(cr_)); + return cairo_xlib_surface_get_width(surface); #endif + case CAIRO_SURFACE_TYPE_GL: + return cairo_gl_surface_get_width(surface); default: throw std::runtime_error("Unsupported surface type"); } @@ -394,11 +397,13 @@ int GraphicsContextRenderer::get_height() { auto surface = cairo_get_target(cr_); switch (cairo_surface_get_type(surface)) { case CAIRO_SURFACE_TYPE_IMAGE: - return cairo_image_surface_get_height(cairo_get_target(cr_)); + return cairo_image_surface_get_height(surface); #ifdef MPLCAIRO_HAS_X11 case CAIRO_SURFACE_TYPE_XLIB: // For Gtk3. - return cairo_xlib_surface_get_height(cairo_get_target(cr_)); + return cairo_xlib_surface_get_height(surface); #endif + case CAIRO_SURFACE_TYPE_GL: + return cairo_gl_surface_get_height(surface); default: throw std::runtime_error("Unsupported surface type"); } @@ -1089,7 +1094,9 @@ PYBIND11_PLUGIN(_mpl_cairo) { &GraphicsContextRenderer::set_ctx_from_pycairo_ctx) .def("set_ctx_from_current_gl", &GraphicsContextRenderer::set_ctx_from_current_gl) + .def("get_data_address", &GraphicsContextRenderer::get_data_address) + .def("flush", &GraphicsContextRenderer::flush) // GraphicsContext API. .def("set_alpha", &GraphicsContextRenderer::set_alpha) diff --git a/src/_mpl_cairo.h b/src/_mpl_cairo.h index 7854dec3..681c03b7 100644 --- a/src/_mpl_cairo.h +++ b/src/_mpl_cairo.h @@ -83,6 +83,7 @@ class GraphicsContextRenderer { void set_ctx_from_current_gl(); uintptr_t get_data_address(); + void flush(); void set_alpha(std::optional alpha); void set_antialiased(cairo_antialias_t aa); From 532d8c5227b10facd9ea542e3781c210aa9f304c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 11 Jul 2017 23:33:02 -0700 Subject: [PATCH 3/3] Update the raw GL attempt. --- mpl_cairo/qtopengl.py | 75 +++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/mpl_cairo/qtopengl.py b/mpl_cairo/qtopengl.py index d8383f4a..1137388c 100644 --- a/mpl_cairo/qtopengl.py +++ b/mpl_cairo/qtopengl.py @@ -8,37 +8,64 @@ rcsetup.interactive_bk += ["module://mpl_cairo.qtopengl"] # NOTE: Should be fixed in Mpl. -class FigureCanvasQTOpenGLCairo( - FigureCanvasCairo, QtWidgets.QOpenGLWidget, FigureCanvasQT): +from .base import GraphicsContextRendererCairo +from matplotlib.backend_bases import * +from matplotlib.backends import backend_qt5 +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * - def initializeGL(self): - print("igl") - def paintGL(self): - print("pgl") - renderer = self.get_renderer() - renderer.set_ctx_from_current_gl() - self.figure.draw(renderer) +class GLWindow(QWindow): + def __init__(self, canvas): + super().__init__() + self.figure = canvas.figure + self._renderer = GraphicsContextRendererCairo(self.figure.dpi) + self._gl_ctx = None + self.setSurfaceType(QWindow.OpenGLSurface) + + def draw(self): + if not self.isExposed(): + return + self._draw_pending = False + if self._gl_ctx is None: + self._gl_ctx = QOpenGLContext(self) + self._gl_ctx.setFormat(self.requestedFormat()) + self._gl_ctx.create() + self._gl_ctx.makeCurrent(self) + print(self.width(), self.height()) + self._renderer.set_ctx_from_current_gl() + self.figure.draw(self._renderer) self._renderer.flush() - # self.context().swapBuffers(self.context().surface()) + self._gl_ctx.swapBuffers(self) - # Currently, FigureCanvasQT.resizeEvent manually forwards the event to - # FigureCanvasBase and QWidget, which mean that QOpenGLWidget.resizeGL - # doen't get triggered. For now, just completely reimplement it here. + def draw_idle(self): + if not self._draw_pending: + self._draw_pending = True + QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) - def resizeEvent(self, event): - FigureCanvasQT.resizeEvent(self, event) - QtWidgets.QOpenGLWidget.resizeEvent(self, event) # Forward to resizeGL. + def event(self, event): + if event.type() in [ + QEvent.Expose, QEvent.Resize, QEvent.UpdateRequest]: + self.draw() + return True + return super().event(event) - def resizeGL(self, w, h): - print("rgl", w, h) - renderer = self.get_renderer() - renderer.set_ctx_from_current_gl() - self.figure.draw(renderer) - self._renderer.flush() - # self.context().swapBuffers(self.context().surface()) + +class FigureManagerQTCairo(FigureManagerBase): + def __init__(self, canvas, num): + backend_qt5._create_qApp() + super().__init__(canvas, num) + self._window = GLWindow(canvas) + fmt = QSurfaceFormat() + fmt.setSamples(4) + self._window.setFormat(fmt) + + def show(self): + self._window.show() @_BackendQT5.export class _BackendQT5Cairo(_BackendQT5): - FigureCanvas = FigureCanvasQTOpenGLCairo + FigureCanvas = FigureCanvasBase + FigureManager = FigureManagerQTCairo