1
0
mirror of https://github.com/thp/pyotherside.git synced 2025-02-05 08:08:23 +08:00

OpenGL rendering in python.

This commit is contained in:
Dennis Tomas 2014-09-04 07:53:59 +02:00
parent 289e5ed616
commit f8de71d449
8 changed files with 450 additions and 0 deletions

View File

@ -864,6 +864,67 @@ This module can now be imported in QML and used as ``source`` in the QML
}
}
OpenGL rendering in Python
==========================
You can render directly to Qt's OpenGL context in your Python code (i.e. via
PyOpenGL or vispy.gloo) using a ``PyGLArea`` item and supplying ``paintGL``
and optional ``initGL`` and ``cleanupGL`` functions. The ``before`` property
controls whether to render before or after the QML scene is rendered.
**rendergl.qml**
.. code-block:: javascript
import QtQuick 2.0
import io.thp.pyotherside 1.3
Item {
width: 800
height: 600
PyGLArea {
id: glArea
anchors.fill: parent
before: true
}
Text {
x: 100
y: 100
text: "Hello World!"
}
Python {
Component.onCompleted: {
importModule('myrenderer', function () {
glArea.initGL = 'myrenderer.initGL';
glArea.paintGL = 'myrenderer.paintGL';
glArea.cleanupGL = 'myrenderer.cleanupGL';
});
}
}
**myrenderer.py**
.. code-block:: python
def initGL():
"""Initialize OpenGL stuff like textures, FBOs etc..."""
def paintGL(x, y, width, height):
"""
Render to the OpenGL context.
(x, y, width, height) indicates the area to render on.
The coordinate system's origin is at the bottom left corner of the
window.
"""
def cleanupGL():
"""Clean up OpenGL stuff initialized in initGL()."""
Building PyOtherSide
====================

132
src/pyglarea.cpp Normal file
View File

@ -0,0 +1,132 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Dennis Tomas <den.t@gmx.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/
#include "qpython_priv.h"
#include "pyglarea.h"
#include <QVariant>
#include <QPointF>
#include <QtQuick/qquickwindow.h>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLContext>
PyGLArea::PyGLArea()
: m_t(0)
, m_before(false)
, m_renderer(0)
{
connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(handleWindowChanged(QQuickWindow*)));
}
PyGLArea::~PyGLArea()
{
delete m_renderer;
}
void PyGLArea::setInitGL(QString initGL)
{
if (initGL == m_initGL)
return;
m_renderer->setInitGL(initGL);
m_initGL = initGL;
if (window())
window()->update();
}
void PyGLArea::setPaintGL(QString paintGL)
{
if (paintGL == m_paintGL)
return;
m_paintGL = paintGL;
m_renderer->setPaintGL(paintGL);
if (window())
window()->update();
}
void PyGLArea::setCleanupGL(QString cleanupGL)
{
if (cleanupGL == m_cleanupGL)
return;
m_cleanupGL = cleanupGL;
m_renderer->setCleanupGL(cleanupGL);
if (window())
window()->update();
}
void PyGLArea::setBefore(bool before)
{
if (before == m_before)
return;
m_before = before;
}
void PyGLArea::setT(qreal t)
{
if (t == m_t)
return;
m_t = t;
emit tChanged();
if (window())
window()->update();
}
void PyGLArea::handleWindowChanged(QQuickWindow *win)
{
if (win) {
connect(win, SIGNAL(beforeSynchronizing()), this, SLOT(sync()), Qt::DirectConnection);
connect(win, SIGNAL(sceneGraphInvalidated()), this, SLOT(cleanup()), Qt::DirectConnection);
// If we allow QML to do the clearing, they would clear what we paint
// and nothing would show.
win->setClearBeforeRendering(false);
}
}
void PyGLArea::update() {
window()->update();
}
void PyGLArea::sync()
{
if (!m_renderer) {
m_renderer = new PyGLRenderer();
if (m_before)
connect(window(), SIGNAL(beforeRendering()), this, SLOT(paint()), Qt::DirectConnection);
else
connect(window(), SIGNAL(afterRendering()), this, SLOT(paint()), Qt::DirectConnection);
}
}
void PyGLArea::paint()
{
QPointF pos = mapToScene(QPointF(.0, .0));
m_renderer->setRect(
QRect(
(long)pos.x(), (long)(window()->height() - this->height() - pos.y()),
(long)this->width(), (long)this->height()
)
);
m_renderer->init();
m_renderer->paint();
window()->resetOpenGLState();
}
void PyGLArea::cleanup()
{
m_renderer->cleanup();
}

77
src/pyglarea.h Normal file
View File

@ -0,0 +1,77 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Dennis Tomas <den.t@gmx.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/
#ifndef PYOTHERSIDE_PYGLAREA_H
#define PYOTHERSIDE_PYGLAREA_H
#include "Python.h"
#include <QString>
#include <QtQuick/QQuickItem>
#include "pyglrenderer.h"
class PyGLArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString initGL READ initGL WRITE setInitGL)
Q_PROPERTY(QString paintGL READ paintGL WRITE setPaintGL)
Q_PROPERTY(QString cleanupGL READ cleanupGL WRITE setCleanupGL)
Q_PROPERTY(bool before READ before WRITE setBefore)
Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
public:
PyGLArea();
~PyGLArea();
QString initGL() const { return m_initGL; };
QString paintGL() const { return m_paintGL; };
QString cleanupGL() const { return m_paintGL; };
bool before() { return m_before; };
void setInitGL(QString initGL);
void setPaintGL(QString paintGL);
void setCleanupGL(QString cleanupGL);
void setBefore(bool before);
qreal t() const { return m_t; }
void setT(qreal t);
signals:
void tChanged();
public slots:
void sync();
void update();
private slots:
void handleWindowChanged(QQuickWindow *win);
void paint();
void cleanup();
private:
qreal m_t;
PyObject *m_paintGLCallable;
bool m_initialized;
QString m_initGL;
QString m_paintGL;
QString m_cleanupGL;
bool m_before;
PyGLRenderer *m_renderer;
};
#endif /* PYOTHERSIDE_PYGLAREA_H */

119
src/pyglrenderer.cpp Normal file
View File

@ -0,0 +1,119 @@
#include "qpython_priv.h"
#include "pyglrenderer.h"
PyGLRenderer::PyGLRenderer()
: m_paintGLCallable(0)
, m_initialized(false)
, m_initGL("")
, m_paintGL("")
{
}
PyGLRenderer::~PyGLRenderer()
{
if (m_paintGLCallable) {
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
Py_DECREF(m_paintGLCallable);
priv->leave();
m_paintGLCallable = 0;
}
}
void PyGLRenderer::setInitGL(QString initGL)
{
if (initGL == m_initGL)
return;
m_initGL = initGL;
m_initialized = false;
}
void PyGLRenderer::setPaintGL(QString paintGL)
{
if (paintGL == m_paintGL)
return;
m_paintGL = paintGL;
if (m_paintGLCallable) {
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
Py_DECREF(m_paintGLCallable);
priv->leave();
}
}
void PyGLRenderer::setCleanupGL(QString cleanupGL)
{
if (cleanupGL == m_cleanupGL)
return;
m_cleanupGL = cleanupGL;
}
void PyGLRenderer::setRect(QRect rect) {
m_rect = rect;
}
void PyGLRenderer::init() {
if (!m_initialized) {
if (!m_initGL.isEmpty()) {
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
PyObject *initGLCallable = priv->eval(m_initGL);
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(initGLCallable, args, NULL);
if (o) Py_DECREF(o);
Py_DECREF(args);
Py_DECREF(initGLCallable);
priv->leave();
}
m_initialized = true;
}
}
void PyGLRenderer::paint()
{
if (!m_initialized || m_paintGL.isEmpty())
return;
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
if (!m_paintGLCallable)
m_paintGLCallable = priv->eval(m_paintGL);
// Call the paintGL callback with arguments x, y, width, height.
// These are the boundaries in which the callback should render,
// though it may choose to ignore them and simply paint anywhere over
// (or below) the QML scene.
// (x, y) is the bottom left corner.
// (x + width, y + height) is the top right corner.
PyObject *x = PyLong_FromLong(m_rect.x());
PyObject *y = PyLong_FromLong(m_rect.y());
PyObject *width = PyLong_FromLong(m_rect.width());
PyObject *height = PyLong_FromLong(m_rect.height());
PyObject *args = PyTuple_Pack(4, x, y, width, height);
PyObject *o = PyObject_Call(m_paintGLCallable, args, NULL);
if (o) Py_DECREF(o);
Py_DECREF(x);
Py_DECREF(y);
Py_DECREF(width);
Py_DECREF(height);
Py_DECREF(args);
priv->leave();
}
void PyGLRenderer::cleanup()
{
if (!m_cleanupGL.isEmpty()) {
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
PyObject *cleanupGLCallable = priv->eval(m_cleanupGL);
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(cleanupGLCallable, args, NULL);
if (o) Py_DECREF(o);
m_initialized = true;
Py_DECREF(args);
Py_DECREF(cleanupGLCallable);
priv->leave();
}
}

54
src/pyglrenderer.h Normal file
View File

@ -0,0 +1,54 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Dennis Tomas <den.t@gmx.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/
#ifndef PYOTHERSIDE_PYGLRENDERER_H
#define PYOTHERSIDE_PYGLRENDERER_H
#include "Python.h"
#include <QtCore/QObject>
#include <QString>
#include <QRect>
class PyGLRenderer : public QObject {
Q_OBJECT
public:
PyGLRenderer();
~PyGLRenderer();
void init();
void paint();
void cleanup();
void setInitGL(QString initGL);
void setPaintGL(QString paintGL);
void setCleanupGL(QString cleanupGL);
void setRect(QRect rect);
private:
QRect m_rect;
PyObject *m_paintGLCallable;
bool m_initialized;
QString m_initGL;
QString m_paintGL;
QString m_cleanupGL;
};
#endif /* PYOTHERSIDE_PYGLRENDERER_H */

View File

@ -18,6 +18,7 @@
#include "qpython_priv.h"
#include "qpython.h"
#include "pyglarea.h"
#include "qpython_imageprovider.h"
#include "global_libpython_loader.h"
#include "pythonlib_loader.h"
@ -66,4 +67,5 @@ PyOtherSideExtensionPlugin::registerTypes(const char *uri)
qmlRegisterType<QPython12>(uri, 1, 2, PYOTHERSIDE_QPYTHON_NAME);
qmlRegisterType<QPython13>(uri, 1, 3, PYOTHERSIDE_QPYTHON_NAME);
qmlRegisterType<QPython14>(uri, 1, 4, PYOTHERSIDE_QPYTHON_NAME);
qmlRegisterType<PyGLArea>(uri, 1, 5, PYOTHERSIDE_QPYGLAREA_NAME);
}

View File

@ -25,6 +25,7 @@
#define PYOTHERSIDE_PLUGIN_ID "io.thp.pyotherside"
#define PYOTHERSIDE_IMAGEPROVIDER_ID "python"
#define PYOTHERSIDE_QPYTHON_NAME "Python"
#define PYOTHERSIDE_QPYGLAREA_NAME "PyGLArea"
class Q_DECL_EXPORT PyOtherSideExtensionPlugin : public QQmlExtensionPlugin {
Q_OBJECT

View File

@ -28,6 +28,10 @@ HEADERS += pyotherside_plugin.h
SOURCES += qpython_imageprovider.cpp
HEADERS += qpython_imageprovider.h
# PyGLArea
SOURCES += pyglarea.cpp pyglrenderer.cpp
HEADERS += pyglarea.h pyglrenderer.h
# Importer from Qt Resources
RESOURCES += qrc_importer.qrc