From 60e659aedcea9ffbacda568d31fb9893549ffb94 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 19 Apr 2026 15:47:48 +0100 Subject: [PATCH 1/4] adds multiple targets to the posteffect class These changes allows for multiple color targets from a post effect pass. This is not 100% complete yet as the api still expects 1 target out from a posteffect and will not pass those on to other stages in a posteffect chain but will be adding that soon. This is a working state and everything renders correctly here. --- Engine/source/postFx/postEffect.cpp | 479 ++++++++++++---------- Engine/source/postFx/postEffect.h | 512 ++++++++++-------------- Engine/source/postFx/postEffectCommon.h | 5 +- Engine/source/postFx/postEffectVis.cpp | 6 +- 4 files changed, 494 insertions(+), 508 deletions(-) diff --git a/Engine/source/postFx/postEffect.cpp b/Engine/source/postFx/postEffect.cpp index ab4363c37..e17509b02 100644 --- a/Engine/source/postFx/postEffect.cpp +++ b/Engine/source/postFx/postEffect.cpp @@ -137,9 +137,6 @@ GFX_ImplementTextureProfile( PostFxTargetProfile, GFXTextureProfile::Pooled, GFXTextureProfile::NONE ); -IMPLEMENT_CONOBJECT(PostEffect); - - GFX_ImplementTextureProfile( PostFxTextureProfile, GFXTextureProfile::DiffuseMap, GFXTextureProfile::Static | GFXTextureProfile::PreserveSize, @@ -162,6 +159,12 @@ GFX_ImplementTextureProfile( VRDepthProfile, GFXTextureProfile::ZTarget, GFXTextureProfile::NONE ); +IMPLEMENT_CONOBJECT(PostEffect); + +//--------------------------------------------------------------------- +// EffectConst +//--------------------------------------------------------------------- + void PostEffect::EffectConst::set( const String &newVal ) { if ( mStringVal == newVal ) @@ -445,69 +448,77 @@ void PostEffect::EffectConst::setToBuffer( GFXShaderConstBufferRef buff ) } } +//--------------------------------------------------------------------- +// EffectConst END +//--------------------------------------------------------------------- //------------------------------------------------------------------------- // PostEffect //------------------------------------------------------------------------- PostEffect::PostEffect() - : mRenderTime( PFXAfterDiffuse ), - mRenderPriority( 1.0 ), - mEnabled( false ), - mStateBlockData( NULL ), - mUpdateShader( true ), - mSkip( false ), - mPreProcessed(false), - mAllowReflectPass( false ), - mTargetClear( PFXTargetClear_None ), - mTargetScale( Point2F::One ), - mTargetViewport( PFXTargetViewport_TargetSize ), - mTargetSize( Point2I::Zero ), - mTargetFormat( GFXFormatR8G8B8A8 ), - mTargetClearColor( LinearColorF::BLACK ), - 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), - mMipCap(1) + : 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) { - 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 ); + // 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); } @@ -527,9 +538,18 @@ void PostEffect::initPersistFields() addField( "stateBlock", TYPEID(), Offset( mStateBlockData, PostEffect ), "Name of a GFXStateBlockData for this effect." ); - addField( "target", TypeRealString, Offset( mTargetName, PostEffect ), + 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" @@ -543,15 +563,6 @@ void PostEffect::initPersistFields() addField( "targetSize", TypePoint2I, Offset( mTargetSize, PostEffect ), "If non-zero this is used as the absolute target size." ); - - addField( "targetFormat", TypeGFXFormat, Offset( mTargetFormat, PostEffect ), - "Format of the target texture, not applicable if writing to the backbuffer." ); - - addField( "targetClearColor", TypeColorF, Offset( mTargetClearColor, PostEffect ), - "Color to which the target texture is cleared before rendering." ); - - addField( "targetClear", TYPEID< PFXTargetClear >(), Offset( mTargetClear, PostEffect ), - "Describes when the target texture should be cleared." ); addField( "targetViewport", TYPEID< PFXTargetViewport >(), Offset( mTargetViewport, PostEffect ), "Specifies how the viewport should be set up for a target texture." ); @@ -624,15 +635,21 @@ bool PostEffect::onAdd() } // Is the target a named target? - if ( mTargetName.isNotEmpty() && mTargetName[0] == '#' ) + bool anyNamed = false; + for (U32 c = 0; c < NumMRTTargets; c++) { - mNamedTarget.registerWithName(mTargetName.substr(1)); - mNamedTarget.getTextureDelegate().bind( this, &PostEffect::_getTargetTexture ); + 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 (mNamedTarget.isRegistered() || mNamedTargetDepthStencil.isRegistered()) + if (anyNamed || mNamedTargetDepthStencil.isRegistered()) GFXTextureManager::addEventDelegate( this, &PostEffect::_onTextureEvent ); // Call onAdd in script @@ -661,15 +678,20 @@ void PostEffect::onRemove() mShader = NULL; _cleanTargets(); - if ( mNamedTarget.isRegistered() || mNamedTargetDepthStencil.isRegistered() ) - GFXTextureManager::removeEventDelegate( this, &PostEffect::_onTextureEvent ); - - if ( mNamedTarget.isRegistered() ) + bool anyNamed = false; + for (U32 c = 0; c < NumMRTTargets; c++) { - mNamedTarget.unregister(); - mNamedTarget.getTextureDelegate().clear(); + 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(); } @@ -1156,7 +1178,7 @@ void PostEffect::_setupTexture( U32 stage, GFXTexHandle &inputTex, const RectI * RectI viewport = GFX->getViewport(); - if ( texFilename.compare( "$inTex", 0, String::NoCase ) == 0 ) + if ( _inTexSlotFromName(texFilename) >= 0 ) { theTex = inputTex; @@ -1236,162 +1258,166 @@ void PostEffect::_setupTransforms() void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarget ) { - if ( mNamedTarget.isRegistered() || - mTargetName.compare( "$outTex", 0, String::NoCase ) == 0 ) + for (U32 c = 0; c < NumMRTTargets; c++) { - // Size it relative to the texture of the first stage or - // if NULL then use the current target. - - Point2I targetSize; - - // If we have an absolute target size then use that. - if ( !mTargetSize.isZero() ) - targetSize = mTargetSize; - - // Else generate a relative size using the target scale. - else if ( mActiveTextures[ 0 ] ) + if (mNamedTarget[c].isRegistered() || _outTexSlotFromName(mTargetName[c]) >= 0) { - const Point3I &texSize = mActiveTextures[ 0 ]->getSize(); + // Size it relative to the texture of the first stage or + // if NULL then use the current target. - targetSize.set( texSize.x * mTargetScale.x, - texSize.y * mTargetScale.y ); - } - else - { - GFXTarget *oldTarget = GFX->getActiveRenderTarget(); - const Point2I &oldTargetSize = oldTarget->getSize(); + Point2I targetSize; - targetSize.set( oldTargetSize.x * mTargetScale.x, - oldTargetSize.y * mTargetScale.y ); - } + // If we have an absolute target size then use that. + if (!mTargetSize.isZero()) + targetSize = mTargetSize; - // Make sure its at least 1x1. - targetSize.setMax( Point2I::One ); - - if ( mNamedTarget.isRegistered() || - !mTargetTex || - mTargetTex.getWidthHeight() != targetSize ) - { - mTargetTex.set( targetSize.x, targetSize.y, mTargetFormat, - &PostFxTargetProfile, "PostEffect::_setupTarget", mMipCap); - - if ( mTargetClear == PFXTargetClear_OnCreate ) - *outClearTarget = true; - - if(mTargetViewport == PFXTargetViewport_GFXViewport) + // Else generate a relative size using the target scale. + else if (mActiveTextures[0]) { - // 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 Point3I& texSize = mActiveTextures[0]->getSize(); - const RectI &viewport = GFX->getViewport(); - - mNamedTarget.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.setViewport( RectI( viewport.point.x*scale.x, viewport.point.y*scale.y, viewport.extent.x*scale.x, viewport.extent.y*scale.y ) ); + targetSize.set(texSize.x * mTargetScale.x, + texSize.y * mTargetScale.y); } else { - // PFXTargetViewport_TargetSize - mNamedTarget.setViewport( RectI( 0, 0, targetSize.x, targetSize.y ) ); + GFXTarget* oldTarget = GFX->getActiveRenderTarget(); + const Point2I& oldTargetSize = oldTarget->getSize(); + + targetSize.set(oldTargetSize.x * mTargetScale.x, + oldTargetSize.y * mTargetScale.y); + } + + // Make sure its at least 1x1. + targetSize.setMax(Point2I::One); + + 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 + { + // PFXTargetViewport_TargetSize + mNamedTarget[c].setViewport(RectI(0, 0, targetSize.x, targetSize.y)); + } } } + else + mTargetTex[c] = NULL; } - else - mTargetTex = NULL; // Do we have a named depthStencil target? - if ( mNamedTargetDepthStencil.isRegistered() ) + if (mNamedTargetDepthStencil.isRegistered()) { // Size it relative to the texture of the first stage or // if NULL then use the current target. Point2I targetSize; // If we have an absolute target size then use that. - if ( !mTargetSize.isZero() ) + if (!mTargetSize.isZero()) targetSize = mTargetSize; // Else generate a relative size using the target scale. - else if ( mActiveTextures[ 0 ] ) + else if (mActiveTextures[0]) { - const Point3I &texSize = mActiveTextures[ 0 ]->getSize(); + const Point3I& texSize = mActiveTextures[0]->getSize(); - targetSize.set( texSize.x * mTargetScale.x, - texSize.y * mTargetScale.y ); + targetSize.set(texSize.x * mTargetScale.x, + texSize.y * mTargetScale.y); } else { - GFXTarget *oldTarget = GFX->getActiveRenderTarget(); - const Point2I &oldTargetSize = oldTarget->getSize(); + GFXTarget* oldTarget = GFX->getActiveRenderTarget(); + const Point2I& oldTargetSize = oldTarget->getSize(); - targetSize.set( oldTargetSize.x * mTargetScale.x, - oldTargetSize.y * mTargetScale.y ); + targetSize.set(oldTargetSize.x * mTargetScale.x, + oldTargetSize.y * mTargetScale.y); } // Make sure its at least 1x1. - targetSize.setMax( Point2I::One ); - - if ( mNamedTargetDepthStencil.isRegistered() && - mTargetDepthStencil.getWidthHeight() != targetSize ) - { - mTargetDepthStencil.set( targetSize.x, targetSize.y, GFXFormatD24S8, - &GFXZTargetProfile, "PostEffect::_setupTarget" ); + targetSize.setMax(Point2I::One); - if ( mTargetClear == PFXTargetClear_OnCreate ) + 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) + 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(); + 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(); + 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 ) ); + 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()) + 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(); + 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(); + 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 ) ); + 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 ) ); + mNamedTargetDepthStencil.setViewport(RectI(0, 0, targetSize.x, targetSize.y)); } } } else mTargetDepthStencil = NULL; - if ( mTargetClear == PFXTargetClear_OnDraw ) + if (mTargetClear[0] == PFXTargetClear_OnDraw) *outClearTarget = true; - if ( !mTarget && (mTargetTex || mTargetDepthStencil) ) + if (!mTarget && (mTargetTex || mTargetDepthStencil)) mTarget = GFX->allocRenderToTextureTarget(); } void PostEffect::_cleanTargets( bool recurse ) { - mTargetTex = NULL; + for (U32 c = 0; c < NumMRTTargets; c++) + mTargetTex[c] = NULL; + mTargetDepthStencil = NULL; mTarget = NULL; @@ -1407,7 +1433,7 @@ void PostEffect::_cleanTargets( bool recurse ) } void PostEffect::process( const SceneRenderState *state, - GFXTexHandle &inOutTex, + GFXTexHandle& inOutTex, const RectI *inTexViewport ) { // If the shader is forced to be skipped... then skip. @@ -1461,27 +1487,56 @@ void PostEffect::process( const SceneRenderState *state, bool clearTarget = false; _setupTarget( state, &clearTarget ); - if ( mTargetTex || mTargetDepthStencil ) + const bool hasDepth = mTargetDepthStencil.isValid(); + bool hasColorTarget = false; + + for (U32 c = 0; c < NumMRTTargets; c++) + { + if (mTargetTex[c].isValid()) + { + hasColorTarget = true; + break; + } + } + + // 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(); - mTarget->attachTexture( GFXTextureTarget::Color0, mTargetTex ); + + for (U32 c = 0; c < NumMRTTargets; c++) + { + mTarget->attachTexture(kSlots[c], mTargetTex[c].isValid() ? mTargetTex[c] : NULL); + } // Set the right depth stencil target. - if ( !mTargetDepthStencil && mTargetTex.getWidthHeight() == GFX->getActiveRenderTarget()->getSize() ) - mTarget->attachTexture( GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil ); + if (!hasDepth && mTargetTex[0].isValid() && mTargetTex[0].getWidthHeight() == oldTarget->getSize()) + mTarget->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); else - mTarget->attachTexture( GFXTextureTarget::DepthStencil, mTargetDepthStencil ); + mTarget->attachTexture(GFXTextureTarget::DepthStencil, mTargetDepthStencil); // Set the render target but not its viewport. We'll do that below. GFX->setActiveRenderTarget( mTarget, false ); - if(mNamedTarget.isRegistered()) + if (mNamedTarget[0].isRegistered()) { - // Always use the name target's viewport, if available. It was set up in _setupTarget(). - GFX->setViewport(mNamedTarget.getViewport()); + GFX->setViewport(mNamedTarget[0].getViewport()); + } + else if (mTargetTex[0].isValid()) + { + GFX->setViewport(RectI(Point2I::Zero, mTargetTex[0].getWidthHeight())); } else if(mTargetViewport == PFXTargetViewport_GFXViewport) { @@ -1509,8 +1564,19 @@ void PostEffect::process( const SceneRenderState *state, } } - if ( clearTarget ) - GFX->clear( GFXClearTarget, mTargetClearColor, 1.f, 0 ); + 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 ) @@ -1545,24 +1611,22 @@ void PostEffect::process( const SceneRenderState *state, // Allow PostEffecVis to hook in. PFXVIS->onPFXProcessed( this ); - if ( mTargetTex || mTargetDepthStencil ) + if (hasColorTarget || hasDepth) { mTarget->resolve(); GFX->popActiveRenderTarget(); } else { - // We wrote to the active back buffer, so release - // the current texture copy held by the manager. - // - // This ensures a new copy is made. PFXMGR->releaseBackBufferTex(); } - // Return and release our target texture. - inOutTex = mTargetTex; - if ( !mNamedTarget.isRegistered() ) - mTargetTex = NULL; + inOutTex = mTargetTex[0]; + for (U32 c = 0; c < NumMRTTargets; c++) + { + if (!mNamedTarget[c].isRegistered()) + mTargetTex[c] = NULL; + } // Restore the transforms before the children // are processed as it screws up the viewport. @@ -1842,19 +1906,22 @@ void PostEffect::_checkRequirements() } // First make sure the target format is supported. - if ( mNamedTarget.isRegistered() ) + for (U32 c = 0; c < NumMRTTargets; c++) { - Vector formats; - formats.push_back( mTargetFormat ); - GFXFormat format = GFX->selectSupportedFormat( &PostFxTargetProfile, - formats, - true, - false, - false ); - - // If we didn't get our format out then its unsupported! - if ( format != mTargetFormat ) - return; + 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. @@ -1981,12 +2048,12 @@ void PostEffect::clearShaderMacros() mUpdateShader = true; } -GFXTextureObject* PostEffect::_getTargetTexture( U32 ) +GFXTextureObject* PostEffect::_getTargetTexture( U32 index) { // A TexGen PostEffect will generate its texture now if it // has not already. if ( mRenderTime == PFXTexGenOnDemand && - ( !mTargetTex || mUpdateShader ) ) + ( !mTargetTex[index] || mUpdateShader ) ) { GFXTexHandle chainTex; process( NULL, chainTex ); @@ -1996,7 +2063,7 @@ GFXTextureObject* PostEffect::_getTargetTexture( U32 ) // amount of non-swappable RTs in use. } - return mTargetTex.getPointer(); + return mTargetTex[index].getPointer(); } DefineEngineMethod( PostEffect, reload, void, (),, diff --git a/Engine/source/postFx/postEffect.h b/Engine/source/postFx/postEffect.h index 101f171dc..801db33c4 100644 --- a/Engine/source/postFx/postEffect.h +++ b/Engine/source/postFx/postEffect.h @@ -67,14 +67,6 @@ class Frustum; class SceneRenderState; class ConditionerFeature; - -/// -GFX_DeclareTextureProfile( PostFxTargetProfile ); - - - - -/// class PostEffect : public SimGroup { typedef SimGroup Parent; @@ -86,389 +78,317 @@ public: enum { NumTextures = 16, + + /// Maximum simultaneous color render target outputs (MRT) + NumMRTTargets = 4, }; protected: + //-------------------------------------------------------------------------- + // Input textures + //-------------------------------------------------------------------------- + DECLARE_IMAGEASSET_ARRAY(PostEffect, Texture, GFXStaticTextureSRGBProfile, NumTextures); - GFXTextureProfile* mTextureProfile[NumTextures]; - GFXTexHandle mTexture[NumTextures]; + GFXTextureProfile* mTextureProfile[NumTextures]; + GFXTexHandle mTexture[NumTextures]; + bool mTexSRGB[NumTextures]; - bool mTexSRGB[NumTextures]; + enum { NormalTextureType = 0, CubemapType, CubemapArrayType } mTextureType[NumTextures]; - enum - { - NormalTextureType = 0, - CubemapType, - CubemapArrayType, - } mTextureType[NumTextures]; - - GFXCubemapHandle mCubemapTextures[NumTextures]; + GFXCubemapHandle mCubemapTextures[NumTextures]; GFXCubemapArrayHandle mCubemapArrayTextures[NumTextures]; - NamedTexTarget mNamedTarget; - NamedTexTarget mNamedTargetDepthStencil; + GFXTextureObject* mActiveTextures[NumTextures]; + NamedTexTarget* mActiveNamedTarget[NumTextures]; + RectI mActiveTextureViewport[NumTextures]; - GFXTextureObject *mActiveTextures[NumTextures]; + //-------------------------------------------------------------------------- + // MRT color output targets + // + // Replaces the original single: mNamedTarget, mTargetName, mTargetTex, + // mTargetFormat, mTargetClearColor, mTargetClear + //-------------------------------------------------------------------------- - NamedTexTarget *mActiveNamedTarget[NumTextures]; + String mTargetName[NumMRTTargets]; ///< Per-slot output name. + GFXFormat mTargetFormat[NumMRTTargets]; ///< Per-slot texture format. + LinearColorF mTargetClearColor[NumMRTTargets]; ///< Per-slot clear colour. + PFXTargetClear mTargetClear[NumMRTTargets]; ///< Per-slot clear policy. - RectI mActiveTextureViewport[NumTextures]; + GFXTexHandle mTargetTex[NumMRTTargets]; ///< Runtime texture per slot. + NamedTexTarget mNamedTarget[NumMRTTargets]; ///< Named target per slot. - GFXStateBlockData *mStateBlockData; - - GFXStateBlockRef mStateBlock; - - String mShaderName; - - GFXShaderRef mShader; - - Vector mShaderMacros; - - GFXShaderConstBufferRef mShaderConsts; - - GFXShaderConstHandle *mRTSizeSC; - GFXShaderConstHandle *mOneOverRTSizeSC; - GFXShaderConstHandle* mRTRatioSC; - - GFXShaderConstHandle *mTexSizeSC[NumTextures]; - GFXShaderConstHandle *mRenderTargetParamsSC[NumTextures]; - GFXShaderConstHandle* mMipCountSC[NumTextures]; - - GFXShaderConstHandle *mViewportOffsetSC; - - GFXShaderConstHandle *mTargetViewportSC; - - GFXShaderConstHandle *mFogDataSC; - GFXShaderConstHandle *mFogColorSC; - GFXShaderConstHandle *mEyePosSC; - GFXShaderConstHandle *mMatWorldToScreenSC; - GFXShaderConstHandle *mMatScreenToWorldSC; - GFXShaderConstHandle *mMatPrevScreenToWorldSC; - GFXShaderConstHandle *mNearFarSC; - GFXShaderConstHandle *mInvNearFarSC; - GFXShaderConstHandle *mWorldToScreenScaleSC; - GFXShaderConstHandle *mProjectionOffsetSC; - GFXShaderConstHandle *mWaterColorSC; - GFXShaderConstHandle *mWaterFogDataSC; - GFXShaderConstHandle *mAmbientColorSC; - GFXShaderConstHandle *mWaterFogPlaneSC; - GFXShaderConstHandle *mWaterDepthGradMaxSC; - GFXShaderConstHandle *mScreenSunPosSC; - GFXShaderConstHandle *mLightDirectionSC; - GFXShaderConstHandle *mCameraForwardSC; - GFXShaderConstHandle *mAccumTimeSC; - GFXShaderConstHandle* mDampnessSC; - GFXShaderConstHandle *mDeltaTimeSC; - GFXShaderConstHandle *mInvCameraMatSC; - GFXShaderConstHandle *mMatCameraToWorldSC; - GFXShaderConstHandle *mInvCameraTransSC; - GFXShaderConstHandle *mMatCameraToScreenSC; - GFXShaderConstHandle *mMatScreenToCameraSC; - - GFXShaderConstHandle* mIsCapturingSC; - - bool mAllowReflectPass; - - /// If true update the shader. - bool mUpdateShader; + // These remain single values — shared across ALL active MRT slots. + Point2F mTargetScale; ///< Relative size vs current GFX render target. + Point2I mTargetSize; ///< Absolute size override; (0,0) = use mTargetScale. + PFXTargetViewport mTargetViewport; ///< How the viewport rect is derived. + /// Single GFXTextureTarget — ALL active MRT slots attach to this one object. GFXTextureTargetRef mTarget; - String mTargetName; - GFXTexHandle mTargetTex; S32 mMipCap; - String mTargetDepthStencilName; - GFXTexHandle mTargetDepthStencil; + //-------------------------------------------------------------------------- + // Depth stencil + //-------------------------------------------------------------------------- - /// If mTargetSize is zero then this scale is - /// used to make a relative texture size to the - /// active render target. - Point2F mTargetScale; + String mTargetDepthStencilName; + GFXTexHandle mTargetDepthStencil; + NamedTexTarget mNamedTargetDepthStencil; - /// If non-zero this is used as the absolute - /// texture target size. - /// @see mTargetScale - Point2I mTargetSize; + //-------------------------------------------------------------------------- + // State block / shader + //-------------------------------------------------------------------------- - GFXFormat mTargetFormat; + GFXStateBlockData* mStateBlockData; + GFXStateBlockRef mStateBlock; + String mShaderName; + GFXShaderRef mShader; + Vector mShaderMacros; + GFXShaderConstBufferRef mShaderConsts; - /// The color to prefill the named target when - /// first created by the effect. - LinearColorF mTargetClearColor; + //-------------------------------------------------------------------------- + // Auto shader constant handles + //-------------------------------------------------------------------------- + GFXShaderConstHandle* mRTSizeSC; + GFXShaderConstHandle* mOneOverRTSizeSC; + GFXShaderConstHandle* mRTRatioSC; + GFXShaderConstHandle* mTexSizeSC[NumTextures]; + GFXShaderConstHandle* mRenderTargetParamsSC[NumTextures]; + GFXShaderConstHandle* mMipCountSC[NumTextures]; + GFXShaderConstHandle* mViewportOffsetSC; + GFXShaderConstHandle* mTargetViewportSC; + GFXShaderConstHandle* mFogDataSC; + GFXShaderConstHandle* mFogColorSC; + GFXShaderConstHandle* mEyePosSC; + GFXShaderConstHandle* mMatWorldToScreenSC; + GFXShaderConstHandle* mMatScreenToWorldSC; + GFXShaderConstHandle* mMatPrevScreenToWorldSC; + GFXShaderConstHandle* mNearFarSC; + GFXShaderConstHandle* mInvNearFarSC; + GFXShaderConstHandle* mWorldToScreenScaleSC; + GFXShaderConstHandle* mProjectionOffsetSC; + GFXShaderConstHandle* mWaterColorSC; + GFXShaderConstHandle* mWaterFogDataSC; + GFXShaderConstHandle* mAmbientColorSC; + GFXShaderConstHandle* mWaterFogPlaneSC; + GFXShaderConstHandle* mWaterDepthGradMaxSC; + GFXShaderConstHandle* mScreenSunPosSC; + GFXShaderConstHandle* mLightDirectionSC; + GFXShaderConstHandle* mCameraForwardSC; + GFXShaderConstHandle* mAccumTimeSC; + GFXShaderConstHandle* mDampnessSC; + GFXShaderConstHandle* mDeltaTimeSC; + GFXShaderConstHandle* mInvCameraMatSC; + GFXShaderConstHandle* mMatCameraToWorldSC; + GFXShaderConstHandle* mInvCameraTransSC; + GFXShaderConstHandle* mMatCameraToScreenSC; + GFXShaderConstHandle* mMatScreenToCameraSC; + GFXShaderConstHandle* mIsCapturingSC; + + //-------------------------------------------------------------------------- + // Render scheduling + //-------------------------------------------------------------------------- + + bool mAllowReflectPass; + bool mUpdateShader; PFXRenderTime mRenderTime; - PFXTargetClear mTargetClear; - PFXTargetViewport mTargetViewport; + PFXTargetClear mTargetClear_unused; // kept for binary compat if needed — see arrays above + String mRenderBin; + S16 mRenderPriority; + bool mIsValid; + bool mEnabled; + bool mSkip; + bool mPreProcessed; + bool mOneFrameOnly; + bool mOnThisFrame; + U32 mShaderReloadKey; - String mRenderBin; - - S16 mRenderPriority; - - /// This is true if the effect has been succesfully - /// initialized and all requirements are met for use. - bool mIsValid; - - /// True if the effect has been enabled by the manager. - bool mEnabled; - - /// Skip processing of this PostEffect and its children even if its parent is enabled. - /// Parent and sibling PostEffects in the chain are still processed. - /// This is intended for debugging purposes. - bool mSkip; - bool mPreProcessed; - - bool mOneFrameOnly; - bool mOnThisFrame; - - U32 mShaderReloadKey; + //-------------------------------------------------------------------------- + // EffectConst + //-------------------------------------------------------------------------- class EffectConst { public: - - EffectConst( const String &name, const String &val ) - : mName( name ), - mHandle( NULL ), - mDirty( true ) - { - set( val ); - } - - EffectConst(const String &name, const F32 &val) - : mName(name), - mHandle(NULL), - mDirty(true) - { + EffectConst(const String& name, const String& val) + : mName(name), mHandle(NULL), mDirty(true) { + set(val); + } + EffectConst(const String& name, const F32& val) + : mName(name), mHandle(NULL), mDirty(true) { set(val); } - EffectConst(const String& name, const int& val) - : mName(name), - mHandle(NULL), - mDirty(true) - { + : mName(name), mHandle(NULL), mDirty(true) { + set(val); + } + EffectConst(const String& name, const Point4F& val) + : mName(name), mHandle(NULL), mDirty(true) { + set(val); + } + EffectConst(const String& name, const MatrixF& val) + : mName(name), mHandle(NULL), mDirty(true) { + set(val); + } + EffectConst(const String& name, const Vector& val) + : mName(name), mHandle(NULL), mDirty(true) { + set(val); + } + EffectConst(const String& name, const Vector& val) + : mName(name), mHandle(NULL), mDirty(true) { set(val); } - EffectConst(const String &name, const Point4F &val) - : mName(name), - mHandle(NULL), - mDirty(true) - { - set(val); - } - - EffectConst(const String &name, const MatrixF &val) - : mName(name), - mHandle(NULL), - mDirty(true) - { - set(val); - } - - EffectConst(const String &name, const Vector &val) - : mName(name), - mHandle(NULL), - mDirty(true) - { - set(val); - } - - EffectConst(const String &name, const Vector &val) - : mName(name), - mHandle(NULL), - mDirty(true) - { - set(val); - } - - void set( const String &newVal ); - void set(const F32 &newVal); + void set(const String& newVal); + void set(const F32& newVal); void set(const int& newVal); - void set(const Point4F &newVal); - void set(const MatrixF &newVal); - void set(const Vector &newVal); - void set(const Vector &newVal); - - void setToBuffer( GFXShaderConstBufferRef buff ); - - String mName; - - GFXShaderConstHandle *mHandle; - - String mStringVal; - - S32 mIntVal; - F32 mFloatVal; - Point4F mPointVal; - MatrixF mMatrixVal; + void set(const Point4F& newVal); + void set(const MatrixF& newVal); + void set(const Vector& newVal); + void set(const Vector& newVal); + void setToBuffer(GFXShaderConstBufferRef buff); + String mName; + GFXShaderConstHandle* mHandle; + String mStringVal; + S32 mIntVal; + F32 mFloatVal; + Point4F mPointVal; + MatrixF mMatrixVal; Vector mPointArrayVal; Vector mMatrixArrayVal; - enum - { - StringType, - IntType, - FloatType, - PointType, - MatrixType, - PointArrayType, - MatrixArrayType + enum { + StringType, IntType, FloatType, PointType, + MatrixType, PointArrayType, MatrixArrayType } mValueType; bool mDirty; }; - typedef HashTable EffectConstTable; - + typedef HashTable EffectConstTable; EffectConstTable mEffectConsts; - /// - virtual void _updateScreenGeometry( const Frustum &frustum, - GFXVertexBufferHandle *outVB ); + //-------------------------------------------------------------------------- + // Internal methods + //-------------------------------------------------------------------------- - /// - virtual void _setupStateBlock( const SceneRenderState *state ); - - /// - virtual void _setupConstants( const SceneRenderState *state ); - - /// + virtual void _updateScreenGeometry(const Frustum& frustum, GFXVertexBufferHandle* outVB); + virtual void _setupStateBlock(const SceneRenderState* state); + virtual void _setupConstants(const SceneRenderState* state); virtual void _setupTransforms(); + virtual void _setupTarget(const SceneRenderState* state, bool* outClearTarget); + virtual void _setupTexture(U32 slot, GFXTexHandle& inputTex, const RectI* inTexViewport); + virtual void _setupCubemapTexture(U32 stage, GFXCubemapHandle& inputTex); + virtual void _setupCubemapArrayTexture(U32 slot, GFXCubemapArrayHandle& inputTex); - /// - virtual void _setupTarget( const SceneRenderState *state, bool *outClearTarget ); + static bool _setIsEnabled(void* object, const char* index, const char* data); - /// - virtual void _setupTexture( U32 slot, GFXTexHandle &inputTex, const RectI *inTexViewport ); - virtual void _setupCubemapTexture(U32 stage, GFXCubemapHandle &inputTex); - virtual void _setupCubemapArrayTexture(U32 slot, GFXCubemapArrayHandle &inputTex); - - - /// Protected set method for toggling the enabled state. - static bool _setIsEnabled( void *object, const char *index, const char *data ); - - /// Called from the light manager activate signal. - /// @see LightManager::addActivateCallback - void _onLMActivate( const char*, bool activate ) + void _onLMActivate(const char*, bool activate) { - if ( activate ) - mUpdateShader = true; + if (activate) mUpdateShader = true; } - /// We handle texture events to release named rendered targets. - /// @see GFXTextureManager::addEventDelegate - void _onTextureEvent( GFXTexCallbackCode code ) + void _onTextureEvent(GFXTexCallbackCode code) { - if ( code == GFXZombify && (mNamedTarget.isRegistered() || mNamedTargetDepthStencil.isRegistered()) ) - _cleanTargets(); + if (code != GFXZombify) return; + bool anyNamed = mNamedTargetDepthStencil.isRegistered(); + for (U32 c = 0; c < NumMRTTargets && !anyNamed; c++) + anyNamed = mNamedTarget[c].isRegistered(); + if (anyNamed) _cleanTargets(); } - /// void _updateConditioners(); - - /// - void _cleanTargets( bool recurse = false ); - - /// + void _cleanTargets(bool recurse = false); void _checkRequirements(); + GFXTextureObject* _getTargetTexture(U32 index); - /// - GFXTextureObject* _getTargetTexture( U32 index ); + /// Map "$outTex" / "$outTex0".."$outTex3" to a slot index [0..NumMRTTargets-1]. + /// Returns -1 if the name is not an $outTexN pattern. + S32 _outTexSlotFromName(const String& name) + { + if (name.compare("$outTex", 0, String::NoCase) == 0) return 0; // backwards compat + if (name.compare("$outTex0", 0, String::NoCase) == 0) return 0; + if (name.compare("$outTex1", 0, String::NoCase) == 0) return 1; + if (name.compare("$outTex2", 0, String::NoCase) == 0) return 2; + if (name.compare("$outTex3", 0, String::NoCase) == 0) return 3; + return -1; + } + + S32 _inTexSlotFromName(const String& name) + { + if (name.compare("$inTex", 0, String::NoCase) == 0) return 0; // backwards compat + if (name.compare("$inTex0", 0, String::NoCase) == 0) return 0; + if (name.compare("$inTex1", 0, String::NoCase) == 0) return 1; + if (name.compare("$inTex2", 0, String::NoCase) == 0) return 2; + if (name.compare("$inTex3", 0, String::NoCase) == 0) return 3; + return -1; + } public: - /// Constructor. PostEffect(); - - /// Destructor. virtual ~PostEffect(); DECLARE_CONOBJECT(PostEffect); - // SimObject bool onAdd() override; void onRemove() override; static void initPersistFields(); - /// @name Callbacks - /// @{ + DECLARE_CALLBACK(void, onAdd, ()); + DECLARE_CALLBACK(void, preProcess, ()); + DECLARE_CALLBACK(void, setShaderConsts, ()); + DECLARE_CALLBACK(bool, onEnabled, ()); + DECLARE_CALLBACK(void, onDisabled, ()); - DECLARE_CALLBACK( void, onAdd, () ); - DECLARE_CALLBACK( void, preProcess, () ); - DECLARE_CALLBACK( void, setShaderConsts, () ); - DECLARE_CALLBACK( bool, onEnabled, () ); - DECLARE_CALLBACK( void, onDisabled, () ); + virtual void process(const SceneRenderState* state, + GFXTexHandle& inOutTex, + const RectI* inTexViewport = NULL); - /// @} - - virtual void process( const SceneRenderState *state, - GFXTexHandle &inOutTex, - const RectI *inTexViewport = NULL ); - - /// void reload(); - - /// void enable(); - - /// void disable(); - /// Dump the shader disassembly to a temporary text file. - /// Returns true and sets outFilename to the file if successful. - bool dumpShaderDisassembly( String &outFilename ) const; - - /// Returns the SimSet which contains all PostEffects. + bool dumpShaderDisassembly(String& outFilename) const; SimSet* getSet() const; - /// bool isEnabled() const { return mEnabled; } - - /// Is set to skip rendering. bool isSkipped() const { return mSkip; } - - /// Set the effect to skip rendering. - void setSkip( bool skip ) { mSkip = skip; } + void setSkip(bool s) { mSkip = s; } PFXRenderTime getRenderTime() const { return mRenderTime; } + const String& getRenderBin() const { return mRenderBin; } + F32 getPriority() const { return mRenderPriority; } - const String& getRenderBin() const { return mRenderBin; } - - F32 getPriority() const { return mRenderPriority; } - - void setTexture( U32 index, const String &filePath ); + void setTexture(U32 index, const String& filePath); void setTexture(U32 index, const GFXTexHandle& texHandle); - void setCubemapTexture(U32 index, const GFXCubemapHandle &cubemapHandle); - void setCubemapArrayTexture(U32 index, const GFXCubemapArrayHandle &cubemapArrayHandle); + void setCubemapTexture(U32 index, const GFXCubemapHandle& handle); + void setCubemapArrayTexture(U32 index, const GFXCubemapArrayHandle& handle); - void setShaderMacro( const String &name, const String &value = String::EmptyString ); - bool removeShaderMacro( const String &name ); + void setShaderMacro(const String& name, const String& value = String::EmptyString); + bool removeShaderMacro(const String& name); void clearShaderMacros(); - /// - void setShaderConst( const String &name, const String &val ); - void setShaderConst(const String &name, const F32 &val); + void setShaderConst(const String& name, const String& val); + void setShaderConst(const String& name, const F32& val); void setShaderConst(const String& name, const int& val); - void setShaderConst(const String &name, const Point4F &val); - void setShaderConst(const String &name, const MatrixF &val); - void setShaderConst(const String &name, const Vector &val); - void setShaderConst(const String &name, const Vector &val); + void setShaderConst(const String& name, const Point4F& val); + void setShaderConst(const String& name, const MatrixF& val); + void setShaderConst(const String& name, const Vector& val); + void setShaderConst(const String& name, const Vector& val); - void setOnThisFrame( bool enabled ) { mOnThisFrame = enabled; } + void setOnThisFrame(bool e) { mOnThisFrame = e; } bool isOnThisFrame() { return mOnThisFrame; } - void setOneFrameOnly( bool enabled ) { mOneFrameOnly = enabled; } - bool isOneFrameOnly() { return mOneFrameOnly; } + void setOneFrameOnly(bool e) { mOneFrameOnly = e; } + bool isOneFrameOnly() { return mOneFrameOnly; } - F32 getAspectRatio() const; - - GFXShaderRef getShader() { return mShader; } + F32 getAspectRatio() const; + GFXShaderRef getShader() { return mShader; } Vector* getShaderMacros() { return &mShaderMacros; } GFXShaderConstBufferRef getShaderConstBuffer() { return mShaderConsts; } - enum PostEffectRequirements { diff --git a/Engine/source/postFx/postEffectCommon.h b/Engine/source/postFx/postEffectCommon.h index d99800295..d3a031337 100644 --- a/Engine/source/postFx/postEffectCommon.h +++ b/Engine/source/postFx/postEffectCommon.h @@ -99,11 +99,10 @@ struct PFXFrameState }; /// +GFX_DeclareTextureProfile( PostFxTargetProfile) ; GFX_DeclareTextureProfile( PostFxTextureProfile ); GFX_DeclareTextureProfile( PostFxTextureSRGBProfile ); - GFX_DeclareTextureProfile( VRTextureProfile ); - GFX_DeclareTextureProfile( VRDepthProfile ); /// @@ -119,4 +118,4 @@ GFXDeclareVertexFormat( PFXVertex ) Point3F wsEyeRay; }; -#endif // _POSTEFFECTCOMMON_H_ \ No newline at end of file +#endif // _POSTEFFECTCOMMON_H_ diff --git a/Engine/source/postFx/postEffectVis.cpp b/Engine/source/postFx/postEffectVis.cpp index 4a7442b72..471727a3b 100644 --- a/Engine/source/postFx/postEffectVis.cpp +++ b/Engine/source/postFx/postEffectVis.cpp @@ -228,8 +228,8 @@ void PostEffectVis::onPFXProcessed( PostEffect *pfx ) GFXTextureObject *tex; - if ( pfx->mTargetTex ) - tex = pfx->mTargetTex; + if ( pfx->mTargetTex[0]) + tex = pfx->mTargetTex[0]; else tex = PFXMGR->getBackBufferTex(); @@ -245,7 +245,7 @@ void PostEffectVis::onPFXProcessed( PostEffect *pfx ) if ( tex ) - dSprintf( caption, 256, "%s[%i] target - %s [ %ix%i ]", name, pfx->getId(), pfx->mTargetName.c_str(), tex->getWidth(), tex->getHeight() ); + dSprintf( caption, 256, "%s[%i] target - %s [ %ix%i ]", name, pfx->getId(), pfx->mTargetName[0].c_str(), tex->getWidth(), tex->getHeight()); else dSprintf( caption, 256, "%s[%i] target", name, pfx->getId() ); From d33edb051c1f11f12552b2539e78a33e073f7744 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 19 Apr 2026 17:36:54 +0100 Subject: [PATCH 2/4] pass on the multiple target results The changes to pass on the results for each target to the children of that posteffect --- .../lighting/common/projectedShadow.cpp | 2 +- Engine/source/postFx/postEffect.cpp | 63 ++++++++++--------- Engine/source/postFx/postEffect.h | 5 +- Engine/source/postFx/postEffectManager.cpp | 4 +- .../renderInstance/renderFormatChanger.cpp | 4 +- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/Engine/source/lighting/common/projectedShadow.cpp b/Engine/source/lighting/common/projectedShadow.cpp index 030c37fd7..867f5c1b8 100644 --- a/Engine/source/lighting/common/projectedShadow.cpp +++ b/Engine/source/lighting/common/projectedShadow.cpp @@ -551,7 +551,7 @@ void ProjectedShadow::_renderToTexture( F32 camDist, const TSRenderState &rdata } if ( smShadowFilter ) - smShadowFilter->process( NULL, mShadowTexture ); + smShadowFilter->process( NULL, &mShadowTexture ); } // Restore frustum diff --git a/Engine/source/postFx/postEffect.cpp b/Engine/source/postFx/postEffect.cpp index e17509b02..7a4ec3353 100644 --- a/Engine/source/postFx/postEffect.cpp +++ b/Engine/source/postFx/postEffect.cpp @@ -1169,7 +1169,10 @@ void PostEffect::_setupConstants( const SceneRenderState *state ) } } -void PostEffect::_setupTexture( U32 stage, GFXTexHandle &inputTex, const RectI *inTexViewport ) +void PostEffect::_setupTexture(U32 stage, + GFXTexHandle* inputTex, + U32 inputTexCount, + const RectI* inTexViewport) { const String &texFilename = mTextureAsset[stage].notNull() ? mTextureAsset[stage]->getImageFile() : ""; @@ -1178,17 +1181,17 @@ void PostEffect::_setupTexture( U32 stage, GFXTexHandle &inputTex, const RectI * RectI viewport = GFX->getViewport(); - if ( _inTexSlotFromName(texFilename) >= 0 ) + S32 inSlot = _inTexSlotFromName(texFilename); + if (inSlot >= 0) { - theTex = inputTex; + if ((U32)inSlot < inputTexCount) + { + theTex = inputTex[inSlot]; - if ( inTexViewport ) - { - viewport = *inTexViewport; - } - else if ( theTex ) - { - viewport.set( 0, 0, theTex->getWidth(), theTex->getHeight() ); + 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 ) @@ -1432,9 +1435,10 @@ void PostEffect::_cleanTargets( bool recurse ) } } -void PostEffect::process( const SceneRenderState *state, - GFXTexHandle& inOutTex, - const RectI *inTexViewport ) +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 ) @@ -1470,11 +1474,16 @@ void PostEffect::process( const SceneRenderState *state, } 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, inOutTex, inTexViewport); + _setupTexture(i, chain, texCount, inTexViewport); else if (mTextureType[i] == CubemapType) _setupCubemapTexture(i, mCubemapTextures[i]); else if (mTextureType[i] == CubemapArrayType) @@ -1490,12 +1499,13 @@ void PostEffect::process( const SceneRenderState *state, const bool hasDepth = mTargetDepthStencil.isValid(); bool hasColorTarget = false; + U32 curTargetCount = 0; for (U32 c = 0; c < NumMRTTargets; c++) { if (mTargetTex[c].isValid()) { hasColorTarget = true; - break; + curTargetCount++; } } @@ -1621,9 +1631,11 @@ void PostEffect::process( const SceneRenderState *state, PFXMGR->releaseBackBufferTex(); } - inOutTex = mTargetTex[0]; for (U32 c = 0; c < NumMRTTargets; c++) { + if (c < texCount) + chain[c] = mTargetTex[c]; + if (!mNamedTarget[c].isRegistered()) mTargetTex[c] = NULL; } @@ -1632,12 +1644,13 @@ void PostEffect::process( const SceneRenderState *state, // 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, inOutTex ); + effect->process( state, chain, nextTexCount); } if ( mOneFrameOnly ) @@ -2048,19 +2061,13 @@ void PostEffect::clearShaderMacros() mUpdateShader = true; } -GFXTextureObject* PostEffect::_getTargetTexture( U32 index) +GFXTextureObject* PostEffect::_getTargetTexture(U32 index) { - // A TexGen PostEffect will generate its texture now if it - // has not already. - if ( mRenderTime == PFXTexGenOnDemand && - ( !mTargetTex[index] || mUpdateShader ) ) + if (mRenderTime == PFXTexGenOnDemand + && (!mTargetTex[index] || mUpdateShader)) { - GFXTexHandle chainTex; - process( NULL, chainTex ); - - // TODO: We should add a conditional copy - // to a non-RT texture here to reduce the - // amount of non-swappable RTs in use. + GFXTexHandle chainTex[NumMRTTargets]; // zero-initialised by GFXTexHandle ctor + process(NULL, chainTex, NumMRTTargets); } return mTargetTex[index].getPointer(); diff --git a/Engine/source/postFx/postEffect.h b/Engine/source/postFx/postEffect.h index 801db33c4..7d7914f22 100644 --- a/Engine/source/postFx/postEffect.h +++ b/Engine/source/postFx/postEffect.h @@ -280,7 +280,7 @@ protected: virtual void _setupConstants(const SceneRenderState* state); virtual void _setupTransforms(); virtual void _setupTarget(const SceneRenderState* state, bool* outClearTarget); - virtual void _setupTexture(U32 slot, GFXTexHandle& inputTex, const RectI* inTexViewport); + virtual void _setupTexture(U32 stage, GFXTexHandle* inputTex, U32 inputTexCount, const RectI* inTexViewport); virtual void _setupCubemapTexture(U32 stage, GFXCubemapHandle& inputTex); virtual void _setupCubemapArrayTexture(U32 slot, GFXCubemapArrayHandle& inputTex); @@ -345,7 +345,8 @@ public: DECLARE_CALLBACK(void, onDisabled, ()); virtual void process(const SceneRenderState* state, - GFXTexHandle& inOutTex, + GFXTexHandle* inOutTex, + U32 inOutTexCount = 1, const RectI* inTexViewport = NULL); void reload(); diff --git a/Engine/source/postFx/postEffectManager.cpp b/Engine/source/postFx/postEffectManager.cpp index d65e25e25..ec30732de 100644 --- a/Engine/source/postFx/postEffectManager.cpp +++ b/Engine/source/postFx/postEffectManager.cpp @@ -284,14 +284,14 @@ void PostEffectManager::renderEffects( const SceneRenderState *state, // This is used to pass the output texture // of one effect into the next effect. - GFXTexHandle chainTex; + GFXTexHandle chainTex[PostEffect::NumMRTTargets]; // Process the effects. for ( U32 i = 0; i < effects->size(); i++ ) { PostEffect *effect = (*effects)[i]; AssertFatal( effect != NULL, "Somehow this happened" ); - effect->process( state, chainTex ); + effect->process(state, chainTex, PostEffect::NumMRTTargets); } } diff --git a/Engine/source/renderInstance/renderFormatChanger.cpp b/Engine/source/renderInstance/renderFormatChanger.cpp index 893ef2cf6..374de6de4 100644 --- a/Engine/source/renderInstance/renderFormatChanger.cpp +++ b/Engine/source/renderInstance/renderFormatChanger.cpp @@ -144,7 +144,7 @@ void RenderFormatToken::process(SceneRenderState *state, RenderPassStateBin *cal // Run the PostEffect which copies data into the new target. if ( mCopyPostEffect.isValid() ) - mCopyPostEffect->process( state, curBackBuffer, &mTarget.getViewport() ); + mCopyPostEffect->process( state, &curBackBuffer,1, &mTarget.getViewport() ); } break; @@ -174,7 +174,7 @@ void RenderFormatToken::process(SceneRenderState *state, RenderPassStateBin *cal { // Need to create a texhandle here, since inOutTex gets assigned during process() GFXTexHandle inOutTex = mTargetColorTexture[mTargetChainIdx]; - mResolvePostEffect->process( state, inOutTex, &mTarget.getViewport() ); + mResolvePostEffect->process( state, &inOutTex, 1, &mTarget.getViewport() ); } } break; From dbc4bb37fe7dc2fc4763358b68cff7857bc49ace Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 19 Apr 2026 18:24:35 +0100 Subject: [PATCH 3/4] Update postEffect.cpp this fixes a small issue where dx doesnt like an empty target, if we do target[0] = backbuffer and target[1] = $outTex it was complaining that target[0] was null. When using the backbuffer this would be null because we write to that a different way. --- Engine/source/postFx/postEffect.cpp | 120 ++++++++++++---------------- 1 file changed, 49 insertions(+), 71 deletions(-) diff --git a/Engine/source/postFx/postEffect.cpp b/Engine/source/postFx/postEffect.cpp index 7a4ec3353..9b2db6808 100644 --- a/Engine/source/postFx/postEffect.cpp +++ b/Engine/source/postFx/postEffect.cpp @@ -1261,39 +1261,26 @@ void PostEffect::_setupTransforms() 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) { - // Size it relative to the texture of the first stage or - // if NULL then use the current target. - - Point2I targetSize; - - // If we have an absolute target size then use that. - if (!mTargetSize.isZero()) - targetSize = mTargetSize; - - // Else generate a relative size using the target scale. - 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); - } - - // Make sure its at least 1x1. - targetSize.setMax(Point2I::One); - if (mNamedTarget[c].isRegistered() || !mTargetTex[c] || mTargetTex[c].getWidthHeight() != targetSize) @@ -1311,24 +1298,22 @@ void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarg 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)); + 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()) + 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)); + 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 { - // PFXTargetViewport_TargetSize mNamedTarget[c].setViewport(RectI(0, 0, targetSize.x, targetSize.y)); } } @@ -1337,39 +1322,30 @@ void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarg mTargetTex[c] = NULL; } - // Do we have a named depthStencil target? + 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()) { - // Size it relative to the texture of the first stage or - // if NULL then use the current target. - Point2I targetSize; - - // If we have an absolute target size then use that. - if (!mTargetSize.isZero()) - targetSize = mTargetSize; - - // Else generate a relative size using the target scale. - 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); - } - - // Make sure its at least 1x1. - targetSize.setMax(Point2I::One); - - if (mNamedTargetDepthStencil.isRegistered() && - mTargetDepthStencil.getWidthHeight() != targetSize) + if (mNamedTargetDepthStencil.isRegistered() && mTargetDepthStencil.getWidthHeight() != targetSize) { mTargetDepthStencil.set(targetSize.x, targetSize.y, GFXFormatD24S8, &GFXZTargetProfile, "PostEffect::_setupTarget"); @@ -1384,7 +1360,6 @@ void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarg 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)); @@ -1394,7 +1369,6 @@ void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarg // 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)); @@ -1412,7 +1386,11 @@ void PostEffect::_setupTarget( const SceneRenderState *state, bool *outClearTarg if (mTargetClear[0] == PFXTargetClear_OnDraw) *outClearTarget = true; - if (!mTarget && (mTargetTex || mTargetDepthStencil)) + bool hasAny = mTargetDepthStencil.isValid(); + for (U32 c = 0; c < NumMRTTargets && !hasAny; c++) + hasAny = mTargetTex[c].isValid(); + + if (!mTarget && hasAny) mTarget = GFX->allocRenderToTextureTarget(); } From 7480ac9754b79277551237a4efce2862834921a0 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 19 Apr 2026 19:40:50 +0100 Subject: [PATCH 4/4] Update postEffectVis.cpp PFXVis few fixes of bugs and what not also allow it to update the names for multiple targets, these would probably need to be multiple windows Furthermore we can support up to 16 inputs and 4 colour targets, but the pfxvis is limited to 4 inputs and 1 output --- Engine/source/postFx/postEffectVis.cpp | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Engine/source/postFx/postEffectVis.cpp b/Engine/source/postFx/postEffectVis.cpp index 471727a3b..69bca92ca 100644 --- a/Engine/source/postFx/postEffectVis.cpp +++ b/Engine/source/postFx/postEffectVis.cpp @@ -103,7 +103,7 @@ void PostEffectVis::open( PostEffect *pfx ) // Only allocate window/bitmaps for input textures that are actually used. if ( i > Target ) { - if ( pfx->mTextureAsset[i-1].notNull() && pfx->mTextureAsset[i - 1]->getImageFile() == StringTable->EmptyString()) + if ( pfx->mTextureAsset[i-1].isNull()) { window.window[i] = NULL; window.bmp[i] = NULL; @@ -197,7 +197,7 @@ void PostEffectVis::onStartOfFrame() if ( !itr->bmp[i] || itr->pfx->getRenderTime() == PFXTexGenOnDemand ) continue; - itr->bmp[i]->setBitmap( NULL ); + itr->bmp[i]->setBitmap( ""); _setDefaultCaption( *itr, i ); } } @@ -225,32 +225,32 @@ void PostEffectVis::onPFXProcessed( PostEffect *pfx ) { pBmpCtrl = itr->bmp[Target]; pWinCtrl = itr->window[Target]; + String caption; - GFXTextureObject *tex; + for (U32 c = 0; c < PostEffect::NumMRTTargets; c++) + { + GFXTextureObject* tex; - if ( pfx->mTargetTex[0]) - tex = pfx->mTargetTex[0]; - else - tex = PFXMGR->getBackBufferTex(); + if (pfx->mTargetTex[c]) + tex = pfx->mTargetTex[c]; + else + tex = PFXMGR->getBackBufferTex(); - pBmpCtrl->setBitmapHandle( tex ); + pBmpCtrl->setBitmapHandle(tex); - char caption[256]; - char name[256]; - - if ( pfx->getName() == NULL || dStrlen( pfx->getName() ) == 0 ) - dSprintf( name, 256, "(none)" ); - else - dSprintf( name, 256, "%s", pfx->getName() ); + if (pfx->getName() == NULL || dStrlen(pfx->getName()) == 0) + caption += "(none)"; + else + caption += String::ToString(pfx->getName()); - if ( tex ) - dSprintf( caption, 256, "%s[%i] target - %s [ %ix%i ]", name, pfx->getId(), pfx->mTargetName[0].c_str(), tex->getWidth(), tex->getHeight()); - else - dSprintf( caption, 256, "%s[%i] target", name, pfx->getId() ); - - - pWinCtrl->setDataField( StringTable->insert("text"), NULL, caption ); + if (tex) + caption += String::ToString("[%i] target - %s [ %ix%i ]", pfx->getId(), pfx->mTargetName[c].c_str(), tex->getWidth(), tex->getHeight()); + else + caption += String::ToString("[%i] target", pfx->getId()); + } + + pWinCtrl->setDataField( StringTable->insert("text"), NULL, caption.c_str() ); } for ( U32 i = Input1; i < TexCount; i++ )