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 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.bind() self._setMeshAttributes(mesh) # Cache and return: mesh.setCachedUserValue(self._shader.getReferenceKey(), vao) vao.release() return vao
class Actor(QObject): 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. Types = [NoType, Solid, Transparent, Overlay] ## The mode to render objects in. These correspond to OpenGL render modes. class RenderMode: Points = GL.GL_POINTS Lines = GL.GL_LINES LineLoop = GL.GL_LINE_LOOP LineStrip = GL.GL_LINE_STRIP Triangles = GL.GL_TRIANGLES TriangleStrip = GL.GL_TRIANGLE_STRIP TriangleFan = GL.GL_TRIANGLE_FAN Modes = [ Points, Lines, LineLoop, LineStrip, Triangles, TriangleStrip, TriangleFan ] ## initialization def __init__(self, scene, **kwargs): """Initialize actor.""" super(Actor, self).__init__() self._state = 0 #1=scale, 2=rotation, 3=scale self._lines = None self._axis = 0 # 1=x, 2=y, 3=z self._scene = scene self._transform = kwargs.get("transform", QMatrix4x4()) self._render_mode = kwargs.get("mode", Actor.RenderMode.Triangles) self._render_type = kwargs.get("type", Actor.RenderType.Solid) self._material = kwargs.get("material", Material()) self._wireframe = kwargs.get( "wireframe", Material(diffuse=QVector3D(0.25, 0.25, 0.25))) self._viewport = kwargs.get("viewport", (0.0, 0.0, 1.0, 1.0)) self._name = kwargs.get("name", "Actor" + str(id(self))) self._shader_collection = Shaders() #self._texture_collection = Textures() self._solid_shader = self._shader_collection.uniformMaterialPhongShader( ) self._solid_flat_shader = self._shader_collection.uniformMaterialPhongFlatShader( ) self._nolight_solid_shader = self._shader_collection.uniformMaterialShader( ) self._wireframe_shader = self._shader_collection.uniformMaterialShader( ) self._nolight_wireframe_shader = self._shader_collection.uniformMaterialShader( ) self._normal_visualizing_shader = self._shader_collection.normalVisShader( ) self._active_shader = self._solid_shader self._active_material = self._material self._vao = QOpenGLVertexArrayObject() self._vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) self._ibo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) self._num_vertices = 0 self._num_indices = 0 self._hasNormals = False self._hasColors = False self._hasTextureCoords = False self._hasIndices = False self._hasFaces = False self._texture = None #self._bbox = None self._visible = True self._enabled = False self._pickable = True self._selectable = False self._selected = False self._highlighted = False self._errorMaterial = Material.ruby() self._errorHighlight = False self._warningMaterial = Material.gold() self._warningHighlight = False self._pickFactor = 1.0 def update(self, **kwargs): """Update this node""" self._transform = kwargs.get("transform", QMatrix4x4()) self._render_mode = kwargs.get("mode", Actor.RenderMode.Triangles) self._render_type = kwargs.get("type", Actor.RenderType.Solid) self._material = kwargs.get("material", Material()) self._wireframe = kwargs.get( "wireframe", Material(diffuse=QVector3D(0.25, 0.25, 0.25))) def scene(self): return self._scene @property def name(self): """Returns the name of this actor""" return self._name def setName(self, name): """Sets this actor's name""" self._name = name @property def material(self): """Returns the material of this node""" return self._material def setTransform(self, xform): self._transform = xform def scale(self, amt): if (self._axis == 1): self._transform.scale(amt, 1.0, 1.0) elif (self._axis == 2): self._transform.scale(1.0, amt, 1.0) elif (self._axis == 3): self._transform.scale(1.0, 1.0, amt) def translate(self, pos): self._transform[0, 3] += pos.x() self._transform[1, 3] += pos.y() self._transform[2, 3] += pos.z() def transform(self): return self._transform def position(self): xform = self.transform() return QVector3D(xform[0, 3], xform[1, 3], xform[2, 3]) def setPosition(self, pos): #print("pos==",pos) self._transform = QMatrix4x4() self._transform.translate(pos.x(), pos.y(), pos.z()) def texture(self): """Returns the texture image""" return self._texture def setTexture(self, texture): """Sets the current texture""" self._texture = texture def isPickable(self): """Sets whether or not this actor is pickable""" return self._pickable def setPickable(self, value): """Sets whether this actor is pickable""" self._pickable = value def isVisible(self): """Sets the visibility of this actor""" return self._visible def setVisible(self, value): """Sets the visibility of this actor""" self._visible = value def isEnabled(self): """Returns whether this actor is enabled or not""" return self._enabled def setEnabled(self, value): """Sets whether this actor is enabled or not""" self._enabled = value def setSelectable(self, value): """Sets whther or not this actor is selectable""" self._selectable = value def isSelectable(self): """Returns true if actor is selectable""" return self._selectable def setSelected(self, value): """Sets selection to value""" self._selected = value def isSelected(self): """Returns true if it is selected""" return self._selected def setHighlighted(self, value): """Sets the highlight value""" self._highlighted = value def isHighlighted(self): """Returns true if it is highlighted""" return self._highlighted def setErrorMaterial(self, material): """Sets the error material""" self._errorMaterial = material def setErrorHighlight(self, value): """Sets the error highlight""" self._errorHighlight = value def setWarningMaterial(self, material): """Sets the error material""" self._warningMaterial = material def setWarningHighlight(self, value): """Sets the warning highlight""" self._warningHighlight = value @property def shaderCollection(self): """Returns the shader collection""" return self._shader_collection @property def renderType(self): """Returns the rendering type of this actor""" return self._render_type @property def renderMode(self): """Returns the rendering mode of this actor""" return self._render_mode @property def solidShader(self): """Returns the default solid shader of this actor""" return self._solid_shader def setSolidShader(self, shader): """Sets the solid shader of this actor""" self._solid_shader = shader @property def solidFlatShader(self): """Returns the default solid flat shader of this actor""" return self._solid_flat_shader def setSolidFlatShader(self, shader): """Sets the solid flat shader of this actor""" self._solid_flat_shader = shader @property def noLightSolidShader(self): """Returns the default no light solid shader of this actor""" return self._nolight_solid_shader def setNoLightSolidShader(self, shader): """Sets the solid shader of this actor""" self._nolight_solid_shader = shader @property def wireframeShader(self): """Returns the default wireframe shader of this actor""" return self._wireframe_shader def setWireframeShader(self, shader): """Sets the default wireframe shader of this actor""" self._wireframe_shader = shader @property def noLightWireframeShader(self): """Returns the default no light wireframe shader of this actor""" return self._nolight_wireframe_shader def setNoLightWireframeShader(self, shader): """Sets the no light wireframe shader of this actor""" self._nolight_wireframe_shader = shader @property def numberOfVertices(self): """Returns the number of vertices of this actor""" return self._num_vertices @property def numberOfIndices(self): """Returns the number of indices of this actor""" return self._num_indices def mapBuffer(self, offset, count, access): """Map the given buffer into a numpy array""" vbo_ptr = self._vbo.mapRange(offset, count, access) vp_array = ctypes.cast( ctypes.c_void_p(int(vbo_ptr)), ctypes.POINTER(ctypes.c_byte * self._vbo.size())).contents # Note: we could have returned the raw ctypes.c_byte array instead... see pyglet github for map/unmap classes array = np.frombuffer(vp_array, 'B') return array def unmapBuffer(self): """Update the GPU with new buffer contents""" self._vbo.unmap() def updateBuffer(self, vertices=None, normals=None, colors=None, texcoords=None): """Update buffer with new data""" self._vbo.bind() if vertices is not None: vertices = vertices.tostring() #vertices = np.fromstring(vertices, dtype="uint8") self._vbo.write(0, vertices, len(vertices)) #buffer = self.mapBuffer(0, len(vertices), QOpenGLBuffer.RangeWrite | QOpenGLBuffer.RangeInvalidate) #buffer[:len(vertices)] = vertices #self.unmapBuffer() if normals is not None: normals = normals.tostring() self._vbo.write(self._offsetNormals, normals, len(normals)) if colors is not None: colors = colors.tostring() self._vbo.write(self._offsetColors, colors, len(colors)) if texcoords is not None: texcoords = texcoords.tostring() self._vbo.write(self._offsetTexCoords, texcoords, len(texcoords)) self._vbo.release() def create(self, vertices, normals=None, colors=None, texcoords=None, indices=None, faces=None, usage=QOpenGLBuffer.StaticDraw): """Create object vertex arrays and buffers""" ## list of shaders shaders = [ self._solid_shader, self._wireframe_shader, self._nolight_solid_shader, self._nolight_wireframe_shader, self._normal_visualizing_shader ] ## bind vao self._vao.create() self._vao.bind() ## define total sizes vertices = vertices.tostring() total_vertices = len(vertices) total_normals = 0 total_colors = 0 total_texcoords = 0 total_faces = 0 self._num_vertices = total_vertices // (np.dtype(np.float32).itemsize * 3) #print('total vertices=', self._num_vertices) if normals is not None: self._hasNormals = True normals = normals.tostring() total_normals = len(normals) if colors is not None: self._hasColors = True colors = colors.tostring() total_colors = len(colors) if texcoords is not None: self._hasTextureCoords = True texcoords = texcoords.tostring() total_texcoords = len(texcoords) if indices is not None: self._hasIndices = True indices = indices.tostring() total_indices = len(indices) self._num_indices = total_indices // np.dtype(np.uint32).itemsize #print('total indices=', self._num_indices) if faces is not None: self._hasFaces = True faces = faces.tostring() total_faces = len(faces) ## create vertex buffer object self._vbo.setUsagePattern(usage) self._vbo.create() self._vbo.bind() ## populate vertex buffer object with data offset = 0 self._vbo.allocate(total_vertices + total_normals + total_colors + total_texcoords + total_faces) self._vbo.write(offset, vertices, total_vertices) for each in shaders: each.setAttributeBuffer('position', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_vertices self._offsetNormals = offset if self._hasNormals: self._vbo.write(offset, normals, total_normals) for each in shaders: each.setAttributeBuffer('normal', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_normals if self._hasColors: self._offsetColors = offset self._vbo.write(offset, colors, total_colors) for each in shaders: each.setAttributeBuffer('color', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_colors if self._hasTextureCoords: self._offsetTexCoords = offset self._vbo.write(offset, texcoords, total_texcoords) for each in shaders: each.setAttributeBuffer('texcoord', GL.GL_FLOAT, offset, 2, 2 * np.dtype(np.float32).itemsize) offset += total_texcoords if self._hasFaces: self._offsetFaces = offset self._vbo.write(offset, faces, total_faces) for each in shaders: each.setAttributeBuffer('faces', GL.GL_INT, offset, 9, 9 * np.dtype(np.int).itemsize) offset += total_faces ## release buffer self._vbo.release(QOpenGLBuffer.VertexBuffer) ## enable arrays as part of the vao state for each in shaders: each.enableAttributeArray('position') if self._hasNormals: for each in shaders: each.enableAttributeArray('normal') if self._hasColors: for each in shaders: each.enableAttributeArray('color') if self._hasTextureCoords: for each in shaders: each.enableAttributeArray('texcoord') if self._hasFaces: for each in shaders: each.enableAttributeArray('faces') ## create index buffer object if required by the actor if self._hasIndices: self._ibo.setUsagePattern(usage) self._ibo.create() self._ibo.bind() self._ibo.allocate(total_indices) self._ibo.write(0, indices, total_indices) ## release vao self._vao.release() ## release ibo if self._hasIndices: self._ibo.release(QOpenGLBuffer.IndexBuffer) def setUniformBindings(self, wireframe=False): """Sets up uniform shader bindings""" normalMatrix = self._transform.normalMatrix() self._active_shader.setUniformValue("modelMatrix", self._transform) self._active_shader.setUniformValue("viewMatrix", self._scene.camera.viewMatrix) self._active_shader.setUniformValue( "projectionMatrix", self._scene.camera.projectionMatrix) self._active_shader.setUniformValue("normalMatrix", normalMatrix) if self.texture() is not None: self._active_shader.setUniformValue("texObject", 1.0) ## bind active material if self.isSelectable() and self.isSelected(): self._active_shader.setUniformValue("selected", 0.5) else: self._active_shader.setUniformValue("selected", 0.0) ## set highlight color if self.isHighlighted(): self._active_shader.setUniformValue("material.emission", QVector3D(0.25, 0.25, 0.25)) else: self._active_shader.setUniformValue( "material.emission", self._active_material.emissionColor) self._active_shader.setUniformValue("material.ambient", self._active_material.ambientColor) ## set the enabled color if self.isEnabled(): self._active_shader.setUniformValue("material.emission", QVector3D(0.25, 0.25, 0.25)) self._active_shader.setUniformValue( "material.diffuse", self._active_material.diffuseColor) else: self._active_shader.setUniformValue( "material.diffuse", self._active_material.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._active_material.specularColor) self._active_shader.setUniformValue("material.shininess", self._active_material.shininess) ## set the error and warning colors if self._errorHighlight: self._active_shader.setUniformValue( "material.ambient", self._errorMaterial.ambientColor) self._active_shader.setUniformValue( "material.diffuse", self._errorMaterial.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._errorMaterial.specularColor) self._active_shader.setUniformValue("material.shininess", self._errorMaterial.shininess) if self._warningHighlight: self._active_shader.setUniformValue( "material.ambient", self._warningMaterial.ambientColor) self._active_shader.setUniformValue( "material.diffuse", self._warningMaterial.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._warningMaterial.specularColor) self._active_shader.setUniformValue( "material.shininess", self._warningMaterial.shininess) ## bind lights camera_position = QVector4D(self._scene.camera.position[0], self._scene.camera.position[1], self._scene.camera.position[2], 1.0) if self._scene.light.headlight: if self._scene.light.directional: self._active_shader.setUniformValue( "lightPosition", QVector4D(0.0, 0.0, 1.0, 0.0)) else: self._active_shader.setUniformValue( "lightPosition", QVector4D(0.0, 0.0, 0.0, 1.0)) else: self._active_shader.setUniformValue( "lightPosition", self._scene.camera.viewMatrix * self._scene.light.position) self._active_shader.setUniformValue("light.ambient", self._scene.light.ambientColor) self._active_shader.setUniformValue("light.diffuse", self._scene.light.diffuseColor) self._active_shader.setUniformValue("light.specular", self._scene.light.specularColor) self._active_shader.setUniformValue("lightAttenuation", self._scene.light.attenuation) ## This should set up any required state before any actual rendering happens. def beginRendering(self, draw_style, lighting, shading, passNumber): ## determine right shader to bind if lighting: if draw_style == GL.GL_LINE: self._active_shader = self._wireframe_shader self._active_material = self._material if passNumber == 0 else self._wireframe else: if shading == GL.GL_SMOOTH: self._active_shader = self._solid_shader else: self._active_shader = self._solid_flat_shader self._active_material = self._material else: if draw_style == GL.GL_LINE: self._active_shader = self._nolight_wireframe_shader self._active_material = self._material if passNumber == 0 else self._wireframe else: self._active_shader = self._nolight_solid_shader self._active_material = self._material GL.glPolygonMode(GL.GL_FRONT_AND_BACK, draw_style) ## determine rendering type to use if self._render_type == self.RenderType.Solid: GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthMask(GL.GL_TRUE) elif self._render_type == self.RenderType.Transparent: GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthMask(GL.GL_FALSE) elif self._render_type == self.RenderType.Overlay: GL.glDisable(GL.GL_DEPTH_TEST) ## bind shader self._active_shader.bind() ## set up uniform variables self.setUniformBindings() if self._texture is not None: #self.glEnable(GL.GL_BLEND) self._texture.bind() ## bind shader self._vao.bind() def render(self): """Render this actor""" raise NotImplementedError( "render() must be implemented in child class") def endRendering(self): """Finished rendering, clean yourself up""" ## unbind vao self._vao.release() ## unbind texture if self._texture is not None: self._texture.release() ## unbind shader self._active_shader.release() def pickFactor(self): """Returns the pick factor for intersection calculations""" return self._pickFactor def setPickFactor(self, value): """Sets the pick factor for intersection calculations""" self._pickFactor = value def destroy(self): self._vao.destroy() self._vbo.destroy() self._ibo.destroy() def intersect(self, ray): """Returns intersection if any""" tMin = -math.inf tMax = math.inf obb_xform = self.transform() obb_center = QVector3D(obb_xform[0, 3], obb_xform[1, 3], obb_xform[2, 3]) point = obb_center - ray.origin() for i in range(3): axis = QVector3D(obb_xform[0, i], obb_xform[1, i], obb_xform[2, i]).normalized() half_length = QVector3D(obb_xform[i, 0], obb_xform[i, 1], obb_xform[i, 2]).length() / 2.0 e = QVector3D.dotProduct(axis, point) f = QVector3D.dotProduct(axis, ray.direction()) if abs(f) > 10E-6: t1 = (e + half_length * self._pickFactor) / f t2 = (e - half_length * self._pickFactor) / f if t1 > t2: w = t1 t1 = t2 t2 = w if t1 > tMin: tMin = t1 if t2 < tMax: tMax = t2 if tMin > tMax: return (False, math.inf) if tMax < 0: return (False, math.inf) elif -e - half_length > 0.0 or -e + half_length < 0.0: return (False, math.inf) if tMin > 0: return (True, tMin) return (True, tMax)
class InFboRenderer(QQuickFramebufferObject.Renderer): def __init__(self): super(InFboRenderer, self).__init__() self.vertices_array = None self.sorted_actors = [] self.vertex_array = None self.render_to_texture = None self.render_to_texture_attachment = -1 self.render_to_texture_array = None self.locked_render_to_texture_array = Array.Array( ndarray=np.empty((0, 0, 4), np.uint8)) self.draw_buffers = [ GL.GL_COLOR_ATTACHMENT0, GL.GL_COLOR_ATTACHMENT1, GL.GL_COLOR_ATTACHMENT2, GL.GL_COLOR_ATTACHMENT3, GL.GL_COLOR_ATTACHMENT4 ] def to_texture_target(self, array): shape = array.ndarray.shape target = None if len(shape) == 2 or (len(shape) == 3 and shape[1] == 1): target = QOpenGLTexture.Target1D elif len(shape) == 3: target = QOpenGLTexture.Target2D elif len(shape) == 4: target = QOpenGLTexture.Target3D return target def attach_texture_object(self, array): if not hasattr(array, "___tex___"): target = self.to_texture_target(array) if target == None: raise RuntimeError( 'Unable to determine texture binding target') array.___tex___ = QOpenGLTexture(target) array.___tex___.setMinificationFilter(QOpenGLTexture.Linear) array.___tex___.setMagnificationFilter(QOpenGLTexture.Linear) array.___tex___.setWrapMode(QOpenGLTexture.DirectionS, QOpenGLTexture.Repeat) if target in [QOpenGLTexture.Target2D, QOpenGLTexture.Target3D]: array.___tex___.setWrapMode(QOpenGLTexture.DirectionT, QOpenGLTexture.Repeat) if target == QOpenGLTexture.Target3D: array.___tex___.setWrapMode(QOpenGLTexture.DirectionR, QOpenGLTexture.Repeat) array.___tex___.dirty = True array.productDirty.connect( lambda array=array: setattr(array.___tex___, "dirty", True)) def attach_buffer_object(self, array, gl_buffer_type): if not hasattr(array, "___bo___"): array.___bo___ = QOpenGLBuffer(gl_buffer_type) array.___bo___.create() array.___bo___.setUsagePattern(QOpenGLBuffer.DynamicDraw) array.___bo___.dirty = True array.productDirty.connect( lambda array=array: setattr(array.___bo___, "dirty", True)) def map_buffer_object(self, bo, ndarray): if bo.dirty: bo.allocate(ndarray.size * ndarray.itemsize) ibo_addr = bo.map(QOpenGLBuffer.WriteOnly) if ibo_addr is not None: c_type = None if ndarray.dtype == np.dtype(np.float32): c_type = ctypes.c_float elif ndarray.dtype == np.dtype(np.float64): c_type = ctypes.c_double else: c_type = getattr(ctypes, 'c_' + str(ndarray.dtype)) assert (c_type is not None) ibo_ptr = ctypes.cast(ibo_addr.__int__(), ctypes.POINTER(c_type)) ibo_np = np.ctypeslib.as_array(ibo_ptr, shape=ndarray.shape) ibo_np[:] = ndarray bo.unmap() bo.shape = ndarray.shape bo.dtype = ndarray.dtype bo.dirty = False def goc_output_texture(self, array): shape = list(array.ndarray.shape) if not len(shape) in (2, 3): raise RuntimeError( "Unexpected shape for output texture, must me 2D, shape was {shape}" ) self.attach_texture_object(array) shape[0] = int(self.viewport_width) shape[1] = int(self.viewport_height) if tuple(shape) != tuple(array.ndarray.shape): array.set_ndarray(np.empty(shape, array.ndarray.dtype)) array.___tex___.dirty = True if array.___tex___.dirty: array.___tex___.destroy() array.___tex___.create() array.___tex___.setSize(array.ndarray.shape[0], array.ndarray.shape[1]) dtype_error = RuntimeError( f"Unsupported dtype {array.ndarray.dtype}, you can easily add it yourself" ) if shape[2] == 4: if array.ndarray.dtype == np.uint8: array.___tex___.setFormat(QOpenGLTexture.RGBA8_UNorm) array.___tex___.allocateStorage( QOpenGLTexture.RGBA, QOpenGLTexture.UInt32_RGBA8 ) #GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 elif array.ndarray.dtype == np.float32: # untested! array.___tex___.setFormat(QOpenGLTexture.RGBA32F) array.___tex___.allocateStorage( QOpenGLTexture.RGBA, QOpenGLTexture.Float32) #GL_RGBA, GL_FLOAT else: raise dtype_error else: raise RuntimeError( f"Unsupported shape {shape}, you can add support yourself") array.___tex___.shape = array.ndarray.shape array.___tex___.dtype = array.ndarray.dtype array.___tex___.dirty = False def get_texture_attachment(self, loc): if loc < 0 or loc > len(self.draw_buffers): raise RuntimeError( f"output texture location is {loc} must be between 0 and {len(self.draw_buffers)}" ) return self.draw_buffers[loc] def del_render_to_texture_attachment(self): if hasattr(self.render_to_texture_array, '___tex___'): # self.locked_render_to_texture_array.___tex___.destroy() self.locked_render_to_texture_array.___tex___.dirty = True def synchronize(self, viewport): # This function is called by Qt before calling render() # render() will then be called from another thread, which means anything # collected here (e.g. sorted_actors) should *not* be accessed during the rendering # I should map all buffers during synchronize, and copy all uniforms and whatnot viewport.setMirrorVertically(True) if self.vertex_array is None: # window = viewport.window() # gl_context = window.openglContext() # profile = QOpenGLVersionProfile() # profile.setProfile(QSurfaceFormat.CoreProfile) # profile.setVersion( 4, 1 ) # self.gl = gl_context.versionFunctions(profile) self.vertex_array = QOpenGLVertexArrayObject() self.vertex_array.create() self.background_color = viewport._backgroundColor self.sorted_actors = [] self.bo_actors = [] self.out_textures = {} if viewport.actors is None: return sorted_actors = sorted(list(viewport.actors.get_visible_actors()), key=attrgetter('renderRank')) sorted_actors.extend(viewport._debug_actors.get_visible_actors()) for actor in sorted_actors: if not actor.update(): if not hasattr(actor, "__error_reported___" ) or not actor.__error_reported___: LoggingManager.instance().warning("Not rendering actor " + str(actor) + ". It is has error " + str(actor._error) + ".") actor.__error_reported___ = True actor.productDirty.connect(lambda actor=actor: setattr( actor, "__error_reported___", False)) continue if not hasattr(actor, 'bo_actor'): actor.bo_actor = {} actor.productDirty.connect( lambda actor=actor: setattr(actor, "bo_actor", {})) if not actor.bo_actor: # actor was dirty or is new indices = actor.geometry.indices attribs = actor.geometry.attribs.get_attributes() bo_actor = { "attribs": {}, "textures": {}, "out_textures": {}, "uniforms": copy.deepcopy(actor.effect.shader0.uniforms), "indices": None, "program": actor.effect.shader0._program, "point_size": actor.effect.pointSize, "line_width": actor.effect.lineWidth, "transform": actor.transform.worldTransform() if actor.transform else QMatrix4x4(), "primitiveType": actor.geometry.primitiveType, "actor_not_thread_safe": actor } for name, value in viewitems(attribs): if value is None: continue self.attach_buffer_object(value, QOpenGLBuffer.VertexBuffer) value.___bo___.bind() self.map_buffer_object( value.___bo___, value.ndarray.astype('f4') if value.ndarray.dtype == np.float64 else value.ndarray) value.___bo___.release() bo_actor["attribs"][name] = value.___bo___ for name, value in viewitems(actor.effect.shader0.textures): if value is None: LoggingManager.instance().warning( 'texture {} is null'.format(name)) continue self.attach_texture_object(value) if value.___tex___.target() != self.to_texture_target( value): LoggingManager.instance().warning( 'expected texture target {}, got {}'.format( value.___tex___.target(), self.to_texture_target(array))) value.___tex___ = None self.attach_texture_object(value) if value.___tex___.dirty: tex_ndarray = value.ndarray if value.___tex___.target() == QOpenGLTexture.Target1D: value.___tex___.setSize(tex_ndarray.shape[0]) if value.___tex___.target() == QOpenGLTexture.Target2D: value.___tex___.setSize(tex_ndarray.shape[0], tex_ndarray.shape[1]) if value.___tex___.target() == QOpenGLTexture.Target3D: value.___tex___.setSize(tex_ndarray.shape[0], tex_ndarray.shape[1], tex_ndarray.shape[2]) value.___tex___.setFormat(QOpenGLTexture.RGBA8_UNorm) value.___tex___.allocateStorage() value.___tex___.setData(QOpenGLTexture.RGBA, QOpenGLTexture.UInt8, tex_ndarray.data) value.___tex___.shape = value.ndarray.shape value.___tex___.dtype = value.ndarray.dtype value.___tex___.dirty = False bo_actor["textures"][name] = value.___tex___ for name, value in viewitems( actor.effect.shader0.outputTextures): if value is None: LoggingManager.instance().warning( f'output texture {name} is null') continue self.goc_output_texture(value) bo_actor["out_textures"][name] = value.___tex___ self.out_textures[value.___tex___] = None if indices is not None and indices.size > 0: self.attach_buffer_object(indices, QOpenGLBuffer.IndexBuffer) indices.___bo___.bind() self.map_buffer_object(indices.___bo___, indices.ndarray) indices.___bo___.release() bo_actor["indices"] = indices.___bo___ actor.bo_actor = bo_actor self.sorted_actors.append(actor) self.bo_actors.append(actor.bo_actor) self.view_matrix = viewport.view_matrix() self.perspective_matrix = viewport.perspective_matrix() self.orthographic_matrix = viewport.orthographic_matrix() self.viewport_width = int(viewport.width()) self.viewport_height = int(viewport.height()) if viewport.render_to_texture_attachment != -1: old = self.render_to_texture_array self.render_to_texture_array = self.locked_render_to_texture_array self.locked_render_to_texture_array = old if old is not None else Array.Array( ndarray=np.empty((0, 0, 4), np.uint8)) if self.render_to_texture_attachment != viewport.render_to_texture_attachment: self.del_render_to_texture_attachment() self.render_to_texture_attachment = viewport.render_to_texture_attachment self.goc_output_texture(self.locked_render_to_texture_array) def render(self): self.framebufferObject().setAttachment( QOpenGLFramebufferObject.CombinedDepthStencil) e = GL.glGetError() #clear any pending errors c = self.background_color GL.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) GL.glEnable(GL.GL_BLEND) GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) GL.glBlendEquation(GL.GL_FUNC_ADD) GL.glEnable(GL.GL_PRIMITIVE_RESTART) GL.glPrimitiveRestartIndex(0xFFFFFFFF) GL.glDrawBuffers(len(self.draw_buffers), self.draw_buffers) #opengl_error_check(currentframe()) if self.render_to_texture_attachment != -1: GL.glFramebufferTexture2D( GL.GL_FRAMEBUFFER, self.draw_buffers[self.render_to_texture_attachment], GL.GL_TEXTURE_2D, self.locked_render_to_texture_array.___tex___.textureId(), 0) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) if GL.glCheckFramebufferStatus( GL.GL_FRAMEBUFFER) != GL.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError("glCheckFramebufferStatus() problem") #opengl_error_check(currentframe()) for actor in self.bo_actors: try: program = actor['program'] indices = actor['indices'] attribs = actor['attribs'] textures = actor['textures'] out_textures = actor['out_textures'] transform = actor['transform'] uniforms = actor['uniforms'] primitiveType = actor['primitiveType'] point_size = actor['point_size'] line_width = actor['line_width'] program.bind() self.vertex_array.bind() for name, bo in viewitems(attribs): if bo is None: continue bo.bind() loc = program.attributeLocation(name) program.enableAttributeArray(loc) dim = bo.shape[1] if len(bo.shape) > 1 else 1 if bo.dtype == np.float32: program.setAttributeBuffer(loc, GL.GL_FLOAT, 0, dim) elif bo.dtype == np.int32: program.setAttributeBuffer(loc, GL.GL_INT, 0, dim) else: raise ValueError( f'Unsupported dtype {bo.dtype} for attrib {name}') #opengl_error_check(currentframe()) m = transform v = self.view_matrix p = self.perspective_matrix if program.uniformLocation("matrix") == -1: program.setUniformValue("model_matrix", m) program.setUniformValue("view_matrix", v) program.setUniformValue("projection_matrix", p) program.setUniformValue("normal_matrix", m.normalMatrix()) program.setUniformValue("view_matrix_inv", v.inverted()[0]) else: program.setUniformValue("matrix", p * v * m) if program.uniformLocation("ortho_matrix") != -1: program.setUniformValue("ortho_matrix", self.orthographic_matrix) if program.uniformLocation("aspect_ratio") != -1: program.setUniformValue( "aspect_ratio", self.viewport_width / self.viewport_height) #opengl_error_check(currentframe()) if program.uniformLocation("point_size") != -1: GL.glDisable(GL.GL_PROGRAM_POINT_SIZE) program.setUniformValue("point_size", float(point_size)) elif point_size != 1: try: GL.glEnable(GL.GL_PROGRAM_POINT_SIZE) GL.glPointSize(GL.GLfloat(point_size)) except Exception as e: # glPointSize is refused by the driver LoggingManager.instance().warning( f"glPointSize failed with: exception {e}") #opengl_error_check(currentframe()) for name, value in viewitems(uniforms): program.setUniformValue(name, value) #opengl_error_check(currentframe(), f"error with uniform {name}, {value}") texture_unit = 0 for name, tex in viewitems(textures): tex.bind(texture_unit) program.setUniformValue(name, texture_unit) #opengl_error_check(currentframe(), f"error with texture {name}, {value}") texture_unit += 1 for name, tex in viewitems(out_textures): if tex is None: LoggingManager.instance().warning( f'output texture {name} is null') continue # in your fragment shader, you can specify the location via the layout decoration # e.g. layout(location = 1) out vec4 my_output; loc = GL.glGetFragDataLocation(program.programId(), name) GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, self.get_texture_attachment(loc), GL.GL_TEXTURE_2D, tex.textureId(), 0) if GL.glCheckFramebufferStatus( GL.GL_FRAMEBUFFER) != GL.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError( "glCheckFramebufferStatus() problem") #opengl_error_check(currentframe()) if line_width != 1: try: GL.glEnable(GL.GL_LINE_SMOOTH) #opengl_error_check(currentframe()) GL.glLineWidth(GL.GLfloat(line_width)) except Exception as e: aliased_r = (GL.GLfloat * 2)() GL.glGetFloatv(GL.GL_ALIASED_LINE_WIDTH_RANGE, aliased_r) smooth_r = (GL.GLfloat * 2)() GL.glGetFloatv(GL.GL_SMOOTH_LINE_WIDTH_RANGE, smooth_r) line_r = (GL.GLfloat * 2)() GL.glGetFloatv(GL.GL_LINE_WIDTH_RANGE, line_r) LoggingManager.instance().warning( f"{e}: yout asked for line_width = {line_width}, line range is {list(line_r)}, aliased range is {list(aliased_r)}, smooth range is {list(smooth_r)}" ) else: GL.glDisable(GL.GL_LINE_SMOOTH) #opengl_error_check(currentframe()) if indices is not None and indices.shape[0] > 0: indices.bind() GL.glDrawElements(primitiveType, indices.shape[0], GL.GL_UNSIGNED_INT, None) indices.release() elif attribs["vertices"].shape[0] > 0: GL.glDrawArrays(primitiveType, 0, attribs["vertices"].shape[0]) for name, bo in viewitems(attribs): if bo is not None: bo.release() program.disableAttributeArray(name) for name, tex in viewitems(textures): if tex is not None: tex.release() for name, tex in viewitems(out_textures): if tex is not None: tex.release() for name, tex in viewitems(out_textures): shape = tex.shape tex.bind() loc = GL.glGetFragDataLocation(program.programId(), name) GL.glReadBuffer(self.get_texture_attachment(loc)) if shape[2] == 4: if tex.dtype == np.uint8: pixels = GL.glReadPixels( 0, 0, shape[0], shape[1], GL.GL_RGBA, GL.GL_UNSIGNED_INT_8_8_8_8) self.out_textures[tex] = pixels.view( np.uint8).reshape(shape[1], shape[0], 4) elif tex.dtype == np.float32: pixels = GL.glReadPixels(0, 0, shape[0], shape[1], GL.GL_RGBA, GL.GL_FLOAT) self.out_textures[tex] = pixels tex.release() except Exception as e: LoggingManager.instance().warning(traceback.format_exc()) finally: self.vertex_array.release() program.release() if self.render_to_texture_attachment != -1: shape = self.locked_render_to_texture_array.ndarray.shape self.locked_render_to_texture_array.___tex___.bind() GL.glReadBuffer( self.draw_buffers[self.render_to_texture_attachment]) r = GL.glReadPixels(0, 0, shape[0], shape[1], GL.GL_RGBA, GL.GL_UNSIGNED_INT_8_8_8_8) self.locked_render_to_texture_array.___tex___.release() self.locked_render_to_texture_array.ndarray = np.flipud( r.view(np.uint8).reshape(shape[1], shape[0], 4)[..., ::-1]) self.update() def createFrameBufferObject(self, size): f = QOpenGLFramebufferObjectFormat() f.setAttachment(QOpenGLFramebufferObject.CombinedDepthStencil) f.setSamples(4) return QOpenGLFramebufferObject(size, f)