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

Unified renderer interface for PyFBO and PyGLArea.

This commit is contained in:
Dennis Tomas 2014-10-20 08:16:54 +02:00
parent 092370631a
commit 77ecb76492
11 changed files with 156 additions and 274 deletions

View File

@ -544,16 +544,20 @@ following properties:
Initialize OpenGL resources required for rendering.
This method is optional.
.. function:: IRenderer.render(x, y, width, height)
.. function:: IRenderer.reshape(x, y, width, height)
Render within the given area.
It is the renderer's responsibility to confine itself to that area.
Also, it must unbind any used resources to leave the context in a clean
state.
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`.
@ -905,7 +909,7 @@ 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.
**pyglarea.py**
**renderer.py**
.. code-block:: python
@ -957,10 +961,10 @@ http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html.
self.vertices_attr = glGetAttribLocation(self.program, b'vertices')
self.t_attr = glGetUniformLocation(self.program, b't')
def render(self, x, y, width, height):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def reshape(self, x, y, width, height):
glViewport(x, y, width, height)
def render(self):
glUseProgram(self.program)
try:
glDisable(GL_DEPTH_TEST)
@ -1041,8 +1045,8 @@ http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html.
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('pyglarea', function () {
call('pyglarea.Renderer', [], function (renderer) {
importModule('renderer', function () {
call('renderer', [], function (renderer) {
glArea.renderer = renderer;
});
});

View File

@ -1,74 +0,0 @@
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 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(self.vertices_attr)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glUseProgram(0)
def cleanup(self):
glDeleteProgram(self.program)
glDeleteBuffers(1, [self.vertexbuffer])

View File

@ -147,8 +147,8 @@ Item {
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('pyfbo', function () {
call('pyfbo.Renderer', [], function (renderer) {
importModule('renderer', function () {
call('renderer.Renderer', [], function (renderer) {
fbo.renderer = renderer;
});
});

View File

@ -50,8 +50,8 @@ Item {
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('pyglarea', function () {
call('pyglarea.Renderer', [], function (renderer) {
importModule('renderer', function () {
call('renderer.Renderer', [], function (renderer) {
glArea.renderer = renderer;
});
});

View File

@ -46,10 +46,10 @@ class Renderer(object):
self.vertices_attr = glGetAttribLocation(self.program, b'vertices')
self.t_attr = glGetUniformLocation(self.program, b't')
def render(self, x, y, width, height):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def reshape(self, x, y, width, height):
glViewport(x, y, width, height)
def render(self):
glUseProgram(self.program)
try:
glDisable(GL_DEPTH_TEST)

View File

@ -19,6 +19,7 @@
#include "pyfbo.h"
#include <QtGui/QOpenGLFramebufferObject>
#include <QSize>
class PyFboRenderer : public QQuickFramebufferObject::Renderer
@ -26,6 +27,7 @@ class PyFboRenderer : public QQuickFramebufferObject::Renderer
public:
PyFboRenderer()
: m_renderer(0)
, m_size(0, 0)
{
}
@ -39,8 +41,7 @@ public:
void render()
{
if (!m_renderer)
return;
if (m_renderer)
m_renderer->render();
}
@ -48,24 +49,33 @@ public:
{
PyFbo *pyFbo = static_cast<PyFbo *>(item);
if (pyFbo->renderer() == m_rendererRef)
return;
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, false);
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);
@ -73,12 +83,10 @@ public:
private:
QVariant m_rendererRef;
PyGLRenderer *m_renderer;
QSize m_size;
bool m_sizeChanged;
};
PyFbo::PyFbo()
{
}
void PyFbo::setRenderer(QVariant rendererRef)
{
if (rendererRef == m_rendererRef)

View File

@ -30,7 +30,6 @@ class PyFbo : public QQuickFramebufferObject
Q_PROPERTY(QVariant renderer READ renderer WRITE setRenderer)
public:
PyFbo();
Renderer *createRenderer() const;
QVariant renderer() const { return m_rendererRef; };

View File

@ -115,10 +115,10 @@ void PyGLArea::render()
if (!m_renderer)
return;
QPointF pos = mapToScene(QPointF(.0, .0));
m_renderer->setRect(
m_renderer->reshape(
QRect(
(long)pos.x(), (long)(window()->height() - this->height() - pos.y()),
(long)this->width(), (long)this->height()
this->width(), this->height()
)
);
m_renderer->render();

View File

@ -25,180 +25,129 @@
#include <QMetaType>
PyGLRenderer::PyGLRenderer(QVariant pyRenderer, bool useRect)
PyGLRenderer::PyGLRenderer(QVariant pyRenderer)
: m_pyRendererObject(0)
, m_initMethod(0)
, m_reshapeMethod(0)
, m_renderMethod(0)
, m_cleanupMethod(0)
, m_initialized(false)
{
m_pyRenderer = pyRenderer;
m_useRect = useRect;
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()
{
if (m_pyRendererObject) {
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
ENSURE_GIL_STATE;
Py_CLEAR(m_initMethod);
Py_CLEAR(m_reshapeMethod);
Py_CLEAR(m_renderMethod);
Py_CLEAR(m_cleanupMethod);
Py_CLEAR(m_pyRendererObject);
if (m_renderMethod)
Py_DECREF(m_renderMethod);
priv->leave();
m_pyRendererObject = 0;
m_renderMethod = 0;
}
}
void PyGLRenderer::setRect(QRect rect) {
m_rect = rect;
}
void PyGLRenderer::init() {
if (m_initialized)
if (m_initialized || !m_initMethod)
return;
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
PyObject *pyRendererObject = getPyRendererObject();
if (!pyRendererObject || pyRendererObject == Py_None) {
priv->leave();
return;
}
if (!PyObject_HasAttrString(m_pyRendererObject, "init")) {
// Optional init() method not found, consider the renderer initialized.
priv->leave();
m_initialized = true;
return;
}
PyObject *initMethod = PyObject_GetAttrString(m_pyRendererObject, "init");
if (!initMethod) {
qWarning() << "Failed to get init method of renderer.";
PyErr_PrintEx(0);
priv->leave();
return;
}
ENSURE_GIL_STATE;
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(initMethod, args, NULL);
PyObject *o = PyObject_Call(m_initMethod, args, NULL);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
Py_DECREF(args);
Py_DECREF(initMethod);
priv->leave();
m_initialized = true;
}
void PyGLRenderer::render()
void PyGLRenderer::reshape(QRect geometry)
{
if (!m_initialized)
if (!m_initialized || !m_reshapeMethod)
return;
QPythonPriv *priv = QPythonPriv::instance();
priv->enter();
PyObject *renderMethod = getRenderMethod();
if (!renderMethod) {
qWarning() << "Failed to get render method of renderer.";
PyErr_PrintEx(0);
priv->leave();
return;
}
PyObject *o = NULL;
if (m_useRect) {
// Call the paintGL callback with arguments x, y, width, height.
// 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 *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);
o = PyObject_Call(renderMethod, args, NULL);
Py_DECREF(x);
Py_DECREF(y);
Py_DECREF(width);
Py_DECREF(height);
PyObject *args = Py_BuildValue(
"llll", geometry.x(), geometry.y(), geometry.width(), geometry.height()
);
PyObject *o = PyObject_Call(m_reshapeMethod, args, NULL);
Py_DECREF(args);
} else {
PyObject *args = PyTuple_New(0);
o = PyObject_Call(renderMethod, args, NULL);
Py_DECREF(args);
}
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
priv->leave();
}
void PyGLRenderer::cleanup()
void PyGLRenderer::render()
{
if (!m_initialized)
if (!m_initialized || !m_renderMethod)
return;
ENSURE_GIL_STATE;
PyObject *pyRendererObject = getPyRendererObject();
if (!pyRendererObject || pyRendererObject == Py_None ||
!PyObject_HasAttrString(m_pyRendererObject, "cleanup")) {
priv->leave();
return;
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);
}
PyObject *cleanupMethod = PyObject_GetAttrString(m_pyRendererObject, "cleanup");
if (!cleanupMethod) {
qWarning() << "Failed to get cleanup method of renderer.";
PyErr_PrintEx(0);
priv->leave();
void PyGLRenderer::cleanup()
{
if (!m_initialized || !m_cleanupMethod)
return;
}
ENSURE_GIL_STATE;
PyObject *args = PyTuple_New(0);
PyObject *o = PyObject_Call(cleanupMethod, args, NULL);
PyObject *o = PyObject_Call(m_cleanupMethod, args, NULL);
if (o) Py_DECREF(o); else PyErr_PrintEx(0);
m_initialized = false;
Py_DECREF(args);
Py_DECREF(cleanupMethod);
priv->leave();
}
PyObject *PyGLRenderer::getPyRendererObject() {
if (m_pyRendererObject) {
return m_pyRendererObject;
}
if (m_pyRenderer.userType() != qMetaTypeId<PyObjectRef>()) {
qWarning() << "Renderer must be of type PyObjectRef (got "
<< m_pyRenderer << ").";
return NULL;
}
m_pyRendererObject = m_pyRenderer.value<PyObjectRef>().newRef();
return m_pyRendererObject;
}
PyObject *PyGLRenderer::getRenderMethod() {
if (m_renderMethod) {
return m_renderMethod;
}
PyObject *pyRendererObject = getPyRendererObject();
if (!pyRendererObject || pyRendererObject == Py_None) {
return NULL;
}
if (!PyObject_HasAttrString(m_pyRendererObject, "render")) {
qWarning() << "Renderer has no render method.";
return NULL;
}
PyObject *m_renderMethod = PyObject_GetAttrString(m_pyRendererObject, "render");
if (!m_renderMethod) {
qWarning() << "Failed to get render method of renderer.";
PyErr_PrintEx(0);
return NULL;
}
return m_renderMethod;
}

View File

@ -29,25 +29,21 @@
class PyGLRenderer {
public:
PyGLRenderer(QVariant pyRenderer, bool useRect=true);
PyGLRenderer(QVariant pyRenderer);
~PyGLRenderer();
void init();
void reshape(QRect geometry);
void render();
void cleanup();
void setRect(QRect rect);
private:
QRect m_rect;
QVariant m_pyRenderer;
PyObject *m_pyRendererObject;
PyObject *m_initMethod;
PyObject *m_reshapeMethod;
PyObject *m_renderMethod;
PyObject *m_cleanupMethod;
bool m_initialized;
bool m_useRect;
PyObject *getPyRendererObject();
PyObject *getRenderMethod();
};
#endif /* PYOTHERSIDE_PYGLRENDERER_H */

View File

@ -32,7 +32,7 @@ HEADERS += qpython_imageprovider.h
SOURCES += pyglarea.cpp pyglrenderer.cpp
HEADERS += pyglarea.h pyglrenderer.h
# PyGLFbo
# PyFBO
SOURCES += pyfbo.cpp
HEADERS += pyfbo.h