[go: up one dir, main page]

File: RenderBatch.py

package info (click to toggle)
uranium 3.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 5,876 kB
  • sloc: python: 22,349; sh: 111; makefile: 11
file content (317 lines) | stat: -rw-r--r-- 14,238 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

import copy

from UM.Logger import Logger

from UM.Math.Vector import Vector

from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext

from PyQt5.QtGui import QOpenGLVertexArrayObject

vertexBufferProperty = "__gl_vertex_buffer"
indexBufferProperty = "__gl_index_buffer"


##  The RenderBatch class represent a batch of objects that should be rendered.
#
#   Each RenderBatch contains a list of objects to render and all state related
#   to those objects. It tries to minimize changes to state between render the
#   individual objects. This means that for example the ShaderProgram used is
#   only bound once, at the start of rendering. There are a few values, like
#   the model-view-projection matrix that are updated for each object.
#
#   Currently RenderBatch objects are created each frame including the
#   VertexArrayObject (VAO). This is done to greatly simplify managing
#   RenderBatch-changes. Whenever (sets of) RenderBatches are managed throughout
#   the lifetime of a session, crossing multiple frames, the usage of VAO's can
#   improve performance by reusing them.
class RenderBatch():
    ##  The type of render batch.
    #
    #   This determines some basic state values, like blending on/off and additionally
    #   is used to determine sorting order.
    class RenderType:
        NoType = 0 ## No special state changes are done.
        Solid = 1 ## Depth testing and depth writing are enabled.
        Transparent = 2 ## Depth testing is enabled, depth writing is disabled.
        Overlay = 3 ## Depth testing is disabled.

    ##  The mode to render objects in. These correspond to OpenGL render modes.
    class RenderMode:
        Points = 0x0000
        Lines = 0x0001
        LineLoop = 0x0002
        LineStrip = 0x0003
        Triangles = 0x0004
        TriangleStrip = 0x0005
        TriangleFan = 0x0006

    ##  Blending mode.
    class BlendMode:
        NoBlending = 0 ## Blending disabled.
        Normal = 1 ## Standard alpha blending, mixing source and destination values based on respective alpha channels.
        Additive = 2 ## Additive blending, the value of the rendered pixel is added to the color already in the buffer.

    ##  Init method.
    #
    #   \param shader The shader to use for this batch.
    #   \param kwargs Keyword arguments.
    #                 Possible values:
    #                 - type: The RenderType to use for this batch. Defaults to RenderType.Solid.
    #                 - mode: The RenderMode to use for this batch. Defaults to RenderMode.Triangles.
    #                 - backface_cull: Whether to enable or disable backface culling. Defaults to True.
    #                 - range: A tuple indicating the start and end of a range of triangles to render. Defaults to None.
    #                 - sort: A modifier to influence object sorting. Lower values will cause the object to be rendered before others. Mostly relevant to Transparent mode.
    #                 - blend_mode: The BlendMode to use to render this batch. Defaults to NoBlending when type is Solid, Normal when type is Transparent or Overlay.
    #                 - state_setup_callback: A callback function to be called just after the state has been set up but before rendering.
    #                                         This can be used to do additional alterations to the state that can not be done otherwise.
    #                                         The callback is passed the OpenGL bindings object as first and only parameter.
    #                 - state_teardown_callback: A callback similar to state_setup_callback, but called after everything was rendered, to handle cleaning up state changes made in state_setup_callback.
    def __init__(self, shader, **kwargs):
        self._shader = shader
        self._render_type = kwargs.get("type", self.RenderType.Solid)
        self._render_mode = kwargs.get("mode", self.RenderMode.Triangles)
        self._backface_cull = kwargs.get("backface_cull", False)
        self._render_range = kwargs.get("range", None)
        self._sort_weight = kwargs.get("sort", 0)
        self._blend_mode = kwargs.get("blend_mode", None)
        if not self._blend_mode:
            self._blend_mode = self.BlendMode.NoBlending if self._render_type == self.RenderType.Solid else self.BlendMode.Normal
        self._state_setup_callback = kwargs.get("state_setup_callback", None)
        self._state_teardown_callback = kwargs.get("state_teardown_callback", None)
        self._items = []

        self._view_matrix = None
        self._projection_matrix = None
        self._view_projection_matrix = None

        self._gl = OpenGL.getInstance().getBindingsObject()

    ##  The RenderType for this batch.
    @property
    def renderType(self):
        return self._render_type

    ##  The RenderMode for this batch.
    @property
    def renderMode(self):
        return self._render_mode

    ##  The shader for this batch.
    @property
    def shader(self):
        return self._shader

    ##  Whether backface culling is enabled or not.
    @property
    def backfaceCull(self):
        return self._backface_cull

    ##  The range of elements to render.
    #
    #   \return The range of elements to render, as a tuple of (start, end)
    @property
    def renderRange(self):
        return self._render_range

    ##  The items to render.
    #
    #   \return A list of tuples, where each item is (transform_matrix, mesh, extra_uniforms)
    @property
    def items(self):
        return self._items

    ##  Less-than comparison method.
    #
    #   This sorts RenderType.Solid before RenderType.Transparent
    #   and RenderType.Transparent before RenderType.Overlay.
    def __lt__(self, other):
        if self._render_type == other._render_type:
            return self._sort_weight < other._sort_weight

        if self._render_type == self.RenderType.Solid:
            return True

        if self._render_type == self.RenderType.Transparent and other._render_type != self.RenderType.Solid:
            return True

        return False

    ##  Add an item to render to this batch.
    #
    #   \param transformation The transformation matrix to use for rendering the item.
    #   \param mesh The mesh to render with the transform matrix.
    #   \param uniforms A dict of additional uniform bindings to set when rendering the item.
    #                   Note these are set specifically for this item.
    def addItem(self, transformation, mesh, uniforms = None):
        if not transformation:
            Logger.log("w", "Tried to add an item to batch without transformation")
            return
        if not mesh:
            Logger.log("w", "Tried to add an item to batch without mesh")
            return

        self._items.append({ "transformation": transformation, "mesh": mesh, "uniforms": uniforms})

    ##  Render the batch.
    #
    #   \param camera The camera to render from.
    def render(self, camera):
        if camera is None:
            Logger.log("e", "Unable to render batch without a camera.")
            return

        self._shader.bind()

        if self._backface_cull:
            self._gl.glEnable(self._gl.GL_CULL_FACE)
        else:
            self._gl.glDisable(self._gl.GL_CULL_FACE)

        if self._render_type == self.RenderType.Solid:
            self._gl.glEnable(self._gl.GL_DEPTH_TEST)
            self._gl.glDepthMask(self._gl.GL_TRUE)
        elif self._render_type == self.RenderType.Transparent:
            self._gl.glEnable(self._gl.GL_DEPTH_TEST)
            self._gl.glDepthMask(self._gl.GL_FALSE)
        elif self._render_type == self.RenderType.Overlay:
            self._gl.glDisable(self._gl.GL_DEPTH_TEST)

        if self._blend_mode == self.BlendMode.NoBlending:
            self._gl.glDisable(self._gl.GL_BLEND)
        elif self._blend_mode == self.BlendMode.Normal:
            self._gl.glEnable(self._gl.GL_BLEND)
            self._gl.glBlendFunc(self._gl.GL_SRC_ALPHA, self._gl.GL_ONE_MINUS_SRC_ALPHA)
        elif self._blend_mode == self.BlendMode.Additive:
            self._gl.glEnable(self._gl.GL_BLEND)
            self._gl.glBlendFunc(self._gl.GL_SRC_ALPHA, self._gl.GL_ONE)

        if self._state_setup_callback:
            self._state_setup_callback(self._gl)

        self._view_matrix = camera.getWorldTransformation().getInverse()
        self._projection_matrix = camera.getProjectionMatrix()
        self._view_projection_matrix = camera.getProjectionMatrix().multiply(self._view_matrix)

        self._shader.updateBindings(
            view_matrix = self._view_matrix,
            projection_matrix = self._projection_matrix,
            view_projection_matrix = self._view_projection_matrix,
            view_position = camera.getWorldPosition(),
            light_0_position = camera.getWorldPosition() + Vector(0, 50, 0)
        )

        # The VertexArrayObject (VAO) works like a VCR, recording buffer activities in the GPU.
        # When the same buffers are used elsewhere, one can bind this VertexArrayObject to
        # the context instead of uploading all buffers again.
        if OpenGLContext.properties["supportsVertexArrayObjects"]:
            vao = QOpenGLVertexArrayObject()
            vao.create()
            if not vao.isCreated():
                Logger.log("e", "VAO not created. Hell breaks loose")
            vao.bind()

        for item in self._items:
            self._renderItem(item)

        if self._state_teardown_callback:
            self._state_teardown_callback(self._gl)

        self._shader.release()

    def _renderItem(self, item):
        transformation = item["transformation"]
        mesh = item["mesh"]

        normal_matrix = None
        if mesh.hasNormals():
            normal_matrix = copy.deepcopy(transformation)
            normal_matrix.setRow(3, [0, 0, 0, 1])
            normal_matrix.setColumn(3, [0, 0, 0, 1])
            normal_matrix = normal_matrix.getInverse().getTransposed()

        model_view_matrix = copy.deepcopy(transformation).preMultiply(self._view_matrix)
        model_view_projection_matrix = copy.deepcopy(transformation).preMultiply(self._view_projection_matrix)

        self._shader.updateBindings(
            model_matrix = transformation,
            normal_matrix = normal_matrix,
            model_view_matrix = model_view_matrix,
            model_view_projection_matrix = model_view_projection_matrix
        )

        if item["uniforms"] is not None:
            self._shader.updateBindings(**item["uniforms"])

        vertex_buffer = OpenGL.getInstance().createVertexBuffer(mesh)
        vertex_buffer.bind()

        if self._render_range is None:
            index_buffer = OpenGL.getInstance().createIndexBuffer(mesh)
        else:
            # glDrawRangeElements does not work as expected and did not get the indices field working..
            # Now we're just uploading a clipped part of the array and the start index always becomes 0.
            index_buffer = OpenGL.getInstance().createIndexBuffer(
                mesh, force_recreate = True, index_start = self._render_range[0], index_stop = self._render_range[1])
        if index_buffer is not None:
            index_buffer.bind()

        self._shader.enableAttribute("a_vertex", "vector3f", 0)
        offset = mesh.getVertexCount() * 3 * 4

        if mesh.hasNormals():
            self._shader.enableAttribute("a_normal", "vector3f", offset)
            offset += mesh.getVertexCount() * 3 * 4

        if mesh.hasColors():
            self._shader.enableAttribute("a_color", "vector4f", offset)
            offset += mesh.getVertexCount() * 4 * 4

        if mesh.hasUVCoordinates():
            self._shader.enableAttribute("a_uvs", "vector2f", offset)
            offset += mesh.getVertexCount() * 2 * 4

        for attribute_name in mesh.attributeNames():
            attribute = mesh.getAttribute(attribute_name)
            self._shader.enableAttribute(attribute["opengl_name"], attribute["opengl_type"], offset)
            if attribute["opengl_type"] == "vector2f":
                offset += mesh.getVertexCount() * 2 * 4
            elif attribute["opengl_type"] == "vector4f":
                offset += mesh.getVertexCount() * 4 * 4
            elif attribute["opengl_type"] == "int":
                offset += mesh.getVertexCount() * 4
            elif attribute["opengl_type"] == "float":
                offset += mesh.getVertexCount() * 4
            else:
                Logger.log("e", "Attribute with name [%s] uses non implemented type [%s]." % (attribute["opengl_name"], attribute["opengl_type"]))
                self._shader.disableAttribute(attribute["opengl_name"])

        if mesh.hasIndices():
            if self._render_range is None:
                if self._render_mode == self.RenderMode.Triangles:
                    self._gl.glDrawElements(self._render_mode, mesh.getFaceCount() * 3 , self._gl.GL_UNSIGNED_INT, None)
                else:
                    self._gl.glDrawElements(self._render_mode, mesh.getFaceCount(), self._gl.GL_UNSIGNED_INT, None)
            else:
                if self._render_mode == self.RenderMode.Triangles:
                    self._gl.glDrawRangeElements(self._render_mode, self._render_range[0], self._render_range[1], self._render_range[1] - self._render_range[0], self._gl.GL_UNSIGNED_INT, None)
                else:
                    self._gl.glDrawElements(self._render_mode, self._render_range[1] - self._render_range[0], self._gl.GL_UNSIGNED_INT, None)
        else:
            self._gl.glDrawArrays(self._render_mode, 0, mesh.getVertexCount())

        self._shader.disableAttribute("a_vertex")
        self._shader.disableAttribute("a_normal")
        self._shader.disableAttribute("a_color")
        self._shader.disableAttribute("a_uvs")
        for attribute_name in mesh.attributeNames():
            attribute = mesh.getAttribute(attribute_name)
            self._shader.disableAttribute(attribute.get("opengl_name"))
        vertex_buffer.release()

        if index_buffer is not None:
            index_buffer.release()