mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
353 lines
12 KiB
C++
353 lines
12 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 "scene/reflectionManager.h"
|
|
|
|
#include "platform/profiler.h"
|
|
#include "platform/platformTimer.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "core/tAlgorithm.h"
|
|
#include "math/mMathFn.h"
|
|
#include "math/mathUtils.h"
|
|
#include "T3D/gameBase/gameConnection.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "gui/3d/guiTSControl.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "gfx/gfxDebugEvent.h"
|
|
#include "gfx/gfxStringEnumTranslate.h"
|
|
#include "gfx/screenshot.h"
|
|
#include "core/module.h"
|
|
#include "scene/reflectionMatHook.h"
|
|
#include "console/engineAPI.h"
|
|
|
|
|
|
MODULE_BEGIN( ReflectionManager )
|
|
|
|
MODULE_INIT
|
|
{
|
|
ManagedSingleton< ReflectionManager >::createSingleton();
|
|
ReflectionManager::initConsole();
|
|
}
|
|
|
|
MODULE_SHUTDOWN
|
|
{
|
|
ManagedSingleton< ReflectionManager >::deleteSingleton();
|
|
}
|
|
|
|
MODULE_END;
|
|
|
|
|
|
GFX_ImplementTextureProfile( ReflectRenderTargetProfile,
|
|
GFXTextureProfile::DiffuseMap,
|
|
GFXTextureProfile::PreserveSize | GFXTextureProfile::RenderTarget | GFXTextureProfile::SRGB | GFXTextureProfile::Pooled,
|
|
GFXTextureProfile::NONE );
|
|
|
|
GFX_ImplementTextureProfile( RefractTextureProfile,
|
|
GFXTextureProfile::DiffuseMap,
|
|
GFXTextureProfile::PreserveSize |
|
|
GFXTextureProfile::RenderTarget |
|
|
GFXTextureProfile::SRGB |
|
|
GFXTextureProfile::Pooled,
|
|
GFXTextureProfile::NONE );
|
|
|
|
static S32 QSORT_CALLBACK compareReflectors( const void *a, const void *b )
|
|
{
|
|
const ReflectorBase *A = *((ReflectorBase**)a);
|
|
const ReflectorBase *B = *((ReflectorBase**)b);
|
|
|
|
F32 dif = B->score - A->score;
|
|
return (S32)mFloor( dif );
|
|
}
|
|
|
|
|
|
U32 ReflectionManager::smFrameReflectionMS = 10;
|
|
F32 ReflectionManager::smRefractTexScale = 0.5f;
|
|
|
|
ReflectionManager::ReflectionManager()
|
|
: mReflectFormat( GFXFormatR8G8B8A8_SRGB ),
|
|
mUpdateRefract( true ),
|
|
mLastUpdateMs( 0 )
|
|
{
|
|
mTimer = PlatformTimer::create();
|
|
|
|
GFXDevice::getDeviceEventSignal().notify( this, &ReflectionManager::_handleDeviceEvent );
|
|
}
|
|
|
|
void ReflectionManager::initConsole()
|
|
{
|
|
Con::addVariable( "$pref::Reflect::refractTexScale", TypeF32, &ReflectionManager::smRefractTexScale, "RefractTex has dimensions equal to the active render target scaled in both x and y by this float.\n"
|
|
"@ingroup Rendering");
|
|
Con::addVariable( "$pref::Reflect::frameLimitMS", TypeS32, &ReflectionManager::smFrameReflectionMS, "ReflectionManager tries not to spend more than this amount of time updating reflections per frame.\n"
|
|
"@ingroup Rendering");
|
|
}
|
|
|
|
ReflectionManager::~ReflectionManager()
|
|
{
|
|
SAFE_DELETE( mTimer );
|
|
AssertFatal( mReflectors.size() == 0, "ReflectionManager, some reflectors were left nregistered!" );
|
|
|
|
GFXDevice::getDeviceEventSignal().remove( this, &ReflectionManager::_handleDeviceEvent );
|
|
}
|
|
|
|
void ReflectionManager::registerReflector( ReflectorBase *reflector )
|
|
{
|
|
mReflectors.push_back_unique( reflector );
|
|
}
|
|
|
|
void ReflectionManager::unregisterReflector( ReflectorBase *reflector )
|
|
{
|
|
mReflectors.remove( reflector );
|
|
}
|
|
|
|
void ReflectionManager::update( F32 timeSlice,
|
|
const Point2I &resolution,
|
|
const CameraQuery &query )
|
|
{
|
|
GFXDEBUGEVENT_SCOPE( UpdateReflections, ColorI::WHITE );
|
|
|
|
if ( mReflectors.empty() )
|
|
return;
|
|
|
|
PROFILE_SCOPE( ReflectionManager_Update );
|
|
|
|
// Calculate our target time from the slice.
|
|
U32 targetMs = timeSlice * smFrameReflectionMS;
|
|
|
|
// Setup a culler for testing the
|
|
// visibility of reflectors.
|
|
Frustum culler;
|
|
|
|
// jamesu - normally we just need a frustum which covers the current ports, however for SBS mode
|
|
// we need something which covers both viewports.
|
|
S32 stereoTarget = GFX->getCurrentStereoTarget();
|
|
if (stereoTarget != -1)
|
|
{
|
|
// In this case we're rendering in stereo using a specific eye
|
|
MathUtils::makeFovPortFrustum(&culler, false, query.nearPlane, query.farPlane, query.fovPort[stereoTarget], query.headMatrix);
|
|
}
|
|
else if (GFX->getCurrentRenderStyle() == GFXDevice::RS_StereoSideBySide)
|
|
{
|
|
// Calculate an ideal culling size here, we'll just assume double fov based on the first fovport based on
|
|
// the head position.
|
|
FovPort port = query.fovPort[0];
|
|
F32 upSize = query.nearPlane * port.upTan;
|
|
F32 downSize = query.nearPlane * port.downTan;
|
|
|
|
F32 top = upSize;
|
|
F32 bottom = -downSize;
|
|
|
|
F32 fovInRadians = mAtan2((top - bottom) / 2.0f, query.nearPlane) * 3.0f;
|
|
|
|
culler.set(false,
|
|
fovInRadians,
|
|
(F32)(query.stereoViewports[0].extent.x + query.stereoViewports[1].extent.x) / (F32)query.stereoViewports[0].extent.y,
|
|
query.nearPlane,
|
|
query.farPlane,
|
|
query.headMatrix);
|
|
}
|
|
else
|
|
{
|
|
// Normal culling
|
|
culler.set(false,
|
|
query.fov,
|
|
(F32)resolution.x / (F32)resolution.y,
|
|
query.nearPlane,
|
|
query.farPlane,
|
|
query.cameraMatrix);
|
|
}
|
|
|
|
// Manipulate the frustum for tiled screenshots
|
|
const bool screenShotMode = gScreenShot && gScreenShot->isPending();
|
|
if ( screenShotMode )
|
|
gScreenShot->tileFrustum( culler );
|
|
|
|
// We use the frame time and not real time
|
|
// here as this may be called multiple times
|
|
// within a frame.
|
|
U32 startOfUpdateMs = Platform::getVirtualMilliseconds();
|
|
|
|
// Save this for interested parties.
|
|
mLastUpdateMs = startOfUpdateMs;
|
|
|
|
ReflectParams refparams;
|
|
refparams.query = &query;
|
|
refparams.viewportExtent = resolution;
|
|
refparams.culler = culler;
|
|
refparams.startOfUpdateMs = startOfUpdateMs;
|
|
refparams.eyeId = stereoTarget;
|
|
|
|
// Update the reflection score.
|
|
ReflectorList::iterator reflectorIter = mReflectors.begin();
|
|
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
|
|
(*reflectorIter)->calcScore( refparams );
|
|
|
|
// Sort them by the score.
|
|
dQsort( mReflectors.address(), mReflectors.size(), sizeof(ReflectorBase*), compareReflectors );
|
|
|
|
// Update as many reflections as we can
|
|
// within the target time limit.
|
|
mTimer->getElapsedMs();
|
|
mTimer->reset();
|
|
U32 numUpdated = 0;
|
|
reflectorIter = mReflectors.begin();
|
|
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
|
|
{
|
|
// We're sorted by score... so once we reach
|
|
// a zero score we have nothing more to update.
|
|
if ( (*reflectorIter)->score <= 0.0f && !screenShotMode )
|
|
break;
|
|
|
|
(*reflectorIter)->updateReflection( refparams );
|
|
|
|
if (stereoTarget != 0) // only update MS if we're not rendering the left eye in separate mode
|
|
{
|
|
(*reflectorIter)->lastUpdateMs = startOfUpdateMs;
|
|
}
|
|
|
|
numUpdated++;
|
|
|
|
// If we run out of update time then stop.
|
|
if ( mTimer->getElapsedMs() > targetMs && !screenShotMode && (*reflectorIter)->score < 1000.0f )
|
|
break;
|
|
}
|
|
|
|
// Set metric/debug related script variables...
|
|
|
|
U32 numVisible = 0;
|
|
U32 numOccluded = 0;
|
|
|
|
reflectorIter = mReflectors.begin();
|
|
for ( ; reflectorIter != mReflectors.end(); reflectorIter++ )
|
|
{
|
|
ReflectorBase *pReflector = (*reflectorIter);
|
|
if ( pReflector->isOccluded() )
|
|
numOccluded++;
|
|
else
|
|
numVisible++;
|
|
}
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
U32 numEnabled = mReflectors.size();
|
|
U32 totalElapsed = mTimer->getElapsedMs();
|
|
const GFXTextureProfileStats &stats = ReflectRenderTargetProfile.getStats();
|
|
|
|
F32 mb = ( stats.activeBytes / 1024.0f ) / 1024.0f;
|
|
char temp[256];
|
|
|
|
dSprintf( temp, 256, "%s %d %0.2f\n",
|
|
ReflectRenderTargetProfile.getName().c_str(),
|
|
stats.activeCount,
|
|
mb );
|
|
|
|
Con::setVariable( "$Reflect::textureStats", temp );
|
|
Con::setIntVariable( "$Reflect::renderTargetsAllocated", stats.allocatedTextures );
|
|
Con::setIntVariable( "$Reflect::poolSize", stats.activeCount );
|
|
Con::setIntVariable( "$Reflect::numObjects", numEnabled );
|
|
Con::setIntVariable( "$Reflect::numVisible", numVisible );
|
|
Con::setIntVariable( "$Reflect::numOccluded", numOccluded );
|
|
Con::setIntVariable( "$Reflect::numUpdated", numUpdated );
|
|
Con::setIntVariable( "$Reflect::elapsed", totalElapsed );
|
|
#endif
|
|
}
|
|
|
|
GFXTexHandle ReflectionManager::allocRenderTarget( const Point2I &size )
|
|
{
|
|
return GFXTexHandle( size.x, size.y, mReflectFormat,
|
|
&ReflectRenderTargetProfile,
|
|
avar("%s() - mReflectTex (line %d)", __FUNCTION__, __LINE__) );
|
|
}
|
|
|
|
GFXTextureObject* ReflectionManager::getRefractTex( bool forceUpdate )
|
|
{
|
|
GFXTarget *target = GFX->getActiveRenderTarget();
|
|
GFXFormat targetFormat = target->getFormat();
|
|
const Point2I &targetSize = target->getSize();
|
|
|
|
// D3D11 needs to be the same size as the active target
|
|
U32 desWidth = targetSize.x;
|
|
U32 desHeight = targetSize.y;
|
|
|
|
|
|
if ( mRefractTex.isNull() ||
|
|
mRefractTex->getWidth() != desWidth ||
|
|
mRefractTex->getHeight() != desHeight ||
|
|
mRefractTex->getFormat() != targetFormat )
|
|
{
|
|
mRefractTex.set( desWidth, desHeight, targetFormat, &RefractTextureProfile, "mRefractTex" );
|
|
mUpdateRefract = true;
|
|
}
|
|
|
|
if ( forceUpdate || mUpdateRefract )
|
|
{
|
|
target->resolveTo( mRefractTex );
|
|
mUpdateRefract = false;
|
|
}
|
|
|
|
return mRefractTex;
|
|
}
|
|
|
|
BaseMatInstance* ReflectionManager::getReflectionMaterial( BaseMatInstance *inMat ) const
|
|
{
|
|
// See if we have an existing material hook.
|
|
ReflectionMaterialHook *hook = static_cast<ReflectionMaterialHook*>( inMat->getHook( ReflectionMaterialHook::Type ) );
|
|
if ( !hook )
|
|
{
|
|
// Create a hook and initialize it using the incoming material.
|
|
hook = new ReflectionMaterialHook;
|
|
hook->init( inMat );
|
|
inMat->addHook( hook );
|
|
}
|
|
|
|
return hook->getReflectMat();
|
|
}
|
|
|
|
bool ReflectionManager::_handleDeviceEvent( GFXDevice::GFXDeviceEventType evt )
|
|
{
|
|
switch( evt )
|
|
{
|
|
case GFXDevice::deStartOfFrame:
|
|
case GFXDevice::deStartOfField:
|
|
|
|
mUpdateRefract = true;
|
|
break;
|
|
|
|
case GFXDevice::deDestroy:
|
|
|
|
mRefractTex = NULL;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DefineEngineFunction( setReflectFormat, void, ( GFXFormat format ),,
|
|
"Set the reflection texture format.\n"
|
|
"@ingroup GFX\n" )
|
|
{
|
|
REFLECTMGR->setReflectFormat( format );
|
|
}
|
|
|