Recently we were asked how to implement super sampling for a viewport render override. It requires you to create new render targets to replace the original ones.
The MRT sample is very good at showing how to do it, however it is too complex to begin with. So we'll create a new one based on the simplest sample - viewOverrideSimple. The full code can be found on GitHub here.
The original sample has three render operations: scene, HUD and present. To render in a higher resolution, we need to modify scene operation and add quad operation for down sampling the render output from the scene operation. The HUD and present will be left untouched.
First, let's begin with the render override itself:
We need to extend the array size of viewOverrideSimple::mOperations and viewOverrideSimple::mOperationNames, it has four operations now. And we need to add new members for storing render targets after operation iteration:
// Temporary of operation iteration
int mCurrentOperation;
// Supersampling render targets
MString mTargetOverrideNames[2];
MHWRender::MRenderTargetDescription* mTargetDescriptions[2];
MHWRender::MRenderTarget* mTargets[2];
The render targets are used for scene operation to render with, and worked as the input parameters for our bicubic shader in the quad operation.
Next, we need to create our render target descriptions inside the constructor:
mOperations[0] = mOperations[1] = mOperations[2] = mOperations[3] = NULL;
mOperationNames[0] = "viewOverrideSimple_Scene";
mOperationNames[1] = "viewOverrideSimple_Quad";
mOperationNames[2] = "viewOverrideSimple_HUD";
mOperationNames[3] = "viewOverrideSimple_Present";
//MSAA sample count, we don't want MSAA here
unsigned int sampleCount =1;
MHWRender::MRasterFormat colorFormat = MHWRender::kR32G32B32A32_FLOAT;
//Create color buffer first
mTargetOverrideNames[0] = MString("_viewRender_SSAA_color");
mTargetDescriptions[0] = new MHWRender::MRenderTargetDescription(mTargetOverrideNames[0], 256, 256, sampleCount, colorFormat, 1, false);
mTargets[0] = NULL;
//We also need to create a depth buffer
mTargetOverrideNames[1] = MString("_viewRender_SSAA_depth");
mTargetDescriptions[1] = new MHWRender::MRenderTargetDescription(mTargetOverrideNames[1], 256, 256, sampleCount, MHWRender::kD24S8, 1, false);
mTargets[1] = NULL;
Both color and depth target are required here. Missing depth buffer will cause the depth test to fail in the scene operation.
In the destructor, we need to release our render target description and render target like below:
for(int i = 0; i < 2; ++i)
{
if(mTargetDescriptions[i])
{
delete mTargetDescriptions[i];
mTargetDescriptions[i] = NULL;
}
MHWRender::MRenderer* theRenderer = MHWRender::MRenderer::theRenderer();
if (theRenderer)
{
auto targetManager = theRenderer->getRenderTargetManager();
if(targetManager)
{
targetManager->releaseRenderTarget(mTargets[i]);
mTargets[i] = NULL;
}
}
}
The next part is setup, and we need to create our render targets at that point and set them as the output target for our scene render operation. We'll also let scene render use its following quad operation.
MStatus viewOverrideSimple::setup( const MString & destination )
{
if (!mOperations[0])
{
MHWRender::MRenderer *theRenderer = MHWRender::MRenderer::theRenderer();
if (!theRenderer)
return MStatus::kFailure;
auto renderTargetManager = theRenderer->getRenderTargetManager();
unsigned int targetWidth = 256;
unsigned int targetHeight = 256;
// Get current render target's output size
theRenderer->outputTargetSize( targetWidth, targetHeight );
// Double size the render target.
targetHeight *= 2;
targetWidth *= 2;
// Create or update our render targets
for(int i = 0; i < 2; ++i)
{
mTargetDescriptions[i]->setWidth(targetWidth );
mTargetDescriptions[i]->setHeight(targetHeight );
if(!mTargets[i])
{
mTargets[i] = renderTargetManager->acquireRenderTarget(*mTargetDescriptions[i]);
}
else
{
mTargets[i]->updateDescription(*mTargetDescriptions[i]);
}
}
mOperations[0] = (MHWRender::MRenderOperation *) new simpleViewRenderSceneRender( mOperationNames[1] );
mOperations[1] = (MHWRender::MRenderOperation *) new simpleViewRenderQuadRender( mOperationNames[2] );
mOperations[2] = (MHWRender::MRenderOperation *) new simpleViewRenderHudRender();
mOperations[3] = (MHWRender::MRenderOperation *) new simpleViewRenderPresentRender( mOperationNames[3] );
}
if (!mOperations[0] ||
!mOperations[1] ||
!mOperations[2] ||
!mOperations[3])
{
return MStatus::kFailure;
}
else
{
//Set custom render targets
((simpleViewRenderSceneRender*)mOperations[0])->setRenderTargets(mTargets, 2);
((simpleViewRenderSceneRender*)mOperations[0])->setQuadRender((simpleViewRenderQuadRender*)mOperations[1]);
}
return MStatus::kSuccess;
}
At last, we need to update the cleanup operations:
MStatus viewOverrideSimple::cleanup()
{
mCurrentOperation = -1;
auto quadOp = (simpleViewRenderQuadRender *)mOperations[1];
if (quadOp)
{
quadOp->setColorTarget(NULL);
quadOp->setDepthTarget(NULL);
quadOp->updateTargets();
}
auto *sceneOp = (simpleViewRenderSceneRender *)mOperations[0];
if (sceneOp)
{
sceneOp->setRenderTargets( NULL, 0 );
}
return MStatus::kSuccess;
}
The work for viewOverrideSimple has been done now, we have updated it in following ways:
- Add a quad operation
- Create two render targets and render target descriptions in constructor
- Release render targets and render target descriptions in the destructor
- Create render targets and setup scene render operations with our render targets and quad operations
- Cleanup
The next part of the work takes in the the MSceneRender, and we'll need to override two functions:
virtual MHWRender::MRenderTarget* const* targetOverrideList(unsigned int &listSize);
virtual void postSceneRender(const MHWRender::MDrawContext & context);
In the MSceneRender::targetOverrideList, we need to return the render targets created earlier to let Maya render in new settings.
//Override render targets
MHWRender::MRenderTarget* const* simpleViewRenderSceneRender::targetOverrideList(unsigned int &listSize)
{
if(mTargets)
{
listSize = numTargets;
return mTargets;
}
listSize = 0;
return NULL;
}
And in the MSceneRender::postSceneRender, we'll update our quad render with latest result, like this:
void simpleViewRenderSceneRender::postSceneRender(const MHWRender::MDrawContext & context)
{
if(mSimpleQuadRender)
{
mSimpleQuadRender->setColorTarget(mTargets[0]);
mSimpleQuadRender->setDepthTarget(mTargets[1]);
mSimpleQuadRender->updateTargets();
}
}
At last, it comes to the quad render. It is also very simple, we need to create a bicubic shader in the constructor and use it for rendering.
simpleViewRenderQuadRender::simpleViewRenderQuadRender(const MString &name)
: MQuadRender( name )
, mShaderInstance(NULL)
, mColorTargetChanged(false)
, mDepthTargetChanged(false)
, fSamplerState(NULL)
{
mDepthTarget.target = NULL;
mColorTarget.target = NULL;
if (!mShaderInstance)
{
MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer();
const MHWRender::MShaderManager* shaderMgr = renderer ? renderer->getShaderManager() : NULL;
if (shaderMgr)
{
//Create our bicubic shader
mShaderInstance = shaderMgr->getEffectsFileShader( "mayaBlitColorDepthBicubic", "" );
}
}
}
Maya will add the extension name for the mayaBlitColorDepthBicubic based on your viewport settings. I've only created an .ogsfx shader file, that is working for OpenGL core profile. The shader is following the BiCubic document on the wikipedia, you can check it out here if you want to. To use this, please put mayaBlitColorDepthBicubic.ogsfx inside Maya/bin/OGSFX folder.
That's all of the essential part of this sample. The rest of the code are setters and updates for shader parameters, please check the full sample code for more details.
Comments
You can follow this conversation by subscribing to the comment feed for this post.