Implement an MRenderOverride

You can create a plug-in with an MRenderOverride to completely replace the rendering pipeline. The plug-in can use standard operations (or passes) in the pipeline, as well as add other custom operations. To do this, the plug-in should first obtain the operation set that represents the current pipeline, then modify it by adding, inserting, or removing operations.

The standard operations are controlled by a variety of settings and filters that determine how the pass operates. This provides ultimate control over the viewport, and provides a few conveniences for filtering the objects displayed, the display modes used, the material overrides, the post processes, and so forth.

The advantage of creating an MRenderOverride plug-in by populating the operation list is that the plug-in does not need to include target creation or management in its implementation. This is handled automatically by the system and pushed down to the operation level.

The drawback to using an MRenderOverride is that it must override the entire pipeline, and any customization to the pipeline must appear as a whole new renderer. For more information about MRenderOverride, see Rendering Overrides.

Classes and interfaces

The following classes and interfaces enable you to create an MRenderOverride as outlined above.

The MRenderOperationList class holds and takes ownership of a collection of MRenderOperations. This class has standard list methods for indexing, adding, removing, and replacing operations in the list, and also includes methods to take ownership of operations from a list.

The MRenderer::getStandardViewportOperations() interface allows you to fill a list with the standard viewport operations used for non-override drawing.

The MRenderOverride protected member:

MRenderOperationList mOperations

is the operation list for this override.

To obtain the list of standard operations

A class deriving from MRenderOverride simply needs to populate the list with operations, either by getting the standard list of operations, by adding their own operations, or a combination of both.

//Get the standard list of operations
MHWRender::MRenderer::theRenderer()->getStandardViewportOperations(mOperations);

//Create a custom quad render operation 
PostQuadRender* swirlOp = new PostQuadRender( kSwirlPassName, "FilterSwirl", "" );
swirlOp->setEnabled(false); // swirl is disabled by default

//Insert swirlOp in the pipeline after the kStandardSceneName operation
mOperations.insertAfter(MHWRender::MRenderOperation::kStandardSceneName, swirlOp);

A few standard operation names have been predefined for ease of use. These names can be used to locate operations in a standard operation list.

static const MString kStandardBackgroundName;  
static const MString kStandardSceneName;    
static const MString kStandardHUDName;
static const MString kStandardPresentName;

Managing targets with operations

Target management is handled at the operation level to allow operations to be more autonomous and be self-describing when added to a larger operation list.

A class deriving from MRenderOperation must do the following to complete its implementation:

  1. Declare the names of the inputs it requires, and declare the names of the output targets it produces.
  2. Provide an MRenderTargetDescription for its named inputs.
  3. Specify the start index and number of targets it will write to.

MRenderOperation has two protected member variables that allow you to perform the first step above:

To declare the inputs and outputs, an operation must add the name (that is, the semantic) of the targets it want to use. A few common target types have been predefined in the MRenderOperation class:

static const MString kColorTargetName;  
static const MString kDepthTargetName;
static const MString kAuxiliaryTargetName;
static const MString kAuxiliaryTarget2Name;
static const MString kAuxiliaryTarget3Name;
static const MString kAuxiliaryTarget4Name;
static const MString kAuxiliaryDepthTargetName;
TIP:When writing post effects, auxiliary targets are required when you need to read from the current rendered result and write out new results. Because you cannot simultaneously read and write to the same buffer, you must create a new render target that is similar to the target used in previous passes. An auxiliary target should have the same size and format as the standard color target. After they are written to, the auxiliary target inputs are returned as the new standard color target to be read from or appended to in additional post passes. The previous standard color target should be returned as an auxiliary target so that it can be reused by other post effects that require an additional auxiliary target.

By default, all MRenderOperations use the standard color and depth target names for their inputs, so that they connect automatically to other operations in the list that write to or read from the standard targets. This is all that is required for operations that simply write to the currently active target, and is implemented for you in the base class.

For example, the following code snippet declares two default inputs and two default outputs:

mInputTargetNames.append(kColorTargetName);
mInputTargetNames.append(kDepthTargetName);
mOutputTargetNames.append(kColorTargetName);
mOutputTargetNames.append(kDepthTargetName);

MRenderOperation classes that want to read from or write to multiple targets can override the default inputs and outputs by clearing the lists and adding their own.

mInputTargetNames.append(kAuxiliaryTargetName);
mInputTargetNames.append(kAuxiliaryDepthTargetName);
mInputTargetNames.append(“sceneTarget”);
mInputTargetNames.append(“sceneDepthTarget”);

mOutputTargetNames.append(kColorTargetName);
mOutputTargetNames.append(kDepthTargetName);

They can then supply descriptions for the named input targets in their custom passes. In the following example, the descriptions are copied from the auxiliary targets in order for the multi-sample anti-aliasing properties and size to match.

bool PostQuadRender::getInputTargetDescription(const MString& name, 
MHWRender::MRenderTargetDescription& description) 
{ 
    if (name == “sceneTarget”)
    {
        MHWRender::MRenderTarget* outTarget = getInputTarget(kAuxiliaryTargetName);
        if (outTarget)
            outTarget->targetDescription(description);
        description.setName("_post_target_1");
        return true;
    }
    else if (name == “sceneDepthTarget”)   
    {
        MHWRender::MRenderTarget* outTarget = getInputTarget(kAuxiliaryDepthTargetName);
        if (outTarget)
            outTarget->targetDescription(description);
        description.setName("_post_target_depth");
        return true;
    }
    return false;
}

An MRenderOverride plug-in can then connect operations by ensuring that the names match.

//Get MRenderOperationList of the standard viewport operations
MHWRender::MRenderer::theRenderer()->getStandardViewportOperations(mOperations);

//Get the index of the operation kStandardSceneName
int sceneOpID = mOperations.indexOf(MHWRender::MRenderOperation::kStandardSceneName);

//Set sceneOp to point to the kStandardSceneName operation in the MRenderOperationList
MRenderOperation* sceneOp = mOperations[sceneOpID];

//Rename the output target of the kStandardSceneName operation from kColorTargetName to sceneTarget
sceneOp->renameOutputTarget(MHWRender::MRenderOperation::kColorTargetName, “sceneTarget”);

//Create the new MRenderOperation swirlOp
PostQuadRender* swirlOp = new PostQuadRender( kSwirlPassName, "FilterSwirl", "" );

// Insert swirlOp after the operation named kStandardSceneName
// Because swirlOp has an input named sceneTarget, and you have renamed the output target
// of kStandardSceneName to sceneTarget, the two operations will share the same target.
// The target is constructed from the description provided by the swirl
// operation and is the target that the scene operation should render to.
mOperations.insertAfter(MHWRender::MRenderOperation::kStandardSceneName, swirlOp);

To specify the start index and number of targets an MRenderOperation should write to, do as follows:

int PostQuadRender::writableTargets(unsigned int& count)
{
    count = 2;  
    return 0;
}