Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graphic Buffers Update #714

Closed
wants to merge 10 commits into from
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 None if key not in self._user_data_cache else self._user_data_cache[key]

def setCachedUserValue(self, key: str, value: Any):
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
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 @@ -331,6 +331,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
147 changes: 82 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: Any) -> None:
bremco marked this conversation as resolved.
Show resolved Hide resolved
self._shader.enableAttribute("a_vertex", "vector3f", 0)
vertex_count = mesh.getVertexCount()
offset = vertex_count * 3 * 4
Expand Down Expand Up @@ -302,21 +261,79 @@ 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

vertex_buffer.release()
# Initialize VAO (VertexArrayObject). On activation, this will wrap around the other vertex/index buffers.
# That enables reusing them without much fuss.
if OpenGLContext.properties["supportsVertexArrayObjects"]:
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]])
self._shader.setUniformValue("draw_range", [self._render_range[0], self._render_range[1]])
else:
self._shader.setUniformValue("u_drawRange", [-1.0, -1.0])
self._shader.setUniformValue("draw_range", [-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 (everyting 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()