Customizing Geometric Data for Shaders

The data representation for Maya DAG objects dictates both the format as well as the types of geometric streams that can be used as inputs for shading algorithms. For a given shader it is possible that either the format or the type of stream provided may be insufficient.

To address these issues, an API to provide data customization is provided to:

All interfaces work within a unified system, which allows for any or all customizations to be used in conjunction with each other.

There are two interfaces which provide the ability for a plug-in to provide custom data streams and custom data indexing.

Geometry Generators

Custom streams can be provided using an MPxVertexBufferGenerator. There is no equivalent legacy class to which matches this functionality.

A generator can supply a stream based on new data and/or existing stream data.

In order to use an MPxVertexBufferGenerator, it needs to be registered via the MDrawRegistry class in a similar fashion to how overrides are registered.

When a shader requires certain data streams, it specifies its requirements using MVertexBufferDescriptors. Each MVertexBufferDescriptor specifies its stream using a stream name and a semantic. Every MPxVertexBufferGenerator is required to have an associated name. To reference a generator, this name can be used by an MVertexBufferDescriptor as its semantic name.

The following is an example for an MPxShaderOverride which specifies the requirement for two custom streams:

// Create a custom position stream requirement. Set a unique semantic name for lookup.
// Add to the list of requirements.
MHWRender::MVertexBufferDescriptor positionDesc(
                empty, 
                MHWRender::MGeometry::kPosition,
                MHWRender::MGeometry::kFloat,
                3);

positionDesc.setSemanticName("customPositionStream");
addGeometryRequirement(positionDesc);

// Create a custom normal stream requirement. Set a unique semantic name for lookup.
// Add to the list of requirements.
MHWRender::MVertexBufferDescriptor normalDesc(
                empty,
                MHWRender::MGeometry:: kNormal,
                MHWRender::MGeometry::kFloat,
                3);
normalDesc.setSemanticName("customNormalStream");
addGeometryRequirement(normalDesc);

A generator is called to fill in data via its createVertexStreams() method. For the given DAG path, the generator updates and returns an MVertexBuffer.

A set of existing source streams can be supplied for situations where new data is generated based on existing data. The specific streams that are required are returned via a query to the generator (getSourceStreams()) before createVertexStreams() is invoked.

As an example, existing position and normal streams can be combined to form a new stream. Since the input streams (MVertexBuffers) are GPU resources, they need to be mapped to CPU memory before they can be read from. Any source stream data should never be modified.

In order to fill in the data correctly, the indexing which is used to reference the data stream is also provided. The generator provides the desired indexing for the custom stream (getSourceIndexing()). It is possible that indexing optimization may be performed based on the indexing for all internal and custom streams. Both the original and the shared indexing data are provided to the generator at stream creation time. Indexing is specified using an MComponentDataIndexing instance. This is a wrapper for a set of index values and a component type.

It is the responsibility of the generator to perform the correct allocation and transfer of data into the MVertexBuffer that it is supplied.

Figure 69: The flow of execution is roughly shown based on the ordering of the methods on the buffer generator. From the top downwards, source stream and indexing requirements are queried. At creation time any requested source streams, the relevant dag path and the overall shared indexing are provided as inputs. The generator is responsible for filling in the MVertexBuffer. In this case, it swizzles an input position and a normal stream into a new stream.

Custom stream indexing should be provided using an: MPxPrimitiveGenerator.

As with stream generators a unique name is used to identify the indexing that can be used. For indexing, a shader instance specifies the required indexing using a MIndexBufferDescriptor. The primitive name in the descriptor can be set to the name of the primitive generator.

In order to use an MPxPrimitiveGenerator, it needs to be registered via the MDrawRegistry class in a similar fashion to that of an MPxVertexBufferGenerator.

There are two entry points which currently can support adding in indexing requirements: For an MPxShaderOverride this is the point when its geometry requirements are defined and for an MPxGeometryOverride this is the point when geometry requirements are defined for additional render items. In either case, the requirements are specified by a MIndexBufferDescriptor.

To add custom indexing as a requirement, an appropriately set up MIndexBufferDescriptor must be added. The type must be set to “custom” and a unique name set. This name should match the name of a registered generator.

This is an example of how a non-custom index requirement is added for an MPxGeometryOverride:

MHWRender::MGeometryRequirements geomRequirements;

// As we are not using MIndexBufferDescriptor::kCustom for custom named index buffers
// just use an empty string here.
MString noName;

// Add in a triangle indexing requirement
MHWRender::MIndexBufferDescriptor triangleDesc(
        MHWRender::MIndexBufferDescriptor::kTriangle, 
        noName, 
        MHWRender::MGeometry::kTriangles, 
        3, 
        compObj);

geomRequirements.addIndexingRequirement(triangleDesc);

This is an example of adding a custom indexing requirement for a MPxShaderOverride:

// Name of custom indexing 
MString customPrimitiveName("customPrimitive");

// Add in a custom triangle indexing requirement
MHWRender::MIndexBufferDescriptor indexingRequirement( 
        MHWRender::MIndexBufferDescriptor::kCustom, 
        customPrimitiveName, 
        MHWRender::MGeometry::kTriangles);
        
addIndexingRequirement(indexingRequirement);

Figure 70: Resulting relationship based on code example.

When custom indexing is required the appropriate calls to a generator instance are made. The generator first provides the indexing size relative to a DAG object and DAG component (computeIndexingCount()). The generator is then asked to create the actual indexing. The source and target indexing data is also provided. An MIndexBuffer must be updated and returned. As this is a GPU resource, it must be mapped back to CPU memory, updated, and then transferred back to GPU memory.

Figure 71

Examples provided in the Developer Kit include:

The crack free mutator creates an indexing buffer with more information than that of a normal triangle list. The triangle indexing provides, for each triangle, the indices of their 3 vertices. The crack free mutator provides indexing fit for tessellation.

The sample currently implements support for the modes “PN-AEN9” and “PN-AEN18” (Point-Normal Triangles using Adjacent Edge Normals).

The PN-AEN9 mode creates indexing for each triangle with the following data:

This results in a stride of 9 for the index array. Hence, the name PN-AEN9.

Based on the above diagram, for a triangle represented by the vertices a, b and c, the PNAEN9 code generates the following indexing:

 [a b c] [d e f g h i]

The PN-AEN18 adds even more data than PN-AEN9:

This results in a stride of 18 for the index array.

A dominant edge is the edge that has the lowest vertex indices. In the same example above, the dominant edge between [ab] and [de] for triangle [abc] will be [ab] and for triangle [dej] it will still be [ab]

A dominant uv position is the triangle for which each vertex has the lowest uv coordinates. In the example above, the dominant position for triangle [abc] is [afh]

The PN-AEN18 code generates the following indexing:

[a b c] [d e f g h i] [a b f g h i] [a f h]

The dx11Shader and CgFx plug-in respectively show usage of generators for HLSL and CgFx. The dx11Shader explicitly registers the crack-free tesselator mutators for shader semantics “PNAEN9” and “PNAEN18”.

The SDK provided sample shader (AutodeskUberShader.fx) uses the following technique declaration to notify the API of the special indexing requirements:

technique11 TessellationON
<
    string index_buffer_type = "PNAEN18"; // Indicate index buffer type required
>
{  
    pass p0
    {
        SetRasterizerState(CullFront);
        SetDepthStencilState(DepthNormal, 0);
        SetVertexShader(CompileShader(vs_5_0, vt()));
        SetHullShader(CompileShader(hs_5_0, HS()));
        SetDomainShader(CompileShader(ds_5_0, DS()));
        SetGeometryShader(NULL);                                
        SetPixelShader(CompileShader(ps_5_0, f()));
    }
}

In this case, an annotation per technique is being parsed within the plug-in code. The customIndexingMutatorName string value “PNAEN18” is the semantic name on an index descriptor requirement, which, at update time, calls the registered crack-free indexing mutator.

MString customIndexingMutatorName;

ID3DX11EffectTechnique * mpD3DTechnique;

ID3DX11EffectVariable* indexBufferType = 
        mpD3DTechnique->GetAnnotationByName("index_buffer_type");

if(indexBufferType && indexBufferType->IsValid())
{
    ID3DX11EffectStringVariable* indexBufferTypeStr = indexBufferType->AsString();
    if(indexBufferTypeStr && indexBufferTypeStr->IsValid())
    {
        LPCSTR value; 
        if( SUCCEEDED ( indexBufferTypeStr->GetString( &value ) ) )
        {
            customIndexingMutatorName = MString(value);
        }
    }
}

Geometry Mutators

As the name implies a mutator can be used to modify the packing of data for a given existing vertex buffer (MVertexBuffer). As with stream generators, mutators are associated with a descriptor through name matching between the mutator and the semantic name on a descriptor (MVertexBufferDescriptor)

A mutator is represented as an MPxVertexBufferMutator. Mutators must be registered with MDrawRegistry.

This example illustrates how to specify an existing mutator for use:

MString empty;

MHWRender::MVertexBufferDescriptor positionDesc(
        empty,
        MHWRender::MGeometry::kPosition,
        MHWRender::MGeometry::kFloat,
        3);
        
// Use the custom semantic name "swizzlePosition" which corresponds to a plug-in which will
// swizzle the x,y, and z channels of a position stream using this positionDesc.setSemanticName("swizzlePosition"); 

// Add the descriptor as part of the requirements for this shader
addGeometryRequirement(positionDesc);

The following diagram illustrates the relationship of the above code example. An additional “customMutator” is added to show the lookup by semantic name.

Figure 73

When a mutator is required to perform an update, an associated Maya DAG path and data stream indexing are passed in. An existing MVertexBuffer is passed in for update and data is modified in-place.

Figure 74

The Developer Kit example vertexBufferMutator has the full code for registering a mutator which performs the swizzle. The hwPhongShader plug-in has code which demonstrates the usage of the custom swizzle.

The following is an excerpt from the sample code which shows how in-place editing is performed:

// Input argument
MVertexBuffer& vertexBuffer;

unsigned int vertexCount = vertexBuffer.vertexCount();
if (vertexCount <= 0)
    return;
    
// Acquire the buffer to fill with data. Data is not in CPU memory
float* buffer = (float*)vertexBuffer.acquire(vertexCount);
float* start = buffer;

for (unsigned int i = 0; i < vertexCount; ++i)
{
    // Here we swap the x, y and z values
    float x = buffer[0];
    buffer[0] = buffer[1];    // y --> x
    buffer[1] = buffer[2];    // z --> y
    buffer[2] = x;            // x --> z
    buffer += 3;
}

// Commit the buffer to signal completion. CPU data will be transferred to GPU memory.
vertexBuffer.commit(start);

Indexing Mutators

An indexing mutator can be used to modify the data for a given existing index buffer (MIndexBuffer). Mutators are associated with a descriptor through name matching between the mutator and the semantic name on a descriptor (MIndexBufferDescriptor)

A mutator is represented as an MPxIndexBufferMutator. Mutators must be registered with MDrawRegistry.

This example illustrates how to specify an existing mutator for use:

MHWRender::MIndexBufferDescriptor indexingRequirement(
        MHWRender::MIndexBufferDescriptor::kCustom,
        customIndexingMutatorName,
        MHWRender::MGeometry::kTriangles);
        
// Add the descriptor as part of the requirements for this shader
addIndexingRequirement(indexingRequirement);

The following diagram illustrates the relationship of the above code example. An additional “customMutator” is added to show the lookup by semantic name.

Figure 75

When a mutator is required to perform an update, an associated data stream indexing and previously generated vetex buffer array are passed in. An existing MIndexBuffer is passed in for update and data is modified in-place.

Figure 76

The Developer Kit example crackFreePrimitiveGenerator, part of the dx11Shader project has the full code for registering a mutator which performs PN AEN patch generation. The dx11Shader plug-in has code which demonstrates the usage of the custom patch when the “TessellationON” or “Wireframe” techniques of the MayaUberShader.fx effect file is selected.

The following is an excerpt from the sample code which shows how in-place editing is performed:

// Input argument
MIndexBuffer& indexBuffer;

// Acquire the buffer to fill with data. Data is not in CPU memory
unsigned int* buffer = (unsigned int *) indexBuffer.acquire(numTri * triSize, true);
unsigned int* start = buffer;

for (unsigned int i = 0; i < numTri; ++i)
{
    // Here we fill PN AEN information
    buffer[0] = vertexId0; 
    buffer[1] = vertexId1;
    buffer[2] = vertexId2;
    // ...
    buffer[17] = dominantVertex2;
    buffer += triSize;
}

// Commit the buffer to signal completion. CPU data will be transferred to GPU memory.
indexBuffer.commit(start);
primitiveStride = triSize; // Output argument
return MHWRender::MGeometry::kPatch;

Pipeline Summary:

Figure 77

The above diagram shows how generators and mutators fit in the overall pipeline. Shown is a shader override which has custom requirements (based on shader parameters) for the generation and mutation of vertex buffers, and primitive generation (as represented by the descriptors). When updates need to occur (Update phase), the shader requirements on an MPxGeometryOverride are name matched to generators and mutators which are invoked to update or create vertex and index buffers. These custom buffers can be used by one or more render items which flow down the pipeline until it reaches the Draw Phase. At this point, the custom vertex and index buffers can be bound to the shader parameters which initiated the custom data requirement.