Render Targets - Mass Confusion. Need an Expert
by Matt Vitelli · in Torque Game Engine Advanced · 06/07/2008 (12:59 am) · 5 replies
Hello, for the past few days I've been playing around with render targets. I'm unfortunately having quite the time trying to get them to function properly. I'm extremely confused and in desperate need of help here. Any suggestions or code would be greatly appreciated.
Now, heres how my current system functions:
A texture is set during the creation of the various render elements.
Next, during the mesh rendering stage, if the texture does not match the client's resolution settings, the texture is adjusted. In this function, a render target is allocated and the active render target is set:
After this function is called, all of the mesh geometry is rendered using a custom shader that outputs to the fragment structure of COLOR0. Once the geometry rendering is complete, the following functions are called:
The result of this is as follows:

For the record, everything renders correctly when I comment out the setupTargets function and the ending render target functions.
What would be great is if someone could walk me through the process of how this render target system works and some of the reasons behind the various functions and types.
Thanks,
Matt Vitelli
Now, heres how my current system functions:
A texture is set during the creation of the various render elements.
Next, during the mesh rendering stage, if the texture does not match the client's resolution settings, the texture is adjusted. In this function, a render target is allocated and the active render target is set:
void RenderMeshMgr::setupTargets()
{
mTarget = GFX->allocRenderToTextureTarget();
GFX->pushActiveRenderTarget();
// Make sure we have a final display target of the same size as the view
// we're rendering.
Point2I goalResolution = gClientSceneGraph->getDisplayTargetResolution();
if(mSurface.isNull() || goalResolution != Point2I(mSurface.getWidth(), mSurface.getHeight()))
{
Con::printf("Deferred Renderer - Resizing normals texture");
mSurface.set( goalResolution.x, goalResolution.y, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetZBufferProfile, 1 );
}
// Set up render target.
mTarget->attachTexture(GFXTextureTarget::Color0, mSurface);
mTarget->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil );
GFX->setActiveRenderTarget( mTarget );
GFX->clear( GFXClearTarget, ColorI(0,0,0,0), 1.0f, 0 );
//GFX->setFillMode(GFXFillSolid);
GFX->pushWorldMatrix();
}After this function is called, all of the mesh geometry is rendered using a custom shader that outputs to the fragment structure of COLOR0. Once the geometry rendering is complete, the following functions are called:
mTarget->clearAttachments(); GFX->popActiveRenderTarget(); GFX->popWorldMatrix();
The result of this is as follows:

For the record, everything renders correctly when I comment out the setupTargets function and the ending render target functions.
What would be great is if someone could walk me through the process of how this render target system works and some of the reasons behind the various functions and types.
Thanks,
Matt Vitelli
#2
Here is my Pre-Pass RT creation:
Then during render:
So basically what is happening:
In your code:
When working with render targets, you really need to use PIX to see what is going on. It's very confusing at first, but it gets easier.
I'll try to answer follow-up questions asap.
06/09/2008 (9:55 am)
Sorry, spaced this off...Here is my Pre-Pass RT creation:
GFXTextureTargetRef RenderPrePassMgr::getNZTarget( const U32 idx /* = 0 */ )
{
GFXTarget *curRT = GFX->getActiveRenderTarget();
if( mNZTarget[idx] == NULL && curRT != NULL )
{
if( GFXMSAADetector::get().isCheckDone() && GFXMSAADetector::get().isMSAAEnabled() )
Con::errorf( "RenderPrePassMgr: MSAA is enabled either by driver or application. Pre-Pass Lighting behavior undefined." );
const Point2I &rtSz = curRT->getSize();
mNZTarget[idx] = GFX->allocRenderToTextureTarget();
mNZTexHandle[idx].set( rtSz.x, rtSz.y, scNZTargetFormat, &GFXDefaultRenderTargetProfile, 1 );
mNZTarget[idx]->attachTexture( GFXTextureTarget::Color0, mNZTexHandle[idx] );
mNZTarget[idx]->attachTexture( GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil );
}
return mNZTarget[idx];
}Then during render:
GFX->pushActiveRenderTarget();
GFX->setActiveRenderTarget( getNZTarget( mNZSwapChainIndex ) );
// Render...
GFX->popActiveRenderTarget();So basically what is happening:
mNZTarget[idx] = GFX->allocRenderToTextureTarget();Allocate a render-to-texture target. This is just a handle to the target, you can attach different textures to a target.
mNZTexHandle[idx].set( rtSz.x, rtSz.y, scNZTargetFormat, &GFXDefaultRenderTargetProfile, 1 );Allocate an anonymous texture. Since this is going to be a destination for render data, I'm using the GFXDefaultRenderTargetProfile. (There are MSAA issues w/ this profile, but I am detecting MSAA, so ignore that for now)
mNZTarget[idx]->attachTexture( GFXTextureTarget::Color0, mNZTexHandle[idx] ); mNZTarget[idx]->attachTexture( GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil );This binds the destinations for the render data.
In your code:
mTarget->clearAttachments(); GFX->popActiveRenderTarget(); GFX->popWorldMatrix();You don't want to call 'clearAttachments()' before you pop the active render target. Popping a target, or setting a different target, is what causes the resolve (again depends on case, but do it in this order to prevent issues). It also looks like you are re-allocating your render target each frame.
When working with render targets, you really need to use PIX to see what is going on. It's very confusing at first, but it gets easier.
I'll try to answer follow-up questions asap.
#3
06/09/2008 (10:22 pm)
Pat, that worked. I tried simply printing out my texture on-screen, as I was still getting the sky-rendering issue from above. After applying the texture and getting the full screen quad overlaid, the render targets are indeed functioning as expected. Thank you so much for all of your help. I realize this is only the beginning of a basic deferred renderer, but there are definitely some powerful ideas here. Now that scene depth and object normals are being correctly saved out, I can now work on post-processing effects such as motion blur, ambient occlusion, and depth of field.
#4
=)
I've decided I'm sticking with 1.7
06/09/2008 (10:28 pm)
I think that rather than working with the old version of Torque, we should embrace 1.7=)
I've decided I'm sticking with 1.7
#5
Now that your stuff is working, one more Caviet.
If you are drawing to a render target, and you are binding the device depth/stencil:
You do not have to bind a depth/stencil surface to a render target, but you won't have depth or stencil. If you are doing some kind of full screen process, you are sampling from a depth texture (usually) so you won't need it, but it is something to be aware of.
Even if your game doesn't expose MSAA options to the player, they can enable it in the driver, and DX won't tell you, so here's a snippet from a class that will check it:
06/10/2008 (9:39 am)
The render targets are a bit more complicated than whatever was in TGEA before, but they are really a win over-all. Now that your stuff is working, one more Caviet.
If you are drawing to a render target, and you are binding the device depth/stencil:
attachTexture( GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil );and you want it to work properly under MSAA, the render target should use the 'GFXDefaultRenderTargetZBufferProfile'. This is a slower version of the render target because, under DX9, you don't have control over MSAA resolves. (I hear this is fixed in DX10, getting to actually coding DX10 is on the "ToDo" list, arg.) Each time a target, with that profile, is deactivated, it does a 'StretchRect' to resolve the MSAA.
You do not have to bind a depth/stencil surface to a render target, but you won't have depth or stencil. If you are doing some kind of full screen process, you are sampling from a depth texture (usually) so you won't need it, but it is something to be aware of.
Even if your game doesn't expose MSAA options to the player, they can enable it in the driver, and DX won't tell you, so here's a snippet from a class that will check it:
void GFXMSAADetector::_doMSAACheck()
{
GFXTarget *curRT = GFX->getActiveRenderTarget();
const Point2I &rtSz = curRT->getSize();
GBitmap msaaGbmp( rtSz.x, rtSz.y, false, MSAA_TARGET_FORMAT );
GFXTexHandle msaaTex( rtSz.x, rtSz.y, MSAA_TARGET_FORMAT, &GFXDefaultRenderTargetZBufferProfile, 1 );
// Save state etc.
GFXTransformSaver magicalTrevor;
GFX->pushState();
// Clear to white
GFX->clear( GFXClearTarget, ColorI( 0, 0, 0, 255 ), 0.0f, 0 );
// Set up state
GFX->setBaseRenderState();
GFX->setVertexColorEnable( true );
// Set transforms to identity
GFX->setWorldMatrix( MatrixF::smIdentity );
GFX->setViewMatrix( MatrixF::smIdentity );
GFX->setProjectionMatrix( MatrixF::smIdentity );
PrimBuild::colorWhite();
PrimBuild::begin( GFXTriangleList, 3 );
PrimBuild::vertex2f( -1.0f, -1.0f );
PrimBuild::vertex2f( 1.0f, -1.0f );
PrimBuild::vertex2f( 0.0f, 1.0f );
PrimBuild::end();
// Copy backbuffer to texture (stretchrect)
msaaTex.readBackBuffer( Point2I( 0, 0 ) );
// Copy texture to a GBitmap, look for gray pixels.
const bool copyBmpWorked = msaaTex.copyToBmp( &msaaGbmp );
AssertFatal( copyBmpWorked, "Unable to copy bitmap for MSAA test." );
//msaaGbmp.writePNGDebug( "gfxmsaatest" );
mMSAAEnabled = false;
const U8 *msaaBits = msaaGbmp.getBits();
for( int y = 0; y < msaaGbmp.getHeight(); y++ )
{
for( int x = 0; x < msaaGbmp.getWidth(); x++ )
{
// Only need to check Red
if( *msaaBits < 255 && *msaaBits != 0 )
{
mMSAAEnabled = true;
break;
}
msaaBits += msaaGbmp.bytesPerPixel;
}
}
mMSAACheckDone = true;
mMSAANotifySignal.trigger( mMSAAEnabled );
if( mMSAAEnabled )
Con::warnf( "GFXMSAADetector: Driver-enabled MSAA detected." );
GFX->popState();
// magicalTrevor does his thing
}It's a cool trick :)
Torque 3D Owner Frank Bignone
Darkhand Studio