Example #1
0
    def _setupSharedVertexData(self):
        """
        Gather data for the drawing pattern Vertex and Index Buffer Objects
        shared by all hunks of the same primitive type.  The drawing pattern is
        replicated HUNK_SIZE times, and sent to graphics card RAM for use in
        every draw command for collections of primitives of this type.
        
        In theory, the vertex shader processes each vertex only once, even if
        it is indexed many times in different faces within the same draw.  In
        practice, locality of vertex referencing in the drawing pattern is
        optimal, since there may be a cache of the most recent N transformed
        vertices in that stage of the drawing pipeline on graphics card.
        
        For indexed gl(Multi)DrawElements, the index is a list of faces
        (typically triangles or quads, specified by the drawingMode.)  Each
        face is represented by a block of subscripts into the vertex block.

        For glMultiDrawElements, there is an additional pair of arrays that give
        offsets into blocks of the index block list, and the lengths of each
        block of indices.  Because the index blocks are all the same length to
        draw individual primitives, we set up a single array containing a Hunk's
        worth of the index block length constant and use it for each Hunk draw.
        """
        self.nIndices = len(self.indexBlock) * len(self.indexBlock[0])
        indexOffset = 0
        # (May cache these someday.  No need now since they don't change.)
        Py_iboIndices = []
        Py_vboVerts = []
        for i in range(HUNK_SIZE):
            # Accumulate a hunk full of blocks of vertices.  Each block is
            # identical, with coordinates relative to its local primitive
            # origin.  Hence, the vertex VBO may be shared among all hunks of
            # primitives of the same type.  A per-vertex attribute gives the
            # spatial location of the primitive origin, and is combined with the
            # local vertex coordinates in the vertex shader program.
            Py_vboVerts += self.vertexBlock

            # Accumulate a hunk full of index blocks, offsetting the indices in
            # each block to point to the vertices for the corresponding
            # primitive block in the vertex hunk.
            for face in self.indexBlock:
                Py_iboIndices += [idx + indexOffset for idx in face]
                continue
            indexOffset += self.nVertices
            continue

        # Push shared vbo/ibo hunk data through C to the graphics card RAM.
        C_vboVerts = numpy.array(Py_vboVerts, dtype=numpy.float32)
        self.hunkVertVBO = GLBufferObject(GL_ARRAY_BUFFER_ARB, C_vboVerts, GL_STATIC_DRAW)
        self.hunkVertVBO.unbind()

        C_iboIndices = numpy.array(Py_iboIndices, dtype=numpy.uint32)
        self.hunkIndexIBO = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB, C_iboIndices, GL_STATIC_DRAW)
        self.hunkIndexIBO.unbind()

        # A Hunk-length of index block length constants for glMultiDrawElements.
        self.C_indexBlockLengths = numpy.array(HUNK_SIZE * [self.nIndices], dtype=numpy.uint32)
        return
Example #2
0
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.
        
        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.

        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return
Example #4
0
def setup_drawer():
    """
    Set up the usual constant display lists in the current OpenGL context.

    WARNING: THIS IS ONLY CORRECT IF ONLY ONE GL CONTEXT CONTAINS DISPLAY LISTS
    -- or more precisely, only the GL context this has last been called in (or
    one which shares its display lists) will work properly with the routines in
    drawer.py, since the allocated display list names are stored in globals set
    by this function, but in general those names might differ if this was called
    in different GL contexts.
    """
    #bruce 060613 added docstring, cleaned up display list name allocation
    # bruce 071030 renamed from setup to setup_drawer

    spherelistbase = glGenLists(numSphereSizes)
    sphereList = []
    for i in range(numSphereSizes):
        sphereList += [spherelistbase + i]
        glNewList(sphereList[i], GL_COMPILE)
        glBegin(GL_TRIANGLE_STRIP)  # GL_LINE_LOOP to see edges.
        stripVerts = getSphereTriStrips(i)
        for vertNorm in stripVerts:
            glNormal3fv(vertNorm)
            glVertex3fv(vertNorm)
            continue
        glEnd()
        glEndList()
        continue
    drawing_globals.sphereList = sphereList

    # Sphere triangle-strip vertices for each level of detail.
    # (Cache and re-use the work of making them.)
    # Can use in converter-wrappered calls like glVertexPointerfv,
    # but the python arrays are re-copied to C each time.
    sphereArrays = []
    for i in range(numSphereSizes):
        sphereArrays += [getSphereTriStrips(i)]
        continue
    drawing_globals.sphereArrays = sphereArrays

    # Sphere glDrawArrays triangle-strip vertices for C calls.
    # (Cache and re-use the work of converting a C version.)
    # Used in thinly-wrappered calls like glVertexPointer.
    sphereCArrays = []
    for i in range(numSphereSizes):
        CArray = numpy.array(sphereArrays[i], dtype=numpy.float32)
        sphereCArrays += [CArray]
        continue
    drawing_globals.sphereCArrays = sphereCArrays

    # Sphere indexed vertices.
    # (Cache and re-use the work of making the indexes.)
    # Can use in converter-wrappered calls like glDrawElementsui,
    # but the python arrays are re-copied to C each time.
    sphereElements = []  # Pairs of lists (index, verts) .
    for i in range(numSphereSizes):
        sphereElements += [indexVerts(sphereArrays[i], .0001)]
        continue
    drawing_globals.sphereElements = sphereElements

    # Sphere glDrawElements index and vertex arrays for C calls.
    sphereCIndexTypes = []  # numpy index unsigned types.
    sphereGLIndexTypes = []  # GL index types for drawElements.
    sphereCElements = []  # Pairs of numpy arrays (Cindex, Cverts) .
    for i in range(numSphereSizes):
        (index, verts) = sphereElements[i]
        if len(index) < 256:
            Ctype = numpy.uint8
            GLtype = GL_UNSIGNED_BYTE
        else:
            Ctype = numpy.uint16
            GLtype = GL_UNSIGNED_SHORT
            pass
        sphereCIndexTypes += [Ctype]
        sphereGLIndexTypes += [GLtype]
        sphereCIndex = numpy.array(index, dtype=Ctype)
        sphereCVerts = numpy.array(verts, dtype=numpy.float32)
        sphereCElements += [(sphereCIndex, sphereCVerts)]
        continue
    drawing_globals.sphereCIndexTypes = sphereCIndexTypes
    drawing_globals.sphereGLIndexTypes = sphereGLIndexTypes
    drawing_globals.sphereCElements = sphereCElements

    if glGetString(GL_EXTENSIONS).find("GL_ARB_vertex_buffer_object") >= 0:

        # A GLBufferObject version for glDrawArrays.
        sphereArrayVBOs = []
        for i in range(numSphereSizes):
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB, sphereCArrays[i],
                                 GL_STATIC_DRAW)
            sphereArrayVBOs += [vbo]
            continue
        drawing_globals.sphereArrayVBOs = sphereArrayVBOs

        # A GLBufferObject version for glDrawElements indexed verts.
        sphereElementVBOs = []  # Pairs of (IBO, VBO)
        for i in range(numSphereSizes):
            ibo = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB,
                                 sphereCElements[i][0], GL_STATIC_DRAW)
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB, sphereCElements[i][1],
                                 GL_STATIC_DRAW)
            sphereElementVBOs += [(ibo, vbo)]
            continue
        drawing_globals.sphereElementVBOs = sphereElementVBOs

        ibo.unbind()
        vbo.unbind()
        pass

    #bruce 060415
    drawing_globals.wiresphere1list = wiresphere1list = glGenLists(1)
    glNewList(wiresphere1list, GL_COMPILE)
    didlines = {}  # don't draw each triangle edge more than once

    def shoulddoline(v1, v2):
        # make sure not list (unhashable) or Numeric array (bug in __eq__)
        v1 = tuple(v1)
        v2 = tuple(v2)
        if (v1, v2) not in didlines:
            didlines[(v1, v2)] = didlines[(v2, v1)] = None
            return True
        return False

    def doline(v1, v2):
        if shoulddoline(v1, v2):
            glVertex3fv(v1)
            glVertex3fv(v2)
        return

    glBegin(GL_LINES)
    ocdec = getSphereTriangles(1)
    for tri in ocdec:
        #e Could probably optim this more, e.g. using a vertex array or VBO or
        #  maybe GL_LINE_STRIP.
        doline(tri[0], tri[1])
        doline(tri[1], tri[2])
        doline(tri[2], tri[0])
    glEnd()
    glEndList()

    drawing_globals.CylList = CylList = glGenLists(1)
    glNewList(CylList, GL_COMPILE)
    glBegin(GL_TRIANGLE_STRIP)
    for (vtop, ntop, vbot, nbot) in drawing_globals.cylinderEdges:
        glNormal3fv(nbot)
        glVertex3fv(vbot)
        glNormal3fv(ntop)
        glVertex3fv(vtop)
    glEnd()
    glEndList()

    drawing_globals.CapList = CapList = glGenLists(1)
    glNewList(CapList, GL_COMPILE)
    glNormal3fv(drawing_globals.cap0n)
    glBegin(GL_POLYGON)
    for p in drawing_globals.drum0:
        glVertex3fv(p)
    glEnd()
    glNormal3fv(drawing_globals.cap1n)
    glBegin(GL_POLYGON)
    #bruce 060609 fix "ragged edge" bug in this endcap: drum1 -> drum2
    for p in drawing_globals.drum2:
        glVertex3fv(p)
    glEnd()
    glEndList()

    drawing_globals.diamondGridList = diamondGridList = glGenLists(1)
    glNewList(diamondGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.digrid:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.lonsGridList = lonsGridList = glGenLists(1)
    glNewList(lonsGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.lonsEdges:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.CubeList = CubeList = glGenLists(1)
    glNewList(CubeList, GL_COMPILE)
    glBegin(GL_QUAD_STRIP)
    # note: CubeList has only 4 faces of the cube; only suitable for use in
    # wireframes; see also solidCubeList [bruce 051215 comment reporting
    # grantham 20051213 observation]
    glVertex((-1, -1, -1))
    glVertex((1, -1, -1))
    glVertex((-1, 1, -1))
    glVertex((1, 1, -1))
    glVertex((-1, 1, 1))
    glVertex((1, 1, 1))
    glVertex((-1, -1, 1))
    glVertex((1, -1, 1))
    glVertex((-1, -1, -1))
    glVertex((1, -1, -1))
    glEnd()
    glEndList()

    drawing_globals.solidCubeList = solidCubeList = glGenLists(1)
    glNewList(solidCubeList, GL_COMPILE)
    glBegin(GL_QUADS)
    for i in xrange(len(drawing_globals.cubeIndices)):
        avenormals = V(0, 0, 0)  #bruce 060302 fixed normals for flat shading
        for j in xrange(4):
            nTuple = tuple(
                drawing_globals.cubeNormals[drawing_globals.cubeIndices[i][j]])
            avenormals += A(nTuple)
        avenormals = norm(avenormals)
        for j in xrange(4):
            vTuple = tuple(drawing_globals.cubeVertices[
                drawing_globals.cubeIndices[i][j]])
            #bruce 060302 made size compatible with glut.glutSolidCube(1.0)
            vTuple = A(vTuple) * 0.5
            glNormal3fv(avenormals)
            glVertex3fv(vTuple)
    glEnd()
    glEndList()

    drawing_globals.rotSignList = rotSignList = glGenLists(1)
    glNewList(rotSignList, GL_COMPILE)
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS0n)):
        glVertex3fv(tuple(drawing_globals.rotS0n[ii]))
    glEnd()
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS1n)):
        glVertex3fv(tuple(drawing_globals.rotS1n[ii]))
    glEnd()
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.arrow0Vertices + drawing_globals.arrow1Vertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearArrowList = linearArrowList = glGenLists(1)
    glNewList(linearArrowList, GL_COMPILE)
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.linearArrowVertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearLineList = linearLineList = glGenLists(1)
    glNewList(linearLineList, GL_COMPILE)
    glEnable(GL_LINE_SMOOTH)
    glBegin(GL_LINES)
    glVertex3f(0.0, 0.0, -drawing_globals.halfHeight)
    glVertex3f(0.0, 0.0, drawing_globals.halfHeight)
    glEnd()
    glDisable(GL_LINE_SMOOTH)
    glEndList()

    drawing_globals.circleList = circleList = glGenLists(1)
    glNewList(circleList, GL_COMPILE)
    glBegin(GL_LINE_LOOP)
    for ii in range(60):
        x = cos(ii * 2.0 * pi / 60)
        y = sin(ii * 2.0 * pi / 60)
        glVertex3f(x, y, 0.0)
    glEnd()
    glEndList()

    # piotr 080405
    drawing_globals.filledCircleList = filledCircleList = glGenLists(1)
    glNewList(filledCircleList, GL_COMPILE)
    glBegin(GL_POLYGON)
    for ii in range(60):
        x = cos(ii * 2.0 * pi / 60)
        y = sin(ii * 2.0 * pi / 60)
        glVertex3f(x, y, 0.0)
    glEnd()
    glEndList()

    drawing_globals.lineCubeList = lineCubeList = glGenLists(1)
    glNewList(lineCubeList, GL_COMPILE)
    glBegin(GL_LINES)
    cvIndices = [
        0, 1, 2, 3, 4, 5, 6, 7, 0, 3, 1, 2, 5, 6, 4, 7, 0, 4, 1, 5, 2, 6, 3, 7
    ]
    for i in cvIndices:
        glVertex3fv(tuple(drawing_globals.cubeVertices[i]))
    glEnd()
    glEndList()

    # Debug Preferences
    from utilities.debug_prefs import debug_pref, Choice_boolean_True
    from utilities.debug_prefs import Choice_boolean_False
    choices = [Choice_boolean_False, Choice_boolean_True]

    # 20060314 grantham
    initial_choice = choices[drawing_globals.allow_color_sorting_default]
    drawing_globals.allow_color_sorting_pref = debug_pref(
        "Use Color Sorting?",
        initial_choice,
        prefs_key=drawing_globals.allow_color_sorting_prefs_key)
    #bruce 060323 removed non_debug = True for A7 release, changed default
    #value to False (far above), and changed its prefs_key so developers
    #start with the new default value.
    #russ 080225: Added.
    initial_choice = choices[drawing_globals.use_color_sorted_dls_default]
    drawing_globals.use_color_sorted_dls_pref = debug_pref(
        "Use Color-sorted Display Lists?",
        initial_choice,
        prefs_key=drawing_globals.use_color_sorted_dls_prefs_key)
    #russ 080225: Added.
    initial_choice = choices[drawing_globals.use_color_sorted_vbos_default]
    drawing_globals.use_color_sorted_vbos_pref = debug_pref(
        "Use Color-sorted Vertex Buffer Objects?",
        initial_choice,
        prefs_key=drawing_globals.use_color_sorted_vbos_prefs_key)

    #russ 080403: Added drawing variant selection
    variants = [
        "0. OpenGL 1.0 - glBegin/glEnd tri-strips vertex-by-vertex.",
        "1. OpenGL 1.1 - glDrawArrays from CPU RAM.",
        "2. OpenGL 1.1 - glDrawElements indexed arrays from CPU RAM.",
        "3. OpenGL 1.5 - glDrawArrays from graphics RAM VBO.",
        "4. OpenGL 1.5 - glDrawElements, verts in VBO, index in CPU.",
        "5. OpenGL 1.5 - VBO/IBO buffered glDrawElements."
    ]
    drawing_globals.use_drawing_variant = debug_pref(
        "GLPane: drawing method",
        Choice(names=variants,
               values=range(len(variants)),
               defaultValue=drawing_globals.use_drawing_variant_default),
        prefs_key=drawing_globals.use_drawing_variant_prefs_key)

    # temporarily always print this, while default setting might be in flux,
    # and to avoid confusion if the two necessary prefs are set differently
    # [bruce 080305]
    if (drawing_globals.allow_color_sorting_pref
            and drawing_globals.use_color_sorted_dls_pref):
        print "\nnote: this session WILL use color sorted display lists"
    else:
        print "\nnote: this session will NOT use color sorted display lists"
    if (drawing_globals.allow_color_sorting_pref
            and drawing_globals.use_color_sorted_vbos_pref):
        print "note: this session WILL use", \
              "color sorted Vertex Buffer Objects\n"
    else:
        print "note: this session will NOT use", \
              "color sorted Vertex Buffer Objects\n"

    # 20060313 grantham Added use_c_renderer debug pref, can
    # take out when C renderer used by default.
    if drawing_globals.quux_module_import_succeeded:
        initial_choice = choices[drawing_globals.use_c_renderer_default]
        drawing_globals.use_c_renderer = (debug_pref(
            "Use native C renderer?",
            initial_choice,
            prefs_key=drawing_globals.use_c_renderer_prefs_key))
        #bruce 060323 removed non_debug = True for A7 release, and changed
        # its prefs_key so developers start over with the default value.

    #initTexture('C:\\Huaicai\\atom\\temp\\newSample.png', 128,128)
    return  # from setup_drawer
Example #5
0
class Hunk:
    """
    Helper class to wrap low-level VBO objects with data caches, change ranges,
    and flushing of the changed range to the hunk VBO in graphics card RAM.
    """
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.
        
        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return

    def unchanged(self):
        """
        Mark a Hunk as unchanged.  (Empty or flushed.) 
        """
        self.low = self.high = 0
        return

    def changedRange(self, chgLowID, chgHighID):
        """
        Record a range of data changes, for later flushing.

        chgLowID and chgHighID are primitive IDs, possibly spanning many hunks.

        The high end is the index of the one *after* the end of the range, as
        usual in Python.
        """
        (lowHunk, lowIndex) = decodePrimID(chgLowID)
        (highHunk, highIndex) = decodePrimID(chgHighID)

        if self.hunkNumber < lowHunk or self.hunkNumber > highHunk:
            return  # This hunk is not in the hunk range.

        if lowHunk < self.hunkNumber:
            self.low = 0
        else:
            self.low = min(self.low, lowIndex)  # Maybe extend the range.
            pass

        if highHunk > self.hunkNumber:
            self.high = HUNK_SIZE
        else:
            self.high = max(self.high, highIndex)  # Maybe extend the range.
            pass

        return

    def flush(self, allData):
        """
        Update a changed range of the data that applies to this hunk, sending it
        to the Buffer Object in graphics card RAM.

        allData - List of data blocks for the whole hunk-list.  We'll extract
        just the part relevant to the changed part of this particular hunk.

        Internally, the data is a list, block-indexed by primitive ID, but
        replicated in block sublists by a factor of self.nVertices to match the
        vertex buffer size.  What reaches the attribute VBO is automatically
        flattened into a sequence.
        """
        rangeSize = self.high - self.low
        assert rangeSize >= 0
        if rangeSize == 0:
            # Nothing to do.
            return

        lowID = (self.hunkNumber * HUNK_SIZE) + self.low
        highID = (self.hunkNumber * HUNK_SIZE) + self.high

        # Send all or part of the Python data to C.
        C_data = numpy.array(allData[lowID:highID], dtype=numpy.float32)

        if rangeSize == HUNK_SIZE:
            # Special case to send the whole Hunk's worth of data.
            self.VBO.updateAll(C_data)
        else:
            # Send a portion of the HunkBuffer, with byte offset within the VBO.
            # (Per-vertex attributes are all multiples (1-4) of Float32.)
            offset = self.low * self.nVertices * self.nCoords * BYTES_PER_FLOAT
            self.VBO.update(offset, C_data)
            pass

        self.unchanged()  # Now we're in sync.
        return

    pass  # End of class HunkBuffer.
Example #6
0
    def _setupSharedVertexData(self):
        """
        Gather data for the drawing pattern Vertex and Index Buffer Objects
        shared by all hunks of the same primitive type.  The drawing pattern is
        replicated HUNK_SIZE times, and sent to graphics card RAM for use in
        every draw command for collections of primitives of this type.
        
        In theory, the vertex shader processes each vertex only once, even if
        it is indexed many times in different faces within the same draw.  In
        practice, locality of vertex referencing in the drawing pattern is
        optimal, since there may be a cache of the most recent N transformed
        vertices in that stage of the drawing pipeline on graphics card.
        
        For indexed gl(Multi)DrawElements, the index is a list of faces
        (typically triangles or quads, specified by the drawingMode.)  Each
        face is represented by a block of subscripts into the vertex block.

        For glMultiDrawElements, there is an additional pair of arrays that give
        offsets into blocks of the index block list, and the lengths of each
        block of indices.  Because the index blocks are all the same length to
        draw individual primitives, we set up a single array containing a Hunk's
        worth of the index block length constant and use it for each Hunk draw.
        """
        self.nIndices = len(self.indexBlock) * len(self.indexBlock[0])
        indexOffset = 0
        # (May cache these someday.  No need now since they don't change.)
        Py_iboIndices = []
        Py_vboVerts = []
        for i in range(HUNK_SIZE):
            # Accumulate a hunk full of blocks of vertices.  Each block is
            # identical, with coordinates relative to its local primitive
            # origin.  Hence, the vertex VBO may be shared among all hunks of
            # primitives of the same type.  A per-vertex attribute gives the
            # spatial location of the primitive origin, and is combined with the
            # local vertex coordinates in the vertex shader program.
            Py_vboVerts += self.vertexBlock

            # Accumulate a hunk full of index blocks, offsetting the indices in
            # each block to point to the vertices for the corresponding
            # primitive block in the vertex hunk.
            for face in self.indexBlock:
                Py_iboIndices += [idx + indexOffset for idx in face]
                continue
            indexOffset += self.nVertices
            continue

        # Push shared vbo/ibo hunk data through C to the graphics card RAM.
        C_vboVerts = numpy.array(Py_vboVerts, dtype=numpy.float32)
        self.hunkVertVBO = GLBufferObject(GL_ARRAY_BUFFER_ARB, C_vboVerts,
                                          GL_STATIC_DRAW)
        self.hunkVertVBO.unbind()

        C_iboIndices = numpy.array(Py_iboIndices, dtype=numpy.uint32)
        self.hunkIndexIBO = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB,
                                           C_iboIndices, GL_STATIC_DRAW)
        self.hunkIndexIBO.unbind()

        # A Hunk-length of index block length constants for glMultiDrawElements.
        self.C_indexBlockLengths = numpy.array(HUNK_SIZE * [self.nIndices],
                                               dtype=numpy.uint32)
        return
Example #7
0
class GLPrimitiveBuffer(object):
    """
    Manage VBO space for drawing primitives in large batches.
    """

    # default values of instance variables
    transform_id_Hunks = None

    def __init__(self, shaderGlobals):
        """
        Fill in the vertex VBO and IBO drawing pattern for this primitive type.

        @param shaderGlobals: the instance of class ShaderGlobals
            we will be associated with, used for its .shader
            and various related constants.
        """
        # set localvars as follows, for the drawing pattern for VBOs/IBOs
        # for this primitive type:
        #
        # shader - The GLShaderObject to use.
        #
        # drawingMode - What kind of primitives to render, e.g. GL_QUADS.
        #
        # vertexBlock, indexBlock - Single blocks (lists) of vertices and indices
        # making up the drawing pattern for this primitive.
        # See description in the module docstrings for this class or its subclasses.
        if debug_pref("GLPane: use billboard primitives? (next session)",
                      Choice_boolean_True,
                      prefs_key=True):
            drawingMode = GL_QUADS
            vertexBlock = shaderGlobals.billboardVerts
            indexBlock = shaderGlobals.billboardIndices
            pass
        else:
            drawingMode = GL_QUADS
            vertexBlock = shaderGlobals.shaderCubeVerts
            indexBlock = shaderGlobals.shaderCubeIndices
            pass

        # Remember the shader.
        self.shader = shader = shaderGlobals.shader

        # Shared data for drawing calls to follow.
        self.drawingMode = drawingMode

        # Remember the drawing pattern.  (We may want to make transformed vertex
        # blocks someday, losing the one-hunk-per-type goodness in favor of some
        # other greater goodness like minimizing use of constant registers.)
        # See below for filling the vertex VBO and IBO with these.
        self.nVertices = len(vertexBlock)
        self.vertexBlock = vertexBlock
        self.indexBlock = indexBlock

        # Allocation of primitives within hunks.
        self.nPrims = 0  # Number of primitives allocated.
        self.freePrimSlotIDs = []  # Free list of freed primitives.
        self.nHunks = 0  # Number of hunks allocated.

        # Common per-vertex attribute hunk VBOs for all primitive types.
        # (The hunkVertVBO and hunkIndexIBO are shared by all hunks.)
        nVerts = self.nVertices
        self.colorHunks = HunkBuffer(shader, "color", nVerts, 4)
        self.glname_color_Hunks = HunkBuffer(shader, "glname_color", nVerts, 4)

        # Subclasses may add their own attributes to the hunkBuffers list,
        # beyond the ones we add here:
        self.hunkBuffers = [
            self.colorHunks,
            self.glname_color_Hunks,
        ]

        if shader.supports_transforms():
            self.transform_id_Hunks = HunkBuffer(shader, "transform_id",
                                                 nVerts, 1)
            self.hunkBuffers += [self.transform_id_Hunks]

        # Support for lazily updating drawing caches, namely a timestamp showing
        # when this GLPrimitiveBuffer was last flushed to graphics card RAM.
        self.flushed = drawing_constants.NO_EVENT_YET

        # Fill in shared data in the graphics card RAM.
        self._setupSharedVertexData()

        # Cached info for blocks of transforms.
        # Transforms here are lists (or Numpy arrays) of 16 numbers.
        self.transforms = []
        self.identityTransform = ([1.0] + 4 * [0.0]) * 3 + [1.0]

        return

    def color4(self, color):
        """
        Minor helper function for colors.  Converts a given (R, G, B) 3-tuple to
        (R, G, B, A) by adding an opacity of 1.0 .
        """
        if len(color) == 3:
            # Add opacity to color if missing.
            color = (color[0], color[1], color[2], 1.0)
            pass
        return color

    def newPrimitives(self, n):
        """
        Allocate a group of primitives. Returns a list of n IDs.
        """
        primIDs = []
        for i in range(n):
            # Take ones from the free list first.
            if len(self.freePrimSlotIDs):
                primID = self.freePrimSlotIDs.pop()
            else:
                # Allocate a new one.
                primID = self.nPrims  # ID is a zero-origin subscript.
                self.nPrims += 1  # nPrims is a counter.

                # Allocate another set of hunks if the new ID has passed a hunk
                # boundary.
                if (primID + HUNK_SIZE) / HUNK_SIZE > self.nHunks:
                    for buffer in self.hunkBuffers:
                        buffer.addHunk()
                        continue
                    self.nHunks += 1
                pass
            primIDs += [primID]
            continue
        return primIDs

    def releasePrimitives(self, idList):
        """
        Release the given primitive IDs into the free-list.
        """
        self.freePrimSlotIDs += idList
        return

    def _setupSharedVertexData(self):
        """
        Gather data for the drawing pattern Vertex and Index Buffer Objects
        shared by all hunks of the same primitive type.  The drawing pattern is
        replicated HUNK_SIZE times, and sent to graphics card RAM for use in
        every draw command for collections of primitives of this type.
        
        In theory, the vertex shader processes each vertex only once, even if
        it is indexed many times in different faces within the same draw.  In
        practice, locality of vertex referencing in the drawing pattern is
        optimal, since there may be a cache of the most recent N transformed
        vertices in that stage of the drawing pipeline on graphics card.
        
        For indexed gl(Multi)DrawElements, the index is a list of faces
        (typically triangles or quads, specified by the drawingMode.)  Each
        face is represented by a block of subscripts into the vertex block.

        For glMultiDrawElements, there is an additional pair of arrays that give
        offsets into blocks of the index block list, and the lengths of each
        block of indices.  Because the index blocks are all the same length to
        draw individual primitives, we set up a single array containing a Hunk's
        worth of the index block length constant and use it for each Hunk draw.
        """
        self.nIndices = len(self.indexBlock) * len(self.indexBlock[0])
        indexOffset = 0
        # (May cache these someday.  No need now since they don't change.)
        Py_iboIndices = []
        Py_vboVerts = []
        for i in range(HUNK_SIZE):
            # Accumulate a hunk full of blocks of vertices.  Each block is
            # identical, with coordinates relative to its local primitive
            # origin.  Hence, the vertex VBO may be shared among all hunks of
            # primitives of the same type.  A per-vertex attribute gives the
            # spatial location of the primitive origin, and is combined with the
            # local vertex coordinates in the vertex shader program.
            Py_vboVerts += self.vertexBlock

            # Accumulate a hunk full of index blocks, offsetting the indices in
            # each block to point to the vertices for the corresponding
            # primitive block in the vertex hunk.
            for face in self.indexBlock:
                Py_iboIndices += [idx + indexOffset for idx in face]
                continue
            indexOffset += self.nVertices
            continue

        # Push shared vbo/ibo hunk data through C to the graphics card RAM.
        C_vboVerts = numpy.array(Py_vboVerts, dtype=numpy.float32)
        self.hunkVertVBO = GLBufferObject(GL_ARRAY_BUFFER_ARB, C_vboVerts,
                                          GL_STATIC_DRAW)
        self.hunkVertVBO.unbind()

        C_iboIndices = numpy.array(Py_iboIndices, dtype=numpy.uint32)
        self.hunkIndexIBO = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB,
                                           C_iboIndices, GL_STATIC_DRAW)
        self.hunkIndexIBO.unbind()

        # A Hunk-length of index block length constants for glMultiDrawElements.
        self.C_indexBlockLengths = numpy.array(HUNK_SIZE * [self.nIndices],
                                               dtype=numpy.uint32)
        return

    def draw(self,
             drawIndex=None,
             highlighted=False,
             selected=False,
             patterning=True,
             highlight_color=None,
             opacity=1.0):
        """
        Draw the buffered geometry, binding vertex attribute values for the
        shaders.

        If no drawIndex is given, the whole array is drawn.
        """
        self.shader.setActive(True)  # Turn on the chosen shader.

        glEnableClientState(GL_VERTEX_ARRAY)

        self.shader.setupDraw(highlighted, selected, patterning,
                              highlight_color, opacity)

        # XXX No transform data until that is more implemented.
        ###self.shader.setupTransforms(self.transforms)
        # (note: the reason TransformControls work in their test case
        #  is due to a manual call of shader.setupTransforms. [bruce 090306 guess])
        if self.shader.get_TEXTURE_XFORMS():
            # Activate a texture unit for transforms.
            ## XXX Not necessary for custom shader programs.
            ##glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, self.transform_memory)
            ### BUG: pylint warning:
            # Instance of 'GLPrimitiveBuffer' has no 'transform_memory' member
            #### REVIEW: should this be the attr of that name in GLShaderObject,
            # i.e. self.shader? I didn't fix it myself as a guess, in case other
            # uses of self also need fixing in the same way. [bruce 090304 comment]

            # Set the sampler to the handle for the active texture image (0).
            ## XXX Not needed if only one texture is being used?
            ##glActiveTexture(GL_TEXTURE0)
            ##glUniform1iARB(self.shader._uniform("transforms"), 0)
            pass

        glDisable(GL_CULL_FACE)

        # Draw the hunks.
        for hunkNumber in range(self.nHunks):
            # Bind the per-vertex generic attribute arrays for one hunk.
            for buffer in self.hunkBuffers:
                buffer.flush()  # Sync graphics card copies of the VBO data.
                buffer.bindHunk(hunkNumber)
                continue

            # Shared vertex coordinate data VBO: GL_ARRAY_BUFFER_ARB.
            self.hunkVertVBO.bind()
            glVertexPointer(3, GL_FLOAT, 0, None)

            # Shared vertex index data IBO: GL_ELEMENT_ARRAY_BUFFER_ARB
            self.hunkIndexIBO.bind()

            if drawIndex is not None:
                # Draw the selected primitives for this Hunk.
                index = drawIndex[hunkNumber]
                primcount = len(index)
                glMultiDrawElementsVBO(self.drawingMode,
                                       self.C_indexBlockLengths,
                                       GL_UNSIGNED_INT, index, primcount)
            else:
                # For initial testing, draw all primitives in the Hunk.
                if hunkNumber < self.nHunks - 1:
                    nToDraw = HUNK_SIZE  # Hunks before the last.
                else:
                    nToDraw = self.nPrims - (self.nHunks - 1) * HUNK_SIZE
                    pass
                glDrawElements(self.drawingMode, self.nIndices * nToDraw,
                               GL_UNSIGNED_INT, None)
                pass
            continue

        self.shader.setActive(False)  # Turn off the chosen shader.
        glEnable(GL_CULL_FACE)

        self.hunkIndexIBO.unbind()  # Deactivate the ibo.
        self.hunkVertVBO.unbind()  # Deactivate all vbo's.

        glDisableClientState(GL_VERTEX_ARRAY)
        for buffer in self.hunkBuffers:
            buffer.unbindHunk()  # glDisableVertexAttribArrayARB.
            continue
        return

    def makeDrawIndex(self, prims):
        """
        Make a drawing index to be used by glMultiDrawElements on Hunk data.

        The return is a list of offset arrays, one for each Hunk of primitives.
        Each value in an offset array gives the *byte* offset in the IBO to the
        block of indices for a given primitive ID within the Hunk.
        """
        hunkOffsets = [[] for i in range(self.nHunks)]

        # Collect the offsets to index blocks for individual primitives.
        for primID in prims:
            (hunk, index) = decodePrimID(primID)
            hunkOffsets[hunk] += [index * self.nIndices * BYTES_PER_UINT]
            continue

        # The indices for each hunk could be sorted here, which might go faster.
        # (But don't worry about it if drawing all primitives in random order is
        # as fast as the test that draws all primitives with glDrawElements.)
        #
        # The sorted offsets arrays could also be reduced to draw contiguous
        # runs of primitives, rather than single primitives.  A list of run
        # lengths for each Hunk would have to be produced as well, rather than
        # using the shared list of constant lengths for individual primitives.

        # Push the offset arrays into C for faster use by glMultiDrawElements.
        C_hunkOffsets = [
            numpy.array(offset, dtype=numpy.uint32) for offset in hunkOffsets
        ]
        return C_hunkOffsets

    pass  # End of class GLPrimitiveBuffer.
Example #8
0
def setup_drawer():
    """
    Set up the usual constant display lists in the current OpenGL context.

    WARNING: THIS IS ONLY CORRECT IF ONLY ONE GL CONTEXT CONTAINS DISPLAY LISTS
    -- or more precisely, only the GL context this has last been called in (or
    one which shares its display lists) will work properly with the routines in
    drawer.py, since the allocated display list names are stored in globals set
    by this function, but in general those names might differ if this was called
    in different GL contexts.
    """
    spherelistbase = glGenLists(_NUM_SPHERE_SIZES)
    sphereList = []
    for i in range(_NUM_SPHERE_SIZES):
        sphereList += [spherelistbase+i]
        glNewList(sphereList[i], GL_COMPILE)
        glBegin(GL_TRIANGLE_STRIP) # GL_LINE_LOOP to see edges
        stripVerts = getSphereTriStrips(i)
        for vertNorm in stripVerts:
            glNormal3fv(vertNorm)
            glVertex3fv(vertNorm)
            continue
        glEnd()
        glEndList()
        continue
    drawing_globals.sphereList = sphereList

    # Sphere triangle-strip vertices for each level of detail.
    # (Cache and re-use the work of making them.)
    # Can use in converter-wrappered calls like glVertexPointerfv,
    # but the python arrays are re-copied to C each time.
    sphereArrays = []
    for i in range(_NUM_SPHERE_SIZES):
        sphereArrays += [getSphereTriStrips(i)]
        continue
    drawing_globals.sphereArrays = sphereArrays

    # Sphere glDrawArrays triangle-strip vertices for C calls.
    # (Cache and re-use the work of converting a C version.)
    # Used in thinly-wrappered calls like glVertexPointer.
    sphereCArrays = []
    for i in range(_NUM_SPHERE_SIZES):
        CArray = numpy.array(sphereArrays[i], dtype = numpy.float32)
        sphereCArrays += [CArray]
        continue
    drawing_globals.sphereCArrays = sphereCArrays

    # Sphere indexed vertices.
    # (Cache and re-use the work of making the indexes.)
    # Can use in converter-wrappered calls like glDrawElementsui,
    # but the python arrays are re-copied to C each time.
    sphereElements = []             # Pairs of lists (index, verts) .
    for i in range(_NUM_SPHERE_SIZES):
        sphereElements += [indexVerts(sphereArrays[i], .0001)]
        continue
    drawing_globals.sphereElements = sphereElements

    # Sphere glDrawElements index and vertex arrays for C calls.
    sphereCIndexTypes = []          # numpy index unsigned types.
    sphereGLIndexTypes = []         # GL index types for drawElements.
    sphereCElements = []            # Pairs of numpy arrays (Cindex, Cverts) .
    for i in range(_NUM_SPHERE_SIZES):
        (index, verts) = sphereElements[i]
        if len(index) < 256:
            Ctype = numpy.uint8
            GLtype = GL_UNSIGNED_BYTE
        else:
            Ctype = numpy.uint16
            GLtype = GL_UNSIGNED_SHORT
            pass
        sphereCIndexTypes += [Ctype]
        sphereGLIndexTypes += [GLtype]
        sphereCIndex = numpy.array(index, dtype = Ctype)
        sphereCVerts = numpy.array(verts, dtype = numpy.float32)
        sphereCElements += [(sphereCIndex, sphereCVerts)]
        continue
    drawing_globals.sphereCIndexTypes = sphereCIndexTypes
    drawing_globals.sphereGLIndexTypes = sphereGLIndexTypes
    drawing_globals.sphereCElements = sphereCElements

    if glGetString(GL_EXTENSIONS).find("GL_ARB_vertex_buffer_object") >= 0:

        # A GLBufferObject version for glDrawArrays.
        sphereArrayVBOs = []
        for i in range(_NUM_SPHERE_SIZES):
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB,
                                 sphereCArrays[i], GL_STATIC_DRAW)
            sphereArrayVBOs += [vbo]
            continue
        drawing_globals.sphereArrayVBOs = sphereArrayVBOs

        # A GLBufferObject version for glDrawElements indexed verts.
        sphereElementVBOs = []              # Pairs of (IBO, VBO)
        for i in range(_NUM_SPHERE_SIZES):
            ibo = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB,
                                 sphereCElements[i][0], GL_STATIC_DRAW)
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB,
                                 sphereCElements[i][1], GL_STATIC_DRAW)
            sphereElementVBOs += [(ibo, vbo)]
            continue
        drawing_globals.sphereElementVBOs = sphereElementVBOs

        ibo.unbind()
        vbo.unbind()
        pass

    #bruce 060415
    drawing_globals.wiresphere1list = wiresphere1list = glGenLists(1)
    glNewList(wiresphere1list, GL_COMPILE)
    didlines = {} # don't draw each triangle edge more than once

    def shoulddoline(v1,v2):
        # make sure not list (unhashable) or Numeric array (bug in __eq__)
        v1 = tuple(v1)
        v2 = tuple(v2)
        if (v1,v2) not in didlines:
            didlines[(v1,v2)] = didlines[(v2,v1)] = None
            return True
        return False
    def doline(v1,v2):
        if shoulddoline(v1,v2):
            glVertex3fv(v1)
            glVertex3fv(v2)
        return
    glBegin(GL_LINES)
    ocdec = getSphereTriangles(1)
    for tri in ocdec:
        #e Could probably optim this more, e.g. using a vertex array or VBO or
        #  maybe GL_LINE_STRIP.
        doline(tri[0], tri[1])
        doline(tri[1], tri[2])
        doline(tri[2], tri[0])
    glEnd()
    glEndList()

    drawing_globals.CylList = CylList = glGenLists(1)
    glNewList(CylList, GL_COMPILE)
    glBegin(GL_TRIANGLE_STRIP)
    for (vtop, ntop, vbot, nbot) in drawing_globals.cylinderEdges:
        glNormal3fv(nbot)
        glVertex3fv(vbot)
        glNormal3fv(ntop)
        glVertex3fv(vtop)
    glEnd()
    glEndList()

    drawing_globals.CapList = CapList = glGenLists(1)
    glNewList(CapList, GL_COMPILE)
    glNormal3fv(drawing_globals.cap0n)
    glBegin(GL_POLYGON)
    for p in drawing_globals.drum0:
        glVertex3fv(p)
    glEnd()
    glNormal3fv(drawing_globals.cap1n)
    glBegin(GL_POLYGON)
    #bruce 060609 fix "ragged edge" bug in this endcap: drum1 -> drum2
    for p in drawing_globals.drum2:
        glVertex3fv(p)
    glEnd()
    glEndList()

    drawing_globals.diamondGridList = diamondGridList = glGenLists(1)
    glNewList(diamondGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.digrid:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.lonsGridList = lonsGridList = glGenLists(1)
    glNewList(lonsGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.lonsEdges:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.CubeList = CubeList = glGenLists(1)
    glNewList(CubeList, GL_COMPILE)
    glBegin(GL_QUAD_STRIP)
    # note: CubeList has only 4 faces of the cube; only suitable for use in
    # wireframes; see also solidCubeList [bruce 051215 comment reporting
    # grantham 20051213 observation]
    glVertex((-1,-1,-1))
    glVertex(( 1,-1,-1))
    glVertex((-1, 1,-1))
    glVertex(( 1, 1,-1))
    glVertex((-1, 1, 1))
    glVertex(( 1, 1, 1))
    glVertex((-1,-1, 1))
    glVertex(( 1,-1, 1))
    glVertex((-1,-1,-1))
    glVertex(( 1,-1,-1))
    glEnd()
    glEndList()

    drawing_globals.solidCubeList = solidCubeList = glGenLists(1)
    glNewList(solidCubeList, GL_COMPILE)
    glBegin(GL_QUADS)
    for i in xrange(len(drawing_globals.cubeIndices)):
        avenormals = V(0,0,0) #bruce 060302 fixed normals for flat shading
        for j in xrange(4) :
            nTuple = tuple(
                drawing_globals.cubeNormals[drawing_globals.cubeIndices[i][j]])
            avenormals += A(nTuple)
        avenormals = norm(avenormals)
        for j in xrange(4) :
            vTuple = tuple(
                drawing_globals.cubeVertices[drawing_globals.cubeIndices[i][j]])
            #bruce 060302 made size compatible with glut.glutSolidCube(1.0)
            vTuple = A(vTuple) * 0.5
            glNormal3fv(avenormals)
            glVertex3fv(vTuple)
    glEnd()
    glEndList()

    drawing_globals.rotSignList = rotSignList = glGenLists(1)
    glNewList(rotSignList, GL_COMPILE)
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS0n)):
        glVertex3fv(tuple(drawing_globals.rotS0n[ii]))
    glEnd()
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS1n)):
        glVertex3fv(tuple(drawing_globals.rotS1n[ii]))
    glEnd()
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.arrow0Vertices + drawing_globals.arrow1Vertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearArrowList = linearArrowList = glGenLists(1)
    glNewList(linearArrowList, GL_COMPILE)
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.linearArrowVertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearLineList = linearLineList = glGenLists(1)
    glNewList(linearLineList, GL_COMPILE)
    glEnable(GL_LINE_SMOOTH)
    glBegin(GL_LINES)
    glVertex3f(0.0, 0.0, -drawing_globals.halfHeight)
    glVertex3f(0.0, 0.0, drawing_globals.halfHeight)
    glEnd()
    glDisable(GL_LINE_SMOOTH)
    glEndList()

    drawing_globals.circleList = circleList = glGenLists(1)
    glNewList(circleList, GL_COMPILE)
    glBegin(GL_LINE_LOOP)
    for ii in range(60):
        x = cos(ii*2.0*pi/60)
        y = sin(ii*2.0*pi/60)
        glVertex3f(x, y, 0.0)
    glEnd()
    glEndList()

    # piotr 080405
    drawing_globals.filledCircleList = filledCircleList = glGenLists(1)
    glNewList(filledCircleList, GL_COMPILE)
    glBegin(GL_POLYGON)
    for ii in range(60):
        x = cos(ii*2.0*pi/60)
        y = sin(ii*2.0*pi/60)
        glVertex3f(x, y, 0.0)
    glEnd()
    glEndList()

    drawing_globals.lineCubeList = lineCubeList = glGenLists(1)
    glNewList(lineCubeList, GL_COMPILE)
    glBegin(GL_LINES)
    cvIndices = [0,1, 2,3, 4,5, 6,7, 0,3, 1,2, 5,6, 4,7, 0,4, 1,5, 2,6, 3,7]
    for i in cvIndices:
        glVertex3fv(tuple(drawing_globals.cubeVertices[i]))
    glEnd()
    glEndList()

    #initTexture('C:\\Huaicai\\atom\\temp\\newSample.png', 128,128)
    return # from setup_drawer
Example #9
0
def test_drawing(glpane, initOnly = False):
    """
    When TEST_DRAWING is enabled at the start of
    graphics/widgets/GLPane_rendering_methods.py,
    and when TestGraphics_Command is run (see its documentation
    for various ways to do that),
    this file is loaded and GLPane.paintGL() calls the
    test_drawing() function instead of the usual body of paintGL().
    """
    # WARNING: this duplicates some code with test_Draw_model().

    # Load the sphere shaders if needed.
    global _USE_SHADERS
    if _USE_SHADERS:
        if not drawing_globals.test_sphereShader:
            print "test_drawing: Loading sphere shaders."

            try:
                from graphics.drawing.gl_shaders import GLSphereShaderObject
                drawing_globals.test_sphereShader = GLSphereShaderObject()
                ##### REVIEW: is this compatible with my refactoring in drawing_globals?
                # If not, use of Test Graphics Performance command might cause subsequent
                # bugs in other code. Ideally we'd call the new methods that encapsulate
                # this, to setup shaders. [bruce 090304 comment]

                print "test_drawing: Sphere-shader initialization is complete.\n"
            except:
                _USE_SHADERS = False
                print "test_drawing: Exception while loading sphere shaders, will reraise and not try again"
                raise
            pass

    global start_pos, first_time

    if first_time:
        # Set up the viewing scale, but then let interactive zooming work.
        glpane.scale = nSpheres * .6
        pass

    # This same function gets called to set up for drawing, and to draw.
    if not initOnly:
        glpane._setup_modelview()
        glpane._setup_projection()
        ##glpane._compute_frustum_planes()

        glClearColor(64.0, 64.0, 64.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT )
        ##glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )

        glMatrixMode(GL_MODELVIEW)
        pass

    global test_csdl, test_dl, test_dls, test_ibo, test_vbo, test_spheres
    global test_DrawingSet, test_endpoints, test_Object

    # See below for test case descriptions and timings on a MacBook Pro.
    # The Qt event toploop in NE1 tops out at about 60 frames-per-second.

    # NE1 with test toploop, single CSDL per draw (test case 1)
    # . 17,424 spheres (132x132, through the color sorter) 4.8 FPS
    #   Russ 080919: More recently, 12.2 FPS.
    # . Level 2 spheres have 9 triangles x 20 faces, 162 distinct vertices,
    #   visited on the average 2.3 times, giving 384 tri-strip vertices.
    # . 17,424 spheres is 6.7 million tri-strip vertices.  (6,690,816)
    if testCase == 1:
        if test_csdl is None:
            print ("Test case 1, %d^2 spheres\n  %s." %
                   (nSpheres, "ColorSorter"))

            test_csdl = ColorSortedDisplayList()
            ColorSorter.start(None, test_csdl)
            drawsphere([0.5, 0.5, 0.5], # color
                       [0.0, 0.0, 0.0], # pos
                       .5, # radius
                       DRAWSPHERE_DETAIL_LEVEL,
                       testloop = nSpheres )
            ColorSorter.finish(draw_now = True)
            pass
        else:
            test_csdl.draw()
        pass

    # NE1 with test toploop, single display list per draw (test case 2)
    # . 10,000 spheres (all drawing modes) 17.5 FPS
    # . 17,424 spheres (132x132, manual display list) 11.1 FPS
    # . 40,000 spheres (mode 5 - VBO/IBO spheres from DL's) 2.2 FPS
    # . 40,000 spheres (mode 6 - Sphere shaders from DL's) 2.5 FPS
    # . 90,000 spheres (all drawing modes) 1.1 FPS
    elif testCase == 2:
        if test_dl is None:
            print ("Test case 2, %d^2 spheres\n  %s." %
                   (nSpheres, "One display list calling primitive dl's"))

            test_dl = glGenLists(1)
            glNewList(test_dl, GL_COMPILE_AND_EXECUTE)
            drawsphere_worker_loop(([0.0, 0.0, 0.0], # pos
                                    .5, # radius
                                    DRAWSPHERE_DETAIL_LEVEL,
                                    nSpheres ))
            glEndList()
            pass
        else:
            glColor3i(127, 127, 127)
            glCallList(test_dl)
        pass

    # NE1 with test toploop, one big chunk VBO/IBO of box quads (test case 3)
    # .  17,424 spheres (1 box/shader draw call) 43.7 FPS
    # .  17,424 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 57.7 FPS
    # .  40,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 56.7 FPS
    # .  90,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 52.7 FPS
    # . 160,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 41.4 FPS
    # . 250,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 27.0 FPS
    elif int(testCase) == 3:
        doTransforms = False
        if test_spheres is None:
            print ("Test case 3, %d^2 spheres\n  %s." %
                   (nSpheres, "One big VBO/IBO chunk buffer"))
            if testCase == 3.1:
                print ("Sub-test 3.1, animate partial updates.")
            elif testCase == 3.2:
                print ("Sub-test 3.2, animate partial updates" +
                       " w/ C per-chunk array buffering.")
            elif testCase == 3.3:
                print ("Sub-test 3.3, animate partial updates" +
                       " w/ Python array buffering.")
            # . 3.4 - Big batch draw, with transforms indexed by IDs added.
            #   (Second FPS number with debug colors in the vertex shader off.)
            #   - 90,000 (300x300) spheres, TEXTURE_XFORMS = True, 26(29) FPS
            #   - 90,000 (300x300) spheres, N_CONST_XFORMS = 250, 26(29) FPS
            #   - 90,000 (300x300) spheres, N_CONST_XFORMS = 275, 0.3(0.6) FPS
            #     (What happens after 250?  CPU usage goes from 40% to 94%.)
            #   -160,000 (400x400) spheres, TEXTURE_XFORMS = True, 26 FPS
            #   -250,000 (500x500) spheres, TEXTURE_XFORMS = True, 26 FPS
            elif testCase == 3.4:
                print ("Sub-test 3.4, add transforms indexed by IDs.")
                from graphics.drawing.gl_shaders import TEXTURE_XFORMS
                from graphics.drawing.gl_shaders import N_CONST_XFORMS
                from graphics.drawing.gl_shaders import UNIFORM_XFORMS
                if TEXTURE_XFORMS:
                    print "Transforms in texture memory."
                elif UNIFORM_XFORMS:
                    print "%d transforms in uniform memory." % N_CONST_XFORMS
                    pass
                else:
                    print "transforms not supported, error is likely"
                doTransforms = True
                pass

            centers = []
            radius = .5
            radii = []
            colors = []
            if not doTransforms:
                transformIDs = None
            else:
                transformIDs = []
                transformChunkID = -1   # Allocate IDs sequentially from 0.
                # For this test, allow arbitrarily chunking the primitives.
                primCounter = transformChunkLength
                transforms = []      # Accumulate transforms as a list of lists.

                # Initialize transforms with an identity matrix.
                # Transforms here are lists (or Numpy arrays) of 16 numbers.
                identity = ([1.0] + 4*[0.0]) * 3 + [1.0]
                pass
            for x in range(nSpheres):
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                    thisRad = radius * (.75 + t*.25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]

                    # Transforms go into a texture memory image if needed.
                    # Per-primitive Transform IDs go into an attribute VBO.
                    if doTransforms:
                        primCounter = primCounter + 1
                        if primCounter >= transformChunkLength:
                            # Start a new chunk, allocating a transform matrix.
                            primCounter = 0
                            transformChunkID += 1
                            if 0: # 1
                                # Debug hack: Label mat[0,0] with the chunk ID.
                                # Vertex shader debug code shows these in blue.
                                # If viewed as geometry, it will be a slight
                                # stretch of the array in the X direction.
                                transforms += [
                                    [1.0+transformChunkID/100.0] + identity[1:]]
                            elif 0: # 1
                                # Debug hack: Fill mat with mat.element pattern.
                                transforms += [
                                    [transformChunkID +
                                     i/100.0 for i in range(16)]]
                            else:
                                transforms += [identity]
                                pass
                            pass
                        # All of the primitives in a chunk have the same ID.
                        transformIDs += [transformChunkID]
                        pass

                    continue
                continue
            test_spheres = GLSphereBuffer()
            test_spheres.addSpheres(centers, radii, colors, transformIDs, None)
            if doTransforms:
                print ("%d primitives in %d transform chunks of size <= %d" %
                       (nSpheres * nSpheres, len(transforms),
                        transformChunkLength))
                shader = drawing_globals.test_sphereShader
                shader.setupTransforms(transforms)
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane)

            # Update portions for animation.
            pulse = time.time()
            pulse -= floor(pulse) # 0 to 1 in each second

            # Test animating updates on 80% of the radii in 45% of the columns.

            # . 3.1 - Update radii Python array per-column, send to graphics RAM.
            #   -  2,500 (50x50)   spheres 55 FPS
            #   - 10,000 (100x100) spheres 35 FPS
            #   - 17,424 (132x132) spheres 22.2 FPS
            #   - 40,000 (200x200) spheres 12.4 FPS
            #   - 90,000 (300x300) spheres  6.0 FPS
            if testCase == 3.1:
                # Not buffered, send each column change.
                radius = .5
                margin = nSpheres/10
                for x in range(margin, nSpheres, 2):
                    radii = []
                    for y in range(margin, nSpheres-margin):
                        t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t*.25)
                        phase = pulse + float(x+y)/nSpheres
                        radii += 8 * [thisRad-.1 + .1*sin(phase * 2*pi)]
                        continue
                    C_radii = numpy.array(radii, dtype=numpy.float32)
                    offset = x*nSpheres + margin
                    test_spheres.radii_vbo.update(offset * 8, C_radii)
                    continue
                pass

            # . 3.2 - Numpy buffered in C array, subscript assignments to C.
            #   -  2,500 (50x50)   spheres 48 FPS
            #   - 10,000 (100x100) spheres 17.4 FPS
            #   - 17,424 (132x132) spheres 11.2 FPS
            #   - 40,000 (200x200) spheres  5.5 FPS
            #   - 90,000 (300x300) spheres  2.5 FPS
            elif testCase == 3.2:
                # Buffered per-chunk at the C array level.
                radius = .5
                margin = nSpheres/10
                global C_array
                if C_array is None:
                    # Replicate.
                    C_array = numpy.zeros((8 * (nSpheres-(2*margin)),),
                                          dtype=numpy.float32)
                    pass
                for x in range(margin, nSpheres, 2):
                    count = 0
                    for y in range(margin, nSpheres-margin):
                        t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t*.25)
                        phase = pulse + float(x+y)/nSpheres
                        C_array[count*8:(count+1)*8] = \
                            thisRad-.1 + .1*sin(phase * 2*pi)
                        count += 1
                        continue
                    offset = x*nSpheres + margin
                    test_spheres.radii_vbo.update(offset * 8, C_array)
                    continue
                pass

            # . 3.3 - updateRadii in Python array, copy via C to graphics RAM.
            #   -  2,500 (50x50)   spheres 57 FPS
            #   - 10,000 (100x100) spheres 32 FPS
            #   - 17,424 (132x132) spheres 20 FPS
            #   - 40,000 (200x200) spheres 10.6 FPS
            #   - 90,000 (300x300) spheres  4.9 FPS
            elif testCase == 3.3:
                # Buffered at the Python level, batch the whole-array update.
                radius = .5
                margin = nSpheres/10
                for x in range(margin, nSpheres, 2):
                    radii = []
                    for y in range(margin, nSpheres-margin):
                        t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t*.25)
                        phase = pulse + float(x+y)/nSpheres
                        radii += [thisRad-.1 + .1*sin(phase * 2*pi)]
                        continue

                    test_spheres.updateRadii( # Update, but don't send yet.
                        x*nSpheres + margin, radii, send = False)
                    continue
                test_spheres.sendRadii()
                pass

            # Options: color = [0.0, 1.0, 0.0], transform_id = 1, radius = 1.0
            test_spheres.draw()
        pass

    # NE1 with test toploop, separate sphere VBO/IBO box/shader draws (test case 4)
    # . 17,424 spheres (132x132 box/shader draw quads calls) 0.7 FPS
    elif testCase == 4:
        if test_ibo is None:
            print ("Test case 4, %d^2 spheres\n  %s." %
                   (nSpheres,
                    "Separate VBO/IBO shader/box buffer sphere calls, no DL"))

            # Collect transformed bounding box vertices and offset indices.
            # Start at the lower-left corner, offset so the whole pattern comes
            # up centered on the origin.
            cubeVerts = drawing_globals.shaderCubeVerts
            cubeIndices = drawing_globals.shaderCubeIndices

            C_indices = numpy.array(cubeIndices, dtype=numpy.uint32)
            test_ibo = GLBufferObject(
                GL_ELEMENT_ARRAY_BUFFER_ARB, C_indices, GL_STATIC_DRAW)
            test_ibo.unbind()

            C_verts = numpy.array(cubeVerts, dtype=numpy.float32)
            test_vbo = GLBufferObject(
                GL_ARRAY_BUFFER_ARB, C_verts, GL_STATIC_DRAW)
            test_vbo.unbind()
            pass
        else:
            drawing_globals.test_sphereShader.configShader(glpane)

            glEnableClientState(GL_VERTEX_ARRAY)

            test_vbo.bind()             # Vertex data comes from the vbo.
            glVertexPointer(3, GL_FLOAT, 0, None)

            drawing_globals.test_sphereShader.setActive(True)
            glDisable(GL_CULL_FACE)

            glColor3i(127, 127, 127)
            test_ibo.bind()             # Index data comes from the ibo.
            for x in range(nSpheres):
                for y in range(nSpheres):
                    # From drawsphere_worker_loop().
                    pos = start_pos + (x+x/10+x/100) * V(1, 0, 0) + \
                          (y+y/10+y/100) * V(0, 1, 0)
                    radius = .5

                    glPushMatrix()
                    glTranslatef(pos[0], pos[1], pos[2])
                    glScale(radius,radius,radius)

                    glDrawElements(GL_QUADS, 6 * 4, GL_UNSIGNED_INT, None)

                    glPopMatrix()
                    continue
                continue

            drawing_globals.test_sphereShader.setActive(False)
            glEnable(GL_CULL_FACE)

            test_ibo.unbind()
            test_vbo.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        pass

    # NE1 with test toploop,
    # One DL around separate VBO/IBO shader/box buffer sphere calls (test case 5)
    # . 17,424 spheres (1 box/shader DL draw call) 9.2 FPS
    elif testCase == 5:
        if test_dl is None:
            print ("Test case 5, %d^2 spheres\n  %s." %
                   (nSpheres,
                    "One DL around separate VBO/IBO shader/box buffer sphere calls"))

            # Collect transformed bounding box vertices and offset indices.
            # Start at the lower-left corner, offset so the whole pattern comes
            # up centered on the origin.
            cubeVerts = drawing_globals.shaderCubeVerts
            cubeIndices = drawing_globals.shaderCubeIndices

            C_indices = numpy.array(cubeIndices, dtype=numpy.uint32)
            test_ibo = GLBufferObject(
                GL_ELEMENT_ARRAY_BUFFER_ARB, C_indices, GL_STATIC_DRAW)
            test_ibo.unbind()

            C_verts = numpy.array(cubeVerts, dtype=numpy.float32)
            test_vbo = GLBufferObject(
                GL_ARRAY_BUFFER_ARB, C_verts, GL_STATIC_DRAW)
            test_vbo.unbind()

            # Wrap a display list around the draws.
            test_dl = glGenLists(1)
            glNewList(test_dl, GL_COMPILE_AND_EXECUTE)

            glEnableClientState(GL_VERTEX_ARRAY)

            test_vbo.bind()             # Vertex data comes from the vbo.
            glVertexPointer(3, GL_FLOAT, 0, None)

            drawing_globals.test_sphereShader.setActive(True)
            glDisable(GL_CULL_FACE)

            glColor3i(127, 127, 127)
            test_ibo.bind()             # Index data comes from the ibo.
            for x in range(nSpheres):
                for y in range(nSpheres):
                    # From drawsphere_worker_loop().
                    pos = start_pos + (x+x/10+x/100) * V(1, 0, 0) + \
                          (y+y/10+y/100) * V(0, 1, 0)
                    radius = .5

                    glPushMatrix()
                    glTranslatef(pos[0], pos[1], pos[2])
                    glScale(radius,radius,radius)

                    glDrawElements(GL_QUADS, 6 * 4, GL_UNSIGNED_INT, None)

                    glPopMatrix()
                    continue
                continue

            drawing_globals.test_sphereShader.setActive(False)
            glEnable(GL_CULL_FACE)

            test_ibo.unbind()
            test_vbo.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)

            glEndList()
        else:
            glColor3i(127, 127, 127)
            glCallList(test_dl)
            pass
        pass

    # NE1 with test toploop,
    # N column DL's around VBO/IBO shader/box buffer sphere calls (test case 6)
    # .   2,500 (50x50)   spheres 58 FPS
    # .  10,000 (100x100) spheres 57 FPS
    # .  17,424 (132x132) spheres 56 FPS
    # .  40,000 (200x200) spheres 50 FPS
    # .  90,000 (300x300) spheres 28 FPS
    # . 160,000 (400x400) spheres 16.5 FPS
    # . 250,000 (500x500) spheres  3.2 FPS
    elif testCase == 6:
        if test_dls is None:
            print ("Test case 6, %d^2 spheres\n  %s." %
                   (nSpheres,
                    "N col DL's around VBO/IBO shader/box buffer sphere calls"))

            # Wrap n display lists around the draws (one per column.)
            test_dls = glGenLists(nSpheres) # Returns ID of first DL in the set.
            test_spheres = []
            for x in range(nSpheres):
                centers = []
                radius = .5
                radii = []
                colors = []
                # Each column is relative to its bottom sphere location.  Start
                # at the lower-left corner, offset so the whole pattern comes up
                # centered on the origin.
                start_pos = V(0, 0, 0)  # So it doesn't get subtracted twice.
                pos = sphereLoc(x, 0) - V(nSpheres/2.0, nSpheres/2.0, 0)
                for y in range(nSpheres):
                    centers += [sphereLoc(0, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                    thisRad = radius * (.75 + t*.25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]
                    continue
                test_sphere = GLSphereBuffer()
                test_sphere.addSpheres(centers, radii, colors, None, None)
                test_spheres += [test_sphere]

                glNewList(test_dls + x, GL_COMPILE_AND_EXECUTE)
                glPushMatrix()
                glTranslatef(pos[0], pos[1], pos[2])

                test_sphere.draw()

                glPopMatrix()
                glEndList()
                continue
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane) # Turn the lights on.
            for x in range(nSpheres):
                glCallList(test_dls + x)
                continue
            pass
        pass

    # NE1 with test toploop,
    # N column VBO sets of shader/box buffer sphere calls (test case 7)
    # .   2,500 (50x50)   spheres 50 FPS
    # .  10,000 (100x100) spheres 30.5 FPS
    # .  17,424 (132x132) spheres 23.5 FPS
    # .  40,000 (200x200) spheres 16.8 FPS
    # .  90,000 (300x300) spheres 10.8 FPS
    # . 160,000 (400x400) spheres  9.1 FPS
    # . 250,000 (500x500) spheres  7.3 FPS
    elif testCase == 7:
        if test_spheres is None:
            print ("Test case 7, %d^2 spheres\n  %s." %
                   (nSpheres, "Per-column VBO/IBO chunk buffers"))
            test_spheres = []
            for x in range(nSpheres):
                centers = []
                radius = .5
                radii = []
                colors = []
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                    thisRad = radius * (.75 + t*.25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]
                    continue
                _spheres1 = GLSphereBuffer()
                _spheres1.addSpheres(centers, radii, colors, None, None)
                test_spheres += [_spheres1]
                continue
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane)
            for chunk in test_spheres:
                chunk.draw()
        pass

    # NE1 with test toploop,
    # Short chunk VBO sets of shader/box buffer sphere calls (test case 8)
    # .     625 (25x25)   spheres 30 FPS,     79 chunk buffers of length 8.
    # .   2,500 (50x50)   spheres 13.6 FPS,  313 chunk buffers of length 8.
    # .  10,000 (100x100) spheres  6.4 FPS,  704 chunk buffers of length 8.
    # .  10,000 (100x100) spheres  3.3 FPS, 1250 chunk buffers of length 8.
    # .  17,424 (132x132) spheres  2.1 FPS, 2178 chunk buffers of length 8.
    # .   2,500 (50x50)   spheres 33.5 FPS,  105 chunk buffers of length 24.
    # .  17,424 (132x132) spheres  5.5 FPS,  726 chunk buffers of length 24.
    #
    # Subcase 8.1: CSDLs in a DrawingSet.  (Initial pass-through version.)
    # .   2,500 (50x50)   spheres 36.5 FPS,  105 chunk buffers of length 24.
    # .   5,625 (75x75)   spheres 16.1 FPS,  235 chunk buffers of length 24.
    # .  10,000 (100x100) spheres  0.5 FPS?!, 414 chunk buffers of length 24.
    #      Has to be <= 250 chunks for constant memory transforms?
    # .  10,000 (100x100) spheres 11.8 FPS, 50 chunk buffers of length 200.
    #      After a minute of startup.
    # .  10,000 (100x100) spheres 9.3 FPS, 200 chunk buffers of length 50.
    #      After a few minutes of startup.
    # Subcase 8.2: CSDLs in a DrawingSet with transforms. (Pass-through.)
    # .  10,000 (100x100) spheres  11.5 FPS, 50 chunk buffers of length 200.
    #
    # Subcase 8.1: CSDLs in a DrawingSet.  (First HunkBuffer version.)
    # Measured with auto-rotate on, ignoring startup and occasional outliers.
    # As before, on a 2 core, 2.4 GHz Intel MacBook Pro with GeForce 8600M GT.
    # HUNK_SIZE = 10000
    # .   2,500 (50x50)   spheres 140-200 FPS, 105 chunks of length 24.
    # .   5,625 (75x75)   spheres 155-175 FPS, 235 chunks of length 24.
    # .  10,000 (100x100) spheres 134-145 FPS, 50 chunks of length 200.
    # .  10,000 (100x100) spheres 130-143 FPS, 200 chunks of length 50.
    # .  10,000 (100x100) spheres 131-140 FPS, 1,250 chunks of length 8.
    #      Chunks are gathered into hunk buffers, so no chunk size speed diff.
    # .  17,424 (132x132) spheres 134-140 FPS, 88 chunks of length 200.
    # .  17,424 (132x132) spheres 131-140 FPS, 2,178 chunks of length 8.
    # HUNK_SIZE = 20000
    # .  17,424 (132x132) spheres 131-140 FPS, 88 chunks of length 200.
    # .  17,424 (132x132) spheres 130-141 FPS, 2,178 chunks of length 8.
    # HUNK_SIZE = 10000
    # .  40,000 (200x200) spheres 77.5-82.8 FPS, 5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 34.9-42.6 FPS, 11,2500 chunks of length 8.
    #      Spheres are getting down to pixel size, causing moire patterns.
    #      Rotate the sphere-array off-axis 45 degrees to minimize.
    #      (Try adding multi-sampled anti-aliasing, to the drawing test...)
    # . 160,000 (400x400) spheres 26.4-27.1 FPS, 20,000 chunks of length 8.
    # . 250,000 (500x500) spheres 16.8-17.1 FPS, 31,250 chunks of length 8.
    #      The pattern is getting too large, far-clipping is setting in.
    # . 360,000 (600x600) spheres 11.6-11.8 FPS, 45,000 chunks of length 8.
    #      Extreme far-clipping in the drawing test pattern.
    # HUNK_SIZE = 20000; no significant speed-up.
    # .  40,000 (200x200) spheres 75.9-81.5 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 41.2-42.4 FPS, 11,250 chunks of length 8.
    #      Spheres are getting down to pixel size, causing moire patterns.
    # . 160,000 (400x400) spheres 26.5-26.9 FPS, 20,000 chunks of length 8.
    # . 250,000 (500x500) spheres 16.5-17.1 FPS, 31,250 chunks of length 8.
    # . 360,000 (600x600) spheres 11.8-12.1 FPS, 45,000 chunks of length 8.
    # HUNK_SIZE = 5000; no significant slowdown or CPU load difference.
    # .  40,000 (200x200) spheres 81.0-83.8 FPS,  5,000 chunks of length 8.
    # . 160,000 (400x400) spheres 27.3-29.4 FPS, 20,000 chunks of length 8.
    # . 360,000 (600x600) spheres 11.7-12.1 FPS, 45,000 chunks of length 8.
    #
    # Retest after updating MacOS to 10.5.5, with TestGraphics, HUNK_SIZE = 5000
    # .  40,000 (200x200) spheres 68.7-74.4 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 39.4-42.0 FPS, 11,250 chunks of length 8.
    # . 160,000 (400x400) spheres 24.4-25.2 FPS, 20,000 chunks of length 8.
    # Retest with glMultiDrawElements drawing indexes in use, HUNK_SIZE = 5000
    # .  40,000 (200x200) spheres 52.8-54.4 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 22.8-23.3 FPS, 11,250 chunks of length 8.
    # . 160,000 (400x400) spheres 13.5-15.2 FPS, 20,000 chunks of length 8.
    #
    # Retest with reworked halo/sphere shader, HUNK_SIZE = 5000     [setup time]
    # .  17,424 (132x132) spheres 52.8-53.7 FPS,  2,178 chunks of length 8. [60]
    # .  40,000 (200x200) spheres 29.3-30.4 FPS,  5,000 chunks of length 8.[156]
    # .  90,000 (300x300) spheres 18.2-19.2 FPS, 11,250 chunks of length 8.[381]
    # . 160,000 (400x400) spheres 10.2-11.6 FPS, 20,000 chunks of length 8.[747]
    # Billboard drawing patterns instead of cubes, HUNK_SIZE = 5000 [setup time]
    # .  17,424 (132x132) spheres 49.7-55.7 FPS,  2,178 chunks of length 8. [35]
    # .  40,000 (200x200) spheres 39.6-40.8 FPS,  5,000 chunks of length 8. [88]
    # .  90,000 (300x300) spheres 18.9-19.5 FPS, 11,250 chunks of length 8.[225]
    # . 160,000 (400x400) spheres 11.2-11.7 FPS, 20,000 chunks of length 8.[476]
    #
    elif int(testCase) == 8:
        doTransforms = False
        doCylinders = False
        if test_spheres is None:
            # Setup.
            print ("Test case 8, %d^2 primitives\n  %s, length %d." %
                   (nSpheres, "Short VBO/IBO chunk buffers", chunkLength))
            if testCase == 8.1:
                print ("Sub-test 8.1, sphere chunks are in CSDL's in a DrawingSet.")
                test_DrawingSet = DrawingSet()
            elif testCase == 8.2:
                print ("Sub-test 8.2, spheres, rotate with TransformControls.")
                test_DrawingSet = DrawingSet()
                doTransforms = True
            elif testCase == 8.3:
                print ("Sub-test 8.3, cylinder chunks are in CSDL's in a DrawingSet.")
                test_DrawingSet = DrawingSet()
                doCylinders = True
                pass
            if test_DrawingSet:
                # note: doesn't happen in test 8.0, which causes a bug then. [bruce 090223 comment]
                print "constructed test_DrawingSet =", test_DrawingSet

            if USE_GRAPHICSMODE_DRAW:
                print ("Use graphicsMode.Draw_model for DrawingSet in paintGL.")
                pass

            t1 = time.time()

            if doTransforms:
                # Provide several TransformControls to test separate action.
                global numTCs, TCs
                numTCs = 3
                TCs = [TransformControl() for i in range(numTCs)]
                pass

            def primCSDL(centers, radii, colors):
                if not doTransforms:
                    csdl = ColorSortedDisplayList() # Transformless.
                else:
                    # Test pattern for TransformControl usage - vertical columns
                    # of TC domains, separated by X coord of first center point.
                    # Chunking will be visible when transforms are changed.
                    xCoord = centers[0][0] - start_pos[0] # Negate centering X.
                    xPercent = (xCoord /
                                (nSpheres + nSpheres/10 +
                                 nSpheres/100 - 1 + (nSpheres <= 1)))
                    xTenth = int(xPercent * 10 + .5)
                    csdl = ColorSortedDisplayList(TCs[xTenth % numTCs])
                    pass

                # Test selection using the CSDL glname.
                ColorSorter.pushName(csdl.glname)
                ColorSorter.start(glpane, csdl)
                for (color, center, radius) in zip(colors, centers, radii):
                    if not doCylinders:
                        # Through ColorSorter to the sphere primitive buffer...
                        drawsphere(color, center, radius,
                                   DRAWSPHERE_DETAIL_LEVEL)
                    else:
                        # Through ColorSorter to cylinder primitive buffer...
                        if not drawing_globals.cylinderShader_available():
                            print "warning: not cylinderShader_available(), error is likely:"
                        if (True and  # Whether to do tapered shader-cylinders.
                            # Display List cylinders don't support taper.
                            glpane.glprefs.cylinderShader_desired()):
                            ###cylRad = (radius/2.0, (.75-radius)/2.0)
                            cylRad = (radius/1.5 - .167, .3 - radius/1.5)
                        else:
                            cylRad = radius/2.0
                            pass
                        endPt2 = center + V(0.5, 0.0, 0.0) # 0.5, -0.5)
                        drawcylinder(color, center, endPt2, cylRad)
                        global test_endpoints
                        test_endpoints += [(center, endPt2)]
                        pass
                    continue
                ColorSorter.popName()
                ColorSorter.finish(draw_now = True)

                test_DrawingSet.addCSDL(csdl)
                return csdl

            if testCase == 8:
                #bruce 090223 revised to try to avoid traceback
                def chunkFn(centers, radii, colors):
                    res = GLSphereBuffer()
                    res.addSpheres(centers, radii, colors, None, None)
                    return res
                pass
            else:
                chunkFn = primCSDL
                pass

            test_spheres = []
            radius = .5
            centers = []
            radii = []
            colors = []
            global test_endpoints
            test_endpoints = []

            for x in range(nSpheres):
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x+y)/(nSpheres+nSpheres) # 0 to 1 fraction.
                    thisRad = radius * (.5 + t*.5)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]

                    # Put out short chunk buffers.
                    if len(centers) >= chunkLength:
                        test_spheres += [
                            chunkFn(centers, radii, colors) ]
                        centers = []
                        radii = []
                        colors = []
                    continue
                continue
            # Remainder fraction buffer.
            if len(centers):
                test_spheres += [chunkFn(centers, radii, colors)]
                pass
            print "Setup time", time.time() - t1, "seconds."
            print "%d chunk buffers" % len(test_spheres)
            pass
        elif not initOnly: # Run.
            test_Draw_8x(glpane)
        pass
    elif testCase == 100: #bruce 090102
        # before making more of these, modularize it somehow
        from commands.TestGraphics.test_selection_redraw import test_selection_redraw
        test_class = test_selection_redraw
        params = ( nSpheres, )
            # note: test size is not directly comparable to other tests with same value of nSpheres
        if test_Object is None \
           or not isinstance(test_Object, test_class) \
           or test_Object.current_params() != params: # review: same_vals?
            # Setup.
            if test_Object:
                test_Object.destroy()
            test_Object = test_class(*params)
            test_Object.activate()
            print test_Object
            pass
        # review: safe to change elif to if? not sure, GL state is only initialized below
        elif not initOnly: # Run.
            test_Object.draw_complete()
            pass
        pass

    if not initOnly:
        glMatrixMode(GL_MODELVIEW)
        glFlush()
        pass

    first_time = False
    return
class Hunk:
    """
    Helper class to wrap low-level VBO objects with data caches, change ranges,
    and flushing of the changed range to the hunk VBO in graphics card RAM.
    """
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.

        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return

    def unchanged(self):
        """
        Mark a Hunk as unchanged.  (Empty or flushed.)
        """
        self.low = self.high = 0
        return

    def changedRange(self, chgLowID, chgHighID):
        """
        Record a range of data changes, for later flushing.

        chgLowID and chgHighID are primitive IDs, possibly spanning many hunks.

        The high end is the index of the one *after* the end of the range, as
        usual in Python.
        """
        (lowHunk, lowIndex) = decodePrimID(chgLowID)
        (highHunk, highIndex) = decodePrimID(chgHighID)

        if self.hunkNumber < lowHunk or self.hunkNumber > highHunk:
            return              # This hunk is not in the hunk range.

        if lowHunk < self.hunkNumber:
            self.low = 0
        else:
            self.low = min(self.low, lowIndex) # Maybe extend the range.
            pass

        if highHunk > self.hunkNumber:
            self.high = HUNK_SIZE
        else:
            self.high = max(self.high, highIndex) # Maybe extend the range.
            pass

        return

    def flush(self, allData):
        """
        Update a changed range of the data that applies to this hunk, sending it
        to the Buffer Object in graphics card RAM.

        allData - List of data blocks for the whole hunk-list.  We'll extract
        just the part relevant to the changed part of this particular hunk.

        Internally, the data is a list, block-indexed by primitive ID, but
        replicated in block sublists by a factor of self.nVertices to match the
        vertex buffer size.  What reaches the attribute VBO is automatically
        flattened into a sequence.
        """
        rangeSize = self.high - self.low
        assert rangeSize >= 0
        if rangeSize == 0:
            # Nothing to do.
            return

        lowID = (self.hunkNumber * HUNK_SIZE) + self.low
        highID = (self.hunkNumber * HUNK_SIZE) + self.high

        # Send all or part of the Python data to C.
        C_data = numpy.array(allData[lowID:highID], dtype=numpy.float32)

        if rangeSize == HUNK_SIZE:
            # Special case to send the whole Hunk's worth of data.
            self.VBO.updateAll(C_data)
        else:
            # Send a portion of the HunkBuffer, with byte offset within the VBO.
            # (Per-vertex attributes are all multiples (1-4) of Float32.)
            offset = self.low * self.nVertices * self.nCoords * BYTES_PER_FLOAT
            self.VBO.update(offset, C_data)
            pass

        self.unchanged()             # Now we're in sync.
        return

    pass # End of class HunkBuffer.
class GLPrimitiveBuffer(object):
    """
    Manage VBO space for drawing primitives in large batches.
    """

    # default values of instance variables
    transform_id_Hunks = None

    def __init__(self, shaderGlobals):
        """
        Fill in the vertex VBO and IBO drawing pattern for this primitive type.

        @param shaderGlobals: the instance of class ShaderGlobals
            we will be associated with, used for its .shader
            and various related constants.
        """
        # set localvars as follows, for the drawing pattern for VBOs/IBOs
        # for this primitive type:
        #
        # shader - The GLShaderObject to use.
        #
        # drawingMode - What kind of primitives to render, e.g. GL_QUADS.
        #
        # vertexBlock, indexBlock - Single blocks (lists) of vertices and indices
        # making up the drawing pattern for this primitive.
        # See description in the module docstrings for this class or its subclasses.
        if debug_pref("GLPane: use billboard primitives? (next session)",
                      Choice_boolean_True, prefs_key = True ):
            drawingMode = GL_QUADS
            vertexBlock = shaderGlobals.billboardVerts
            indexBlock = shaderGlobals.billboardIndices
            pass
        else:
            drawingMode = GL_QUADS
            vertexBlock = shaderGlobals.shaderCubeVerts
            indexBlock = shaderGlobals.shaderCubeIndices
            pass

        # Remember the shader.
        self.shader = shader = shaderGlobals.shader

        # Shared data for drawing calls to follow.
        self.drawingMode = drawingMode

        # Remember the drawing pattern.  (We may want to make transformed vertex
        # blocks someday, losing the one-hunk-per-type goodness in favor of some
        # other greater goodness like minimizing use of constant registers.)
        # See below for filling the vertex VBO and IBO with these.
        self.nVertices = len(vertexBlock)
        self.vertexBlock = vertexBlock
        self.indexBlock = indexBlock

        # Allocation of primitives within hunks.
        self.nPrims = 0             # Number of primitives allocated.
        self.freePrimSlotIDs = []   # Free list of freed primitives.
        self.nHunks = 0             # Number of hunks allocated.

        # Common per-vertex attribute hunk VBOs for all primitive types.
        # (The hunkVertVBO and hunkIndexIBO are shared by all hunks.)
        nVerts = self.nVertices
        self.colorHunks = HunkBuffer(shader, "color", nVerts, 4)
        self.glname_color_Hunks = HunkBuffer(shader, "glname_color", nVerts, 4)

        # Subclasses may add their own attributes to the hunkBuffers list,
        # beyond the ones we add here:
        self.hunkBuffers = [self.colorHunks,
                            self.glname_color_Hunks,
                           ]

        if shader.supports_transforms():
            self.transform_id_Hunks = HunkBuffer(shader, "transform_id", nVerts, 1)
            self.hunkBuffers += [self.transform_id_Hunks]

        # Support for lazily updating drawing caches, namely a timestamp showing
        # when this GLPrimitiveBuffer was last flushed to graphics card RAM.
        self.flushed = drawing_constants.NO_EVENT_YET

        # Fill in shared data in the graphics card RAM.
        self._setupSharedVertexData()

        # Cached info for blocks of transforms.
        # Transforms here are lists (or Numpy arrays) of 16 numbers.
        self.transforms = []
        self.identityTransform = ([1.0] + 4*[0.0]) * 3 + [1.0]

        return

    def color4(self,color):
        """
        Minor helper function for colors.  Converts a given (R, G, B) 3-tuple to
        (R, G, B, A) by adding an opacity of 1.0 .
        """
        if len(color) == 3:
            # Add opacity to color if missing.
            color = (color[0], color[1], color[2], 1.0)
            pass
        return color

    def newPrimitives(self, n):
        """
        Allocate a group of primitives. Returns a list of n IDs.
        """
        primIDs = []
        for i in range(n):
            # Take ones from the free list first.
            if len(self.freePrimSlotIDs):
                primID = self.freePrimSlotIDs.pop()
            else:
                # Allocate a new one.
                primID = self.nPrims     # ID is a zero-origin subscript.
                self.nPrims += 1        # nPrims is a counter.

                # Allocate another set of hunks if the new ID has passed a hunk
                # boundary.
                if (primID + HUNK_SIZE) / HUNK_SIZE > self.nHunks:
                    for buffer in self.hunkBuffers:
                        buffer.addHunk()
                        continue
                    self.nHunks += 1
                pass
            primIDs += [primID]
            continue
        return primIDs

    def releasePrimitives(self, idList):
        """
        Release the given primitive IDs into the free-list.
        """
        self.freePrimSlotIDs += idList
        return

    def _setupSharedVertexData(self):
        """
        Gather data for the drawing pattern Vertex and Index Buffer Objects
        shared by all hunks of the same primitive type.  The drawing pattern is
        replicated HUNK_SIZE times, and sent to graphics card RAM for use in
        every draw command for collections of primitives of this type.

        In theory, the vertex shader processes each vertex only once, even if
        it is indexed many times in different faces within the same draw.  In
        practice, locality of vertex referencing in the drawing pattern is
        optimal, since there may be a cache of the most recent N transformed
        vertices in that stage of the drawing pipeline on graphics card.

        For indexed gl(Multi)DrawElements, the index is a list of faces
        (typically triangles or quads, specified by the drawingMode.)  Each
        face is represented by a block of subscripts into the vertex block.

        For glMultiDrawElements, there is an additional pair of arrays that give
        offsets into blocks of the index block list, and the lengths of each
        block of indices.  Because the index blocks are all the same length to
        draw individual primitives, we set up a single array containing a Hunk's
        worth of the index block length constant and use it for each Hunk draw.
        """
        self.nIndices = len(self.indexBlock) * len(self.indexBlock[0])
        indexOffset = 0
        # (May cache these someday.  No need now since they don't change.)
        Py_iboIndices = []
        Py_vboVerts = []
        for i in range(HUNK_SIZE):
            # Accumulate a hunk full of blocks of vertices.  Each block is
            # identical, with coordinates relative to its local primitive
            # origin.  Hence, the vertex VBO may be shared among all hunks of
            # primitives of the same type.  A per-vertex attribute gives the
            # spatial location of the primitive origin, and is combined with the
            # local vertex coordinates in the vertex shader program.
            Py_vboVerts += self.vertexBlock

            # Accumulate a hunk full of index blocks, offsetting the indices in
            # each block to point to the vertices for the corresponding
            # primitive block in the vertex hunk.
            for face in self.indexBlock:
                Py_iboIndices += [idx + indexOffset for idx in face]
                continue
            indexOffset += self.nVertices
            continue

        # Push shared vbo/ibo hunk data through C to the graphics card RAM.
        C_vboVerts = numpy.array(Py_vboVerts, dtype=numpy.float32)
        self.hunkVertVBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB, C_vboVerts, GL_STATIC_DRAW)
        self.hunkVertVBO.unbind()

        C_iboIndices = numpy.array(Py_iboIndices, dtype=numpy.uint32)
        self.hunkIndexIBO = GLBufferObject(
            GL_ELEMENT_ARRAY_BUFFER_ARB, C_iboIndices, GL_STATIC_DRAW)
        self.hunkIndexIBO.unbind()

        # A Hunk-length of index block length constants for glMultiDrawElements.
        self.C_indexBlockLengths = numpy.array(HUNK_SIZE * [self.nIndices],
                                               dtype=numpy.uint32)
        return

    def draw(self, drawIndex = None, highlighted = False, selected = False,
             patterning = True, highlight_color = None, opacity = 1.0):
        """
        Draw the buffered geometry, binding vertex attribute values for the
        shaders.

        If no drawIndex is given, the whole array is drawn.
        """
        self.shader.setActive(True)                # Turn on the chosen shader.

        glEnableClientState(GL_VERTEX_ARRAY)

        self.shader.setupDraw(highlighted, selected, patterning,
                              highlight_color, opacity)

        # XXX No transform data until that is more implemented.
        ###self.shader.setupTransforms(self.transforms)
        # (note: the reason TransformControls work in their test case
        #  is due to a manual call of shader.setupTransforms. [bruce 090306 guess])
        if self.shader.get_TEXTURE_XFORMS():
            # Activate a texture unit for transforms.
            ## XXX Not necessary for custom shader programs.
            ##glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, self.transform_memory)
                ### BUG: pylint warning:
                # Instance of 'GLPrimitiveBuffer' has no 'transform_memory' member
                #### REVIEW: should this be the attr of that name in GLShaderObject,
                # i.e. self.shader? I didn't fix it myself as a guess, in case other
                # uses of self also need fixing in the same way. [bruce 090304 comment]

            # Set the sampler to the handle for the active texture image (0).
            ## XXX Not needed if only one texture is being used?
            ##glActiveTexture(GL_TEXTURE0)
            ##glUniform1iARB(self.shader._uniform("transforms"), 0)
            pass

        glDisable(GL_CULL_FACE)

        # Draw the hunks.
        for hunkNumber in range(self.nHunks):
            # Bind the per-vertex generic attribute arrays for one hunk.
            for buffer in self.hunkBuffers:
                buffer.flush()      # Sync graphics card copies of the VBO data.
                buffer.bindHunk(hunkNumber)
                continue

            # Shared vertex coordinate data VBO: GL_ARRAY_BUFFER_ARB.
            self.hunkVertVBO.bind()
            glVertexPointer(3, GL_FLOAT, 0, None)

            # Shared vertex index data IBO: GL_ELEMENT_ARRAY_BUFFER_ARB
            self.hunkIndexIBO.bind()

            if drawIndex is not None:
                # Draw the selected primitives for this Hunk.
                index = drawIndex[hunkNumber]
                primcount = len(index)
                glMultiDrawElementsVBO(
                    self.drawingMode, self.C_indexBlockLengths,
                    GL_UNSIGNED_INT, index, primcount)
            else:
                # For initial testing, draw all primitives in the Hunk.
                if hunkNumber < self.nHunks-1:
                    nToDraw = HUNK_SIZE # Hunks before the last.
                else:
                    nToDraw = self.nPrims - (self.nHunks-1) * HUNK_SIZE
                    pass
                glDrawElements(self.drawingMode, self.nIndices * nToDraw,
                               GL_UNSIGNED_INT, None)
                pass
            continue

        self.shader.setActive(False)            # Turn off the chosen shader.
        glEnable(GL_CULL_FACE)

        self.hunkIndexIBO.unbind()   # Deactivate the ibo.
        self.hunkVertVBO.unbind()    # Deactivate all vbo's.

        glDisableClientState(GL_VERTEX_ARRAY)
        for buffer in self.hunkBuffers:
            buffer.unbindHunk()      # glDisableVertexAttribArrayARB.
            continue
        return

    def makeDrawIndex(self, prims):
        """
        Make a drawing index to be used by glMultiDrawElements on Hunk data.

        The return is a list of offset arrays, one for each Hunk of primitives.
        Each value in an offset array gives the *byte* offset in the IBO to the
        block of indices for a given primitive ID within the Hunk.
        """
        hunkOffsets = [[] for i in range(self.nHunks)]

        # Collect the offsets to index blocks for individual primitives.
        for primID in prims:
            (hunk, index) = decodePrimID(primID)
            hunkOffsets[hunk] += [index * self.nIndices * BYTES_PER_UINT]
            continue

        # The indices for each hunk could be sorted here, which might go faster.
        # (But don't worry about it if drawing all primitives in random order is
        # as fast as the test that draws all primitives with glDrawElements.)
        #
        # The sorted offsets arrays could also be reduced to draw contiguous
        # runs of primitives, rather than single primitives.  A list of run
        # lengths for each Hunk would have to be produced as well, rather than
        # using the shared list of constant lengths for individual primitives.

        # Push the offset arrays into C for faster use by glMultiDrawElements.
        C_hunkOffsets = [numpy.array(offset, dtype=numpy.uint32)
                         for offset in hunkOffsets]
        return C_hunkOffsets

    pass # End of class GLPrimitiveBuffer.
Example #12
0
def test_drawing(glpane, initOnly=False):
    """
    When TEST_DRAWING is enabled at the start of
    graphics/widgets/GLPane_rendering_methods.py,
    and when TestGraphics_Command is run (see its documentation
    for various ways to do that),
    this file is loaded and GLPane.paintGL() calls the
    test_drawing() function instead of the usual body of paintGL().
    """
    # WARNING: this duplicates some code with test_Draw_model().

    # Load the sphere shaders if needed.
    global _USE_SHADERS
    if _USE_SHADERS:
        if not drawing_globals.test_sphereShader:
            print "test_drawing: Loading sphere shaders."

            try:
                from graphics.drawing.gl_shaders import GLSphereShaderObject
                drawing_globals.test_sphereShader = GLSphereShaderObject()
                ##### REVIEW: is this compatible with my refactoring in drawing_globals?
                # If not, use of Test Graphics Performance command might cause subsequent
                # bugs in other code. Ideally we'd call the new methods that encapsulate
                # this, to setup shaders. [bruce 090304 comment]

                print "test_drawing: Sphere-shader initialization is complete.\n"
            except:
                _USE_SHADERS = False
                print "test_drawing: Exception while loading sphere shaders, will reraise and not try again"
                raise
            pass

    global start_pos, first_time

    if first_time:
        # Set up the viewing scale, but then let interactive zooming work.
        glpane.scale = nSpheres * .6
        pass

    # This same function gets called to set up for drawing, and to draw.
    if not initOnly:
        glpane._setup_modelview()
        glpane._setup_projection()
        ##glpane._compute_frustum_planes()

        glClearColor(64.0, 64.0, 64.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
                | GL_STENCIL_BUFFER_BIT)
        ##glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )

        glMatrixMode(GL_MODELVIEW)
        pass

    global test_csdl, test_dl, test_dls, test_ibo, test_vbo, test_spheres
    global test_DrawingSet, test_endpoints, test_Object

    # See below for test case descriptions and timings on a MacBook Pro.
    # The Qt event toploop in NE1 tops out at about 60 frames-per-second.

    # NE1 with test toploop, single CSDL per draw (test case 1)
    # . 17,424 spheres (132x132, through the color sorter) 4.8 FPS
    #   Russ 080919: More recently, 12.2 FPS.
    # . Level 2 spheres have 9 triangles x 20 faces, 162 distinct vertices,
    #   visited on the average 2.3 times, giving 384 tri-strip vertices.
    # . 17,424 spheres is 6.7 million tri-strip vertices.  (6,690,816)
    if testCase == 1:
        if test_csdl is None:
            print("Test case 1, %d^2 spheres\n  %s." %
                  (nSpheres, "ColorSorter"))

            test_csdl = ColorSortedDisplayList()
            ColorSorter.start(None, test_csdl)
            drawsphere(
                [0.5, 0.5, 0.5],  # color
                [0.0, 0.0, 0.0],  # pos
                .5,  # radius
                DRAWSPHERE_DETAIL_LEVEL,
                testloop=nSpheres)
            ColorSorter.finish(draw_now=True)
            pass
        else:
            test_csdl.draw()
        pass

    # NE1 with test toploop, single display list per draw (test case 2)
    # . 10,000 spheres (all drawing modes) 17.5 FPS
    # . 17,424 spheres (132x132, manual display list) 11.1 FPS
    # . 40,000 spheres (mode 5 - VBO/IBO spheres from DL's) 2.2 FPS
    # . 40,000 spheres (mode 6 - Sphere shaders from DL's) 2.5 FPS
    # . 90,000 spheres (all drawing modes) 1.1 FPS
    elif testCase == 2:
        if test_dl is None:
            print("Test case 2, %d^2 spheres\n  %s." %
                  (nSpheres, "One display list calling primitive dl's"))

            test_dl = glGenLists(1)
            glNewList(test_dl, GL_COMPILE_AND_EXECUTE)
            drawsphere_worker_loop((
                [0.0, 0.0, 0.0],  # pos
                .5,  # radius
                DRAWSPHERE_DETAIL_LEVEL,
                nSpheres))
            glEndList()
            pass
        else:
            glColor3i(127, 127, 127)
            glCallList(test_dl)
        pass

    # NE1 with test toploop, one big chunk VBO/IBO of box quads (test case 3)
    # .  17,424 spheres (1 box/shader draw call) 43.7 FPS
    # .  17,424 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 57.7 FPS
    # .  40,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 56.7 FPS
    # .  90,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 52.7 FPS
    # . 160,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 41.4 FPS
    # . 250,000 spheres (1 box/shader draw call w/ rad/ctrpt attrs) 27.0 FPS
    elif int(testCase) == 3:
        doTransforms = False
        if test_spheres is None:
            print("Test case 3, %d^2 spheres\n  %s." %
                  (nSpheres, "One big VBO/IBO chunk buffer"))
            if testCase == 3.1:
                print("Sub-test 3.1, animate partial updates.")
            elif testCase == 3.2:
                print("Sub-test 3.2, animate partial updates" +
                      " w/ C per-chunk array buffering.")
            elif testCase == 3.3:
                print("Sub-test 3.3, animate partial updates" +
                      " w/ Python array buffering.")
            # . 3.4 - Big batch draw, with transforms indexed by IDs added.
            #   (Second FPS number with debug colors in the vertex shader off.)
            #   - 90,000 (300x300) spheres, TEXTURE_XFORMS = True, 26(29) FPS
            #   - 90,000 (300x300) spheres, N_CONST_XFORMS = 250, 26(29) FPS
            #   - 90,000 (300x300) spheres, N_CONST_XFORMS = 275, 0.3(0.6) FPS
            #     (What happens after 250?  CPU usage goes from 40% to 94%.)
            #   -160,000 (400x400) spheres, TEXTURE_XFORMS = True, 26 FPS
            #   -250,000 (500x500) spheres, TEXTURE_XFORMS = True, 26 FPS
            elif testCase == 3.4:
                print("Sub-test 3.4, add transforms indexed by IDs.")
                from graphics.drawing.gl_shaders import TEXTURE_XFORMS
                from graphics.drawing.gl_shaders import N_CONST_XFORMS
                from graphics.drawing.gl_shaders import UNIFORM_XFORMS
                if TEXTURE_XFORMS:
                    print "Transforms in texture memory."
                elif UNIFORM_XFORMS:
                    print "%d transforms in uniform memory." % N_CONST_XFORMS
                    pass
                else:
                    print "transforms not supported, error is likely"
                doTransforms = True
                pass

            centers = []
            radius = .5
            radii = []
            colors = []
            if not doTransforms:
                transformIDs = None
            else:
                transformIDs = []
                transformChunkID = -1  # Allocate IDs sequentially from 0.
                # For this test, allow arbitrarily chunking the primitives.
                primCounter = transformChunkLength
                transforms = []  # Accumulate transforms as a list of lists.

                # Initialize transforms with an identity matrix.
                # Transforms here are lists (or Numpy arrays) of 16 numbers.
                identity = ([1.0] + 4 * [0.0]) * 3 + [1.0]
                pass
            for x in range(nSpheres):
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x + y) / (nSpheres + nSpheres
                                        )  # 0 to 1 fraction.
                    thisRad = radius * (.75 + t * .25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]

                    # Transforms go into a texture memory image if needed.
                    # Per-primitive Transform IDs go into an attribute VBO.
                    if doTransforms:
                        primCounter = primCounter + 1
                        if primCounter >= transformChunkLength:
                            # Start a new chunk, allocating a transform matrix.
                            primCounter = 0
                            transformChunkID += 1
                            if 0:  # 1
                                # Debug hack: Label mat[0,0] with the chunk ID.
                                # Vertex shader debug code shows these in blue.
                                # If viewed as geometry, it will be a slight
                                # stretch of the array in the X direction.
                                transforms += [
                                    [1.0 + transformChunkID / 100.0] +
                                    identity[1:]
                                ]
                            elif 0:  # 1
                                # Debug hack: Fill mat with mat.element pattern.
                                transforms += [[
                                    transformChunkID + i / 100.0
                                    for i in range(16)
                                ]]
                            else:
                                transforms += [identity]
                                pass
                            pass
                        # All of the primitives in a chunk have the same ID.
                        transformIDs += [transformChunkID]
                        pass

                    continue
                continue
            test_spheres = GLSphereBuffer()
            test_spheres.addSpheres(centers, radii, colors, transformIDs, None)
            if doTransforms:
                print("%d primitives in %d transform chunks of size <= %d" %
                      (nSpheres * nSpheres, len(transforms),
                       transformChunkLength))
                shader = drawing_globals.test_sphereShader
                shader.setupTransforms(transforms)
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane)

            # Update portions for animation.
            pulse = time.time()
            pulse -= floor(pulse)  # 0 to 1 in each second

            # Test animating updates on 80% of the radii in 45% of the columns.

            # . 3.1 - Update radii Python array per-column, send to graphics RAM.
            #   -  2,500 (50x50)   spheres 55 FPS
            #   - 10,000 (100x100) spheres 35 FPS
            #   - 17,424 (132x132) spheres 22.2 FPS
            #   - 40,000 (200x200) spheres 12.4 FPS
            #   - 90,000 (300x300) spheres  6.0 FPS
            if testCase == 3.1:
                # Not buffered, send each column change.
                radius = .5
                margin = nSpheres / 10
                for x in range(margin, nSpheres, 2):
                    radii = []
                    for y in range(margin, nSpheres - margin):
                        t = float(x + y) / (nSpheres + nSpheres
                                            )  # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t * .25)
                        phase = pulse + float(x + y) / nSpheres
                        radii += 8 * [thisRad - .1 + .1 * sin(phase * 2 * pi)]
                        continue
                    C_radii = numpy.array(radii, dtype=numpy.float32)
                    offset = x * nSpheres + margin
                    test_spheres.radii_vbo.update(offset * 8, C_radii)
                    continue
                pass

            # . 3.2 - Numpy buffered in C array, subscript assignments to C.
            #   -  2,500 (50x50)   spheres 48 FPS
            #   - 10,000 (100x100) spheres 17.4 FPS
            #   - 17,424 (132x132) spheres 11.2 FPS
            #   - 40,000 (200x200) spheres  5.5 FPS
            #   - 90,000 (300x300) spheres  2.5 FPS
            elif testCase == 3.2:
                # Buffered per-chunk at the C array level.
                radius = .5
                margin = nSpheres / 10
                global C_array
                if C_array is None:
                    # Replicate.
                    C_array = numpy.zeros((8 * (nSpheres - (2 * margin)), ),
                                          dtype=numpy.float32)
                    pass
                for x in range(margin, nSpheres, 2):
                    count = 0
                    for y in range(margin, nSpheres - margin):
                        t = float(x + y) / (nSpheres + nSpheres
                                            )  # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t * .25)
                        phase = pulse + float(x + y) / nSpheres
                        C_array[count*8:(count+1)*8] = \
                            thisRad-.1 + .1*sin(phase * 2*pi)
                        count += 1
                        continue
                    offset = x * nSpheres + margin
                    test_spheres.radii_vbo.update(offset * 8, C_array)
                    continue
                pass

            # . 3.3 - updateRadii in Python array, copy via C to graphics RAM.
            #   -  2,500 (50x50)   spheres 57 FPS
            #   - 10,000 (100x100) spheres 32 FPS
            #   - 17,424 (132x132) spheres 20 FPS
            #   - 40,000 (200x200) spheres 10.6 FPS
            #   - 90,000 (300x300) spheres  4.9 FPS
            elif testCase == 3.3:
                # Buffered at the Python level, batch the whole-array update.
                radius = .5
                margin = nSpheres / 10
                for x in range(margin, nSpheres, 2):
                    radii = []
                    for y in range(margin, nSpheres - margin):
                        t = float(x + y) / (nSpheres + nSpheres
                                            )  # 0 to 1 fraction.
                        # Sphere radii progress from 3/4 to full size.
                        thisRad = radius * (.75 + t * .25)
                        phase = pulse + float(x + y) / nSpheres
                        radii += [thisRad - .1 + .1 * sin(phase * 2 * pi)]
                        continue

                    test_spheres.updateRadii(  # Update, but don't send yet.
                        x * nSpheres + margin,
                        radii,
                        send=False)
                    continue
                test_spheres.sendRadii()
                pass

            # Options: color = [0.0, 1.0, 0.0], transform_id = 1, radius = 1.0
            test_spheres.draw()
        pass

    # NE1 with test toploop, separate sphere VBO/IBO box/shader draws (test case 4)
    # . 17,424 spheres (132x132 box/shader draw quads calls) 0.7 FPS
    elif testCase == 4:
        if test_ibo is None:
            print("Test case 4, %d^2 spheres\n  %s." %
                  (nSpheres,
                   "Separate VBO/IBO shader/box buffer sphere calls, no DL"))

            # Collect transformed bounding box vertices and offset indices.
            # Start at the lower-left corner, offset so the whole pattern comes
            # up centered on the origin.
            cubeVerts = drawing_globals.shaderCubeVerts
            cubeIndices = drawing_globals.shaderCubeIndices

            C_indices = numpy.array(cubeIndices, dtype=numpy.uint32)
            test_ibo = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB, C_indices,
                                      GL_STATIC_DRAW)
            test_ibo.unbind()

            C_verts = numpy.array(cubeVerts, dtype=numpy.float32)
            test_vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB, C_verts,
                                      GL_STATIC_DRAW)
            test_vbo.unbind()
            pass
        else:
            drawing_globals.test_sphereShader.configShader(glpane)

            glEnableClientState(GL_VERTEX_ARRAY)

            test_vbo.bind()  # Vertex data comes from the vbo.
            glVertexPointer(3, GL_FLOAT, 0, None)

            drawing_globals.test_sphereShader.setActive(True)
            glDisable(GL_CULL_FACE)

            glColor3i(127, 127, 127)
            test_ibo.bind()  # Index data comes from the ibo.
            for x in range(nSpheres):
                for y in range(nSpheres):
                    # From drawsphere_worker_loop().
                    pos = start_pos + (x+x/10+x/100) * V(1, 0, 0) + \
                          (y+y/10+y/100) * V(0, 1, 0)
                    radius = .5

                    glPushMatrix()
                    glTranslatef(pos[0], pos[1], pos[2])
                    glScale(radius, radius, radius)

                    glDrawElements(GL_QUADS, 6 * 4, GL_UNSIGNED_INT, None)

                    glPopMatrix()
                    continue
                continue

            drawing_globals.test_sphereShader.setActive(False)
            glEnable(GL_CULL_FACE)

            test_ibo.unbind()
            test_vbo.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)
        pass

    # NE1 with test toploop,
    # One DL around separate VBO/IBO shader/box buffer sphere calls (test case 5)
    # . 17,424 spheres (1 box/shader DL draw call) 9.2 FPS
    elif testCase == 5:
        if test_dl is None:
            print("Test case 5, %d^2 spheres\n  %s." % (
                nSpheres,
                "One DL around separate VBO/IBO shader/box buffer sphere calls"
            ))

            # Collect transformed bounding box vertices and offset indices.
            # Start at the lower-left corner, offset so the whole pattern comes
            # up centered on the origin.
            cubeVerts = drawing_globals.shaderCubeVerts
            cubeIndices = drawing_globals.shaderCubeIndices

            C_indices = numpy.array(cubeIndices, dtype=numpy.uint32)
            test_ibo = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB, C_indices,
                                      GL_STATIC_DRAW)
            test_ibo.unbind()

            C_verts = numpy.array(cubeVerts, dtype=numpy.float32)
            test_vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB, C_verts,
                                      GL_STATIC_DRAW)
            test_vbo.unbind()

            # Wrap a display list around the draws.
            test_dl = glGenLists(1)
            glNewList(test_dl, GL_COMPILE_AND_EXECUTE)

            glEnableClientState(GL_VERTEX_ARRAY)

            test_vbo.bind()  # Vertex data comes from the vbo.
            glVertexPointer(3, GL_FLOAT, 0, None)

            drawing_globals.test_sphereShader.setActive(True)
            glDisable(GL_CULL_FACE)

            glColor3i(127, 127, 127)
            test_ibo.bind()  # Index data comes from the ibo.
            for x in range(nSpheres):
                for y in range(nSpheres):
                    # From drawsphere_worker_loop().
                    pos = start_pos + (x+x/10+x/100) * V(1, 0, 0) + \
                          (y+y/10+y/100) * V(0, 1, 0)
                    radius = .5

                    glPushMatrix()
                    glTranslatef(pos[0], pos[1], pos[2])
                    glScale(radius, radius, radius)

                    glDrawElements(GL_QUADS, 6 * 4, GL_UNSIGNED_INT, None)

                    glPopMatrix()
                    continue
                continue

            drawing_globals.test_sphereShader.setActive(False)
            glEnable(GL_CULL_FACE)

            test_ibo.unbind()
            test_vbo.unbind()
            glDisableClientState(GL_VERTEX_ARRAY)

            glEndList()
        else:
            glColor3i(127, 127, 127)
            glCallList(test_dl)
            pass
        pass

    # NE1 with test toploop,
    # N column DL's around VBO/IBO shader/box buffer sphere calls (test case 6)
    # .   2,500 (50x50)   spheres 58 FPS
    # .  10,000 (100x100) spheres 57 FPS
    # .  17,424 (132x132) spheres 56 FPS
    # .  40,000 (200x200) spheres 50 FPS
    # .  90,000 (300x300) spheres 28 FPS
    # . 160,000 (400x400) spheres 16.5 FPS
    # . 250,000 (500x500) spheres  3.2 FPS
    elif testCase == 6:
        if test_dls is None:
            print("Test case 6, %d^2 spheres\n  %s." %
                  (nSpheres,
                   "N col DL's around VBO/IBO shader/box buffer sphere calls"))

            # Wrap n display lists around the draws (one per column.)
            test_dls = glGenLists(
                nSpheres)  # Returns ID of first DL in the set.
            test_spheres = []
            for x in range(nSpheres):
                centers = []
                radius = .5
                radii = []
                colors = []
                # Each column is relative to its bottom sphere location.  Start
                # at the lower-left corner, offset so the whole pattern comes up
                # centered on the origin.
                start_pos = V(0, 0, 0)  # So it doesn't get subtracted twice.
                pos = sphereLoc(x, 0) - V(nSpheres / 2.0, nSpheres / 2.0, 0)
                for y in range(nSpheres):
                    centers += [sphereLoc(0, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x + y) / (nSpheres + nSpheres
                                        )  # 0 to 1 fraction.
                    thisRad = radius * (.75 + t * .25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]
                    continue
                test_sphere = GLSphereBuffer()
                test_sphere.addSpheres(centers, radii, colors, None, None)
                test_spheres += [test_sphere]

                glNewList(test_dls + x, GL_COMPILE_AND_EXECUTE)
                glPushMatrix()
                glTranslatef(pos[0], pos[1], pos[2])

                test_sphere.draw()

                glPopMatrix()
                glEndList()
                continue
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane)  # Turn the lights on.
            for x in range(nSpheres):
                glCallList(test_dls + x)
                continue
            pass
        pass

    # NE1 with test toploop,
    # N column VBO sets of shader/box buffer sphere calls (test case 7)
    # .   2,500 (50x50)   spheres 50 FPS
    # .  10,000 (100x100) spheres 30.5 FPS
    # .  17,424 (132x132) spheres 23.5 FPS
    # .  40,000 (200x200) spheres 16.8 FPS
    # .  90,000 (300x300) spheres 10.8 FPS
    # . 160,000 (400x400) spheres  9.1 FPS
    # . 250,000 (500x500) spheres  7.3 FPS
    elif testCase == 7:
        if test_spheres is None:
            print("Test case 7, %d^2 spheres\n  %s." %
                  (nSpheres, "Per-column VBO/IBO chunk buffers"))
            test_spheres = []
            for x in range(nSpheres):
                centers = []
                radius = .5
                radii = []
                colors = []
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x + y) / (nSpheres + nSpheres
                                        )  # 0 to 1 fraction.
                    thisRad = radius * (.75 + t * .25)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]
                    continue
                _spheres1 = GLSphereBuffer()
                _spheres1.addSpheres(centers, radii, colors, None, None)
                test_spheres += [_spheres1]
                continue
            pass
        else:
            shader = drawing_globals.test_sphereShader
            shader.configShader(glpane)
            for chunk in test_spheres:
                chunk.draw()
        pass

    # NE1 with test toploop,
    # Short chunk VBO sets of shader/box buffer sphere calls (test case 8)
    # .     625 (25x25)   spheres 30 FPS,     79 chunk buffers of length 8.
    # .   2,500 (50x50)   spheres 13.6 FPS,  313 chunk buffers of length 8.
    # .  10,000 (100x100) spheres  6.4 FPS,  704 chunk buffers of length 8.
    # .  10,000 (100x100) spheres  3.3 FPS, 1250 chunk buffers of length 8.
    # .  17,424 (132x132) spheres  2.1 FPS, 2178 chunk buffers of length 8.
    # .   2,500 (50x50)   spheres 33.5 FPS,  105 chunk buffers of length 24.
    # .  17,424 (132x132) spheres  5.5 FPS,  726 chunk buffers of length 24.
    #
    # Subcase 8.1: CSDLs in a DrawingSet.  (Initial pass-through version.)
    # .   2,500 (50x50)   spheres 36.5 FPS,  105 chunk buffers of length 24.
    # .   5,625 (75x75)   spheres 16.1 FPS,  235 chunk buffers of length 24.
    # .  10,000 (100x100) spheres  0.5 FPS?!, 414 chunk buffers of length 24.
    #      Has to be <= 250 chunks for constant memory transforms?
    # .  10,000 (100x100) spheres 11.8 FPS, 50 chunk buffers of length 200.
    #      After a minute of startup.
    # .  10,000 (100x100) spheres 9.3 FPS, 200 chunk buffers of length 50.
    #      After a few minutes of startup.
    # Subcase 8.2: CSDLs in a DrawingSet with transforms. (Pass-through.)
    # .  10,000 (100x100) spheres  11.5 FPS, 50 chunk buffers of length 200.
    #
    # Subcase 8.1: CSDLs in a DrawingSet.  (First HunkBuffer version.)
    # Measured with auto-rotate on, ignoring startup and occasional outliers.
    # As before, on a 2 core, 2.4 GHz Intel MacBook Pro with GeForce 8600M GT.
    # HUNK_SIZE = 10000
    # .   2,500 (50x50)   spheres 140-200 FPS, 105 chunks of length 24.
    # .   5,625 (75x75)   spheres 155-175 FPS, 235 chunks of length 24.
    # .  10,000 (100x100) spheres 134-145 FPS, 50 chunks of length 200.
    # .  10,000 (100x100) spheres 130-143 FPS, 200 chunks of length 50.
    # .  10,000 (100x100) spheres 131-140 FPS, 1,250 chunks of length 8.
    #      Chunks are gathered into hunk buffers, so no chunk size speed diff.
    # .  17,424 (132x132) spheres 134-140 FPS, 88 chunks of length 200.
    # .  17,424 (132x132) spheres 131-140 FPS, 2,178 chunks of length 8.
    # HUNK_SIZE = 20000
    # .  17,424 (132x132) spheres 131-140 FPS, 88 chunks of length 200.
    # .  17,424 (132x132) spheres 130-141 FPS, 2,178 chunks of length 8.
    # HUNK_SIZE = 10000
    # .  40,000 (200x200) spheres 77.5-82.8 FPS, 5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 34.9-42.6 FPS, 11,2500 chunks of length 8.
    #      Spheres are getting down to pixel size, causing moire patterns.
    #      Rotate the sphere-array off-axis 45 degrees to minimize.
    #      (Try adding multi-sampled anti-aliasing, to the drawing test...)
    # . 160,000 (400x400) spheres 26.4-27.1 FPS, 20,000 chunks of length 8.
    # . 250,000 (500x500) spheres 16.8-17.1 FPS, 31,250 chunks of length 8.
    #      The pattern is getting too large, far-clipping is setting in.
    # . 360,000 (600x600) spheres 11.6-11.8 FPS, 45,000 chunks of length 8.
    #      Extreme far-clipping in the drawing test pattern.
    # HUNK_SIZE = 20000; no significant speed-up.
    # .  40,000 (200x200) spheres 75.9-81.5 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 41.2-42.4 FPS, 11,250 chunks of length 8.
    #      Spheres are getting down to pixel size, causing moire patterns.
    # . 160,000 (400x400) spheres 26.5-26.9 FPS, 20,000 chunks of length 8.
    # . 250,000 (500x500) spheres 16.5-17.1 FPS, 31,250 chunks of length 8.
    # . 360,000 (600x600) spheres 11.8-12.1 FPS, 45,000 chunks of length 8.
    # HUNK_SIZE = 5000; no significant slowdown or CPU load difference.
    # .  40,000 (200x200) spheres 81.0-83.8 FPS,  5,000 chunks of length 8.
    # . 160,000 (400x400) spheres 27.3-29.4 FPS, 20,000 chunks of length 8.
    # . 360,000 (600x600) spheres 11.7-12.1 FPS, 45,000 chunks of length 8.
    #
    # Retest after updating MacOS to 10.5.5, with TestGraphics, HUNK_SIZE = 5000
    # .  40,000 (200x200) spheres 68.7-74.4 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 39.4-42.0 FPS, 11,250 chunks of length 8.
    # . 160,000 (400x400) spheres 24.4-25.2 FPS, 20,000 chunks of length 8.
    # Retest with glMultiDrawElements drawing indexes in use, HUNK_SIZE = 5000
    # .  40,000 (200x200) spheres 52.8-54.4 FPS,  5,000 chunks of length 8.
    # .  90,000 (300x300) spheres 22.8-23.3 FPS, 11,250 chunks of length 8.
    # . 160,000 (400x400) spheres 13.5-15.2 FPS, 20,000 chunks of length 8.
    #
    # Retest with reworked halo/sphere shader, HUNK_SIZE = 5000     [setup time]
    # .  17,424 (132x132) spheres 52.8-53.7 FPS,  2,178 chunks of length 8. [60]
    # .  40,000 (200x200) spheres 29.3-30.4 FPS,  5,000 chunks of length 8.[156]
    # .  90,000 (300x300) spheres 18.2-19.2 FPS, 11,250 chunks of length 8.[381]
    # . 160,000 (400x400) spheres 10.2-11.6 FPS, 20,000 chunks of length 8.[747]
    # Billboard drawing patterns instead of cubes, HUNK_SIZE = 5000 [setup time]
    # .  17,424 (132x132) spheres 49.7-55.7 FPS,  2,178 chunks of length 8. [35]
    # .  40,000 (200x200) spheres 39.6-40.8 FPS,  5,000 chunks of length 8. [88]
    # .  90,000 (300x300) spheres 18.9-19.5 FPS, 11,250 chunks of length 8.[225]
    # . 160,000 (400x400) spheres 11.2-11.7 FPS, 20,000 chunks of length 8.[476]
    #
    elif int(testCase) == 8:
        doTransforms = False
        doCylinders = False
        if test_spheres is None:
            # Setup.
            print("Test case 8, %d^2 primitives\n  %s, length %d." %
                  (nSpheres, "Short VBO/IBO chunk buffers", chunkLength))
            if testCase == 8.1:
                print(
                    "Sub-test 8.1, sphere chunks are in CSDL's in a DrawingSet."
                )
                test_DrawingSet = DrawingSet()
            elif testCase == 8.2:
                print("Sub-test 8.2, spheres, rotate with TransformControls.")
                test_DrawingSet = DrawingSet()
                doTransforms = True
            elif testCase == 8.3:
                print(
                    "Sub-test 8.3, cylinder chunks are in CSDL's in a DrawingSet."
                )
                test_DrawingSet = DrawingSet()
                doCylinders = True
                pass
            if test_DrawingSet:
                # note: doesn't happen in test 8.0, which causes a bug then. [bruce 090223 comment]
                print "constructed test_DrawingSet =", test_DrawingSet

            if USE_GRAPHICSMODE_DRAW:
                print("Use graphicsMode.Draw_model for DrawingSet in paintGL.")
                pass

            t1 = time.time()

            if doTransforms:
                # Provide several TransformControls to test separate action.
                global numTCs, TCs
                numTCs = 3
                TCs = [TransformControl() for i in range(numTCs)]
                pass

            def primCSDL(centers, radii, colors):
                if not doTransforms:
                    csdl = ColorSortedDisplayList()  # Transformless.
                else:
                    # Test pattern for TransformControl usage - vertical columns
                    # of TC domains, separated by X coord of first center point.
                    # Chunking will be visible when transforms are changed.
                    xCoord = centers[0][0] - start_pos[
                        0]  # Negate centering X.
                    xPercent = (
                        xCoord /
                        (nSpheres + nSpheres / 10 + nSpheres / 100 - 1 +
                         (nSpheres <= 1)))
                    xTenth = int(xPercent * 10 + .5)
                    csdl = ColorSortedDisplayList(TCs[xTenth % numTCs])
                    pass

                # Test selection using the CSDL glname.
                ColorSorter.pushName(csdl.glname)
                ColorSorter.start(glpane, csdl)
                for (color, center, radius) in zip(colors, centers, radii):
                    if not doCylinders:
                        # Through ColorSorter to the sphere primitive buffer...
                        drawsphere(color, center, radius,
                                   DRAWSPHERE_DETAIL_LEVEL)
                    else:
                        # Through ColorSorter to cylinder primitive buffer...
                        if not drawing_globals.cylinderShader_available():
                            print "warning: not cylinderShader_available(), error is likely:"
                        if (True and  # Whether to do tapered shader-cylinders.
                                # Display List cylinders don't support taper.
                                glpane.glprefs.cylinderShader_desired()):
                            ###cylRad = (radius/2.0, (.75-radius)/2.0)
                            cylRad = (radius / 1.5 - .167, .3 - radius / 1.5)
                        else:
                            cylRad = radius / 2.0
                            pass
                        endPt2 = center + V(0.5, 0.0, 0.0)  # 0.5, -0.5)
                        drawcylinder(color, center, endPt2, cylRad)
                        global test_endpoints
                        test_endpoints += [(center, endPt2)]
                        pass
                    continue
                ColorSorter.popName()
                ColorSorter.finish(draw_now=True)

                test_DrawingSet.addCSDL(csdl)
                return csdl

            if testCase == 8:
                #bruce 090223 revised to try to avoid traceback
                def chunkFn(centers, radii, colors):
                    res = GLSphereBuffer()
                    res.addSpheres(centers, radii, colors, None, None)
                    return res

                pass
            else:
                chunkFn = primCSDL
                pass

            test_spheres = []
            radius = .5
            centers = []
            radii = []
            colors = []
            global test_endpoints
            test_endpoints = []

            for x in range(nSpheres):
                for y in range(nSpheres):
                    centers += [sphereLoc(x, y)]

                    # Sphere radii progress from 3/4 to full size.
                    t = float(x + y) / (nSpheres + nSpheres
                                        )  # 0 to 1 fraction.
                    thisRad = radius * (.5 + t * .5)
                    radii += [thisRad]

                    # Colors progress from red to blue.
                    colors += [rainbow(t)]

                    # Put out short chunk buffers.
                    if len(centers) >= chunkLength:
                        test_spheres += [chunkFn(centers, radii, colors)]
                        centers = []
                        radii = []
                        colors = []
                    continue
                continue
            # Remainder fraction buffer.
            if len(centers):
                test_spheres += [chunkFn(centers, radii, colors)]
                pass
            print "Setup time", time.time() - t1, "seconds."
            print "%d chunk buffers" % len(test_spheres)
            pass
        elif not initOnly:  # Run.
            test_Draw_8x(glpane)
        pass
    elif testCase == 100:  #bruce 090102
        # before making more of these, modularize it somehow
        from commands.TestGraphics.test_selection_redraw import test_selection_redraw
        test_class = test_selection_redraw
        params = (nSpheres, )
        # note: test size is not directly comparable to other tests with same value of nSpheres
        if test_Object is None \
           or not isinstance(test_Object, test_class) \
           or test_Object.current_params() != params: # review: same_vals?
            # Setup.
            if test_Object:
                test_Object.destroy()
            test_Object = test_class(*params)
            test_Object.activate()
            print test_Object
            pass
        # review: safe to change elif to if? not sure, GL state is only initialized below
        elif not initOnly:  # Run.
            test_Object.draw_complete()
            pass
        pass

    if not initOnly:
        glMatrixMode(GL_MODELVIEW)
        glFlush()
        pass

    first_time = False
    return
Example #13
0
def setup_drawer():
    """
    Set up the usual constant display lists in the current OpenGL context.

    WARNING: THIS IS ONLY CORRECT IF ONLY ONE GL CONTEXT CONTAINS DISPLAY LISTS
    -- or more precisely, only the GL context this has last been called in (or
    one which shares its display lists) will work properly with the routines in
    drawer.py, since the allocated display list names are stored in globals set
    by this function, but in general those names might differ if this was called
    in different GL contexts.
    """
    #bruce 060613 added docstring, cleaned up display list name allocation
    # bruce 071030 renamed from setup to setup_drawer

    spherelistbase = glGenLists(numSphereSizes)
    sphereList = []
    for i in range(numSphereSizes):
        sphereList += [spherelistbase+i]
        glNewList(sphereList[i], GL_COMPILE)
        glBegin(GL_TRIANGLE_STRIP) # GL_LINE_LOOP to see edges.
        stripVerts = getSphereTriStrips(i)
        for vertNorm in stripVerts:
            glNormal3fv(vertNorm)
            glVertex3fv(vertNorm)
            continue
        glEnd()
        glEndList()
        continue
    drawing_globals.sphereList = sphereList

    # Sphere triangle-strip vertices for each level of detail.
    # (Cache and re-use the work of making them.)
    # Can use in converter-wrappered calls like glVertexPointerfv,
    # but the python arrays are re-copied to C each time.
    sphereArrays = []
    for i in range(numSphereSizes):
        sphereArrays += [getSphereTriStrips(i)]
        continue
    drawing_globals.sphereArrays = sphereArrays

    # Sphere glDrawArrays triangle-strip vertices for C calls.
    # (Cache and re-use the work of converting a C version.)
    # Used in thinly-wrappered calls like glVertexPointer.
    sphereCArrays = []
    for i in range(numSphereSizes):
        CArray = numpy.array(sphereArrays[i], dtype=numpy.float32)
        sphereCArrays += [CArray]
        continue
    drawing_globals.sphereCArrays = sphereCArrays

    # Sphere indexed vertices.
    # (Cache and re-use the work of making the indexes.)
    # Can use in converter-wrappered calls like glDrawElementsui,
    # but the python arrays are re-copied to C each time.
    sphereElements = []             # Pairs of lists (index, verts) .
    for i in range(numSphereSizes):
        sphereElements += [indexVerts(sphereArrays[i], .0001)]
        continue
    drawing_globals.sphereElements = sphereElements

    # Sphere glDrawElements index and vertex arrays for C calls.
    sphereCIndexTypes = []          # numpy index unsigned types.
    sphereGLIndexTypes = []         # GL index types for drawElements.
    sphereCElements = []            # Pairs of numpy arrays (Cindex, Cverts) .
    for i in range(numSphereSizes):
        (index, verts) = sphereElements[i]
        if len(index) < 256:
            Ctype = numpy.uint8
            GLtype = GL_UNSIGNED_BYTE
        else:
            Ctype = numpy.uint16
            GLtype = GL_UNSIGNED_SHORT
            pass
        sphereCIndexTypes += [Ctype]
        sphereGLIndexTypes += [GLtype]
        sphereCIndex = numpy.array(index, dtype=Ctype)
        sphereCVerts = numpy.array(verts, dtype=numpy.float32)
        sphereCElements += [(sphereCIndex, sphereCVerts)]
        continue
    drawing_globals.sphereCIndexTypes = sphereCIndexTypes
    drawing_globals.sphereGLIndexTypes = sphereGLIndexTypes
    drawing_globals.sphereCElements = sphereCElements

    if glGetString(GL_EXTENSIONS).find("GL_ARB_vertex_buffer_object") >= 0:

        # A GLBufferObject version for glDrawArrays.
        sphereArrayVBOs = []
        for i in range(numSphereSizes):
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB,
                                 sphereCArrays[i], GL_STATIC_DRAW)
            sphereArrayVBOs += [vbo]
            continue
        drawing_globals.sphereArrayVBOs = sphereArrayVBOs

        # A GLBufferObject version for glDrawElements indexed verts.
        sphereElementVBOs = []              # Pairs of (IBO, VBO)
        for i in range(numSphereSizes):
            ibo = GLBufferObject(GL_ELEMENT_ARRAY_BUFFER_ARB,
                                 sphereCElements[i][0], GL_STATIC_DRAW)
            vbo = GLBufferObject(GL_ARRAY_BUFFER_ARB,
                                 sphereCElements[i][1], GL_STATIC_DRAW)
            sphereElementVBOs += [(ibo, vbo)]
            continue
        drawing_globals.sphereElementVBOs = sphereElementVBOs

        ibo.unbind()
        vbo.unbind()
        pass

    #bruce 060415
    drawing_globals.wiresphere1list = wiresphere1list = glGenLists(1)
    glNewList(wiresphere1list, GL_COMPILE)
    didlines = {} # don't draw each triangle edge more than once

    def shoulddoline(v1,v2):
        # make sure not list (unhashable) or Numeric array (bug in __eq__)
        v1 = tuple(v1)
        v2 = tuple(v2)
        if (v1,v2) not in didlines:
            didlines[(v1,v2)] = didlines[(v2,v1)] = None
            return True
        return False
    def doline(v1,v2):
        if shoulddoline(v1,v2):
            glVertex3fv(v1)
            glVertex3fv(v2)
        return
    glBegin(GL_LINES)
    ocdec = getSphereTriangles(1)
    for tri in ocdec:
        #e Could probably optim this more, e.g. using a vertex array or VBO or
        #  maybe GL_LINE_STRIP.
        doline(tri[0], tri[1])
        doline(tri[1], tri[2])
        doline(tri[2], tri[0])
    glEnd()
    glEndList()

    drawing_globals.CylList = CylList = glGenLists(1)
    glNewList(CylList, GL_COMPILE)
    glBegin(GL_TRIANGLE_STRIP)
    for (vtop, ntop, vbot, nbot) in drawing_globals.cylinderEdges:
        glNormal3fv(nbot)
        glVertex3fv(vbot)
        glNormal3fv(ntop)
        glVertex3fv(vtop)
    glEnd()
    glEndList()

    drawing_globals.CapList = CapList = glGenLists(1)
    glNewList(CapList, GL_COMPILE)
    glNormal3fv(drawing_globals.cap0n)
    glBegin(GL_POLYGON)
    for p in drawing_globals.drum0:
        glVertex3fv(p)
    glEnd()
    glNormal3fv(drawing_globals.cap1n)
    glBegin(GL_POLYGON)
    #bruce 060609 fix "ragged edge" bug in this endcap: drum1 -> drum2
    for p in drawing_globals.drum2:
        glVertex3fv(p)
    glEnd()
    glEndList()

    drawing_globals.diamondGridList = diamondGridList = glGenLists(1)
    glNewList(diamondGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.digrid:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.lonsGridList = lonsGridList = glGenLists(1)
    glNewList(lonsGridList, GL_COMPILE)
    glBegin(GL_LINES)
    for p in drawing_globals.lonsEdges:
        glVertex(p[0])
        glVertex(p[1])
    glEnd()
    glEndList()

    drawing_globals.CubeList = CubeList = glGenLists(1)
    glNewList(CubeList, GL_COMPILE)
    glBegin(GL_QUAD_STRIP)
    # note: CubeList has only 4 faces of the cube; only suitable for use in
    # wireframes; see also solidCubeList [bruce 051215 comment reporting
    # grantham 20051213 observation]
    glVertex((-1,-1,-1))
    glVertex(( 1,-1,-1))
    glVertex((-1, 1,-1))
    glVertex(( 1, 1,-1))
    glVertex((-1, 1, 1))
    glVertex(( 1, 1, 1))
    glVertex((-1,-1, 1))
    glVertex(( 1,-1, 1))
    glVertex((-1,-1,-1))
    glVertex(( 1,-1,-1))
    glEnd()
    glEndList()

    drawing_globals.solidCubeList = solidCubeList = glGenLists(1)
    glNewList(solidCubeList, GL_COMPILE)
    glBegin(GL_QUADS)
    for i in xrange(len(drawing_globals.cubeIndices)):
        avenormals = V(0,0,0) #bruce 060302 fixed normals for flat shading 
        for j in xrange(4) :    
            nTuple = tuple(
                drawing_globals.cubeNormals[drawing_globals.cubeIndices[i][j]])
            avenormals += A(nTuple)
        avenormals = norm(avenormals)
        for j in xrange(4) :    
            vTuple = tuple(
                drawing_globals.cubeVertices[drawing_globals.cubeIndices[i][j]])
            #bruce 060302 made size compatible with glut.glutSolidCube(1.0)
            vTuple = A(vTuple) * 0.5
            glNormal3fv(avenormals)
            glVertex3fv(vTuple)
    glEnd()
    glEndList()                

    drawing_globals.rotSignList = rotSignList = glGenLists(1)
    glNewList(rotSignList, GL_COMPILE)
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS0n)):
        glVertex3fv(tuple(drawing_globals.rotS0n[ii]))
    glEnd()
    glBegin(GL_LINE_STRIP)
    for ii in xrange(len(drawing_globals.rotS1n)):
        glVertex3fv(tuple(drawing_globals.rotS1n[ii]))
    glEnd()
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.arrow0Vertices + drawing_globals.arrow1Vertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearArrowList = linearArrowList = glGenLists(1)
    glNewList(linearArrowList, GL_COMPILE)
    glBegin(GL_TRIANGLES)
    for v in drawing_globals.linearArrowVertices:
        glVertex3f(v[0], v[1], v[2])
    glEnd()
    glEndList()

    drawing_globals.linearLineList = linearLineList = glGenLists(1)
    glNewList(linearLineList, GL_COMPILE)
    glEnable(GL_LINE_SMOOTH)
    glBegin(GL_LINES)
    glVertex3f(0.0, 0.0, -drawing_globals.halfHeight)
    glVertex3f(0.0, 0.0, drawing_globals.halfHeight)
    glEnd()
    glDisable(GL_LINE_SMOOTH)
    glEndList()

    drawing_globals.circleList = circleList = glGenLists(1)
    glNewList(circleList, GL_COMPILE)
    glBegin(GL_LINE_LOOP)
    for ii in range(60):
        x = cos(ii*2.0*pi/60)
        y = sin(ii*2.0*pi/60)
        glVertex3f(x, y, 0.0)
    glEnd()    
    glEndList()

    # piotr 080405
    drawing_globals.filledCircleList = filledCircleList = glGenLists(1)
    glNewList(filledCircleList, GL_COMPILE)
    glBegin(GL_POLYGON)
    for ii in range(60):
        x = cos(ii*2.0*pi/60)
        y = sin(ii*2.0*pi/60)
        glVertex3f(x, y, 0.0)
    glEnd()    
    glEndList()

    drawing_globals.lineCubeList = lineCubeList = glGenLists(1)
    glNewList(lineCubeList, GL_COMPILE)
    glBegin(GL_LINES)
    cvIndices = [0,1, 2,3, 4,5, 6,7, 0,3, 1,2, 5,6, 4,7, 0,4, 1,5, 2,6, 3,7]
    for i in cvIndices:
        glVertex3fv(tuple(drawing_globals.cubeVertices[i]))
    glEnd()    
    glEndList()

    # Debug Preferences
    from utilities.debug_prefs import debug_pref, Choice_boolean_True
    from utilities.debug_prefs import Choice_boolean_False
    choices = [Choice_boolean_False, Choice_boolean_True]

    # 20060314 grantham
    initial_choice = choices[drawing_globals.allow_color_sorting_default]
    drawing_globals.allow_color_sorting_pref = debug_pref(
        "Use Color Sorting?", initial_choice,
        prefs_key = drawing_globals.allow_color_sorting_prefs_key)
        #bruce 060323 removed non_debug = True for A7 release, changed default
        #value to False (far above), and changed its prefs_key so developers
        #start with the new default value.
    #russ 080225: Added.
    initial_choice = choices[drawing_globals.use_color_sorted_dls_default]
    drawing_globals.use_color_sorted_dls_pref = debug_pref(
        "Use Color-sorted Display Lists?", initial_choice,
        prefs_key = drawing_globals.use_color_sorted_dls_prefs_key)
    #russ 080225: Added.
    initial_choice = choices[drawing_globals.use_color_sorted_vbos_default]
    drawing_globals.use_color_sorted_vbos_pref = debug_pref(
        "Use Color-sorted Vertex Buffer Objects?", initial_choice,
        prefs_key = drawing_globals.use_color_sorted_vbos_prefs_key)

    #russ 080403: Added drawing variant selection
    variants = [
        "0. OpenGL 1.0 - glBegin/glEnd tri-strips vertex-by-vertex.",
        "1. OpenGL 1.1 - glDrawArrays from CPU RAM.",
        "2. OpenGL 1.1 - glDrawElements indexed arrays from CPU RAM.",
        "3. OpenGL 1.5 - glDrawArrays from graphics RAM VBO.",
        "4. OpenGL 1.5 - glDrawElements, verts in VBO, index in CPU.",
        "5. OpenGL 1.5 - VBO/IBO buffered glDrawElements."]
    drawing_globals.use_drawing_variant = debug_pref(
        "GLPane: drawing method",
        Choice(names = variants, values = range(len(variants)),
               defaultValue = drawing_globals.use_drawing_variant_default),
        prefs_key = drawing_globals.use_drawing_variant_prefs_key)

    # temporarily always print this, while default setting might be in flux,
    # and to avoid confusion if the two necessary prefs are set differently
    # [bruce 080305]
    if (drawing_globals.allow_color_sorting_pref and
        drawing_globals.use_color_sorted_dls_pref):
        print "\nnote: this session WILL use color sorted display lists"
    else:
        print "\nnote: this session will NOT use color sorted display lists"
    if (drawing_globals.allow_color_sorting_pref and
        drawing_globals.use_color_sorted_vbos_pref):
        print "note: this session WILL use", \
              "color sorted Vertex Buffer Objects\n"
    else:
        print "note: this session will NOT use", \
              "color sorted Vertex Buffer Objects\n"

    # 20060313 grantham Added use_c_renderer debug pref, can
    # take out when C renderer used by default.
    if drawing_globals.quux_module_import_succeeded:
        initial_choice = choices[drawing_globals.use_c_renderer_default]
        drawing_globals.use_c_renderer = (
            debug_pref("Use native C renderer?",
                       initial_choice,
                       prefs_key = drawing_globals.use_c_renderer_prefs_key))
            #bruce 060323 removed non_debug = True for A7 release, and changed
            # its prefs_key so developers start over with the default value.

    #initTexture('C:\\Huaicai\\atom\\temp\\newSample.png', 128,128)
    return # from setup_drawer