//----------------------------------------------------------------------------- // 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 "postFx/postEffect.h" #include "console/engineAPI.h" #include "core/stream/fileStream.h" #include "core/strings/stringUnit.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "math/util/frustum.h" #include "math/mathUtils.h" #include "gfx/gfxTransformSaver.h" #include "gfx/gfxStringEnumTranslate.h" #include "gfx/gfxTextureManager.h" #include "gfx/gfxDebugEvent.h" #include "gfx/util/screenspace.h" #include "gfx/sim/gfxStateBlockData.h" #include "scene/sceneRenderState.h" #include "shaderGen/shaderGenVars.h" #include "lighting/lightInfo.h" #include "lighting/lightManager.h" #include "materials/materialManager.h" #include "materials/shaderData.h" #include "postFx/postEffectManager.h" #include "postFx/postEffectVis.h" #include using namespace Torque; ConsoleDocClass( PostEffect, "@brief A fullscreen shader effect.\n\n" "@section PFXTextureIdentifiers\n\n" "@ingroup Rendering\n" ); IMPLEMENT_CALLBACK( PostEffect, onAdd, void, (), (), "Called when this object is first created and registered." ); IMPLEMENT_CALLBACK( PostEffect, preProcess, void, (), (), "Called when an effect is processed but before textures are bound. This " "allows the user to change texture related paramaters or macros at runtime.\n" "@tsexample\n" "function SSAOPostFx::preProcess( %this )\n" "{\n" " if ( $SSAOPostFx::quality !$= %this.quality )\n" " {\n" " %this.quality = mClamp( mRound( $SSAOPostFx::quality ), 0, 2 );\n" " \n" " %this.setShaderMacro( \"QUALITY\", %this.quality );\n" " }\n" " %this.targetScale = $SSAOPostFx::targetScale;\n" "}\n" "@endtsexample\n" "@see setShaderConst\n" "@see setShaderMacro" ); IMPLEMENT_CALLBACK( PostEffect, setShaderConsts, void, (), (), "Called immediate before processing this effect. This is the user's chance " "to set the value of shader uniforms (constants).\n" "@see setShaderConst" ); IMPLEMENT_CALLBACK( PostEffect, onEnabled, bool, (), (), "Called when this effect becomes enabled. If the user returns false from " "this callback the effect will not be enabled.\n" "@return True to allow this effect to be enabled." ); IMPLEMENT_CALLBACK( PostEffect, onDisabled, void, (), (), "Called when this effect becomes disabled." ); ImplementEnumType( PFXRenderTime, "When to process this effect during the frame.\n" "@ingroup Rendering\n\n") { PFXBeforeBin, "PFXBeforeBin", "Before a RenderInstManager bin.\n" }, { PFXAfterBin, "PFXAfterBin", "After a RenderInstManager bin.\n" }, { PFXAfterDiffuse, "PFXAfterDiffuse", "After the diffuse rendering pass.\n" }, { PFXEndOfFrame, "PFXEndOfFrame", "When the end of the frame is reached.\n" }, { PFXTexGenOnDemand, "PFXTexGenOnDemand", "This PostEffect is not processed by the manager. It will generate its texture when it is requested.\n" } EndImplementEnumType; ImplementEnumType( PFXTargetClear, "Describes when the target texture should be cleared\n" "@ingroup Rendering\n\n") { PFXTargetClear_None, "PFXTargetClear_None", "Never clear the PostEffect target.\n" }, { PFXTargetClear_OnCreate, "PFXTargetClear_OnCreate", "Clear once on create.\n" }, { PFXTargetClear_OnDraw, "PFXTargetClear_OnDraw", "Clear before every draw.\n" }, EndImplementEnumType; ImplementEnumType( PFXTargetViewport, "Specifies how the viewport should be set up for a PostEffect's target.\n" "@note Applies to both the diffuse target and the depth target (if defined).\n" "@ingroup Rendering\n\n") { PFXTargetViewport_TargetSize, "PFXTargetViewport_TargetSize", "Set viewport to match target size (default).\n" }, { PFXTargetViewport_GFXViewport, "PFXTargetViewport_GFXViewport", "Use the current GFX viewport (scaled to match target size).\n" }, { PFXTargetViewport_NamedInTexture0, "PFXTargetViewport_NamedInTexture0", "Use the input texture 0 if it is named (scaled to match target size), otherwise revert to PFXTargetViewport_TargetSize if there is none.\n" }, EndImplementEnumType; GFXImplementVertexFormat( PFXVertex ) { addElement( "POSITION", GFXDeclType_Float3 ); addElement( "TEXCOORD", GFXDeclType_Float2, 0 ); addElement( "TEXCOORD", GFXDeclType_Float3, 1 ); }; GFX_ImplementTextureProfile( PostFxTargetProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::PreserveSize | GFXTextureProfile::RenderTarget | GFXTextureProfile::Pooled, GFXTextureProfile::NONE ); GFX_ImplementTextureProfile( PostFxTextureProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::Static | GFXTextureProfile::PreserveSize, GFXTextureProfile::NONE ); GFX_ImplementTextureProfile( PostFxTextureSRGBProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::Static | GFXTextureProfile::PreserveSize | GFXTextureProfile::SRGB, GFXTextureProfile::NONE); GFX_ImplementTextureProfile( VRTextureProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::PreserveSize | GFXTextureProfile::RenderTarget, GFXTextureProfile::NONE ); GFX_ImplementTextureProfile( VRDepthProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::PreserveSize | GFXTextureProfile::ZTarget, GFXTextureProfile::NONE ); IMPLEMENT_CONOBJECT(PostEffect); //--------------------------------------------------------------------- // EffectConst //--------------------------------------------------------------------- void PostEffect::EffectConst::set( const String &newVal ) { if ( mStringVal == newVal ) return; mStringVal = newVal; mDirty = true; mValueType = StringType; } void PostEffect::EffectConst::set(const F32 &newVal) { if (mFloatVal == newVal) return; mFloatVal = newVal; mDirty = true; mValueType = FloatType; } void PostEffect::EffectConst::set(const int& newVal) { if (mIntVal == newVal) return; mIntVal = newVal; mDirty = true; mValueType = IntType; } void PostEffect::EffectConst::set(const Point4F &newVal) { if (mPointVal == newVal) return; mPointVal = newVal; mDirty = true; mValueType = PointType; } void PostEffect::EffectConst::set(const MatrixF &newVal) { if (mMatrixVal == newVal) return; mMatrixVal = newVal; mDirty = true; mValueType = MatrixType; } void PostEffect::EffectConst::set(const Vector &newVal) { //if (mPointArrayVal == newVal) // return; mPointArrayVal = newVal; mDirty = true; mValueType = PointArrayType; } void PostEffect::EffectConst::set(const Vector &newVal) { //if (mMatrixArrayVal == newVal) // return; mMatrixArrayVal = newVal; mDirty = true; mValueType = MatrixArrayType; } void PostEffect::EffectConst::setToBuffer( GFXShaderConstBufferRef buff ) { // Nothing to do if the value hasn't changed. if ( !mDirty ) return; mDirty = false; // If we don't have a handle... get it now. if ( !mHandle ) mHandle = buff->getShader()->getShaderConstHandle( mName ); // If the handle isn't valid then we're done. if ( !mHandle->isValid() ) return; const GFXShaderConstType type = mHandle->getType(); // For now, we're only going // to support float4 arrays. // Expand to other types as necessary. U32 arraySize = mHandle->getArraySize(); if (mValueType == StringType) { const char *strVal = mStringVal.c_str(); if (type == GFXSCT_Int) { S32 val; Con::setData(TypeS32, &val, 0, 1, &strVal); buff->set(mHandle, val); } else if (type == GFXSCT_Float) { F32 val; Con::setData(TypeF32, &val, 0, 1, &strVal); buff->set(mHandle, val); } else if (type == GFXSCT_Float2) { Point2F val; Con::setData(TypePoint2F, &val, 0, 1, &strVal); buff->set(mHandle, val); } else if (type == GFXSCT_Float3) { Point3F val; Con::setData(TypePoint3F, &val, 0, 1, &strVal); buff->set(mHandle, val); } else if (type == GFXSCT_Float4) { Point4F val; if (arraySize > 1) { // Do array setup! //U32 unitCount = StringUnit::getUnitCount( strVal, "\t" ); //AssertFatal( unitCount == arraySize, "" ); String tmpString; Vector valArray; for (U32 i = 0; i < arraySize; i++) { tmpString = StringUnit::getUnit(strVal, i, "\t"); valArray.increment(); const char *tmpCStr = tmpString.c_str(); Con::setData(TypePoint4F, &valArray.last(), 0, 1, &tmpCStr); } AlignedArray rectData(valArray.size(), sizeof(Point4F), (U8*)valArray.address(), false); buff->set(mHandle, rectData); } else { // Do regular setup. Con::setData(TypePoint4F, &val, 0, 1, &strVal); buff->set(mHandle, val); } } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == FloatType) { if (type == GFXSCT_Float) { buff->set(mHandle, mFloatVal); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == IntType) { if (type == GFXSCT_Int) { buff->set(mHandle, mIntVal); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == PointType) { if (type == GFXSCT_Float2) { buff->set(mHandle, Point2F(mPointVal.x, mPointVal.y)); } else if (type == GFXSCT_Float3) { buff->set(mHandle, Point3F(mPointVal.x, mPointVal.y, mPointVal.z)); } else if (type == GFXSCT_Float4) { buff->set(mHandle, mPointVal); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == MatrixType) { if (type == GFXSCT_Float4x4) { buff->set(mHandle, mMatrixVal); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == PointArrayType) { if (type == GFXSCT_Float4) { if (arraySize != mPointArrayVal.size()) { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer PointArrayType, attempted to feed an array that does not match the uniform array's size!"); Con::errorf(err); GFXAssertFatal(0, err); #endif return; } AlignedArray alignedVal = AlignedArray(arraySize, sizeof(Point4F), (U8*)mPointArrayVal.address(), false); buff->set(mHandle, alignedVal); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } else if (mValueType == MatrixArrayType) { if (type == GFXSCT_Float4x4) { if (arraySize != mMatrixArrayVal.size()) { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer MatrixArrayType, attempted to feed an array that does not match the uniform array's size!"); Con::errorf(err); GFXAssertFatal(0, err); #endif return; } buff->set(mHandle, mMatrixArrayVal.address(), arraySize); } else { #if TORQUE_DEBUG const char* err = avar("PostEffect::EffectConst::setToBuffer $s type is not implemented", mName.c_str()); Con::errorf(err); GFXAssertFatal(0, err); #endif } } } //--------------------------------------------------------------------- // EffectConst END //--------------------------------------------------------------------- //------------------------------------------------------------------------- // PostEffect //------------------------------------------------------------------------- PostEffect::PostEffect() : mRenderTime(PFXAfterDiffuse), mRenderPriority(1.0), mEnabled(false), mStateBlockData(NULL), mUpdateShader(true), mSkip(false), mPreProcessed(false), mAllowReflectPass(false), mTargetScale(Point2F::One), mTargetSize(Point2I::Zero), mTargetViewport(PFXTargetViewport_TargetSize), mMipCap(1), mOneFrameOnly(false), mOnThisFrame(true), mRTSizeSC(NULL), mIsValid(false), mShaderReloadKey(0), mOneOverRTSizeSC(NULL), mViewportOffsetSC(NULL), mTargetViewportSC(NULL), mFogDataSC(NULL), mFogColorSC(NULL), mEyePosSC(NULL), mMatWorldToScreenSC(NULL), mMatScreenToWorldSC(NULL), mMatPrevScreenToWorldSC(NULL), mNearFarSC(NULL), mInvNearFarSC(NULL), mWorldToScreenScaleSC(NULL), mProjectionOffsetSC(NULL), mWaterColorSC(NULL), mWaterFogDataSC(NULL), mAmbientColorSC(NULL), mWaterFogPlaneSC(NULL), mWaterDepthGradMaxSC(NULL), mScreenSunPosSC(NULL), mLightDirectionSC(NULL), mCameraForwardSC(NULL), mAccumTimeSC(NULL), mDampnessSC(NULL), mDeltaTimeSC(NULL), mInvCameraMatSC(NULL), mMatCameraToWorldSC(NULL), mInvCameraTransSC(NULL), mMatCameraToScreenSC(NULL), mMatScreenToCameraSC(NULL), mIsCapturingSC(NULL) { // MRT arrays — slot 0 gets primary defaults, 1-3 are inactive. for (U32 c = 0; c < NumMRTTargets; c++) { mTargetFormat[c] = GFXFormatR8G8B8A8; mTargetClearColor[c] = LinearColorF::BLACK; mTargetClear[c] = PFXTargetClear_None; } dMemset(mTexSRGB, 0, sizeof(bool) * NumTextures); dMemset(mActiveTextures, 0, sizeof(GFXTextureObject*) * NumTextures); dMemset(mActiveNamedTarget, 0, sizeof(NamedTexTarget*) * NumTextures); dMemset(mActiveTextureViewport, 0, sizeof(RectI) * NumTextures); dMemset(mTexSizeSC, 0, sizeof(GFXShaderConstHandle*) * NumTextures); dMemset(mRenderTargetParamsSC, 0, sizeof(GFXShaderConstHandle*) * NumTextures); dMemset(mMipCountSC, 0, sizeof(GFXShaderConstHandle*) * NumTextures); } PostEffect::~PostEffect() { EffectConstTable::Iterator iter = mEffectConsts.begin(); for ( ; iter != mEffectConsts.end(); iter++ ) delete iter->value; } void PostEffect::initPersistFields() { docsURL; addField( "shader", TypeRealString, Offset( mShaderName, PostEffect ), "Name of a GFXShaderData for this effect." ); addField( "stateBlock", TYPEID(), Offset( mStateBlockData, PostEffect ), "Name of a GFXStateBlockData for this effect." ); addField( "target", TypeRealString, Offset( mTargetName, PostEffect ), NumMRTTargets, "String identifier of this effect's target texture.\n" "@see PFXTextureIdentifiers" ); addField("targetFormat", TypeGFXFormat, Offset(mTargetFormat, PostEffect), NumMRTTargets, "Format of the target texture, not applicable if writing to the backbuffer."); addField("targetClearColor", TypeColorF, Offset(mTargetClearColor, PostEffect), NumMRTTargets, "Color to which the target texture is cleared before rendering."); addField("targetClear", TYPEID< PFXTargetClear >(), Offset(mTargetClear, PostEffect), NumMRTTargets, "Describes when the target texture should be cleared."); addField( "targetDepthStencil", TypeRealString, Offset( mTargetDepthStencilName, PostEffect ), "Optional string identifier for this effect's target depth/stencil texture.\n" "@see PFXTextureIdentifiers" ); addField( "targetScale", TypePoint2F, Offset( mTargetScale, PostEffect ), "If targetSize is zero this is used to set a relative size from the current target." ); addField("mipCap", TypeS32, Offset(mMipCap, PostEffect), "generate up to this many mips. 0 = all, 1 = none, >1 = as specified max."); //todo: de-stupid addField( "targetSize", TypePoint2I, Offset( mTargetSize, PostEffect ), "If non-zero this is used as the absolute target size." ); addField( "targetViewport", TYPEID< PFXTargetViewport >(), Offset( mTargetViewport, PostEffect ), "Specifies how the viewport should be set up for a target texture." ); addProtectedField("Texture", TypeImageFilename, Offset(mTextureAsset, PostEffect), _setTextureData, &defaultProtectedGetFn, NumTextures, "Input textures to this effect(samplers).\n", AbstractClassRep::FIELD_HideInInspectors); INITPERSISTFIELD_IMAGEASSET_ARRAY(Texture, NumTextures, PostEffect, "Input textures to this effect ( samplers ).\n" "@see PFXTextureIdentifiers"); addField("textureSRGB", TypeBool, Offset(mTexSRGB, PostEffect), NumTextures, "Set input texture to be sRGB"); addField( "renderTime", TYPEID< PFXRenderTime >(), Offset( mRenderTime, PostEffect ), "When to process this effect during the frame." ); addField( "renderBin", TypeRealString, Offset( mRenderBin, PostEffect ), "Name of a renderBin, used if renderTime is PFXBeforeBin or PFXAfterBin." ); addField( "renderPriority", TypeS16, Offset( mRenderPriority, PostEffect ), "PostEffects are processed in DESCENDING order of renderPriority if more than one has the same renderBin/Time." ); addField( "allowReflectPass", TypeBool, Offset( mAllowReflectPass, PostEffect ), "Is this effect processed during reflection render passes." ); addProtectedField( "enabled", TypeBool, Offset( mEnabled, PostEffect), &PostEffect::_setIsEnabled, &defaultProtectedGetFn, "Is the effect on." ); addField( "onThisFrame", TypeBool, Offset( mOnThisFrame, PostEffect ), "Allows you to turn on a PostEffect for only a single frame." ); addField( "oneFrameOnly", TypeBool, Offset( mOneFrameOnly, PostEffect ), "Allows you to turn on a PostEffect for only a single frame." ); addField( "skip", TypeBool, Offset( mSkip, PostEffect ), "Skip processing of this PostEffect and its children even if its parent " "is enabled. Parent and sibling PostEffects in the chain are still processed." ); Parent::initPersistFields(); } bool PostEffect::onAdd() { if( !Parent::onAdd() ) return false; LightManager::smActivateSignal.notify( this, &PostEffect::_onLMActivate ); mUpdateShader = true; // Grab the script path. Torque::Path scriptPath( Con::getVariable( "$Con::File" ) ); scriptPath.setFileName( String::EmptyString ); scriptPath.setExtension( String::EmptyString ); // Find additional textures for (S32 i = 0; i < NumTextures; i++) { mTextureType[i] = NormalTextureType; if (mTextureAsset[i].notNull()) { String texFilename = mTextureAsset[i]->getImageFile(); // Skip empty stages or ones with variable or target names. if (texFilename.isEmpty() || texFilename[0] == '$' || texFilename[0] == '#') continue; mTextureProfile[i] = (mTexSRGB[i]) ? &PostFxTextureSRGBProfile : &PostFxTextureProfile; _setTexture(texFilename, i); } } // Is the target a named target? bool anyNamed = false; for (U32 c = 0; c < NumMRTTargets; c++) { if (mTargetName[c].isNotEmpty() && mTargetName[c][0] == '#') { mNamedTarget[c].registerWithName(mTargetName[c].substr(1)); mNamedTarget[c].getTextureDelegate().bind(this, &PostEffect::_getTargetTexture); anyNamed = true; } } if ( mTargetDepthStencilName.isNotEmpty() && mTargetDepthStencilName[0] == '#' ) mNamedTargetDepthStencil.registerWithName( mTargetDepthStencilName.substr( 1 ) ); if (anyNamed || mNamedTargetDepthStencil.isRegistered()) GFXTextureManager::addEventDelegate( this, &PostEffect::_onTextureEvent ); // Call onAdd in script onAdd_callback(); // Should we start enabled? if ( mEnabled ) { mEnabled = false; enable(); } getSet()->addObject( this ); return true; } void PostEffect::onRemove() { Parent::onRemove(); PFXMGR->_removeEffect( this ); LightManager::smActivateSignal.remove( this, &PostEffect::_onLMActivate ); mShader = NULL; _cleanTargets(); bool anyNamed = false; for (U32 c = 0; c < NumMRTTargets; c++) { if (mNamedTarget[c].isRegistered()) { mNamedTarget[c].unregister(); mNamedTarget[c].getTextureDelegate().clear(); anyNamed = true; } } if (anyNamed || mNamedTargetDepthStencil.isRegistered() ) GFXTextureManager::removeEventDelegate( this, &PostEffect::_onTextureEvent ); if ( mNamedTargetDepthStencil.isRegistered() ) mNamedTargetDepthStencil.unregister(); } void PostEffect::_updateScreenGeometry( const Frustum &frustum, GFXVertexBufferHandle *outVB ) { outVB->set( GFX, 4, GFXBufferTypeVolatile ); const Point3F *frustumPoints = frustum.getPoints(); const Point3F& cameraPos = frustum.getPosition(); // Perform a camera offset. We need to manually perform this offset on the postFx's // polygon, which is at the far plane. const Point2F& projOffset = frustum.getProjectionOffset(); Point3F cameraOffsetPos = cameraPos; if(!projOffset.isZero()) { // First we need to calculate the offset at the near plane. The projOffset // given above can be thought of a percent as it ranges from 0..1 (or 0..-1). F32 nearOffset = frustum.getNearRight() * projOffset.x; // Now given the near plane distance from the camera we can solve the right // triangle and calcuate the SIN theta for the offset at the near plane. // SIN theta = x/y F32 sinTheta = nearOffset / frustum.getNearDist(); // Finally, we can calcuate the offset at the far plane, which is where our sun (or vector) // light's polygon is drawn. F32 farOffset = frustum.getFarDist() * sinTheta; // We can now apply this far plane offset to the far plane itself, which then compensates // for the project offset. MatrixF camTrans = frustum.getTransform(); VectorF offset = camTrans.getRightVector(); offset *= farOffset; cameraOffsetPos += offset; } PFXVertex *vert = outVB->lock(); vert->point.set(-1.0, 1.0, 0.0); vert->texCoord.set(0.0f, 0.0f); vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - cameraOffsetPos; vert++; vert->point.set(1.0, 1.0, 0.0); vert->texCoord.set(1.0f, 0.0f); vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - cameraOffsetPos; vert++; vert->point.set(-1.0, -1.0, 0.0); vert->texCoord.set(0.0f, 1.0f); vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - cameraOffsetPos; vert++; vert->point.set(1.0, -1.0, 0.0); vert->texCoord.set(1.0f, 1.0f); vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - cameraOffsetPos; vert++; outVB->unlock(); } void PostEffect::_setupStateBlock( const SceneRenderState *state ) { if ( mStateBlock.isNull() ) { GFXStateBlockDesc desc; if ( mStateBlockData ) desc = mStateBlockData->getState(); mStateBlock = GFX->createStateBlock( desc ); } GFX->setStateBlock( mStateBlock ); } void PostEffect::_setupConstants( const SceneRenderState *state ) { // Alloc the const buffer. if ( mShaderConsts.isNull() ) { mShaderConsts = mShader->allocConstBuffer(); mRTSizeSC = mShader->getShaderConstHandle( "$targetSize" ); mOneOverRTSizeSC = mShader->getShaderConstHandle( "$oneOverTargetSize" ); mRTRatioSC = mShader->getShaderConstHandle("$targetRatio"); for (U32 i = 0; i < NumTextures; i++) { mTexSizeSC[i] = mShader->getShaderConstHandle(String::ToString("$texSize%d", i)); mRenderTargetParamsSC[i] = mShader->getShaderConstHandle(String::ToString("$rtParams%d",i)); mMipCountSC[i] = mShader->getShaderConstHandle(String::ToString("$mipCount%d", i)); } mTargetViewportSC = mShader->getShaderConstHandle( "$targetViewport" ); mFogDataSC = mShader->getShaderConstHandle( ShaderGenVars::fogData ); mFogColorSC = mShader->getShaderConstHandle( ShaderGenVars::fogColor ); mEyePosSC = mShader->getShaderConstHandle( ShaderGenVars::eyePosWorld ); mNearFarSC = mShader->getShaderConstHandle( "$nearFar" ); mInvNearFarSC = mShader->getShaderConstHandle( "$invNearFar" ); mWorldToScreenScaleSC = mShader->getShaderConstHandle( "$worldToScreenScale" ); mMatWorldToScreenSC = mShader->getShaderConstHandle( "$matWorldToScreen" ); mMatScreenToWorldSC = mShader->getShaderConstHandle( "$matScreenToWorld" ); mMatPrevScreenToWorldSC = mShader->getShaderConstHandle( "$matPrevScreenToWorld" ); mProjectionOffsetSC = mShader->getShaderConstHandle( "$projectionOffset" ); mWaterColorSC = mShader->getShaderConstHandle( "$waterColor" ); mAmbientColorSC = mShader->getShaderConstHandle( "$ambientColor" ); mWaterFogDataSC = mShader->getShaderConstHandle( "$waterFogData" ); mWaterFogPlaneSC = mShader->getShaderConstHandle( "$waterFogPlane" ); mWaterDepthGradMaxSC = mShader->getShaderConstHandle( "$waterDepthGradMax" ); mScreenSunPosSC = mShader->getShaderConstHandle( "$screenSunPos" ); mLightDirectionSC = mShader->getShaderConstHandle( "$lightDirection" ); mCameraForwardSC = mShader->getShaderConstHandle( "$camForward" ); mAccumTimeSC = mShader->getShaderConstHandle( "$accumTime" ); mDampnessSC = mShader->getShaderConstHandle("$dampness"); mDeltaTimeSC = mShader->getShaderConstHandle( "$deltaTime" ); mInvCameraMatSC = mShader->getShaderConstHandle( "$invCameraMat" ); mMatCameraToWorldSC = mShader->getShaderConstHandle("$cameraToWorld"); mInvCameraTransSC = mShader->getShaderConstHandle("$invCameraTrans"); mMatCameraToScreenSC = mShader->getShaderConstHandle("$cameraToScreen"); mMatScreenToCameraSC = mShader->getShaderConstHandle("$screenToCamera"); mIsCapturingSC = mShader->getShaderConstHandle("$isCapturing"); } if (mIsCapturingSC->isValid()) mShaderConsts->set(mIsCapturingSC, (S32)state->isCapturing()); // Set up shader constants for source image size if ( mRTSizeSC->isValid() ) { const Point2I &resolution = GFX->getActiveRenderTarget()->getSize(); Point2F pixelShaderConstantData; pixelShaderConstantData.x = resolution.x; pixelShaderConstantData.y = resolution.y; mShaderConsts->set( mRTSizeSC, pixelShaderConstantData ); } if ( mOneOverRTSizeSC->isValid() ) { const Point2I &resolution = GFX->getActiveRenderTarget()->getSize(); Point2F oneOverTargetSize( 1.0f / (F32)resolution.x, 1.0f / (F32)resolution.y ); mShaderConsts->set( mOneOverRTSizeSC, oneOverTargetSize ); } if (mRTRatioSC->isValid()) { const Point2I& resolution = GFX->getActiveRenderTarget()->getSize(); mShaderConsts->set(mRTRatioSC, (F32)resolution.x/ (F32)resolution.y); } // Set up additional textures Point2F texSizeConst; for( U32 i = 0; i < NumTextures; i++ ) { if( !mActiveTextures[i] ) continue; if ( mTexSizeSC[i]->isValid() ) { texSizeConst.x = (F32)mActiveTextures[i]->getWidth(); texSizeConst.y = (F32)mActiveTextures[i]->getHeight(); mShaderConsts->set( mTexSizeSC[i], texSizeConst ); } if (mMipCountSC[i]->isValid()) { mShaderConsts->set(mMipCountSC[i], (S32)mActiveTextures[i]->getMipLevels()); } } for ( U32 i = 0; i < NumTextures; i++ ) { if ( !mRenderTargetParamsSC[i]->isValid() ) continue; Point4F rtParams( Point4F::One ); if ( mActiveTextures[i] ) { const Point3I &targetSz = mActiveTextures[i]->getSize(); RectI targetVp = mActiveTextureViewport[i]; ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); } mShaderConsts->set( mRenderTargetParamsSC[i], rtParams ); } // Target viewport (in target space) if ( mTargetViewportSC->isValid() ) { const Point2I& targetSize = GFX->getActiveRenderTarget()->getSize(); Point3I size(targetSize.x, targetSize.y, 0); const RectI& viewport = GFX->getViewport(); Point2F offset((F32)viewport.point.x / (F32)targetSize.x, (F32)viewport.point.y / (F32)targetSize.y ); Point2F scale((F32)viewport.extent.x / (F32)targetSize.x, (F32)viewport.extent.y / (F32)targetSize.y ); Point4F targetParams; targetParams.x = offset.x; targetParams.y = offset.y; targetParams.z = offset.x + scale.x; targetParams.w = offset.y + scale.y; mShaderConsts->set( mTargetViewportSC, targetParams ); } // Set the fog data. if ( mFogDataSC->isValid() ) { const FogData &data = state->getSceneManager()->getFogData(); Point3F params; params.x = data.density; params.y = data.densityOffset; if ( !mIsZero( data.atmosphereHeight ) ) params.z = 1.0f / data.atmosphereHeight; else params.z = 0.0f; mShaderConsts->set( mFogDataSC, params ); } const PFXFrameState &thisFrame = PFXMGR->getFrameState(); if ( mMatWorldToScreenSC->isValid() ) { // Screen space->world space MatrixF tempMat = thisFrame.cameraToScreen; tempMat.mul( thisFrame.worldToCamera ); tempMat.fullInverse(); tempMat.transpose(); // Support using these matrices as float3x3 or float4x4... mShaderConsts->set( mMatWorldToScreenSC, tempMat, mMatWorldToScreenSC->getType() ); } if ( mMatScreenToWorldSC->isValid() ) { // World space->screen space MatrixF tempMat = thisFrame.cameraToScreen; tempMat.mul( thisFrame.worldToCamera ); tempMat.transpose(); // Support using these matrices as float3x3 or float4x4... mShaderConsts->set( mMatScreenToWorldSC, tempMat, mMatScreenToWorldSC->getType() ); } if ( mMatPrevScreenToWorldSC->isValid() ) { const PFXFrameState &lastFrame = PFXMGR->getLastFrameState(); // Previous frame world space->screen space MatrixF tempMat = lastFrame.cameraToScreen; tempMat.mul( lastFrame.worldToCamera ); tempMat.transpose(); mShaderConsts->set( mMatPrevScreenToWorldSC, tempMat ); } if (mAmbientColorSC->isValid() && state) { const LinearColorF &sunlight = state->getAmbientLightColor(); Point3F ambientColor( sunlight.red, sunlight.green, sunlight.blue ); mShaderConsts->set( mAmbientColorSC, ambientColor ); } if (mMatCameraToWorldSC->isValid()) { MatrixF tempMat = thisFrame.worldToCamera; tempMat.inverse(); mShaderConsts->set(mMatCameraToWorldSC, tempMat); } if (mInvCameraTransSC->isValid()) { MatrixF mat = state->getCameraTransform(); mat.fullInverse(); mShaderConsts->set(mInvCameraTransSC, mat, mInvCameraTransSC->getType()); } //Projection Matrix if (mMatCameraToScreenSC->isValid()) { MatrixF tempMat = thisFrame.cameraToScreen; mShaderConsts->set(mMatCameraToScreenSC, tempMat, mMatCameraToScreenSC->getType()); } //Inverse Projection Matrix if (mMatScreenToCameraSC->isValid()) { MatrixF tempMat = thisFrame.cameraToScreen; tempMat.fullInverse(); mShaderConsts->set(mMatScreenToCameraSC, tempMat, mMatScreenToCameraSC->getType()); } mShaderConsts->setSafe( mAccumTimeSC, MATMGR->getTotalTime() ); mShaderConsts->setSafe( mDeltaTimeSC, MATMGR->getDeltaTime() ); mShaderConsts->setSafe(mDampnessSC, MATMGR->getDampnessClamped()); // Now set all the constants that are dependent on the scene state. if ( state ) { mShaderConsts->setSafe( mEyePosSC, state->getDiffuseCameraPosition() ); mShaderConsts->setSafe( mNearFarSC, Point2F( state->getNearPlane(), state->getFarPlane() ) ); mShaderConsts->setSafe( mInvNearFarSC, Point2F( 1.0f / state->getNearPlane(), 1.0f / state->getFarPlane() ) ); mShaderConsts->setSafe( mWorldToScreenScaleSC, state->getWorldToScreenScale() ); mShaderConsts->setSafe( mProjectionOffsetSC, state->getCameraFrustum().getProjectionOffset() ); mShaderConsts->setSafe( mFogColorSC, state->getSceneManager()->getFogData().color ); if ( mWaterColorSC->isValid() ) { LinearColorF color( state->getSceneManager()->getWaterFogData().color ); mShaderConsts->set( mWaterColorSC, color ); } if ( mWaterFogDataSC->isValid() ) { const WaterFogData &data = state->getSceneManager()->getWaterFogData(); Point4F params( data.density, data.densityOffset, data.wetDepth, data.wetDarkening ); mShaderConsts->set( mWaterFogDataSC, params ); } if ( mWaterFogPlaneSC->isValid() ) { const PlaneF &plane = state->getSceneManager()->getWaterFogData().plane; mShaderConsts->set( mWaterFogPlaneSC, plane ); } if ( mWaterDepthGradMaxSC->isValid() ) { mShaderConsts->set( mWaterDepthGradMaxSC, state->getSceneManager()->getWaterFogData().depthGradMax ); } if ( mScreenSunPosSC->isValid() ) { // Grab our projection matrix // from the frustum. Frustum frust = state->getCameraFrustum(); MatrixF proj( true ); frust.getProjectionMatrix( &proj ); // Grab the ScatterSky world matrix. MatrixF camMat = state->getCameraTransform(); camMat.inverse(); MatrixF tmp( true ); tmp = camMat; tmp.setPosition( Point3F( 0, 0, 0 ) ); Point3F sunPos( 0, 0, 0 ); // Get the light manager and sun light object. LightInfo *sunLight = LIGHTMGR->getSpecialLight( LightManager::slSunLightType ); // Grab the light direction and scale // by the ScatterSky radius to get the world // space sun position. const VectorF &lightDir = sunLight->getDirection(); Point3F lightPos( lightDir.x * (6378.0f * 1000.0f), lightDir.y * (6378.0f * 1000.0f), lightDir.z * (6378.0f * 1000.0f) ); RectI viewPort = GFX->getViewport(); // Get the screen space sun position. MathUtils::mProjectWorldToScreen(lightPos, &sunPos, viewPort, tmp, proj); // And normalize it to the 0 to 1 range. sunPos.x -= (F32)viewPort.point.x; sunPos.y -= (F32)viewPort.point.y; sunPos.x /= (F32)viewPort.extent.x; sunPos.y /= (F32)viewPort.extent.y; mShaderConsts->set( mScreenSunPosSC, Point2F( sunPos.x, sunPos.y ) ); } if ( mLightDirectionSC->isValid() ) { LightInfo *sunLight = LIGHTMGR->getSpecialLight( LightManager::slSunLightType ); const VectorF &lightDir = sunLight->getDirection(); mShaderConsts->set( mLightDirectionSC, lightDir ); } if ( mCameraForwardSC->isValid() ) { const MatrixF &camMat = state->getCameraTransform(); VectorF camFwd( 0, 0, 0 ); camMat.getColumn( 1, &camFwd ); mShaderConsts->set( mCameraForwardSC, camFwd ); } if ( mInvCameraMatSC->isValid() ) { MatrixF mat = state->getCameraTransform(); mat.inverse(); mShaderConsts->set( mInvCameraMatSC, mat, mInvCameraMatSC->getType() ); } } // if ( state ) // Set EffectConsts - specified from script // If our shader has reloaded since last frame we must mark all // EffectConsts dirty so they will be reset. if ( mShader->getReloadKey() != mShaderReloadKey ) { mShaderReloadKey = mShader->getReloadKey(); EffectConstTable::Iterator iter = mEffectConsts.begin(); for ( ; iter != mEffectConsts.end(); iter++ ) { iter->value->mDirty = true; iter->value->mHandle = NULL; } } // Doesn't look like anyone is using this anymore. // But if we do want to pass this info to script, // we should do so in the same way as I am doing below. /* Point2F texSizeScriptConst( 0, 0 ); String buffer; if ( mActiveTextures[0] ) { texSizeScriptConst.x = (F32)mActiveTextures[0]->getWidth(); texSizeScriptConst.y = (F32)mActiveTextures[0]->getHeight(); dSscanf( buffer.c_str(), "%g %g", texSizeScriptConst.x, texSizeScriptConst.y ); } */ { { PROFILE_SCOPE(PostEffect_SetShaderConsts); // Pass some data about the current render state to script. // // TODO: This is pretty messy... it should go away. This info // should be available from some other script accessible method // or field which isn't PostEffect specific. // if (state) { Con::setFloatVariable("$Param::NearDist", state->getNearPlane()); Con::setFloatVariable("$Param::FarDist", state->getFarPlane()); } bool tracing = Con::gTraceOn; Con::gTraceOn = false; setShaderConsts_callback(); Con::gTraceOn = tracing; } EffectConstTable::Iterator iter = mEffectConsts.begin(); for (; iter != mEffectConsts.end(); iter++) iter->value->setToBuffer(mShaderConsts); } } void PostEffect::_setupTexture(U32 stage, GFXTexHandle* inputTex, U32 inputTexCount, const RectI* inTexViewport) { const String &texFilename = mTextureAsset[stage].notNull() ? mTextureAsset[stage]->getImageFile() : ""; GFXTexHandle theTex; NamedTexTarget *namedTarget = NULL; RectI viewport = GFX->getViewport(); S32 inSlot = _inTexSlotFromName(texFilename); if (inSlot >= 0) { if ((U32)inSlot < inputTexCount) { theTex = inputTex[inSlot]; if (inSlot == 0 && inTexViewport) viewport = *inTexViewport; else if (theTex) viewport.set(0, 0, theTex->getWidth(), theTex->getHeight()); } } else if ( texFilename.compare( "$backBuffer", 0, String::NoCase ) == 0 ) { theTex = PFXMGR->getBackBufferTex(); // Always use the GFX viewport when reading from the backbuffer } else if ( texFilename.isNotEmpty() && texFilename[0] == '#' ) { namedTarget = NamedTexTarget::find( texFilename.c_str() + 1 ); if ( namedTarget ) { theTex = namedTarget->getTexture( 0 ); viewport = namedTarget->getViewport(); } } else { theTex = mTexture[stage]; if (!theTex && mTextureAsset[stage].notNull()) theTex = mTextureAsset[stage]->getTexture(mTextureProfile[stage]); if ( theTex ) viewport.set( 0, 0, theTex->getWidth(), theTex->getHeight() ); } mActiveTextures[ stage ] = theTex; mActiveNamedTarget[ stage ] = namedTarget; mActiveTextureViewport[ stage ] = viewport; if ( theTex.isValid() ) GFX->setTexture( stage, theTex ); } void PostEffect::_setupCubemapTexture(U32 stage, GFXCubemapHandle &inputTex) { RectI viewport = GFX->getViewport(); mActiveTextures[stage] = NULL; mActiveNamedTarget[stage] = NULL; mActiveTextureViewport[stage] = viewport; if (inputTex.isValid()) GFX->setCubeTexture(stage, inputTex); } void PostEffect::_setupCubemapArrayTexture(U32 stage, GFXCubemapArrayHandle &inputTex) { RectI viewport = GFX->getViewport(); mActiveTextures[stage] = NULL; mActiveNamedTarget[stage] = NULL; mActiveTextureViewport[stage] = viewport; if (inputTex.isValid()) GFX->setCubeArrayTexture(stage, inputTex); } void PostEffect::_setupTransforms() { // Set everything to identity. GFX->setWorldMatrix( MatrixF::Identity ); GFX->setProjectionMatrix( MatrixF::Identity ); } void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarget ) { Point2I targetSize; if (!mTargetSize.isZero()) targetSize = mTargetSize; else if (mActiveTextures[0]) { const Point3I& texSize = mActiveTextures[0]->getSize(); targetSize.set(texSize.x * mTargetScale.x, texSize.y * mTargetScale.y); } else { GFXTarget* oldTarget = GFX->getActiveRenderTarget(); const Point2I& oldTargetSize = oldTarget->getSize(); targetSize.set(oldTargetSize.x * mTargetScale.x, oldTargetSize.y * mTargetScale.y); } targetSize.setMax(Point2I::One); for (U32 c = 0; c < NumMRTTargets; c++) { if (mNamedTarget[c].isRegistered() || _outTexSlotFromName(mTargetName[c]) >= 0) { if (mNamedTarget[c].isRegistered() || !mTargetTex[c] || mTargetTex[c].getWidthHeight() != targetSize) { mTargetTex[c].set(targetSize.x, targetSize.y, mTargetFormat[c], &PostFxTargetProfile, "PostEffect::_setupTarget", mMipCap); if (mTargetClear[c] == PFXTargetClear_OnCreate) *outClearTarget = true; if (mTargetViewport == PFXTargetViewport_GFXViewport) { // We may need to scale the GFX viewport to fit within // our target texture size GFXTarget* oldTarget = GFX->getActiveRenderTarget(); const Point2I& oldTargetSize = oldTarget->getSize(); Point2F scale(targetSize.x / F32(oldTargetSize.x), targetSize.y / F32(oldTargetSize.y)); const RectI& viewport = GFX->getViewport(); mNamedTarget[c].setViewport(RectI(viewport.point.x * scale.x, viewport.point.y * scale.y, viewport.extent.x * scale.x, viewport.extent.y * scale.y)); } else if (mTargetViewport == PFXTargetViewport_NamedInTexture0 && mActiveNamedTarget[0] && mActiveNamedTarget[0]->getTexture()) { // Scale the named input texture's viewport to match our target const Point3I& namedTargetSize = mActiveNamedTarget[0]->getTexture()->getSize(); Point2F scale(targetSize.x / F32(namedTargetSize.x), targetSize.y / F32(namedTargetSize.y)); const RectI& viewport = mActiveNamedTarget[0]->getViewport(); mNamedTarget[c].setViewport(RectI(viewport.point.x * scale.x, viewport.point.y * scale.y, viewport.extent.x * scale.x, viewport.extent.y * scale.y)); } else { mNamedTarget[c].setViewport(RectI(0, 0, targetSize.x, targetSize.y)); } } } else mTargetTex[c] = NULL; } bool auxActive = false; for (U32 c = 1; c < NumMRTTargets; c++) { if (mTargetTex[c].isValid()) { auxActive = true; break; } } if (auxActive && !mTargetTex[0].isValid()) { /* If we have issues later on, uncomment this! Con::warnf("PostEffect '%s': auxiliary MRT slots are active but target[0] " "has no texture. Auto-promoting slot 0 to a temporary. " "Use a named target (#name) on slot 0 for MRT setups.", getName());*/ mTargetTex[0].set(targetSize.x, targetSize.y, mTargetFormat[0], &PostFxTargetProfile, "PostEffect::MRTSlot[0]_auto", mMipCap); } if (mNamedTargetDepthStencil.isRegistered()) { if (mNamedTargetDepthStencil.isRegistered() && mTargetDepthStencil.getWidthHeight() != targetSize) { mTargetDepthStencil.set(targetSize.x, targetSize.y, GFXFormatD24S8, &GFXZTargetProfile, "PostEffect::_setupTarget"); if (mTargetClear[0] == PFXTargetClear_OnCreate) *outClearTarget = true; if (mTargetViewport == PFXTargetViewport_GFXViewport) { // We may need to scale the GFX viewport to fit within // our target texture size GFXTarget* oldTarget = GFX->getActiveRenderTarget(); const Point2I& oldTargetSize = oldTarget->getSize(); Point2F scale(targetSize.x / F32(oldTargetSize.x), targetSize.y / F32(oldTargetSize.y)); const RectI& viewport = GFX->getViewport(); mNamedTargetDepthStencil.setViewport(RectI(viewport.point.x * scale.x, viewport.point.y * scale.y, viewport.extent.x * scale.x, viewport.extent.y * scale.y)); } else if (mTargetViewport == PFXTargetViewport_NamedInTexture0 && mActiveNamedTarget[0] && mActiveNamedTarget[0]->getTexture()) { // Scale the named input texture's viewport to match our target const Point3I& namedTargetSize = mActiveNamedTarget[0]->getTexture()->getSize(); Point2F scale(targetSize.x / F32(namedTargetSize.x), targetSize.y / F32(namedTargetSize.y)); const RectI& viewport = mActiveNamedTarget[0]->getViewport(); mNamedTargetDepthStencil.setViewport(RectI(viewport.point.x * scale.x, viewport.point.y * scale.y, viewport.extent.x * scale.x, viewport.extent.y * scale.y)); } else { // PFXTargetViewport_TargetSize mNamedTargetDepthStencil.setViewport(RectI(0, 0, targetSize.x, targetSize.y)); } } } else mTargetDepthStencil = NULL; if (mTargetClear[0] == PFXTargetClear_OnDraw) *outClearTarget = true; bool hasAny = mTargetDepthStencil.isValid(); for (U32 c = 0; c < NumMRTTargets && !hasAny; c++) hasAny = mTargetTex[c].isValid(); if (!mTarget && hasAny) mTarget = GFX->allocRenderToTextureTarget(); } void PostEffect::_cleanTargets( bool recurse ) { for (U32 c = 0; c < NumMRTTargets; c++) mTargetTex[c] = NULL; mTargetDepthStencil = NULL; mTarget = NULL; if ( !recurse ) return; // Clear the children too! for ( U32 i = 0; i < size(); i++ ) { PostEffect *effect = (PostEffect*)(*this)[i]; effect->_cleanTargets( true ); } } void PostEffect::process( const SceneRenderState* state, GFXTexHandle* inOutTex, U32 inOutTexCount, const RectI* inTexViewport) { // If the shader is forced to be skipped... then skip. if ( mSkip ) return; // Skip out if we don't support reflection passes. if ( state && state->isReflectPass() && !mAllowReflectPass ) return; if ( mOneFrameOnly && !mOnThisFrame ) return; // Check requirements if the shader needs updating. if ( mUpdateShader ) { _checkRequirements(); // Clear the targets if we failed passing // the requirements at this time. if ( !mIsValid ) _cleanTargets( true ); } // If we're not valid then we cannot render. if ( !mIsValid ) return; GFXDEBUGEVENT_SCOPE_EX( PostEffect_Process, ColorI::GREEN, avar("PostEffect: %s", getName()) ); if (!mPreProcessed || mShader->getReloadKey() != mShaderReloadKey) { mPreProcessed = true; preProcess_callback(); } GFXTransformSaver saver; GFXTexHandle chain[NumMRTTargets]; U32 texCount = getMin(inOutTexCount, (U32)NumMRTTargets); for (U32 c = 0; c < texCount; c++) chain[c] = inOutTex[c]; // Set the textures. for (U32 i = 0; i < NumTextures; i++) { if (mTextureType[i] == NormalTextureType) _setupTexture(i, chain, texCount, inTexViewport); else if (mTextureType[i] == CubemapType) _setupCubemapTexture(i, mCubemapTextures[i]); else if (mTextureType[i] == CubemapArrayType) _setupCubemapArrayTexture(i, mCubemapArrayTextures[i]); } _setupStateBlock( state ) ; _setupTransforms(); bool clearTarget = false; _setupTarget( state, &clearTarget ); const bool hasDepth = mTargetDepthStencil.isValid(); bool hasColorTarget = false; U32 curTargetCount = 0; for (U32 c = 0; c < NumMRTTargets; c++) { if (mTargetTex[c].isValid()) { hasColorTarget = true; curTargetCount++; } } // Attach each color slot. NULL explicitly detaches unused slots, // preventing stale attachments from a previous frame. static const GFXTextureTarget::RenderSlot kSlots[NumMRTTargets] = { GFXTextureTarget::Color0, GFXTextureTarget::Color1, GFXTextureTarget::Color2, GFXTextureTarget::Color3, }; if (hasColorTarget || hasDepth) { const RectI &oldViewport = GFX->getViewport(); GFXTarget *oldTarget = GFX->getActiveRenderTarget(); GFX->pushActiveRenderTarget(); for (U32 c = 0; c < NumMRTTargets; c++) { mTarget->attachTexture(kSlots[c], mTargetTex[c].isValid() ? mTargetTex[c] : NULL); } // Set the right depth stencil target. if (!hasDepth && mTargetTex[0].isValid() && mTargetTex[0].getWidthHeight() == oldTarget->getSize()) mTarget->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); else mTarget->attachTexture(GFXTextureTarget::DepthStencil, mTargetDepthStencil); // Set the render target but not its viewport. We'll do that below. GFX->setActiveRenderTarget( mTarget, false ); if (mNamedTarget[0].isRegistered()) { GFX->setViewport(mNamedTarget[0].getViewport()); } else if (mTargetTex[0].isValid()) { GFX->setViewport(RectI(Point2I::Zero, mTargetTex[0].getWidthHeight())); } else if(mTargetViewport == PFXTargetViewport_GFXViewport) { // Go with the current viewport as scaled against our render target. const Point2I &oldTargetSize = oldTarget->getSize(); const Point2I &targetSize = mTarget->getSize(); Point2F scale(targetSize.x / F32(oldTargetSize.x), targetSize.y / F32(oldTargetSize.y)); GFX->setViewport( RectI( oldViewport.point.x*scale.x, oldViewport.point.y*scale.y, oldViewport.extent.x*scale.x, oldViewport.extent.y*scale.y ) ); } else if(mTargetViewport == PFXTargetViewport_NamedInTexture0 && mActiveNamedTarget[0] && mActiveNamedTarget[0]->getTexture()) { // Go with the first input texture, if it is named. Scale the named input texture's viewport to match our target const Point3I &namedTargetSize = mActiveNamedTarget[0]->getTexture()->getSize(); const Point2I &targetSize = mTarget->getSize(); Point2F scale(targetSize.x / F32(namedTargetSize.x), targetSize.y / F32(namedTargetSize.y)); const RectI &viewport = mActiveNamedTarget[0]->getViewport(); GFX->setViewport( RectI( viewport.point.x*scale.x, viewport.point.y*scale.y, viewport.extent.x*scale.x, viewport.extent.y*scale.y ) ); } else { // Default to using the whole target as the viewport GFX->setViewport( RectI( Point2I::Zero, mTarget->getSize() ) ); } } if (clearTarget) { LinearColorF clearColor = LinearColorF::BLACK; for (U32 c = 0; c < NumMRTTargets; c++) { if (mTargetTex[c].isValid() && mTargetClear[c] != PFXTargetClear_None) { clearColor = mTargetClearColor[c]; break; } } GFX->clear(GFXClearTarget, clearColor, 1.f, 0); } // Setup the shader and constants. if ( mShader ) { GFX->setShader( mShader ); _setupConstants( state ); GFX->setShaderConstBuffer( mShaderConsts ); } else GFX->setupGenericShaders(); Frustum frustum; if ( state ) frustum = state->getCameraFrustum(); else { // If we don't have a scene state then setup // a dummy frustum... you better not be depending // on this being related to the camera in any way. frustum = Frustum(); } GFXVertexBufferHandle vb; _updateScreenGeometry( frustum, &vb ); // Draw it. GFX->setVertexBuffer( vb ); GFX->drawPrimitive( GFXTriangleStrip, 0, 2 ); // Allow PostEffecVis to hook in. PFXVIS->onPFXProcessed( this ); if (hasColorTarget || hasDepth) { mTarget->resolve(); GFX->popActiveRenderTarget(); } else { PFXMGR->releaseBackBufferTex(); } for (U32 c = 0; c < NumMRTTargets; c++) { if (c < texCount) chain[c] = mTargetTex[c]; if (!mNamedTarget[c].isRegistered()) mTargetTex[c] = NULL; } // Restore the transforms before the children // are processed as it screws up the viewport. saver.restore(); const U32 nextTexCount = getMax(getMax(texCount, curTargetCount), 1u); // Now process my children. iterator i = begin(); for ( ; i != end(); i++ ) { PostEffect *effect = static_cast(*i); effect->process( state, chain, nextTexCount); } if ( mOneFrameOnly ) mOnThisFrame = false; } bool PostEffect::_setIsEnabled( void *object, const char *index, const char *data ) { bool enabled = dAtob( data ); if ( enabled ) static_cast( object )->enable(); else static_cast( object )->disable(); // Always return false from a protected field. return false; } void PostEffect::enable() { mPreProcessed = false; // Don't add TexGen PostEffects to the PostEffectManager! if ( mRenderTime == PFXTexGenOnDemand ) return; // Ignore it if its already enabled. if ( mEnabled ) return; mEnabled = true; // We cannot really enable the effect // until its been registed. if ( !isProperlyAdded() ) return; // If the enable callback returns 'false' then // leave the effect disabled. if ( !onEnabled_callback() ) { mEnabled = false; return; } PFXMGR->_addEffect( this ); } void PostEffect::disable() { if ( !mEnabled ) return; mEnabled = false; _cleanTargets( true ); if ( isProperlyAdded() ) { PFXMGR->_removeEffect( this ); onDisabled_callback(); } } void PostEffect::reload() { mPreProcessed = false; // Reload the shader if we have one or mark it // for updating when its processed next. if ( mShader ) mShader->reload(); else mUpdateShader = true; // Null stateblock so it is reloaded. mStateBlock = NULL; // Call reload on any children // this PostEffect may have. for ( U32 i = 0; i < size(); i++ ) { PostEffect *effect = (PostEffect*)(*this)[i]; effect->reload(); } } void PostEffect::setTexture( U32 index, const String &texFilePath ) { // Set the new texture name. mTexture[index].free(); // Skip empty stages or ones with variable or target names. if ( texFilePath.isEmpty() || texFilePath[0] == '$' || texFilePath[0] == '#' ) return; mTextureProfile[index] = (mTexSRGB[index])? &PostFxTextureSRGBProfile : &PostFxTextureProfile; _setTexture(texFilePath, index); mTexture[index] = mTextureAsset[index]->getTexture(mTextureProfile[index]); mTextureType[index] = NormalTextureType; } void PostEffect::setTexture(U32 index, const GFXTexHandle& texHandle) { // Set the new texture name. mTexture[index].free(); // Skip empty stages or ones with variable or target names. if (!texHandle.isValid()) return; // Try to load the texture. mTexture[index] = texHandle; mTextureType[index] = NormalTextureType; } void PostEffect::setCubemapTexture(U32 index, const GFXCubemapHandle &cubemapHandle) { // Set the new texture name. mCubemapTextures[index].free(); // Skip empty stages or ones with variable or target names. if (cubemapHandle.isNull()) return; // Try to load the texture. mCubemapTextures[index] = cubemapHandle; mTextureType[index] = CubemapType; } void PostEffect::setCubemapArrayTexture(U32 index, const GFXCubemapArrayHandle &cubemapArrayHandle) { // Set the new texture name. mCubemapArrayTextures[index].free(); // Skip empty stages or ones with variable or target names. if (cubemapArrayHandle.isNull()) return; // Try to load the texture. mCubemapArrayTextures[index] = cubemapArrayHandle; mTextureType[index] = CubemapArrayType; } void PostEffect::setShaderConst( const String &name, const String &val ) { PROFILE_SCOPE( PostEffect_SetShaderConst ); EffectConstTable::Iterator iter = mEffectConsts.find( name ); if ( iter == mEffectConsts.end() ) { EffectConst *newConst = new EffectConst( name, val ); iter = mEffectConsts.insertUnique( name, newConst ); } iter->value->set( val ); } void PostEffect::setShaderConst(const String &name, const F32 &val) { PROFILE_SCOPE(PostEffect_SetShaderConst_Float); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst *newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } void PostEffect::setShaderConst(const String& name, const int& val) { PROFILE_SCOPE(PostEffect_SetShaderConst_Int); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst* newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } void PostEffect::setShaderConst(const String &name, const Point4F &val) { PROFILE_SCOPE(PostEffect_SetShaderConst_Point); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst *newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } void PostEffect::setShaderConst(const String &name, const MatrixF &val) { PROFILE_SCOPE(PostEffect_SetShaderConst_Matrix); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst *newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } void PostEffect::setShaderConst(const String &name, const Vector &val) { PROFILE_SCOPE(PostEffect_SetShaderConst_PointArray); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst *newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } void PostEffect::setShaderConst(const String &name, const Vector &val) { PROFILE_SCOPE(PostEffect_SetShaderConst_MatrixArray); EffectConstTable::Iterator iter = mEffectConsts.find(name); if (iter == mEffectConsts.end()) { EffectConst *newConst = new EffectConst(name, val); iter = mEffectConsts.insertUnique(name, newConst); } iter->value->set(val); } F32 PostEffect::getAspectRatio() const { const Point2I &rtSize = GFX->getActiveRenderTarget()->getSize(); return (F32)rtSize.x / (F32)rtSize.y; } void PostEffect::_checkRequirements() { // This meets requirements if its shader loads // properly, we can find all the input textures, // and its formats are supported. mIsValid = false; mUpdateShader = false; mShader = NULL; mShaderConsts = NULL; EffectConstTable::Iterator iter = mEffectConsts.begin(); for ( ; iter != mEffectConsts.end(); iter++ ) { iter->value->mDirty = true; iter->value->mHandle = NULL; } // First make sure the target format is supported. for (U32 c = 0; c < NumMRTTargets; c++) { if (mNamedTarget[c].isRegistered()) { Vector formats; formats.push_back(mTargetFormat[c]); GFXFormat format = GFX->selectSupportedFormat(&PostFxTargetProfile, formats, true, false, false); // If we didn't get our format out then its unsupported! if (format != mTargetFormat[c]) return; } } // Gather macros specified on this PostEffect. Vector macros( mShaderMacros ); // Now check the input named targets and make sure // they exist... else we're invalid. for ( U32 i=0; i < NumTextures; i++ ) { if (mTextureType[i] == NormalTextureType) { if (mTextureAsset[i].notNull()) { const String& texFilename = mTextureAsset[i]->getImageFile(); if (texFilename.isNotEmpty() && texFilename[0] == '#') { NamedTexTarget* namedTarget = NamedTexTarget::find(texFilename.c_str() + 1); if (!namedTarget) { return; } // Grab the macros for shader initialization. namedTarget->getShaderMacros(¯os); } } } } // Finally find and load the shader. ShaderData *shaderData; if ( Sim::findObject( mShaderName, shaderData ) ) if ( shaderData->getPixVersion() <= GFX->getPixelShaderVersion() ) mShader = shaderData->getShader( macros ); // If we didn't get a shader... we're done. if ( !mShader ) return; // If we got here then we're valid. mIsValid = true; } bool PostEffect::dumpShaderDisassembly( String &outFilename ) const { String data; if ( !mShader || !mShader->getDisassembly( data ) ) return false; outFilename = FS::MakeUniquePath( "", "ShaderDisassembly", "txt" ); FileStream *fstream = FileStream::createAndOpen( outFilename, Torque::FS::File::Write ); if ( !fstream ) return false; fstream->write( data ); fstream->close(); delete fstream; return true; } SimSet* PostEffect::getSet() const { SimSet *set; if ( !Sim::findObject( "PFXSet", set ) ) { set = new SimSet(); set->registerObject( "PFXSet" ); Sim::getRootGroup()->addObject( set ); } return set; } void PostEffect::setShaderMacro( const String &name, const String &value ) { // Check to see if we already have this macro. Vector::iterator iter = mShaderMacros.begin(); for ( ; iter != mShaderMacros.end(); iter++ ) { if ( iter->name == name ) { if ( iter->value != value ) { iter->value = value; mUpdateShader = true; } return; } } // Add a new macro. mShaderMacros.increment(); mShaderMacros.last().name = name; mShaderMacros.last().value = value; mUpdateShader = true; } bool PostEffect::removeShaderMacro( const String &name ) { Vector::iterator iter = mShaderMacros.begin(); for ( ; iter != mShaderMacros.end(); iter++ ) { if ( iter->name == name ) { mShaderMacros.erase( iter ); mUpdateShader = true; return true; } } return false; } void PostEffect::clearShaderMacros() { if ( mShaderMacros.empty() ) return; mShaderMacros.clear(); mUpdateShader = true; } GFXTextureObject* PostEffect::_getTargetTexture(U32 index) { if (mRenderTime == PFXTexGenOnDemand && (!mTargetTex[index] || mUpdateShader)) { GFXTexHandle chainTex[NumMRTTargets]; // zero-initialised by GFXTexHandle ctor process(NULL, chainTex, NumMRTTargets); } return mTargetTex[index].getPointer(); } DefineEngineMethod( PostEffect, reload, void, (),, "Reloads the effect shader and textures." ) { return object->reload(); } DefineEngineMethod( PostEffect, enable, void, (),, "Enables the effect." ) { object->enable(); } DefineEngineMethod( PostEffect, disable, void, (),, "Disables the effect." ) { object->disable(); } DefineEngineMethod( PostEffect, toggle, bool, (),, "Toggles the effect between enabled / disabled.\n" "@return True if effect is enabled." ) { if ( object->isEnabled() ) object->disable(); else object->enable(); return object->isEnabled(); } DefineEngineMethod( PostEffect, isEnabled, bool, (),, "@return True if the effect is enabled." ) { return object->isEnabled(); } DefineEngineMethod( PostEffect, setTexture, void, ( S32 index, const char *filePath ),, "This is used to set the texture file and load the texture on a running effect. " "If the texture file is not different from the current file nothing is changed. If " "the texture cannot be found a null texture is assigned.\n" "@param index The texture stage index.\n" "@param filePath The file name of the texture to set.\n" ) { if ( index > -1 && index < PostEffect::NumTextures ) object->setTexture( index, filePath ); } DefineEngineMethod( PostEffect, setShaderConst, void, ( const char* name, const char* value ),, "Sets the value of a uniform defined in the shader. This will usually " "be called within the setShaderConsts callback. Array type constants are " "not supported.\n" "@param name Name of the constanst, prefixed with '$'.\n" "@param value Value to set, space seperate values with more than one element.\n" "@tsexample\n" "function MyPfx::setShaderConsts( %this )\n" "{\n" " // example float4 uniform\n" " %this.setShaderConst( \"$colorMod\", \"1.0 0.9 1.0 1.0\" );\n" " // example float1 uniform\n" " %this.setShaderConst( \"$strength\", \"3.0\" );\n" " // example integer uniform\n" " %this.setShaderConst( \"$loops\", \"5\" );" "}\n" "@endtsexample" ) { object->setShaderConst( name, value ); } DefineEngineMethod( PostEffect, getAspectRatio, F32, (),, "@return Width over height of the backbuffer." ) { return object->getAspectRatio(); } DefineEngineMethod( PostEffect, dumpShaderDisassembly, String, (),, "Dumps this PostEffect shader's disassembly to a temporary text file.\n" "@return Full path to the dumped file or an empty string if failed." ) { String fileName; object->dumpShaderDisassembly( fileName ); return fileName; } DefineEngineMethod( PostEffect, setShaderMacro, void, ( const char* key, const char* value ), ( "" ), "Adds a macro to the effect's shader or sets an existing one's value. " "This will usually be called within the onAdd or preProcess callback.\n" "@param key lval of the macro." "@param value rval of the macro, or may be empty." "@tsexample\n" "function MyPfx::onAdd( %this )\n" "{\n" " %this.setShaderMacro( \"NUM_SAMPLES\", \"10\" );\n" " %this.setShaderMacro( \"HIGH_QUALITY_MODE\" );\n" " \n" " // In the shader looks like... \n" " // #define NUM_SAMPLES 10\n" " // #define HIGH_QUALITY_MODE\n" "}\n" "@endtsexample" ) { object->setShaderMacro( key, value ); } DefineEngineMethod( PostEffect, removeShaderMacro, void, ( const char* key ),, "Remove a shader macro. This will usually be called within the preProcess callback.\n" "@param key Macro to remove." ) { object->removeShaderMacro( key ); } DefineEngineMethod( PostEffect, clearShaderMacros, void, (),, "Remove all shader macros." ) { object->clearShaderMacros(); } DefineEngineFunction( dumpRandomNormalMap, void, (),, "Creates a 64x64 normal map texture filled with noise. The texture is saved " "to randNormTex.png in the location of the game executable.\n\n" "@ingroup GFX") { GFXTexHandle tex; tex.set( 64, 64, GFXFormatR8G8B8A8, &GFXTexturePersistentProfile, "" ); GFXLockedRect *rect = tex.lock(); U8 *f = rect->bits; for ( U32 i = 0; i < 64*64; i++, f += 4 ) { VectorF vec; vec.x = mRandF( -1.0f, 1.0f ); vec.y = mRandF( -1.0f, 1.0f ); vec.z = mRandF( -1.0f, 1.0f ); vec.normalizeSafe(); f[0] = U8_MAX * ( ( 1.0f + vec.x ) * 0.5f ); f[1] = U8_MAX * ( ( 1.0f + vec.y ) * 0.5f ); f[2] = U8_MAX * ( ( 1.0f + vec.z ) * 0.5f ); f[3] = U8_MAX; } tex.unlock(); String path = Torque::FS::MakeUniquePath( "", "randNormTex", "png" ); tex->dumpToDisk( "png", path ); }