1
0
mirror of https://github.com/thp/pyotherside.git synced 2025-01-28 23:52:55 +08:00

Merge pull request #39 from dtomas/pyopengl

PyOpenGL in PyOtherSide
This commit is contained in:
Thomas Perl 2015-02-21 19:54:41 +01:00
commit 3ce90c46ec
15 changed files with 1050 additions and 0 deletions

View File

@ -525,6 +525,44 @@ deleted (there's no way for PyOtherSide to prevent referenced QObjects from
being deleted, but PyOtherSide tries hard to detect the deletion of objects
and give meaningful error messages in case the reference is accessed).
OpenGL rendering in Python
==========================
You can render directly to a QML application's OpenGL context in your Python
code (i.e. via PyOpenGL or vispy.gloo) by using a ``PyGLArea`` item with the
following properties:
**before**
Whether to draw the ``PyGLArea`` before (under) or after (over) the scene.
Defaults to ``false``.
**renderer**
Reference to a python object providing the ``IRenderer`` interface:
.. function:: IRenderer.init()
Initialize OpenGL resources required for rendering.
This method is optional.
.. function:: IRenderer.reshape(x, y, width, height)
Called when the geometry has changed.
``(x, y)`` is the position of the bottom left corner of the area, in
window coordinates, e.g. (0, 0) is the bottom left corner of the window.
.. function:: IRenderer.render()
Render to the OpenGL context.
It is the renderer's responsibility to unbind any used resources to leave
the context in a clean state.
.. function:: IRenderer.cleanup()
Free any resources allocated by :func:`IRenderer.init`.
This method is optional.
Cookbook
========
@ -864,6 +902,161 @@ This module can now be imported in QML and used as ``source`` in the QML
}
}
Rendering with PyOpenGL
-----------------------
The example below shows how to do raw OpenGL rendering in PyOpenGL using a
PyGLArea. It has been adapted from the tutorial in the Qt documentation at
http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html.
**renderer.py**
.. code-block:: python
import numpy
from OpenGL.GL import *
from OpenGL.GL.shaders import compileShader, compileProgram
VERTEX_SHADER = """#version 130
attribute highp vec4 vertices;
varying highp vec2 coords;
void main() {
gl_Position = vertices;
coords = vertices.xy;
}
"""
FRAGMENT_SHADER = """#version 130
uniform lowp float t;
varying highp vec2 coords;
void main() {
lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));
i = smoothstep(t - 0.8, t + 0.8, i);
i = floor(i * 20.) / 20.;
gl_FragColor = vec4(coords * .5 + .5, i, i);
}
"""
class Renderer(object):
def __init__(self):
self.t = 0.0
self.values = numpy.array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0
], dtype=numpy.float32)
def set_t(self, t):
self.t = t
def init(self):
self.vertexbuffer = glGenBuffers(1)
vertex_shader = compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragment_shader = compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
self.program = compileProgram(vertex_shader, fragment_shader)
self.vertices_attr = glGetAttribLocation(self.program, b'vertices')
self.t_attr = glGetUniformLocation(self.program, b't')
def reshape(self, x, y, width, height):
glViewport(x, y, width, height)
def render(self):
glUseProgram(self.program)
try:
glDisable(GL_DEPTH_TEST)
glClearColor(0, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE)
glBindBuffer(GL_ARRAY_BUFFER, self.vertexbuffer)
glEnableVertexAttribArray(self.vertices_attr)
glBufferData(GL_ARRAY_BUFFER, self.values, GL_STATIC_DRAW)
glVertexAttribPointer(self.vertices_attr, 2, GL_FLOAT, GL_FALSE, 0, None)
glUniform1f(self.t_attr, self.t)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
finally:
glDisableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glUseProgram(0)
def cleanup(self):
glDeleteProgram(self.program)
glDeleteBuffers(1, [self.vertexbuffer])
**pyglarea.qml**
.. code-block:: javascript
import QtQuick 2.0
import io.thp.pyotherside 1.3
Item {
width: 320
height: 480
PyGLArea {
id: glArea
anchors.fill: parent
before: true
property var t: 0
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
onTChanged: {
if (renderer) {
py.call(py.getattr(renderer, 'set_t'), [t], update);
}
}
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
radius: 10
border.width: 1
border.color: "white"
anchors.fill: label
anchors.margins: -10
}
Text {
id: label
color: "black"
wrapMode: Text.WordWrap
text: "The background here is a squircle rendered with raw OpenGL using a PyGLArea. This text label and its border is rendered using QML"
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 20
}
Python {
id: py
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('renderer', function () {
call('renderer', [], function (renderer) {
glArea.renderer = renderer;
});
});
}
onError: console.log(traceback);
}
}
Building PyOtherSide
====================

160
examples/pyfbo.qml Normal file
View File

@ -0,0 +1,160 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.0
import io.thp.pyotherside 1.3
Item {
width: 400
height: 400
// The checkers background
ShaderEffect {
id: tileBackground
anchors.fill: parent
property real tileSize: 16
property color color1: Qt.rgba(0.9, 0.9, 0.9, 1);
property color color2: Qt.rgba(0.85, 0.85, 0.85, 1);
property size pixelSize: Qt.size(width / tileSize, height / tileSize);
fragmentShader:
"
uniform lowp vec4 color1;
uniform lowp vec4 color2;
uniform highp vec2 pixelSize;
varying highp vec2 qt_TexCoord0;
void main() {
highp vec2 tc = sign(sin(3.14152 * qt_TexCoord0 * pixelSize));
if (tc.x != tc.y)
gl_FragColor = color1;
else
gl_FragColor = color2;
}
"
}
PyFBO {
id: fbo
anchors.fill: parent
anchors.margins: 10
property var t: 0
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
// The transform is just to show something interesting..
transform: [
Rotation { id: rotation; axis.x: 0; axis.z: 0; axis.y: 1; angle: 0; origin.x: fbo.width / 2; origin.y: fbo.height / 2; },
Translate { id: txOut; x: -fbo.width / 2; y: -fbo.height / 2 },
Scale { id: scale; },
Translate { id: txIn; x: fbo.width / 2; y: fbo.height / 2 }
]
onTChanged: {
if (renderer) {
py.callMethod(renderer, 'set_t', [t], update);
}
}
}
// Just to show something interesting
SequentialAnimation {
PauseAnimation { duration: 5000 }
ParallelAnimation {
NumberAnimation { target: scale; property: "xScale"; to: 0.6; duration: 1000; easing.type: Easing.InOutBack }
NumberAnimation { target: scale; property: "yScale"; to: 0.6; duration: 1000; easing.type: Easing.InOutBack }
}
NumberAnimation { target: rotation; property: "angle"; to: 80; duration: 1000; easing.type: Easing.InOutCubic }
NumberAnimation { target: rotation; property: "angle"; to: -80; duration: 1000; easing.type: Easing.InOutCubic }
NumberAnimation { target: rotation; property: "angle"; to: 0; duration: 1000; easing.type: Easing.InOutCubic }
NumberAnimation { target: fbo; property: "opacity"; to: 0.5; duration: 1000; easing.type: Easing.InOutCubic }
PauseAnimation { duration: 1000 }
NumberAnimation { target: fbo; property: "opacity"; to: 0.8; duration: 1000; easing.type: Easing.InOutCubic }
ParallelAnimation {
NumberAnimation { target: scale; property: "xScale"; to: 1; duration: 1000; easing.type: Easing.InOutBack }
NumberAnimation { target: scale; property: "yScale"; to: 1; duration: 1000; easing.type: Easing.InOutBack }
}
running: true
loops: Animation.Infinite
}
Rectangle {
id: labelFrame
anchors.margins: -10
radius: 5
color: "white"
border.color: "black"
opacity: 0.8
anchors.fill: label
}
Text {
id: label
anchors.bottom: fbo.bottom
anchors.left: fbo.left
anchors.right: fbo.right
anchors.margins: 20
wrapMode: Text.WordWrap
text: "The squircle is an FBO, rendered by the application on the scene graph rendering thread. The FBO is managed and displayed using a PyFBO item."
}
Python {
id: py
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('renderer', function () {
call('renderer.Renderer', [], function (renderer) {
fbo.renderer = renderer;
});
});
}
onError: console.log(traceback);
}
}

62
examples/pyglarea.qml Normal file
View File

@ -0,0 +1,62 @@
import QtQuick 2.0
import io.thp.pyotherside 1.3
Item {
width: 320
height: 480
PyGLArea {
id: glArea
anchors.fill: parent
before: true
property var t: 0
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
onTChanged: {
if (renderer) {
py.callMethod(renderer, 'set_t', [t], update);
}
}
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
radius: 10
border.width: 1
border.color: "white"
anchors.fill: label
anchors.margins: -10
}
Text {
id: label
color: "black"
wrapMode: Text.WordWrap
text: "The background here is a squircle rendered with raw OpenGL using a PyGLArea. This text label and its border is rendered using QML"
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 20
}
Python {
id: py
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('renderer', function () {
call('renderer.Renderer', [], function (renderer) {
glArea.renderer = renderer;
});
});
}
onError: console.log(traceback);
}
}

75
examples/renderer.py Normal file
View File

@ -0,0 +1,75 @@
import numpy
from OpenGL.GL import *
from OpenGL.GL.shaders import compileShader, compileProgram
VERTEX_SHADER = """#version 130
attribute highp vec4 vertices;
varying highp vec2 coords;
void main() {
gl_Position = vertices;
coords = vertices.xy;
}
"""
FRAGMENT_SHADER = """#version 130
uniform lowp float t;
varying highp vec2 coords;
void main() {
lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));
i = smoothstep(t - 0.8, t + 0.8, i);
i = floor(i * 20.) / 20.;
gl_FragColor = vec4(coords * .5 + .5, i, i);
}
"""
class Renderer(object):
def __init__(self):
self.t = 0.0
self.values = numpy.array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0
], dtype=numpy.float32)
def set_t(self, t):
self.t = t
def init(self):
self.vertexbuffer = glGenBuffers(1)
vertex_shader = compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragment_shader = compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
self.program = compileProgram(vertex_shader, fragment_shader)
self.vertices_attr = glGetAttribLocation(self.program, b'vertices')
self.t_attr = glGetUniformLocation(self.program, b't')
def reshape(self, x, y, width, height):
glViewport(x, y, width, height)
def render(self):
glUseProgram(self.program)
try:
glDisable(GL_DEPTH_TEST)
glClearColor(0, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE)
glBindBuffer(GL_ARRAY_BUFFER, self.vertexbuffer)
glEnableVertexAttribArray(self.vertices_attr)
glBufferData(GL_ARRAY_BUFFER, self.values, GL_STATIC_DRAW)
glVertexAttribPointer(self.vertices_attr, 2, GL_FLOAT, GL_FALSE, 0, None)
glUniform1f(self.t_attr, self.t)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
finally:
glDisableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glUseProgram(0)
def cleanup(self):
glDeleteProgram(self.program)
glDeleteBuffers(1, [self.vertexbuffer])

101
src/pyfbo.cpp Normal file
View File

@ -0,0 +1,101 @@
/**
* 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 "pyfbo.h"
#include <QtGui/QOpenGLFramebufferObject>
#include <QSize>
class PyFboRenderer : public QQuickFramebufferObject::Renderer
{
public:
PyFboRenderer()
: m_renderer(0)
, m_size(0, 0)
{
}
~PyFboRenderer()
{
if (m_renderer) {
delete m_renderer;
m_renderer = 0;
}
}
void render()
{
if (m_renderer)
m_renderer->render();
}
void synchronize(QQuickFramebufferObject *item)
{
PyFbo *pyFbo = static_cast<PyFbo *>(item);
if (pyFbo->renderer() != m_rendererRef) {
// The renderer has changed.
if (m_renderer) {
m_renderer->cleanup();
delete m_renderer;
m_renderer = 0;
}
m_rendererRef = pyFbo->renderer();
if (!m_rendererRef.isNull()) {
m_renderer = new PyGLRenderer(m_rendererRef);
m_renderer->init();
m_sizeChanged = true;
}
}
if (m_renderer && m_sizeChanged) {
// The size has changed.
m_renderer->reshape(QRect(QPoint(0, 0), m_size));
m_sizeChanged = false;
update();
}
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size)
{
m_size = size;
m_sizeChanged = true;
QOpenGLFramebufferObjectFormat format;
// TODO: get the FBO format from the PyGLRenderer.
return new QOpenGLFramebufferObject(size, format);
}
private:
QVariant m_rendererRef;
PyGLRenderer *m_renderer;
QSize m_size;
bool m_sizeChanged;
};
void PyFbo::setRenderer(QVariant rendererRef)
{
if (rendererRef == m_rendererRef)
return;
m_rendererRef = rendererRef;
update();
}
QQuickFramebufferObject::Renderer *PyFbo::createRenderer() const
{
return new PyFboRenderer();
}

41
src/pyfbo.h Normal file
View File

@ -0,0 +1,41 @@
/**
* 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 PYFBO_H
#define PYFBO_H
#include "pyglrenderer.h"
#include <QVariant>
#include <QtQuick/QQuickFramebufferObject>
class PyFbo : public QQuickFramebufferObject
{
Q_OBJECT
Q_PROPERTY(QVariant renderer READ renderer WRITE setRenderer)
public:
Renderer *createRenderer() const;
QVariant renderer() const { return m_rendererRef; };
void setRenderer(QVariant rendererRef);
private:
QVariant m_rendererRef;
};
#endif

131
src/pyglarea.cpp Normal file
View File

@ -0,0 +1,131 @@
/**
* 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_before(false)
, m_renderer(0)
, m_rendererChanged(false)
, m_beforeChanged(true)
{
connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(handleWindowChanged(QQuickWindow*)));
}
PyGLArea::~PyGLArea()
{
if (m_renderer) {
delete m_renderer;
m_renderer = 0;
}
}
void PyGLArea::setRenderer(QVariant renderer)
{
if (renderer == m_pyRenderer)
return;
m_pyRenderer = renderer;
// Defer creating the PyGLRenderer until sync() is called,
// when we have an OpenGL context.
m_rendererChanged = true;
update();
}
void PyGLArea::setBefore(bool before)
{
if (before == m_before)
return;
m_before = before;
m_beforeChanged = true;
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);
}
}
void PyGLArea::update() {
if (window())
window()->update();
}
void PyGLArea::sync()
{
if (m_beforeChanged) {
disconnect(window(), SIGNAL(beforeRendering()), this, SLOT(render()));
disconnect(window(), SIGNAL(afterRendering()), this, SLOT(render()));
if (m_before) {
// If we allow QML to do the clearing, they would clear what we paint
// and nothing would show.
window()->setClearBeforeRendering(false);
connect(window(), SIGNAL(beforeRendering()), this, SLOT(render()), Qt::DirectConnection);
} else {
window()->setClearBeforeRendering(true);
connect(window(), SIGNAL(afterRendering()), this, SLOT(render()), Qt::DirectConnection);
}
m_beforeChanged = false;
}
if (m_rendererChanged) {
if (m_renderer) {
m_renderer->cleanup();
delete m_renderer;
m_renderer = 0;
}
if (!m_pyRenderer.isNull()) {
m_renderer = new PyGLRenderer(m_pyRenderer);
m_renderer->init();
window()->resetOpenGLState();
}
m_rendererChanged = false;
}
}
void PyGLArea::render()
{
if (!m_renderer)
return;
QPointF pos = mapToScene(QPointF(.0, .0));
m_renderer->reshape(
QRect(
(long)pos.x(), (long)(window()->height() - this->height() - pos.y()),
this->width(), this->height()
)
);
m_renderer->render();
window()->resetOpenGLState();
}
void PyGLArea::cleanup()
{
if (m_renderer) m_renderer->cleanup();
}

65
src/pyglarea.h Normal file
View File

@ -0,0 +1,65 @@
/**
* 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 <QVariant>
#include <QtQuick/QQuickItem>
#include "pyobject_ref.h"
#include "pyglrenderer.h"
class PyGLArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QVariant renderer READ renderer WRITE setRenderer)
Q_PROPERTY(bool before READ before WRITE setBefore)
public:
PyGLArea();
~PyGLArea();
QVariant renderer() const { return m_pyRenderer; };
bool before() { return m_before; };
void setRenderer(QVariant renderer);
void setBefore(bool before);
public slots:
void sync();
void update();
private slots:
void handleWindowChanged(QQuickWindow *win);
void render();
void cleanup();
private:
QVariant m_pyRenderer;
bool m_before;
PyGLRenderer *m_renderer;
bool m_rendererChanged;
bool m_beforeChanged;
};
#endif /* PYOTHERSIDE_PYGLAREA_H */

152
src/pyglrenderer.cpp Normal file
View File

@ -0,0 +1,152 @@
/**
* 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 "converter.h"
#include "pyobject_ref.h"
#include "pyglrenderer.h"
#include "ensure_gil_state.h"
#include <QDebug>
#include <QMetaType>
PyGLRenderer::PyGLRenderer(QVariant pyRenderer)
: m_pyRendererObject(0)
, m_initMethod(0)
, m_reshapeMethod(0)
, m_renderMethod(0)
, m_cleanupMethod(0)
, m_initialized(false)
{
ENSURE_GIL_STATE;
if (pyRenderer.userType() != qMetaTypeId<PyObjectRef>()) {
qWarning() << "Renderer must be of type PyObjectRef (got "
<< pyRenderer << ").";
return;
}
m_pyRendererObject = pyRenderer.value<PyObjectRef>().newRef();
if (PyObject_HasAttrString(m_pyRendererObject, "render")) {
m_renderMethod = PyObject_GetAttrString(m_pyRendererObject, "render");
if (!m_renderMethod) {
qWarning() << "Error getting render method of renderer.";
PyErr_PrintEx(0);
}
} else {
qWarning() << "Renderer has no render method.";
}
if (PyObject_HasAttrString(m_pyRendererObject, "init")) {
m_initMethod = PyObject_GetAttrString(m_pyRendererObject, "init");
if (!m_initMethod) {
qWarning() << "Error getting init method of renderer.";
PyErr_PrintEx(0);
}
}
if (PyObject_HasAttrString(m_pyRendererObject, "reshape")) {
m_reshapeMethod = PyObject_GetAttrString(m_pyRendererObject, "reshape");
if (!m_reshapeMethod) {
qWarning() << "Error getting reshape method of renderer.";
PyErr_PrintEx(0);
}
}
if (PyObject_HasAttrString(m_pyRendererObject, "cleanup")) {
m_cleanupMethod = PyObject_GetAttrString(m_pyRendererObject, "cleanup");
if (!m_cleanupMethod) {
qWarning() << "Error getting cleanup method of renderer.";
PyErr_PrintEx(0);
}
}
}
PyGLRenderer::~PyGLRenderer()
{
ENSURE_GIL_STATE;
Py_CLEAR(m_initMethod);
Py_CLEAR(m_reshapeMethod);
Py_CLEAR(m_renderMethod);
Py_CLEAR(m_cleanupMethod);
Py_CLEAR(m_pyRendererObject);
}
void PyGLRenderer::init() {
if (m_initialized || !m_initMethod)
return;
ENSURE_GIL_STATE;
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(m_initMethod, args, NULL);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
Py_DECREF(args);
m_initialized = true;
}
void PyGLRenderer::reshape(QRect geometry)
{
if (!m_initialized || !m_reshapeMethod)
return;
ENSURE_GIL_STATE;
// Call the reshape 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 *args = Py_BuildValue(
"llll", geometry.x(), geometry.y(), geometry.width(), geometry.height()
);
PyObject *o = PyObject_Call(m_reshapeMethod, args, NULL);
Py_DECREF(args);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
}
void PyGLRenderer::render()
{
if (!m_initialized || !m_renderMethod)
return;
ENSURE_GIL_STATE;
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(m_renderMethod, args, NULL);
Py_DECREF(args);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
}
void PyGLRenderer::cleanup()
{
if (!m_initialized || !m_cleanupMethod)
return;
ENSURE_GIL_STATE;
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(m_cleanupMethod, args, NULL);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
m_initialized = false;
Py_DECREF(args);
}

49
src/pyglrenderer.h Normal file
View File

@ -0,0 +1,49 @@
/**
* 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 <QVariant>
#include <QString>
#include <QRect>
class PyGLRenderer {
public:
PyGLRenderer(QVariant pyRenderer);
~PyGLRenderer();
void init();
void reshape(QRect geometry);
void render();
void cleanup();
private:
PyObject *m_pyRendererObject;
PyObject *m_initMethod;
PyObject *m_reshapeMethod;
PyObject *m_renderMethod;
PyObject *m_cleanupMethod;
bool m_initialized;
};
#endif /* PYOTHERSIDE_PYGLRENDERER_H */

View File

@ -67,6 +67,12 @@ PyObjectRef::operator=(const PyObjectRef &other)
return *this;
}
bool
PyObjectRef::operator==(const PyObjectRef &other)
{
return pyobject == other.pyobject;
}
PyObject *
PyObjectRef::newRef() const
{

View File

@ -30,6 +30,7 @@ class PyObjectRef {
PyObjectRef(const PyObjectRef &other);
virtual ~PyObjectRef();
PyObjectRef &operator=(const PyObjectRef &other);
bool operator==(const PyObjectRef &other);
PyObject *newRef() const;

View File

@ -18,6 +18,8 @@
#include "qpython_priv.h"
#include "qpython.h"
#include "pyglarea.h"
#include "pyfbo.h"
#include "qpython_imageprovider.h"
#include "global_libpython_loader.h"
#include "pythonlib_loader.h"
@ -66,4 +68,6 @@ 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);
qmlRegisterType<PyFbo>(uri, 1, 5, PYOTHERSIDE_PYFBO_NAME);
}

View File

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

View File

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