tsLastDetail.cpp
Engine/source/ts/tsLastDetail.cpp
Public Functions
AFTER_MODULE_INIT(Sim )
DefineEngineFunction(tsUpdateImposterImages , void , (bool forceUpdate) , (false) , "tsUpdateImposterImages( bool forceupdate )" )
GFXImplementVertexFormat(ImposterState )
Detailed Description
Public Functions
AFTER_MODULE_INIT(Sim )
DefineEngineFunction(tsUpdateImposterImages , void , (bool forceUpdate) , (false) , "tsUpdateImposterImages( bool forceupdate )" )
GFXImplementVertexFormat(ImposterState )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "ts/tsLastDetail.h" 26 27#include "renderInstance/renderPassManager.h" 28#include "ts/tsShapeInstance.h" 29#include "scene/sceneManager.h" 30#include "scene/sceneRenderState.h" 31#include "lighting/lightInfo.h" 32#include "renderInstance/renderImposterMgr.h" 33#include "gfx/gfxTransformSaver.h" 34#include "gfx/bitmap/ddsFile.h" 35#include "gfx/bitmap/imageUtils.h" 36#include "gfx/gfxTextureManager.h" 37#include "math/mRandom.h" 38#include "core/stream/fileStream.h" 39#include "util/imposterCapture.h" 40#include "materials/materialManager.h" 41#include "materials/materialFeatureTypes.h" 42#include "console/consoleTypes.h" 43#include "console/engineAPI.h" 44 45 46GFXImplementVertexFormat( ImposterState ) 47{ 48 addElement( "POSITION", GFXDeclType_Float4 ); 49 50 addElement( "ImposterParams", GFXDeclType_Float2, 0 ); 51 52 addElement( "ImposterUpVec", GFXDeclType_Float3, 1 ); 53 addElement( "ImposterRightVec", GFXDeclType_Float3, 2 ); 54}; 55 56 57Vector<TSLastDetail*> TSLastDetail::smLastDetails; 58 59bool TSLastDetail::smCanShadow = true; 60 61 62AFTER_MODULE_INIT( Sim ) 63{ 64 Con::addVariable( "$pref::imposter::canShadow", TypeBool, &TSLastDetail::smCanShadow, 65 "User preference which toggles shadows from imposters. Defaults to true.\n" 66 "@ingroup Rendering\n" ); 67} 68 69 70TSLastDetail::TSLastDetail( TSShape *shape, 71 const String &cachePath, 72 U32 numEquatorSteps, 73 U32 numPolarSteps, 74 F32 polarAngle, 75 bool includePoles, 76 S32 dl, S32 dim ) 77{ 78 mNumEquatorSteps = getMax( numEquatorSteps, (U32)1 ); 79 mNumPolarSteps = numPolarSteps; 80 mPolarAngle = polarAngle; 81 mIncludePoles = includePoles; 82 mShape = shape; 83 mDl = dl; 84 mDim = getMax( dim, (S32)32 ); 85 86 mRadius = mShape->mRadius; 87 mCenter = mShape->center; 88 89 mCachePath = cachePath; 90 91 mMaterial = NULL; 92 mMatInstance = NULL; 93 94 // Store this in the static list. 95 smLastDetails.push_back( this ); 96} 97 98TSLastDetail::~TSLastDetail() 99{ 100 SAFE_DELETE( mMatInstance ); 101 if ( mMaterial ) 102 mMaterial->deleteObject(); 103 104 // Remove ourselves from the list. 105 Vector<TSLastDetail*>::iterator iter = T3D::find( smLastDetails.begin(), smLastDetails.end(), this ); 106 smLastDetails.erase( iter ); 107} 108 109void TSLastDetail::render( const TSRenderState &rdata, F32 alpha ) 110{ 111 // Early out if we have nothing to render. 112 if ( alpha < 0.01f || 113 !mMatInstance || 114 mMaterial->mImposterUVs.size() == 0 ) 115 return; 116 117 const MatrixF &mat = GFX->getWorldMatrix(); 118 119 // Post a render instance for this imposter... the special 120 // imposter render manager will do the magic! 121 RenderPassManager *renderPass = rdata.getSceneState()->getRenderPass(); 122 123 ImposterRenderInst *ri = renderPass->allocInst<ImposterRenderInst>(); 124 ri->mat = rdata.getSceneState()->getOverrideMaterial( mMatInstance ); 125 126 ri->state.alpha = alpha; 127 128 // Store the up and right vectors of the rotation 129 // and we'll generate the up vector in the shader. 130 // 131 // This is faster than building a quat on the 132 // CPU and then rebuilding the matrix on the GPU. 133 // 134 // NOTE: These vector include scale. 135 // 136 mat.getColumn( 2, &ri->state.upVec ); 137 mat.getColumn( 0, &ri->state.rightVec ); 138 139 // We send the unscaled size and the vertex shader 140 // will use the orientation vectors above to scale it. 141 ri->state.halfSize = mRadius; 142 143 // We use the center of the object bounds for 144 // the center of the billboard quad. 145 mat.mulP( mCenter, &ri->state.center ); 146 147 // We sort by the imposter type first so that RIT_Imposter and s 148 // RIT_ImposterBatches do not get mixed together. 149 // 150 // We then sort by material. 151 // 152 ri->defaultKey = 1; 153 ri->defaultKey2 = ri->mat->getStateHint(); 154 155 renderPass->addInst( ri ); 156} 157 158void TSLastDetail::update( bool forceUpdate ) 159{ 160 // This should never be called on a dedicated server or 161 // anywhere else where we don't have a GFX device! 162 AssertFatal( GFXDevice::devicePresent(), "TSLastDetail::update() - Cannot update without a GFX device!" ); 163 164 // Clear the materialfirst. 165 SAFE_DELETE( mMatInstance ); 166 if ( mMaterial ) 167 { 168 mMaterial->deleteObject(); 169 mMaterial = NULL; 170 } 171 172 // Make sure imposter textures have been flushed (and not just queued for deletion) 173 TEXMGR->cleanupCache(); 174 175 // Get the real path to the source shape for doing modified time 176 // comparisons... this might be different if the DAEs have been 177 // deleted from the install. 178 String shapeFile( mCachePath ); 179 if ( !Platform::isFile( shapeFile ) ) 180 { 181 Torque::Path path(shapeFile); 182 path.setExtension("cached.dts"); 183 shapeFile = path.getFullPath(); 184 if ( !Platform::isFile( shapeFile ) ) 185 { 186 Con::errorf( "TSLastDetail::update - '%s' could not be found!", mCachePath.c_str() ); 187 return; 188 } 189 } 190 191 // Do we need to update the imposter? 192 const String diffuseMapPath = _getDiffuseMapPath(); 193 if ( forceUpdate || 194 Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) <= 0 ) 195 _update(); 196 197 // If the time check fails now then the update must have not worked. 198 if ( Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) < 0 ) 199 { 200 Con::errorf( "TSLastDetail::update - Failed to create imposters for '%s'!", mCachePath.c_str() ); 201 return; 202 } 203 204 // Figure out what our vertex format will be. 205 // 206 // If we're on SM 3.0 we can do multiple vertex streams 207 // and the performance win is big as we send 3x less data 208 // on each imposter instance. 209 // 210 // The problem is SM 2.0 won't do this, so we need to 211 // support fallback to regular single stream imposters. 212 // 213 //mImposterVertDecl.copy( *getGFXVertexFormat<ImposterCorner>() ); 214 //mImposterVertDecl.append( *getGFXVertexFormat<ImposterState>(), 1 ); 215 //mImposterVertDecl.getDecl(); 216 mImposterVertDecl.clear(); 217 mImposterVertDecl.copy( *getGFXVertexFormat<ImposterState>() ); 218 219 // Setup the material for this imposter. 220 mMaterial = MATMGR->allocateAndRegister( String::EmptyString ); 221 mMaterial->mAutoGenerated = true; 222 mMaterial->mDiffuseMapFilename[0] = diffuseMapPath; 223 mMaterial->mNormalMapFilename[0] = _getNormalMapPath(); 224 mMaterial->mImposterLimits.set( (mNumPolarSteps * 2) + 1, mNumEquatorSteps, mPolarAngle, mIncludePoles ); 225 mMaterial->mTranslucent = true; 226 mMaterial->mTranslucentBlendOp = Material::None; 227 mMaterial->mTranslucentZWrite = true; 228 mMaterial->mDoubleSided = true; 229 mMaterial->mAlphaTest = true; 230 mMaterial->mAlphaRef = 84; 231 232 // Create the material instance. 233 FeatureSet features = MATMGR->getDefaultFeatures(); 234 features.addFeature( MFT_ImposterVert ); 235 mMatInstance = mMaterial->createMatInstance(); 236 if ( !mMatInstance->init( features, &mImposterVertDecl ) ) 237 { 238 delete mMatInstance; 239 mMatInstance = NULL; 240 } 241 242 // Get the diffuse texture and from its size and 243 // the imposter dimensions we can generate the UVs. 244 GFXTexHandle diffuseTex( diffuseMapPath, &GFXStaticTextureSRGBProfile, String::EmptyString ); 245 Point2I texSize( diffuseTex->getWidth(), diffuseTex->getHeight() ); 246 247 _validateDim(); 248 249 S32 downscaledDim = mDim >> GFXTextureManager::getTextureDownscalePower(&GFXStaticTextureSRGBProfile); 250 251 // Ok... pack in bitmaps till we run out. 252 Vector<RectF> imposterUVs; 253 for ( S32 y=0; y+downscaledDim <= texSize.y; ) 254 { 255 for ( S32 x=0; x+downscaledDim <= texSize.x; ) 256 { 257 // Store the uv for later lookup. 258 RectF info; 259 info.point.set( (F32)x / (F32)texSize.x, (F32)y / (F32)texSize.y ); 260 info.extent.set( (F32)downscaledDim / (F32)texSize.x, (F32)downscaledDim / (F32)texSize.y ); 261 imposterUVs.push_back( info ); 262 263 x += downscaledDim; 264 } 265 266 y += downscaledDim; 267 } 268 269 AssertFatal( imposterUVs.size() != 0, "hey" ); 270 271 mMaterial->mImposterUVs = imposterUVs; 272} 273 274void TSLastDetail::_validateDim() 275{ 276 // Loop till they fit. 277 S32 newDim = mDim; 278 while ( true ) 279 { 280 S32 maxImposters = ( smMaxTexSize / newDim ) * ( smMaxTexSize / newDim ); 281 S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 ); 282 if ( imposterCount <= maxImposters ) 283 break; 284 285 // There are too many imposters to fit a single 286 // texture, so we fail. These imposters are for 287 // rendering small distant objects. If you need 288 // a really high resolution imposter or many images 289 // around the equator and poles, maybe you need a 290 // custom solution. 291 292 newDim /= 2; 293 } 294 295 if ( newDim != mDim ) 296 { 297 Con::printf( "TSLastDetail::_validateDim - '%s' detail dimensions too big! Reduced from %d to %d.", 298 mCachePath.c_str(), 299 mDim, newDim ); 300 301 mDim = newDim; 302 } 303} 304 305void TSLastDetail::_update() 306{ 307 // We're gonna render... make sure we can. 308 bool sceneBegun = GFX->canCurrentlyRender(); 309 if ( !sceneBegun ) 310 GFX->beginScene(); 311 312 _validateDim(); 313 314 Vector<GBitmap*> bitmaps; 315 Vector<GBitmap*> normalmaps; 316 317 // We need to create our own instance to render with. 318 TSShapeInstance *shape = new TSShapeInstance( mShape, true ); 319 320 // Animate the shape once. 321 shape->animate( mDl ); 322 323 // So we don't have to change it everywhere. 324 const GFXFormat format = GFXFormatR8G8B8A8; 325 326 S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 ); 327 328 // Figure out the optimal texture size. 329 Point2I texSize( smMaxTexSize, smMaxTexSize ); 330 while ( true ) 331 { 332 Point2I halfSize( texSize.x / 2, texSize.y / 2 ); 333 U32 count = ( halfSize.x / mDim ) * ( halfSize.y / mDim ); 334 if ( count < imposterCount ) 335 { 336 // Try half of the height. 337 count = ( texSize.x / mDim ) * ( halfSize.y / mDim ); 338 if ( count >= imposterCount ) 339 texSize.y = halfSize.y; 340 break; 341 } 342 343 texSize = halfSize; 344 } 345 346 GBitmap *imposter = NULL; 347 GBitmap *normalmap = NULL; 348 GBitmap destBmp( texSize.x, texSize.y, true, format ); 349 GBitmap destNormal( texSize.x, texSize.y, true, format ); 350 351 U32 mipLevels = destBmp.getNumMipLevels(); 352 353 ImposterCapture *imposterCap = new ImposterCapture(); 354 355 F32 equatorStepSize = M_2PI_F / (F32)mNumEquatorSteps; 356 357 static const MatrixF topXfm( EulerF( -M_PI_F / 2.0f, 0, 0 ) ); 358 static const MatrixF bottomXfm( EulerF( M_PI_F / 2.0f, 0, 0 ) ); 359 360 MatrixF angMat; 361 362 F32 polarStepSize = 0.0f; 363 if ( mNumPolarSteps > 0 ) 364 polarStepSize = -( 0.5f * M_PI_F - mDegToRad( mPolarAngle ) ) / (F32)mNumPolarSteps; 365 366 PROFILE_START(TSLastDetail_snapshots); 367 368 S32 currDim = mDim; 369 for ( S32 mip = 0; mip < mipLevels; mip++ ) 370 { 371 if ( currDim < 1 ) 372 currDim = 1; 373 374 dMemset( destBmp.getWritableBits(mip), 0, destBmp.getWidth(mip) * destBmp.getHeight(mip) * GFXFormat_getByteSize( format ) ); 375 dMemset( destNormal.getWritableBits(mip), 0, destNormal.getWidth(mip) * destNormal.getHeight(mip) * GFXFormat_getByteSize( format ) ); 376 377 bitmaps.clear(); 378 normalmaps.clear(); 379 380 F32 rotX = 0.0f; 381 if ( mNumPolarSteps > 0 ) 382 rotX = -( mDegToRad( mPolarAngle ) - 0.5f * M_PI_F ); 383 384 // We capture the images in a particular order which must 385 // match the order expected by the imposter renderer. 386 387 imposterCap->begin( shape, mDl, currDim, mRadius, mCenter ); 388 389 for ( U32 j=0; j < (2 * mNumPolarSteps + 1); j++ ) 390 { 391 F32 rotZ = -M_PI_F / 2.0f; 392 393 for ( U32 k=0; k < mNumEquatorSteps; k++ ) 394 { 395 angMat.mul( MatrixF( EulerF( rotX, 0, 0 ) ), 396 MatrixF( EulerF( 0, 0, rotZ ) ) ); 397 398 imposterCap->capture( angMat, &imposter, &normalmap ); 399 400 bitmaps.push_back( imposter ); 401 normalmaps.push_back( normalmap ); 402 403 rotZ += equatorStepSize; 404 } 405 406 rotX += polarStepSize; 407 408 if ( mIncludePoles ) 409 { 410 imposterCap->capture( topXfm, &imposter, &normalmap ); 411 412 bitmaps.push_back(imposter); 413 normalmaps.push_back( normalmap ); 414 415 imposterCap->capture( bottomXfm, &imposter, &normalmap ); 416 417 bitmaps.push_back( imposter ); 418 normalmaps.push_back( normalmap ); 419 } 420 } 421 422 imposterCap->end(); 423 424 Point2I atlasSize( destBmp.getWidth(mip), destBmp.getHeight(mip) ); 425 426 // Ok... pack in bitmaps till we run out. 427 for ( S32 y=0; y+currDim <= atlasSize.y; ) 428 { 429 for ( S32 x=0; x+currDim <= atlasSize.x; ) 430 { 431 // Copy the next bitmap to the dest texture. 432 GBitmap* cell = bitmaps.first(); 433 bitmaps.pop_front(); 434 destBmp.copyRect(cell, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip ); 435 delete cell; 436 437 // Copy the next normal to the dest texture. 438 GBitmap* cellNormalmap = normalmaps.first(); 439 normalmaps.pop_front(); 440 destNormal.copyRect(cellNormalmap, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip ); 441 delete cellNormalmap; 442 443 // Did we finish? 444 if ( bitmaps.empty() ) 445 break; 446 447 x += currDim; 448 } 449 450 // Did we finish? 451 if ( bitmaps.empty() ) 452 break; 453 454 y += currDim; 455 } 456 457 // Next mip... 458 currDim /= 2; 459 } 460 461 PROFILE_END(); // TSLastDetail_snapshots 462 463 delete imposterCap; 464 delete shape; 465 466 467 // Should we dump the images? 468 if ( Con::getBoolVariable( "$TSLastDetail::dumpImposters", false ) ) 469 { 470 String imposterPath = mCachePath + ".imposter.png"; 471 String normalsPath = mCachePath + ".imposter_normals.png"; 472 473 FileStream stream; 474 if ( stream.open( imposterPath, Torque::FS::File::Write ) ) 475 destBmp.writeBitmap( "png", stream ); 476 stream.close(); 477 478 if ( stream.open( normalsPath, Torque::FS::File::Write ) ) 479 destNormal.writeBitmap( "png", stream ); 480 stream.close(); 481 } 482 483 // DEBUG: Some code to force usage of a test image. 484 //GBitmap* tempMap = GBitmap::load( "./forest/data/test1234.png" ); 485 //tempMap->extrudeMipLevels(); 486 //mTexture.set( tempMap, &GFXStaticTextureSRGBProfile, false ); 487 //delete tempMap; 488 489 DDSFile *ddsDest = DDSFile::createDDSFileFromGBitmap( &destBmp ); 490 ImageUtil::ddsCompress( ddsDest, GFXFormatBC2 ); 491 492 DDSFile *ddsNormals = DDSFile::createDDSFileFromGBitmap( &destNormal ); 493 ImageUtil::ddsCompress( ddsNormals, GFXFormatBC3 ); 494 495 // Finally save the imposters to disk. 496 FileStream fs; 497 if ( fs.open( _getDiffuseMapPath(), Torque::FS::File::Write ) ) 498 { 499 ddsDest->write( fs ); 500 fs.close(); 501 } 502 if ( fs.open( _getNormalMapPath(), Torque::FS::File::Write ) ) 503 { 504 ddsNormals->write( fs ); 505 fs.close(); 506 } 507 508 delete ddsDest; 509 delete ddsNormals; 510 511 // If we did a begin then end it now. 512 if ( !sceneBegun ) 513 GFX->endScene(); 514} 515 516void TSLastDetail::deleteImposterCacheTextures() 517{ 518 const String diffuseMap = _getDiffuseMapPath(); 519 if ( diffuseMap.length() ) 520 dFileDelete( diffuseMap ); 521 522 const String normalMap = _getNormalMapPath(); 523 if ( normalMap.length() ) 524 dFileDelete( normalMap ); 525} 526 527void TSLastDetail::updateImposterImages( bool forceUpdate ) 528{ 529 // Can't do it without GFX! 530 if ( !GFXDevice::devicePresent() ) 531 return; 532 533 //D3DPERF_SetMarker( D3DCOLOR_RGBA( 0, 255, 0, 255 ), L"TSLastDetail::makeImposter" ); 534 535 bool sceneBegun = GFX->canCurrentlyRender(); 536 if ( !sceneBegun ) 537 GFX->beginScene(); 538 539 Vector<TSLastDetail*>::iterator iter = smLastDetails.begin(); 540 for ( ; iter != smLastDetails.end(); iter++ ) 541 (*iter)->update( forceUpdate ); 542 543 if ( !sceneBegun ) 544 GFX->endScene(); 545} 546 547DefineEngineFunction( tsUpdateImposterImages, void, (bool forceUpdate), (false), "tsUpdateImposterImages( bool forceupdate )") 548{ 549 TSLastDetail::updateImposterImages(forceUpdate); 550} 551 552 553