source: orange/orange/OrangeWidgets/plot/owopenglrenderer.py @ 8975:0a8877e6a08f

Revision 8975:0a8877e6a08f, 15.6 KB checked in by matejd <matejd@…>, 3 years ago (diff)

More docs + internal cleanup

Line 
1'''
2
3#################
4OpenGL Renderer (``owopenglrenderer``)
5#################
6
7.. autoclass:: VertexBuffer
8
9.. autoclass:: OWOpenGLRenderer
10
11'''
12
13from ctypes import c_void_p
14
15from PyQt4.QtCore import *
16from PyQt4.QtGui import *
17from PyQt4 import QtOpenGL
18
19import OpenGL
20OpenGL.ERROR_CHECKING = False
21OpenGL.ERROR_LOGGING = False
22OpenGL.FULL_LOGGING = False
23OpenGL.ERROR_ON_COPY = False
24from OpenGL.GL import *
25from OpenGL.GL.ARB.vertex_array_object import *
26from OpenGL.GL.ARB.vertex_buffer_object import *
27import numpy
28
29class VertexBuffer:
30    '''
31    A thin abstraction simplifying the usage of Vertex Buffer Objects (VBO). Warning: understanding what this code does
32    requires basic knowledge of OpenGL.
33
34    VBOs are necessary in OpenGL 2+ world, since immediate mode (glBegin/glVertex/glEnd paradigm) has been deprecated
35    and is slow (even more so using it through PyOpenGL). Vertex Array Objects (VAO) were introduced in OpenGL version 3.0; they
36    reduce the amount of function calls that need to be made by storing the set of bindings between vertex attributes
37    and vertex data. VAOs are used only if the underlying hardware supports them. This class provides a simple usage pattern
38    which is suitable for many applications::
39
40        data = numpy.array([1.3, 5.1, 42.,   0.,
41                            3.3, 4.3, 5.1,   1.], dtype=numpy.float32)
42        buffer = VertexBuffer(data, [(3, GL_FLOAT), (1, GL_FLOAT)])
43        # ... Later when drawing:
44        buffer.draw(GL_LINES)
45        # Possible setup in a vertex shader:
46        #
47        # attribute vec3 position;
48        # attribute float index;
49        # ...
50
51    What the example above does depends on the rest of the vertex shader (left to your imagination). VertexBuffer constructor
52    takes data and data format description as its parameters. Format specifies mapping between data values and vertex attributes.
53    See the constructor for more info.
54    '''
55
56    def __init__(self, data, format_description, usage=GL_STATIC_DRAW):
57        '''
58        Constructs VBO and prepares vertex attribute bindings. OpenGL context must be up already (initializeGL
59        has been called).
60
61        :param data: Data array (vertices) to be sent to the GPU.
62        :type numpy.array
63
64        :param format_description: Describes vertex attribute bindings. This parameter must be an iterable
65            of tuples. Each tuple specifies a generic vertex attribute (the order is important!) by specifying
66            the number of components (must be 1,2,3 or 4) and data type (e.g. GL_FLOAT, GL_INT). See the example
67            above. Normalization for fixed-point values is turned off.
68        :type an iterable of tuples
69
70        :param usage: Specifies the expected usage pattern. The symbolic constant must be GL_STREAM_DRAW,
71            GL_STREAM_READ, GL_STREAM_COPY, GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
72            GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, or GL_DYNAMIC_COPY. Default is GL_STATIC_DRAW.
73        :type GLenum
74        '''
75
76        self._format_description = format_description
77
78        if glGenVertexArrays:
79            self._vao = GLuint(42)
80            glGenVertexArrays(1, self._vao)
81            glBindVertexArray(self._vao)
82            vertex_buffer_id = glGenBuffers(1)
83            glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id)
84            glBufferData(GL_ARRAY_BUFFER, data, usage)
85
86            vertex_size = sum(attribute[0]*4 for attribute in format_description) # TODO: sizeof(type)
87            self._num_vertices = len(data) / (vertex_size / 4)
88            current_size = 0
89            for i, (num_components, type) in enumerate(format_description):
90                glVertexAttribPointer(i, num_components, type, GL_FALSE, vertex_size, c_void_p(current_size))
91                glEnableVertexAttribArray(i)
92                current_size += num_components*4
93
94            glBindVertexArray(0)
95            glBindBuffer(GL_ARRAY_BUFFER, 0)
96        else:
97            self._vbo_id = glGenBuffers(1)
98            glBindBuffer(GL_ARRAY_BUFFER, self._vbo_id)
99            glBufferData(GL_ARRAY_BUFFER, data, usage)
100            self._data_length = len(data)
101            glBindBuffer(GL_ARRAY_BUFFER, 0)
102
103    def __del__(self):
104        # TODO
105        pass
106
107    def draw(self, primitives=GL_TRIANGLES, first=0, count=-1):
108        '''
109        Renders primitives from data array. By default it renders triangles using all the data. Consult
110        OpenGL documentation (specifically ``glDrawArrays``) for detail info.
111
112        :param primitives: What kind of primitives to render. Symbolic constants GL_POINTS, GL_LINE_STRIP,
113            GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP,
114            GL_QUADS, and GL_POLYGON are accepted.
115        :type GLenum
116
117        :param first: Specifies the starting index into data.
118        :type int
119
120        :param count: The number of indices (not primitives) to be rendered.
121        :type int
122        '''
123        if hasattr(self, '_vao'):
124            glBindVertexArray(self._vao)
125            glDrawArrays(primitives, first,
126                count if count != -1 else self._num_vertices - first)
127            glBindVertexArray(0)
128        else:
129            glBindBuffer(GL_ARRAY_BUFFER, self._vbo_id)
130
131            vertex_size = sum(attribute[0]*4 for attribute in self._format_description)
132            current_size = 0
133            for i, (num_components, type) in enumerate(self._format_description):
134                glVertexAttribPointer(i, num_components, type, GL_FALSE, vertex_size, c_void_p(current_size))
135                glEnableVertexAttribArray(i)
136                current_size += num_components*4
137
138            glDrawArrays(primitives, first,
139                count if count != -1 else self._data_length / (vertex_size / 4) - first)
140
141            for i in range(len(self._format_description)):
142                glDisableVertexAttribArray(i)
143            glBindBuffer(GL_ARRAY_BUFFER, 0)
144
145class OWOpenGLRenderer:
146    '''
147    OpenGL 3 deprecated a lot of old (1.x) functions, particulary, it removed
148    immediate mode (glBegin, glEnd, glVertex paradigm). Vertex buffer objects and similar
149    (through glDrawArrays for example) should be used instead. This class simplifies
150    the usage of that functionality by providing methods which resemble immediate mode.
151
152    Example usage::
153
154        renderer = OWOpenGLRenderer()
155        # Setup vanilla transforms
156        projection = QMatrix4x4()
157        projection.perspective(45., 1., 1., 50.)
158        model = view = QMatrix4x4()
159        renderer.set_transform(model, view, projection)
160        renderer.draw_line(QVector3D(0, 0, 0), QVector3D(1, 1, 1), color=QColor(0, 0, 255))
161
162    Note that usually you don't have to create an instance of the renderer, since it's available in Plot3D: ``OWPlot3D.renderer``.
163    Also, current projection and modelview transforms can be accessed (``OWPlot3D.projection`` and ``OWPlot3D.modelview``) in
164    OWPlot3D callbacks.
165
166    **2D drawing**
167
168        The API of this class is general enough to support drawing 2D shapes. An example setup::
169
170            projection = QMatrix4x4()
171            # Use ortho projection, specify width and height of the window
172            projection.ortho(0, self.width(), self.height(), 0, -1, 1)
173            model = view = QMatrix4x4()
174            renderer.set_transform(model, view, projection)
175            # Red triangle. Z-value is not important.
176            renderer.draw_triangle(QVector3D(1, 2, 0),
177                                   QVector3D(3, 2, 0),
178                                   QVector3D(2, 3, 0),
179                                   color=QColor(255, 0, 0))
180    '''
181
182    def __init__(self):
183        self._projection = QMatrix4x4()
184        self._model = QMatrix4x4()
185        self._view = QMatrix4x4()
186
187        ## Shader used to draw primitives. Position and color of vertices specified through uniforms. Nothing fancy.
188        vertex_shader_source = '''
189            attribute float index;
190            varying vec4 color;
191
192            uniform vec3 positions[6]; // 6 vertices for quad
193            uniform vec4 colors[6];
194
195            uniform mat4 projection, model, view;
196
197            void main(void)
198            {
199                int i = int(index);
200                gl_Position = projection * view * model * vec4(positions[i], 1.);
201                color = colors[i];
202            }
203            '''
204
205        fragment_shader_source = '''
206            varying vec4 color;
207
208            void main(void)
209            {
210                gl_FragColor = color;
211            }
212            '''
213
214        self._shader = QtOpenGL.QGLShaderProgram()
215        self._shader.addShaderFromSourceCode(QtOpenGL.QGLShader.Vertex, vertex_shader_source)
216        self._shader.addShaderFromSourceCode(QtOpenGL.QGLShader.Fragment, fragment_shader_source)
217
218        self._shader.bindAttributeLocation('index', 0)
219
220        if not self._shader.link():
221            print('Failed to link dummy renderer shader!')
222
223        indices = numpy.array(range(6), dtype=numpy.float32) 
224        self._vertex_buffer = VertexBuffer(indices, [(1, GL_FLOAT)])
225
226    def set_transform(self, model, view=None, projection=None, viewport=None):
227        '''
228        Sets current projection, model, view and viewport transforms. ``model`` parameter
229        is most often changed. Passing None as a parameter value keeps the internal value
230        unmodified.
231
232        :param model: Model transform (usually translation, rotation, scale or a combination of these).
233        :type QMatrix4x4
234
235        :param view: View transform (camera transformation).
236        :type QMatrix4x4
237
238        :param projection: Projection transform (e.g. ortho, perspective).
239        :type QMatrix4x4
240
241        :param viewport: Viewport transform. A list of 4 ints specifying viewport rectangle (lower left corner + width and height).
242        :type list of 4 int
243        '''
244        if model != None:
245            self._model = model
246        if view != None:
247            self._view = view
248        if projection != None:
249            self._projection = projection
250        if viewport:
251            glViewport(*viewport)
252        self._shader.bind()
253        self._shader.setUniformValue('projection', self._projection)
254        self._shader.setUniformValue('model', self._model)
255        self._shader.setUniformValue('view', self._view)
256        self._shader.release()
257
258    def draw_line(self, position0, position1, color0=QColor(0, 0, 0), color1=QColor(0, 0 ,0), color=None):
259        '''
260        Draws a line using current transform.
261
262        :param position0: Beginning of line.
263        :type QVector3D
264
265        :param position1: End of line.
266        :type QVector3D
267
268        :param color0: Color of the beginning of the line.
269        :type QColor
270
271        :param color1: Color of the end of the line.
272        :type QColor
273
274        :param color: A convenience parameter. Overrides ``color0`` and ``color1``.
275        :type QColor
276        '''
277
278        if color:
279            colors = [color.redF(), color.greenF(), color.blueF(), color.alphaF()] * 2
280        else:
281            colors = [color0.redF(), color0.greenF(), color0.blueF(), color0.alphaF(),
282                      color1.redF(), color1.greenF(), color1.blueF(), color1.alphaF()]
283
284        positions = [position0.x(), position0.y(), position0.z(),
285                     position1.x(), position1.y(), position1.z()]
286
287        self._shader.bind()
288        glUniform4fv(glGetUniformLocation(self._shader.programId(), 'colors'), len(colors)/4, numpy.array(colors, numpy.float32))
289        glUniform3fv(glGetUniformLocation(self._shader.programId(), 'positions'), len(positions)/3, numpy.array(positions, numpy.float32))
290        self._vertex_buffer.draw(GL_LINES, 0, 2)
291        self._shader.release()
292
293    def draw_rectangle(self, position0, position1, position2, position3,
294                       color0=QColor(0, 0, 0), color1=QColor(0, 0, 0), color2=QColor(0, 0, 0), color3=QColor(0, 0, 0), color=None):
295        '''
296        Draws a rectangle using current transform. Vertices must specified in clockwise or counter-clockwise order (otherwise nothing
297        might be drawn).
298
299        :param position0: First vertex position.
300        :type QVector3D
301
302        :param position1: Second vertex position.
303        :type QVector3D
304
305        :param position2: Third vertex position.
306        :type QVector3D
307
308        :param position3: Fourth vertex position.
309        :type QVector3D
310
311        :param color0: First vertex color.
312        :type QColor
313
314        :param color1: Second vertex color.
315        :type QColor
316
317        :param color2: Third vertex color.
318        :type QColor
319
320        :param color3: Fourth vertex color.
321        :type QColor
322
323        :param color: A convenience parameter: Overrides color values.
324        :type QColor
325        '''
326        if color:
327            colors = [color.redF(), color.greenF(), color.blueF(), color.alphaF()] * 6
328        else:
329            colors = [color0.redF(), color0.greenF(), color0.blueF(), color0.alphaF(),
330                      color1.redF(), color1.greenF(), color1.blueF(), color1.alphaF(),
331                      color3.redF(), color3.greenF(), color3.blueF(), color3.alphaF(),
332
333                      color3.redF(), color3.greenF(), color3.blueF(), color3.alphaF(),
334                      color1.redF(), color1.greenF(), color1.blueF(), color1.alphaF(),
335                      color2.redF(), color2.greenF(), color2.blueF(), color2.alphaF()]
336
337        positions = [position0.x(), position0.y(), position0.z(),
338                     position1.x(), position1.y(), position1.z(),
339                     position3.x(), position3.y(), position3.z(),
340
341                     position3.x(), position3.y(), position3.z(),
342                     position1.x(), position1.y(), position1.z(),
343                     position2.x(), position2.y(), position2.z()]
344
345        self._shader.bind()
346        glUniform4fv(glGetUniformLocation(self._shader.programId(), 'colors'), len(colors)/4, numpy.array(colors, numpy.float32))
347        glUniform3fv(glGetUniformLocation(self._shader.programId(), 'positions'), len(positions)/3, numpy.array(positions, numpy.float32))
348        self._vertex_buffer.draw(GL_TRIANGLES, 0, 6)
349        self._shader.release()
350
351    def draw_triangle(self, position0, position1, position2,
352                       color0=QColor(0, 0, 0), color1=QColor(0, 0, 0), color2=QColor(0, 0, 0), color=None):
353        '''
354        Draws a triangle using current transform.
355
356        :param position0: First vertex position.
357        :type QVector3D
358
359        :param position1: Second vertex position.
360        :type QVector3D
361
362        :param position2: Third vertex position.
363        :type QVector3D
364
365        :param color0: First vertex color.
366        :type QColor
367
368        :param color1: Second vertex color.
369        :type QColor
370
371        :param color2: Third vertex color.
372        :type QColor
373
374        :param color: A convenience parameter: Overrides color values.
375        :type QColor
376        '''
377        if color:
378            colors = [color.redF(), color.greenF(), color.blueF(), color.alphaF()] * 3
379        else:
380            colors = [color0.redF(), color0.greenF(), color0.blueF(), color0.alphaF(),
381                      color1.redF(), color1.greenF(), color1.blueF(), color1.alphaF(),
382                      color2.redF(), color2.greenF(), color2.blueF(), color2.alphaF()]
383
384        positions = [position0.x(), position0.y(), position0.z(),
385                     position1.x(), position1.y(), position1.z(),
386                     position2.x(), position2.y(), position2.z()]
387
388        self._shader.bind()
389        glUniform4fv(glGetUniformLocation(self._shader.programId(), 'colors'), len(colors)/4, numpy.array(colors, numpy.float32))
390        glUniform3fv(glGetUniformLocation(self._shader.programId(), 'positions'), len(positions)/3, numpy.array(positions, numpy.float32))
391        self._vertex_buffer.draw(GL_TRIANGLES, 0, 3)
392        self._shader.release()
Note: See TracBrowser for help on using the repository browser.