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()
|