mirror of
https://github.com/thp/pyotherside.git
synced 2025-01-28 23:52:55 +08:00
commit
3ce90c46ec
193
docs/index.rst
193
docs/index.rst
@ -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
160
examples/pyfbo.qml
Normal 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
62
examples/pyglarea.qml
Normal 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
75
examples/renderer.py
Normal 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
101
src/pyfbo.cpp
Normal 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
41
src/pyfbo.h
Normal 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
131
src/pyglarea.cpp
Normal 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
65
src/pyglarea.h
Normal 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
152
src/pyglrenderer.cpp
Normal 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
49
src/pyglrenderer.h
Normal 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 */
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user