mirror of
https://github.com/thp/pyotherside.git
synced 2025-01-28 23:52:55 +08:00
Improved PyGLArea documentation, added example.
This commit is contained in:
parent
eef5266555
commit
873d168444
198
docs/index.rst
198
docs/index.rst
@ -525,6 +525,40 @@ deleted (there's no way for PyOtherSide to prevent referenced QObjects from
|
|||||||
being deleted, but PyOtherSide tries hard to detect the deletion of objects
|
being deleted, but PyOtherSide tries hard to detect the deletion of objects
|
||||||
and give meaningful error messages in case the reference is accessed).
|
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.render(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.
|
||||||
|
|
||||||
|
``(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.cleanup()
|
||||||
|
|
||||||
|
Free any resources allocated by :func:`IRenderer.init`.
|
||||||
|
This method is optional.
|
||||||
|
|
||||||
|
|
||||||
Cookbook
|
Cookbook
|
||||||
========
|
========
|
||||||
@ -864,17 +898,94 @@ This module can now be imported in QML and used as ``source`` in the QML
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGL rendering in Python
|
Rendering with PyOpenGL
|
||||||
==========================
|
-----------------------
|
||||||
|
|
||||||
You can render directly to Qt's OpenGL context in your Python code (i.e. via
|
The example below shows how to do raw OpenGL rendering in PyOpenGL using a
|
||||||
PyOpenGL or vispy.gloo) using a ``PyGLArea`` item and supplying a ``renderer``
|
PyGLArea. It has been adapted from the tutorial in the Qt documentation at
|
||||||
which must provide a ``render()`` method and may optionally provide ``init()``
|
http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html.
|
||||||
and ``cleanup()`` methods.
|
|
||||||
The ``before`` property controls whether to render before or after the QML
|
|
||||||
scene is rendered.
|
|
||||||
|
|
||||||
**rendergl.qml**
|
**pyglarea.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 render(self, x, y, width, height):
|
||||||
|
glMatrixMode(GL_PROJECTION)
|
||||||
|
glLoadIdentity()
|
||||||
|
glViewport(x, y, width, height)
|
||||||
|
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
|
.. code-block:: javascript
|
||||||
|
|
||||||
@ -882,52 +993,65 @@ scene is rendered.
|
|||||||
import io.thp.pyotherside 1.3
|
import io.thp.pyotherside 1.3
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 800
|
width: 320
|
||||||
height: 600
|
height: 480
|
||||||
|
|
||||||
PyGLArea {
|
PyGLArea {
|
||||||
id: glArea
|
id: glArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
before: true
|
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 {
|
Text {
|
||||||
x: 100
|
id: label
|
||||||
y: 100
|
color: "black"
|
||||||
text: "Hello World!"
|
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 {
|
Python {
|
||||||
|
id: py
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
importModule('myrenderer', function () {
|
addImportPath(Qt.resolvedUrl('.'));
|
||||||
call('myrenderer.Renderer', [], function (renderer) {
|
importModule('pyglarea', function () {
|
||||||
|
call('pyglarea.Renderer', [], function (renderer) {
|
||||||
glArea.renderer = renderer;
|
glArea.renderer = renderer;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onError: console.log(traceback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**myrenderer.py**
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Renderer(object):
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
"""Initialize OpenGL stuff like textures, FBOs etc..."""
|
|
||||||
|
|
||||||
def render(self, 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 cleanup(self):
|
|
||||||
"""Clean up OpenGL stuff initialized in initGL()."""
|
|
||||||
|
|
||||||
Building PyOtherSide
|
Building PyOtherSide
|
||||||
====================
|
====================
|
||||||
|
75
examples/pyglarea.py
Normal file
75
examples/pyglarea.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 render(self, x, y, width, height):
|
||||||
|
glMatrixMode(GL_PROJECTION)
|
||||||
|
glLoadIdentity()
|
||||||
|
glViewport(x, y, width, height)
|
||||||
|
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])
|
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('pyglarea', function () {
|
||||||
|
call('pyglarea.Renderer', [], function (renderer) {
|
||||||
|
glArea.renderer = renderer;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onError: console.log(traceback);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user