3.3 Geometry Overrides

 
 
 

MPxGeometryOverride is the API entry point for defining high level, draw API agnostic support for custom DAG objects in Viewport 2.0. There is no corresponding class in the legacy interfaces. Viewport 2.0 is primarily structured around retained mode drawing; all geometric data is kept as buffers on the graphics card whenever possible. Any implementation of MPxGeometryOverride is best thought of as a “data feeder” which gets associated with a particular type of DAG object. In addition to providing data, implementations of MPxGeometryOverride may also define additional renderables (render items) which are needed to draw the object. The MPxGeometryOverride interface is very similar to the internal interfaces used to support native Maya objects like polygonal meshes and NURBS surfaces. Thus by using this class, plug-in authors gain access to all internal optimizations such as consolidation and vertex data sharing. In addition, implementations of MPxGeometryOverride work with internal Maya screen space effects and all supported shaders (including plug-in shaders).

MPxGeometryOverride is not meant for performing custom drawing and thus there is no “draw” method and no access to a draw context. It is also not a plug-in shape. It is simply a class that may be associated with a plug-in shape to describe how to create data buffers for drawing in Viewport 2.0. The actual draw for the DAG object is handled by the shader associated with each render item.

Implementations of MPxGeometryOverride must be registered with MDrawRegistry using classification strings. The classification string must begin with “drawdb/geometry” in order to be properly recognized by the system. DAG objects whose classification string satisfies the override classification string are evaluated using the override. Maya creates one instance of the registered MPxGeometryOverride for each associated DAG object.

Figure 34: An MPxGeometryOverride is associated with a given DAG object by its classification string. Shown is a possible configuration where render items from an internal surface shader as well as custom render items are part of the set of render items that the override would handle.

The interface is divided into several sections or phases. The first phase is updateDG(), where all attribute data from the associated Maya DAG object must be queried and cached. It is invalid to attempt to query attribute data in the final populateGeometry() phase, and such behaviour may result in instability. This separation keeps dependency graph evaluation separate from graphics device access in order to facilitate multi-threaded update.

The second phase is updateRenderItems(). During this phase the override is given a list of pre-created render items, one for each supported shader assigned to the object. The override may disable those render items and/or add additional render items to augment the display of the object. Additional render items need to be associated with shaders using the MShaderInstance interface. The shaders from each render item determine the total set of geometry requirements that the override needs to fulfill during the populateGeometry() stage. In MPxGeometryOverride, additional render items are comparable to the old MDrawRequest interface.

The third phase is populateGeometry(). During this phase, the override is given the list of geometry requirements it needs to satisfy, as well as the list of render items that generated those requirements. The override must fill the provided MGeometry structure with vertex buffers (MVertexBuffer) to satisfy the requirements. It must also produce an index buffer (MIndexBuffer) for each render item. The index buffers are stored on the MGeometry object and must also be directly associated with their corresponding render item using MRenderItem::associateIndexBuffer().

Figure 35: The three phases are used to label the connections between the main constructs which are involved in the update of an MPxGeometryOverride. The first phase (1.) involves accessing the associated DAG object. The second phase (2.) updates the render items and determines the geometry requirements. The third phase (3.) updates vertex and index buffer data and the indexing association for each render item. In this example, three render items drive the update requirements: one for an associated surface shader and two for custom render items that the override wishes to draw.

The phases of MPxGeometryOverride are triggered under several different circumstances. The first time a DAG object associated with an MPxGeometryOverride is created, all phases of the override run. Redraws of the scene do not trigger the update process of the override again unless the associated DAG object has changed. For a DAG object, there are two types of changes that can occur: topology change and data change. Data changes do not trigger a call to updateRenderItems(); only updateDG() and populateGeometry() are called in that case. Topology changes represent a full rebuild and all phases of the override are called. If an override adds additional render items in updateRenderItems() the first time it runs, those render items are retained across topology updates; therefore it is unnecessary to add them again. A plug-in DAG object can indicate to the Viewport 2.0 change manager that it has changed by calling the static method MRenderer::setGeometryDrawDirty(). There is a parameter on the method indicating whether the change is a topology or data change.

The Maya SDK example apiMeshShape uses MPxGeometryOverride. It defines a new class apiMeshGeometryOverride which provides geometry streams and indexing for shaded draw. It also defines additional render items for wireframe draw which are used for wireframe mode and for displaying the selection wireframe over the shaded object when the object is selected.

The following are some excerpts from apiMeshGeometryOverride to demonstrate sample behaviour:

  1. 1. Registration with the appropriate classification in initializePlugin()
    MFnPlugin plugin(...);
    // Define some drawDb classification string starting with drawdb/geometry
    MString apiMesh::drawDbClassification("drawdb/geometry/apiMesh");
    
    // Register a shape, with a drawdb classification
    plugin.registerShape( "apiMesh", apiMesh::id,
        &apiMesh::creator,
        &apiMesh::initialize,
        &apiMeshUI::creator,
        &apiMesh::drawDbClassification )
        
    // Register a geometry override with the same drawDB classification
    MHWRender::MDrawRegistry::registerGeometryOverrideCreator(
        apiMesh::drawDbClassification,
        apiMesh::drawRegistrantId,
        apiMeshGeometryOverride::Creator);
  2. updateRenderItems() method creating a “custom” render item to show wireframe.
    // Add a wire frame item if it's not there
    MHWRender::MRenderItem* dormantItem = NULL;
    
    int index = list.indexOf(
            "apiMeshWire",
            MHWRender::MGeometry::kLines,
            MHWRender::MGeometry::kWireframe);
            
    if (index < 0)
    {
        MHWRender::MRenderItem* wireItem = new MHWRender::MRenderItem(
                "apiMeshWire",
                MHWRender::MGeometry::kLines,
                MHWRender::MGeometry::kWireframe,
                false);
        list.append(wireItem);
    
        // Use a shader which will provide drawing using a solid color.
        MHWRender::MShaderInstance* shader = shaderMgr->getStockShader(
            MHWRender::MShaderManager::k3dSolidShader, NULL, NULL );
        if (shader)
        {
            // Set the color on the shader instance using the parameter interface
            static const float theColor[] = {1.0f, 0.0f, 0.0f, 1.0f};
            shader->setParameter("solidColor", theColor);
    
            // Assign the shader to the custom render item
            wireItem->setShader(shader);
        }
    }
  3. Vertex and index buffers are updated, and the index buffer association with render items is performed in populateGeometry()
    unsigned int totalVerts = N;
    
    // Set up three data buffers. One for position, one for normals and one for texture
    // coordinates.
    MHWRender::MVertexBuffer* positionBuffer = NULL;
    float* positions = NULL;
    MHWRender::MVertexBuffer* normalBuffer = NULL;
    float* normals = NULL;
    MHWRender::MVertexBuffer* uvBuffer = NULL;
    float* uvs = NULL;
    int numUVs = fMeshGeom->uvcoords.uvcount();
    
    // Scan through the requirements (descriptor list) one and a time. For each
    // descriptor check the semantic. Based on the semantic, update
    // the appropriate buffers that this override supports (position, normals and
    // texture coordinates).
    //
    const MHWRender::MVertexBufferDescriptorList& descList = requirements.vertexRequirements();
    int numVertexReqs = descList.length();
    MHWRender::MVertexBufferDescriptor desc;
    for (int reqNum=0; reqNum<numVertexReqs; reqNum++)
    {
        if (!descList.getDescriptor(reqNum, desc))
        {
            continue;
        }
    
        switch (desc.semantic())
        {
            case MHWRender::MGeometry::kPosition: // Handle position requirement
            {
                if (!positionBuffer)
                {
                    positionBuffer = data.createVertexBuffer(desc);
                    if (positionBuffer)
                    {
                        positions = (float*)positionBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            case MHWRender::MGeometry::kNormal: // Handle normal requirement
            {
                if (!normalBuffer)
                {
                    normalBuffer = data.createVertexBuffer(desc);
                    if (normalBuffer)
                    {
                        normals = (float*)normalBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            case MHWRender::MGeometry::kTexture: // Handle texture coordinate requirement
            {
                if (!uvBuffer)
                {
                    uvBuffer = data.createVertexBuffer(desc);
                    if (uvBuffer)
                    {
                        uvs = (float*)uvBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            default:
                // do nothing for stuff we don't understand
            break;
        }
    }
    
    // Copy data from shape into data buffers
    for (int i=0; i<fMeshGeom->faceCount; i++)
    {
        // ignore degenerate faces
        int numVerts = fMeshGeom->face_counts[i];
        if (numVerts > 2)
        {
            for (int j=0; j<numVerts; j++)
            {
                if (positions)
                {
                    // Fill in position data…
                }
                if (normals)
                {
                    // Fill in normal data…
                }
    
                if (uvs)
                {
                    // Fill in uv data…
                }
    
                vid++;
            }
        }
    }
    
    // Commit the updated buffers 
    if (positions)
        positionBuffer->commit(positions);
    if (normals)
        normalBuffer->commit(normals);
    if (uvs)
        uvBuffer->commit(uvs);
    
    // Fill in indexing data for the custom wireframe render item
    //
    MHWRender::MIndexBuffer* wireIndexBuffer = NULL; 
    int numItems = renderItems.length();
    for (int i=0; i<numItems; i++)
    {
        const MHWRender::MRenderItem* item = renderItems.itemAt(i);
        if (!item) continue;
    
        // Check for the wireframe render item created updateRenderItems()
        if (item->name() == "apiMeshWire”) 
        {
            // Allocate a wireframe buffer
            if (!wireIndexBuffer)
            {
                wireIndexBuffer = data.createIndexBuffer(MHWRender::MGeometry::kUnsignedInt32);
                if (wireIndexBuffer)
                {
                    unsigned int* buffer = (unsigned int*)wireIndexBuffer->acquire(2*totalVerts);
                    if (buffer)
                    {
                        // Fill in the buffer here…
    
                        // Commit the buffer
                        wireIndexBuffer->commit(buffer);
                    }
                }
            }
    
            // Associate same index buffer with either render item
            if (wireIndexBuffer)
            {
                item->associateWithIndexBuffer(wireIndexBuffer);
            }    
            
        }
    
        // Handle other render items…
    }