//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "renderInstance/renderFormatChanger.h" #include "console/consoleTypes.h" #include "gfx/gfxStringEnumTranslate.h" #include "gfx/gfxTextureManager.h" #include "gfx/gfxDebugEvent.h" #include "postFx/postEffect.h" #include "postFx/postEffectManager.h" extern ColorI gCanvasClearColor; IMPLEMENT_CONOBJECT(RenderFormatToken); ConsoleDocClass( RenderFormatToken, "@brief Used to change the render target format when rendering in AL.\n\n" "RenderFormatToken is an implementation which changes the format of the " "back buffer and/or the depth buffer.\n\n" "The RenderPassStateBin manager changes the rendering state associated with " "this token. In stock Torque 3D, a single example exists in the " "way of AL_FormatToken (found in renderManager." TORQUE_SCRIPT_EXTENSION "). In that script file, all the " "render managers are intialized, and a single RenderFormatToken is used. This " "implementation basically exists to ensure Advanced Lighting works with MSAA.\n\n" "The actions for this token toggle the format of the back/depth buffers " "and it lets you specify a custom shader to \"copy\" the data so it can " "be reformatted or altered. This is done through the variables copyEffect and " "resolveEffect (which are post processes just like fog or glow)\n\n" "@tsexample\n" "// This token, and the associated render managers, ensure " "that driver MSAA does not get used for Advanced Lighting renders.\n" "// The 'AL_FormatResolve' PostEffect copies the result to the backbuffer.\n" "new RenderFormatToken(AL_FormatToken)\n" "{\n" " enabled = \"false\";\n\n" " format = \"GFXFormatR8G8B8A8\";\n" " depthFormat = \"GFXFormatD24S8\";\n" " aaLevel = 0; // -1 = match backbuffer\n\n" " // The contents of the back buffer before this format token is executed\n" " // is provided in $inTex\n" " copyEffect = \"AL_FormatCopy\";\n\n" " // The contents of the render target created by this format token is\n" " // provided in $inTex\n" " resolveEffect = \"AL_FormatCopy\";\n" "};\n" "@endtsexample\n\n" "@see RenderPassToken\n\n" "@see RenderPassStateBin\n" "@see game/core/scripts/client/renderManager." TORQUE_SCRIPT_EXTENSION "\n" "@ingroup GFX\n" ); RenderFormatToken::RenderFormatToken() : Parent(), mFCState(FTSDisabled), mColorFormat(GFXFormat_COUNT), mDepthFormat(GFXFormat_COUNT), mTargetUpdatePending(true), mTargetChainIdx(0), mTargetSize(Point2I::Zero), mTargetAALevel(GFXTextureManager::AA_MATCH_BACKBUFFER), mCopyPostEffect(NULL), mResolvePostEffect(NULL) { GFXDevice::getDeviceEventSignal().notify(this, &RenderFormatToken::_handleGFXEvent); GFXTextureManager::addEventDelegate(this, &RenderFormatToken::_onTextureEvent); } RenderFormatToken::~RenderFormatToken() { GFXTextureManager::removeEventDelegate(this, &RenderFormatToken::_onTextureEvent); GFXDevice::getDeviceEventSignal().remove(this, &RenderFormatToken::_handleGFXEvent); _teardownTargets(); } void RenderFormatToken::process(SceneRenderState *state, RenderPassStateBin *callingBin) { switch(mFCState) { case FTSWaiting: { GFXDEBUGEVENT_SCOPE_EX(RFT_Waiting, ColorI::BLUE, avar("[%s Activate] (%s)", getName(), GFXStringTextureFormat[mColorFormat])); mFCState = FTSActive; mTarget.setViewport( GFX->getViewport() ); // Update targets _updateTargets(); // If we have a copy PostEffect then get the active backbuffer copy // now before we swap the render targets. GFXTexHandle curBackBuffer; if(mCopyPostEffect.isValid()) curBackBuffer = PFXMGR->getBackBufferTex(); // Push target GFX->pushActiveRenderTarget(); GFX->setActiveRenderTarget(mTargetChain[mTargetChainIdx]); // Set viewport GFX->setViewport( mTarget.getViewport() ); // Clear GFX->clear(GFXClearTarget | GFXClearZBuffer | GFXClearStencil, gCanvasClearColor, 1.0f, 0); // Set active z target on render pass if(mTargetDepthStencilTexture[mTargetChainIdx].isValid()) { if(callingBin->getRenderPass()->getDepthTargetTexture() != GFXTextureTarget::sDefaultDepthStencil) mStoredPassZTarget = callingBin->getRenderPass()->getDepthTargetTexture(); else mStoredPassZTarget = NULL; callingBin->getRenderPass()->setDepthTargetTexture(mTargetDepthStencilTexture[mTargetChainIdx]); } // Run the PostEffect which copies data into the new target. if ( mCopyPostEffect.isValid() ) mCopyPostEffect->process( state, curBackBuffer, &mTarget.getViewport() ); } break; case FTSActive: { GFXDEBUGEVENT_SCOPE_EX(RFT_Active, ColorI::BLUE, avar("[%s Deactivate]", getName())); mFCState = FTSComplete; // Pop target AssertFatal(GFX->getActiveRenderTarget() == mTargetChain[mTargetChainIdx], "Render target stack went wrong somewhere"); mTargetChain[mTargetChainIdx]->resolve(); GFX->popActiveRenderTarget(); mTarget.setTexture( mTargetColorTexture[mTargetChainIdx] ); // This is the GFX viewport when we were first processed. GFX->setViewport( mTarget.getViewport() ); // Restore active z-target if(mTargetDepthStencilTexture[mTargetChainIdx].isValid()) { callingBin->getRenderPass()->setDepthTargetTexture(mStoredPassZTarget.getPointer()); mStoredPassZTarget = NULL; } // Run the PostEffect which copies data to the backbuffer if(mResolvePostEffect.isValid()) { // Need to create a texhandle here, since inOutTex gets assigned during process() GFXTexHandle inOutTex = mTargetColorTexture[mTargetChainIdx]; mResolvePostEffect->process( state, inOutTex, &mTarget.getViewport() ); } } break; case FTSComplete: AssertFatal(false, "process() called on a RenderFormatToken which was already complete."); // fall through case FTSDisabled: break; } } void RenderFormatToken::reset() { AssertFatal(mFCState != FTSActive, "RenderFormatToken still active during reset()!"); if(mFCState != FTSDisabled) mFCState = FTSWaiting; } void RenderFormatToken::_updateTargets() { if ( GFX->getActiveRenderTarget() == NULL ) return; const Point2I &rtSize = GFX->getActiveRenderTarget()->getSize(); if ( rtSize.x <= mTargetSize.x && rtSize.y <= mTargetSize.y && !mTargetUpdatePending ) return; mTargetSize = rtSize; mTargetUpdatePending = false; mTargetChainIdx = 0; for( U32 i = 0; i < TargetChainLength; i++ ) { if( !mTargetChain[i] ) mTargetChain[i] = GFX->allocRenderToTextureTarget(); // Update color target if(mColorFormat != GFXFormat_COUNT) { // try reuse of old color texture if( !mTargetColorTexture[i] || mTargetColorTexture[i].getFormat() != mColorFormat || mTargetColorTexture[i].getWidthHeight() != rtSize) { mTargetColorTexture[i].set( rtSize.x, rtSize.y, mColorFormat, &GFXRenderTargetSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ), 1, mTargetAALevel ); mTargetChain[i]->attachTexture( GFXTextureTarget::Color0, mTargetColorTexture[i] ); } } // Update depth target if(mDepthFormat != GFXFormat_COUNT) { // try reuse of old depth texture if( !mTargetDepthStencilTexture[i] || mTargetDepthStencilTexture[i].getFormat() != mColorFormat || mTargetDepthStencilTexture[i].getWidthHeight() != rtSize) { mTargetDepthStencilTexture[i].set( rtSize.x, rtSize.y, mDepthFormat, &GFXZTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ), 1, mTargetAALevel ); mTargetChain[i]->attachTexture( GFXTextureTarget::DepthStencil, mTargetDepthStencilTexture[i] ); } } } //set the texture for now as the first color target texture mTarget.setTexture(mTargetColorTexture[0]); } void RenderFormatToken::_teardownTargets() { mTarget.release(); for(S32 i = 0; i < TargetChainLength; i++) { mTargetColorTexture[i] = NULL; mTargetDepthStencilTexture[i] = NULL; mTargetChain[i] = NULL; } } bool RenderFormatToken::_setFmt( void *object, const char *index, const char *data ) { // Flag update pending reinterpret_cast( object )->mTargetUpdatePending = true; // Allow console system to assign value return true; } const char* RenderFormatToken::_getCopyPostEffect( void* object, const char* data ) { RenderFormatToken* token = reinterpret_cast< RenderFormatToken* >( object ); if( token->mCopyPostEffect.isValid() ) return token->mCopyPostEffect->getIdString(); return "0"; } const char* RenderFormatToken::_getResolvePostEffect( void* object, const char* data ) { RenderFormatToken* token = reinterpret_cast< RenderFormatToken* >( object ); if( token->mResolvePostEffect.isValid() ) return token->mResolvePostEffect->getIdString(); return "0"; } bool RenderFormatToken::_setCopyPostEffect( void* object, const char* index, const char* data ) { RenderFormatToken* token = reinterpret_cast< RenderFormatToken* >( object ); PostEffect* effect; Sim::findObject( data, effect ); token->mCopyPostEffect = effect; return false; } bool RenderFormatToken::_setResolvePostEffect( void* object, const char* index, const char* data ) { RenderFormatToken* token = reinterpret_cast< RenderFormatToken* >( object ); PostEffect* effect; Sim::findObject( data, effect ); token->mResolvePostEffect = effect; return false; } void RenderFormatToken::enable( bool enabled /*= true*/ ) { AssertFatal(mFCState != FTSActive, "RenderFormatToken is active, cannot change state now!"); if(enabled) mFCState = FTSWaiting; else mFCState = FTSDisabled; } bool RenderFormatToken::isEnabled() const { return (mFCState != FTSDisabled); } void RenderFormatToken::initPersistFields() { addProtectedField("format", TypeGFXFormat, Offset(mColorFormat, RenderFormatToken), &_setFmt, &defaultProtectedGetFn, "Sets the color buffer format for this token."); addProtectedField("depthFormat", TypeGFXFormat, Offset(mDepthFormat, RenderFormatToken), &_setFmt, &defaultProtectedGetFn, "Sets the depth/stencil buffer format for this token."); addProtectedField("copyEffect", TYPEID(), Offset(mCopyPostEffect, RenderFormatToken), &_setCopyPostEffect, &_getCopyPostEffect, "This PostEffect will be run when the render target is changed to the format specified " "by this token. It is used to copy/format data into the token rendertarget"); addProtectedField("resolveEffect", TYPEID(), Offset(mResolvePostEffect, RenderFormatToken), &_setResolvePostEffect, &_getResolvePostEffect, "This PostEffect will be run when the render target is changed back to the format " "active prior to this token. It is used to copy/format data from the token rendertarget to the backbuffer."); addField("aaLevel", TypeS32, Offset(mTargetAALevel, RenderFormatToken), "Anti-ailiasing level for the this token. 0 disables, -1 uses adapter default."); Parent::initPersistFields(); } bool RenderFormatToken::_handleGFXEvent(GFXDevice::GFXDeviceEventType event_) { if ( event_ == GFXDevice::deStartOfFrame ) { mTargetChainIdx++; if ( mTargetChainIdx >= TargetChainLength ) mTargetChainIdx = 0; } return true; } void RenderFormatToken::_onTextureEvent( GFXTexCallbackCode code ) { if(code == GFXZombify) { _teardownTargets(); mTargetUpdatePending = true; } } bool RenderFormatToken::onAdd() { if(!Parent::onAdd()) return false; mTarget.registerWithName( getName() ); mTarget.setSamplerState( GFXSamplerStateDesc::getClampPoint() ); return true; } void RenderFormatToken::onRemove() { mTarget.unregister(); mTarget.release(); Parent::onRemove(); }