Skip to content

Commit

Permalink
Merge branch 'bremco-graphics_buffer_update'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ghostkeeper committed Nov 15, 2021
2 parents 817ff7f + 79fade9 commit 4869329
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 69 deletions.
7 changes: 7 additions & 0 deletions UM/Mesh/MeshData.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def __init__(self, vertices=None, normals=None, indices=None, colors=None, uvs=N
self._convex_hull_vertices = None # type: Optional[numpy.ndarray]
self._convex_hull_lock = threading.Lock()

self._user_data_cache = {} # type: Dict[str, Any]
self._attributes = {} # type: Dict[str, Any]
if attributes is not None:
for key, attribute in attributes.items():
Expand Down Expand Up @@ -349,6 +350,12 @@ def getAttribute(self, key: str):

return self._attributes[key]

def getCachedUserValue(self, key: str) -> Any:
return self._user_data_cache.get(key)

def setCachedUserValue(self, key: str, value: Any) -> None:
self._user_data_cache[key] = value

def attributeNames(self) -> List[str]:
"""Return attribute names in alphabetical order
Expand Down
13 changes: 9 additions & 4 deletions UM/Qt/QtRenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import numpy
from PyQt5.QtGui import QColor, QOpenGLBuffer, QOpenGLVertexArrayObject
from typing import List, Tuple, Dict, Optional
from typing import List, Optional, Tuple, Dict

import UM.Qt.QtApplication
from UM.View.Renderer import Renderer
Expand Down Expand Up @@ -52,6 +52,7 @@ def __init__(self) -> None:
self._batches = [] # type: List[RenderBatch]
self._named_batches = {} # type: Dict[str, RenderBatch]
self._quad_buffer = None # type: QOpenGLBuffer
self._vao = None # type: Optional[QOpenGLVertexArrayObject]

initialized = Signal()

Expand Down Expand Up @@ -190,9 +191,13 @@ def renderFullScreenQuad(self, shader: "ShaderProgram") -> None:
shader.setUniformValue("u_modelViewProjectionMatrix", Matrix())

if OpenGLContext.properties["supportsVertexArrayObjects"]:
vao = QOpenGLVertexArrayObject()
vao.create()
vao.bind()
if self._vao is None:
self._vao = QOpenGLVertexArrayObject()
self._vao.create()
if self._vao is None or not self._vao.isCreated():
Logger.log("e", "QtRenderer: VAO not created.")
else:
self._vao.bind()

self._quad_buffer.bind()

Expand Down
7 changes: 7 additions & 0 deletions UM/View/GL/ShaderProgram.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ def removeAttributeBinding(self, key: str) -> None:

del self._attribute_bindings[key]

def getReferenceKey(self) -> str:
""" Uniquely identify this specific shader-object with a string.
"""
if not hasattr(self, "_reference_key"):
self._reference_key = str(id(self))
return self._reference_key

def _matrixToQMatrix4x4(self, m):
return QMatrix4x4(m.getData().flatten())

Expand Down
148 changes: 83 additions & 65 deletions UM/View/RenderBatch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from typing import List, Dict, Union, Optional, Any
from typing import cast, List, Dict, Union, Optional, Any

from UM.Logger import Logger
from UM.Math.Matrix import Matrix
Expand Down Expand Up @@ -56,6 +56,12 @@ class RenderMode:
TriangleStrip = 0x0005
TriangleFan = 0x0006

_render_mode_to_vertex_count = {
RenderMode.Points: 1,
RenderMode.Lines: 2,
RenderMode.Triangles: 3
}

class BlendMode:
"""Blending mode."""
NoBlending = 0 ## Blending disabled.
Expand Down Expand Up @@ -215,17 +221,6 @@ def render(self, camera: Optional[Camera]):
light_0_position = camera.getCameraLightPosition()
)

# 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")
else:
vao.bind()

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

Expand All @@ -234,43 +229,7 @@ def render(self, camera: Optional[Camera]):

self._shader.release()

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

# Do not render if there's no vertex (empty mesh)
if mesh.getVertexCount() == 0:
return

normal_matrix = item["normal_transformation"]
if mesh.hasNormals() and normal_matrix is None:
normal_matrix = Matrix(transformation.getData())
normal_matrix.setRow(3, [0, 0, 0, 1])
normal_matrix.setColumn(3, [0, 0, 0, 1])
normal_matrix.invert()
normal_matrix.transpose()

self._shader.updateBindings(
model_matrix = transformation,
normal_matrix = normal_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()

def _setMeshAttributes(self, mesh: MeshData) -> None:
self._shader.enableAttribute("a_vertex", "vector3f", 0)
vertex_count = mesh.getVertexCount()
offset = vertex_count * 3 * 4
Expand Down Expand Up @@ -302,21 +261,80 @@ def _renderItem(self, item: Dict[str, Any]):
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, vertex_count)
def _vertexBuffersSetup(self, mesh: MeshData) -> Optional[QOpenGLVertexArrayObject]:
# See if the mesh has already been stored to the GPU:
vao = cast(Optional[QOpenGLVertexArrayObject], mesh.getCachedUserValue(self._shader.getReferenceKey()))
if vao is not None:
return vao

# Initialize VAO (VertexArrayObject). On activation, this will wrap around the other vertex/index buffers.
# That enables reusing them without much fuss.
if not OpenGLContext.properties["supportsVertexArrayObjects"]:
Logger.log("e", "RenderBatch: This OpenGL doesn't support VAO? You will not go to R^3 today.")
return None

vertex_buffer.release()
vao = QOpenGLVertexArrayObject()
vao.create()
if not vao.isCreated():
Logger.log("e", "RenderBatch: VAO not created. You will not go to R^3 today.")
return None

# Setup VAO:
vao.bind()

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

index_buffer = OpenGL.getInstance().createIndexBuffer(mesh)
if index_buffer is not None:
index_buffer.release()
index_buffer.bind()

self._setMeshAttributes(mesh)

# Cache and return:
mesh.setCachedUserValue(self._shader.getReferenceKey(), vao)
vao.release()
return vao

def _renderItem(self, item: Dict[str, Any]) -> None:
mesh = cast(MeshData, item["mesh"])
if mesh.getVertexCount() == 0:
return

if self._render_range is not None:
self._shader.setUniformValue("u_drawRange", [self._render_range[0], self._render_range[1]])
else:
self._shader.setUniformValue("u_drawRange", [-1.0, -1.0])

transformation = item["transformation"]
normal_matrix = item["normal_transformation"]
if mesh.hasNormals() and normal_matrix is None:
normal_matrix = Matrix(transformation.getData())
normal_matrix.setRow(3, [0, 0, 0, 1])
normal_matrix.setColumn(3, [0, 0, 0, 1])
normal_matrix.invert()
normal_matrix.transpose()

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

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

vao = self._vertexBuffersSetup(mesh)
if vao is None:
return
vao.bind()

if mesh.hasIndices():
# The last parameter here is supposed to take either an array, or an offset into the current buffer.
# However, this Python wrapper can only handle either the array, or None, which serves as a 0-offset.
# As other offsets do not seem possible (everything was tried), the range is instead handled in the shader.
elem_count = mesh.getFaceCount() * self._render_mode_to_vertex_count.get(self._render_mode, 1)
self._gl.glDrawElements(self._render_mode, elem_count, self._gl.GL_UNSIGNED_INT, None)
else:
self._gl.glDrawArrays(self._render_mode, 0, mesh.getVertexCount())

vao.release()

0 comments on commit 4869329

Please sign in to comment.