Esempio n. 1
0
    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
Esempio n. 2
0
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)
Esempio n. 3
0
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)