913 lines
27 KiB
C++
913 lines
27 KiB
C++
|
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||
|
// Copyright (C) 2010 Winch Gate Property Limited
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as
|
||
|
// published by the Free Software Foundation, either version 3 of the
|
||
|
// License, or (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#include "std3d.h"
|
||
|
|
||
|
#include "nel/3d/shape_bank.h"
|
||
|
#include "nel/3d/mesh_base.h"
|
||
|
#include "nel/misc/file.h"
|
||
|
#include "nel/misc/path.h"
|
||
|
#include "nel/misc/rect.h"
|
||
|
#include "nel/misc/algo.h"
|
||
|
#include "nel/misc/progress_callback.h"
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace NLMISC;
|
||
|
|
||
|
namespace NL3D
|
||
|
{
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::CShapeBank()
|
||
|
{
|
||
|
// Default cache creation
|
||
|
addShapeCache( "default" );
|
||
|
_MaxUploadPerFrame = 16*1024;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::~CShapeBank()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
IShape*CShapeBank::addRef(const string &shapeNameNotLwr)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
// get the shape info (must succeed)
|
||
|
TShapeInfoMap::iterator scfpmIt = ShapePtrToShapeInfo.find( getShapePtrFromShapeName( shapeName ) );
|
||
|
nlassert( scfpmIt != ShapePtrToShapeInfo.end() );
|
||
|
|
||
|
// If The shape is not inserted into a cache, just return it
|
||
|
if( !scfpmIt->second.isAdded )
|
||
|
return getShapePtrFromShapeName( shapeName );
|
||
|
|
||
|
// else If the shape is inserted in a shape cache remove it
|
||
|
scfpmIt->second.isAdded = false;
|
||
|
CShapeCache *pShpCache = scfpmIt->second.pShpCache;
|
||
|
nlassert( pShpCache != NULL );
|
||
|
// Search the shape cache for the shape we want to remove
|
||
|
list<IShape*>::iterator lsIt = pShpCache->Elements.begin();
|
||
|
while(lsIt != pShpCache->Elements.end())
|
||
|
{
|
||
|
const string *sTemp = getShapeNameFromShapePtr(*lsIt);
|
||
|
if( *sTemp == shapeName )
|
||
|
{
|
||
|
// Ok the shape cache contains the shape remove it and return
|
||
|
pShpCache->Elements.erase( lsIt );
|
||
|
return getShapePtrFromShapeName( shapeName );
|
||
|
}
|
||
|
++lsIt;
|
||
|
}
|
||
|
nlassert( false );
|
||
|
return getShapePtrFromShapeName( shapeName );
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::release(IShape* pShp)
|
||
|
{
|
||
|
// Do we have the last smartPtr on the shape ?
|
||
|
const string* str = getShapeNameFromShapePtr( pShp );
|
||
|
|
||
|
if (str == NULL)
|
||
|
{
|
||
|
nlwarning ("Trying to release a mesh that have not be added to the shape bank");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TShapeMap::iterator smIt = ShapeMap.find( *str );
|
||
|
if( smIt != ShapeMap.end() )
|
||
|
{
|
||
|
if( smIt->second.getNbRef() == 1 )
|
||
|
{
|
||
|
// Yes -> add the shape to its shapeCache
|
||
|
CShapeCache *pShpCache = getShapeCachePtrFromShapePtr( pShp );
|
||
|
pShpCache->Elements.push_front( pShp );
|
||
|
|
||
|
TShapeInfoMap::iterator scfpmIt = ShapePtrToShapeInfo.find( pShp );
|
||
|
if( scfpmIt != ShapePtrToShapeInfo.end() )
|
||
|
{
|
||
|
scfpmIt->second.isAdded = true;
|
||
|
}
|
||
|
|
||
|
// check the shape cache
|
||
|
checkShapeCache(getShapeCachePtrFromShapePtr(pShp));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlassert( false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::processWaitingShapes ()
|
||
|
{
|
||
|
uint32 nTotalUploaded = 0;
|
||
|
TWaitingShapesMap::iterator wsmmIt = WaitingShapes.begin();
|
||
|
while( wsmmIt != WaitingShapes.end() )
|
||
|
{
|
||
|
// Backup next iterator
|
||
|
TWaitingShapesMap::iterator wsmmItNext = wsmmIt;
|
||
|
wsmmItNext++;
|
||
|
|
||
|
const string &shapeName = wsmmIt->first;
|
||
|
CWaitingShape &rWS = wsmmIt->second;
|
||
|
IShape *pShp = rWS.ShapePtr; // Take care this value is shared between thread so copy it in a local variable first
|
||
|
|
||
|
switch (rWS.State)
|
||
|
{
|
||
|
case AsyncLoad_Shape: // Check if we can pass to the AsyncLoad_Texture state
|
||
|
if (pShp != NULL)
|
||
|
{
|
||
|
if (pShp == (IShape*)-1)
|
||
|
rWS.State = AsyncLoad_Error;
|
||
|
else
|
||
|
rWS.State = AsyncLoad_Texture;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case AsyncLoad_Texture:
|
||
|
{
|
||
|
// Setup all textures and lightmaps of the shape
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
|
||
|
CMeshBase *pMesh = dynamic_cast<CMeshBase*>(pShp);
|
||
|
if( pMesh != NULL )
|
||
|
{
|
||
|
uint8 j;
|
||
|
uint32 i, CurrentProgress = 0;
|
||
|
uint32 nNbMat = pMesh->getNbMaterial();
|
||
|
|
||
|
for (i = 0; i < nNbMat; ++i)
|
||
|
{
|
||
|
const CMaterial &rMat = pMesh->getMaterial(i);
|
||
|
// Parse all textures from this material and setup
|
||
|
for (j = 0; j < IDRV_MAT_MAXTEXTURES; ++j)
|
||
|
{
|
||
|
if (CurrentProgress >= rWS.UpTextProgress)
|
||
|
{
|
||
|
if (rMat.texturePresent(j))
|
||
|
{
|
||
|
if ((!_pDriver->isTextureExist(*rMat.getTexture(j))) ||
|
||
|
(rWS.UpTextLine > 0) || (rWS.UpTextMipMap > 0))
|
||
|
{
|
||
|
//_pDriver->setupTexture (*rMat.getTexture(j));
|
||
|
|
||
|
if (!processWSUploadTexture (rWS, nTotalUploaded, rMat.getTexture(j)))
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
Must release texture data because of the following scenario (common!)
|
||
|
- An IG with 2 different meshs is async loaded
|
||
|
- the 2 meshs access the same texture "pipo.tga"
|
||
|
- in CAsyncFileManager3D::loadMesh(), if this case arise, the
|
||
|
textures are loaded twice in RAM!!!! (is managed per mesh, but not
|
||
|
thourhg meshs)
|
||
|
- hence the first mesh setup and upload above the texture in VRAM
|
||
|
but the second still have data in RAM!! (because 2nd will
|
||
|
say that isTextureExist() ....
|
||
|
|
||
|
Note: you could say that the cool stuff would be to not load the 2 :)
|
||
|
but it appears that this case arise rarely because of the following statement:
|
||
|
- while the second mesh is async loaded, the first upload in VRAM
|
||
|
so there is good change that the second don't load the texture
|
||
|
(isTextureExist() return true in the thread)
|
||
|
*/
|
||
|
ITexture *tex= rMat.getTexture(j);
|
||
|
if(tex->getReleasable())
|
||
|
tex->release();
|
||
|
}
|
||
|
}
|
||
|
++rWS.UpTextProgress;
|
||
|
}
|
||
|
++CurrentProgress;
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
|
||
|
// Do the same with lightmaps
|
||
|
if (rMat.getShader() == CMaterial::LightMap)
|
||
|
{
|
||
|
uint j = 0; ITexture *pText = rMat.getLightMap (j);
|
||
|
while (pText != NULL)
|
||
|
{
|
||
|
if (CurrentProgress >= rWS.UpTextProgress)
|
||
|
{
|
||
|
if ((!_pDriver->isTextureExist(*pText)) ||
|
||
|
(rWS.UpTextLine > 0) || (rWS.UpTextMipMap > 0))
|
||
|
{
|
||
|
//_pDriver->setupTexture (*pText);
|
||
|
|
||
|
if (!processWSUploadTexture (rWS, nTotalUploaded, pText))
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// see above for explanation
|
||
|
if(pText->getReleasable())
|
||
|
pText->release();
|
||
|
}
|
||
|
|
||
|
++rWS.UpTextProgress;
|
||
|
}
|
||
|
++CurrentProgress;
|
||
|
++j; pText = rMat.getLightMap (j);
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
break;
|
||
|
|
||
|
rWS.State = AsyncLoad_Ready;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case AsyncLoad_Ready:
|
||
|
add (wsmmIt->first, pShp);
|
||
|
rWS.State = AsyncLoad_Delete;
|
||
|
break;
|
||
|
|
||
|
// The delete operation can take several frames to complete but this is not a problem
|
||
|
|
||
|
// For error do the same as delete but let the flag to error if a shape is asked just after
|
||
|
// the error was found
|
||
|
|
||
|
case AsyncLoad_Error:
|
||
|
case AsyncLoad_Delete:
|
||
|
rWS.RefCnt -= 1;
|
||
|
if (rWS.RefCnt == 0)
|
||
|
{
|
||
|
// We have to signal if we are the last
|
||
|
std::set<bool *>::iterator ite = rWS.Signal.begin();
|
||
|
while (ite != rWS.Signal.end())
|
||
|
{
|
||
|
bool *bSignal = *ite;
|
||
|
if (bSignal != NULL)
|
||
|
{
|
||
|
bool bFound = false;
|
||
|
TWaitingShapesMap::iterator wsmmIt2 = WaitingShapes.begin();
|
||
|
while (wsmmIt2 != WaitingShapes.end())
|
||
|
{
|
||
|
const string &shapeName2 = wsmmIt2->first;
|
||
|
if (shapeName2 != shapeName)
|
||
|
{
|
||
|
std::set<bool *>::iterator ite2 = wsmmIt2->second.Signal.begin();
|
||
|
while (ite2 != wsmmIt2->second.Signal.end())
|
||
|
{
|
||
|
if (*ite2 == bSignal)
|
||
|
{
|
||
|
bFound = true;
|
||
|
break;
|
||
|
}
|
||
|
ite2++;
|
||
|
}
|
||
|
if (ite2 != wsmmIt2->second.Signal.end())
|
||
|
break;
|
||
|
}
|
||
|
++wsmmIt2;
|
||
|
}
|
||
|
if (!bFound)
|
||
|
*bSignal = true;
|
||
|
}
|
||
|
|
||
|
// Next iterator
|
||
|
ite++;
|
||
|
}
|
||
|
WaitingShapes.erase (wsmmIt);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
nlstop; // This must never happen
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
wsmmIt = wsmmItNext;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void CShapeBank::setMaxBytesToUpload (uint32 MaxUploadPerFrame)
|
||
|
{
|
||
|
_MaxUploadPerFrame = MaxUploadPerFrame;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
bool CShapeBank::processWSUploadTexture (CWaitingShape &rWS, uint32 &nTotalUploaded, ITexture *pText)
|
||
|
{
|
||
|
CRect zeRect;
|
||
|
uint32 nFace, nWeight = 0, nMipMap;
|
||
|
|
||
|
if ((rWS.UpTextMipMap == 0) && (rWS.UpTextLine == 0))
|
||
|
{
|
||
|
// Create the texture only and do not upload anything
|
||
|
bool isRel = pText->getReleasable ();
|
||
|
pText->setReleasable (false);
|
||
|
bool isAllUploaded = false;
|
||
|
_pDriver->setupTextureEx (*pText, false, isAllUploaded);
|
||
|
pText->setReleasable (isRel);
|
||
|
if (isAllUploaded)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (pText->mipMapOn())
|
||
|
nMipMap = pText->getMipMapCount();
|
||
|
else
|
||
|
nMipMap = 1;
|
||
|
|
||
|
// Upload all mipmaps
|
||
|
for (; rWS.UpTextMipMap < nMipMap;)
|
||
|
{
|
||
|
uint32 nMM = rWS.UpTextMipMap;
|
||
|
// What is left to upload ?
|
||
|
nWeight = pText->getSize (nMM) - rWS.UpTextLine*pText->getWidth(nMM);
|
||
|
// Yoyo: important: in case of DXTC1, must mul before div
|
||
|
nWeight= (nWeight*CBitmap::bitPerPixels[pText->getPixelFormat()]) / 8;
|
||
|
if (pText->isTextureCube())
|
||
|
nWeight *= 6;
|
||
|
|
||
|
// NB: nWeight can be 0 in case of 1x1 in DXTC1. Estimate 1 byte
|
||
|
if(nWeight==0)
|
||
|
nWeight= 1;
|
||
|
|
||
|
// Setup rectangle
|
||
|
if ((nTotalUploaded + nWeight) > _MaxUploadPerFrame)
|
||
|
{
|
||
|
// We cannot upload the whole mipmap -> we have to cut it
|
||
|
uint32 nSizeToUpload = _MaxUploadPerFrame - nTotalUploaded;
|
||
|
uint32 nLineWeight = (pText->getWidth(nMM)*CBitmap::bitPerPixels[pText->getPixelFormat()]) / 8;
|
||
|
// NB: nLineWeight can be 0 in case of 1x1 in DXTC1. Estimate 1 byte (avoid divide by zero)
|
||
|
if(nLineWeight==0)
|
||
|
nLineWeight= 1;
|
||
|
if (pText->isTextureCube())
|
||
|
nLineWeight *= 6;
|
||
|
// compute the number of lines we'll upload
|
||
|
uint32 nNbLineToUpload = nSizeToUpload / nLineWeight;
|
||
|
nNbLineToUpload = nNbLineToUpload / 4;
|
||
|
if (nNbLineToUpload == 0)
|
||
|
nNbLineToUpload = 1;
|
||
|
nNbLineToUpload *= 4; // Upload 4 line by 4 line
|
||
|
// setup the rect
|
||
|
uint32 nNewLine = rWS.UpTextLine + nNbLineToUpload;
|
||
|
if (nNewLine > pText->getHeight(nMM))
|
||
|
nNewLine = pText->getHeight(nMM);
|
||
|
zeRect.set (0, rWS.UpTextLine, pText->getWidth(nMM), nNewLine);
|
||
|
rWS.UpTextLine = nNewLine;
|
||
|
if (rWS.UpTextLine == pText->getHeight(nMM))
|
||
|
{
|
||
|
rWS.UpTextLine = 0;
|
||
|
rWS.UpTextMipMap += 1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We can upload the whole mipmap (or the whole rest of the mipmap)
|
||
|
zeRect.set (0, rWS.UpTextLine, pText->getWidth(nMM), pText->getHeight(nMM));
|
||
|
rWS.UpTextLine = 0;
|
||
|
rWS.UpTextMipMap += 1;
|
||
|
}
|
||
|
|
||
|
// Upload !
|
||
|
if (pText->isTextureCube())
|
||
|
{
|
||
|
for (nFace = 0; nFace < 6; ++nFace)
|
||
|
_pDriver->uploadTextureCube (*pText, zeRect, (uint8)nMM, (uint8)nFace);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_pDriver->uploadTexture (*pText, zeRect, (uint8)nMM);
|
||
|
}
|
||
|
|
||
|
nTotalUploaded += nWeight;
|
||
|
if (nTotalUploaded > _MaxUploadPerFrame)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (pText->getReleasable())
|
||
|
pText->release();
|
||
|
|
||
|
rWS.UpTextMipMap = 0;
|
||
|
rWS.UpTextLine = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::TShapeState CShapeBank::getPresentState (const string &shapeNameNotLwr)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
// Is the shape is found in the shape map so return Present
|
||
|
TShapeMap::iterator smIt = ShapeMap.find (shapeName);
|
||
|
if( smIt != ShapeMap.end() )
|
||
|
return Present;
|
||
|
// Look in the waiting shapes
|
||
|
TWaitingShapesMap::iterator wsmmIt = WaitingShapes.find (shapeName);
|
||
|
if (wsmmIt != WaitingShapes.end())
|
||
|
return wsmmIt->second.State; // AsyncLoad_*
|
||
|
return NotPresent;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
IShape *CShapeBank::getShape (const std::string &shapeNameNotLwr)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
// Is the shape is found in the shape map so return Present
|
||
|
TShapeMap::iterator smIt = ShapeMap.find (shapeName);
|
||
|
if( smIt != ShapeMap.end() )
|
||
|
return smIt->second;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::load (const string &shapeNameNotLwr)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
TShapeMap::iterator smIt = ShapeMap.find(shapeName);
|
||
|
if( smIt == ShapeMap.end() )
|
||
|
{
|
||
|
// If we are loading it asynchronously so we do not have to try to load it in sync mode
|
||
|
TWaitingShapesMap::iterator wsmmIt = WaitingShapes.find (shapeName);
|
||
|
if (wsmmIt != WaitingShapes.end())
|
||
|
return;
|
||
|
|
||
|
CShapeStream mesh;
|
||
|
CIFile meshfile;
|
||
|
if (meshfile.open(CPath::lookup(shapeName, false)))
|
||
|
{
|
||
|
meshfile.serial( mesh );
|
||
|
meshfile.close();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlwarning ("CShapeBank::load() : Can't open file %s", shapeName.c_str());
|
||
|
}
|
||
|
|
||
|
if (mesh.getShapePointer() != NULL)
|
||
|
{
|
||
|
// Add the shape to the map.
|
||
|
add( shapeName, mesh.getShapePointer() );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::loadAsync (const std::string &shapeNameNotLwr, IDriver *pDriver, const CVector &position, bool *bSignal, uint selectedTexture)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
TShapeMap::iterator smIt = ShapeMap.find(shapeName);
|
||
|
if (smIt != ShapeMap.end())
|
||
|
return;
|
||
|
_pDriver = pDriver; // Backup the pointer to the driver for later use
|
||
|
TWaitingShapesMap::iterator wsmmIt = WaitingShapes.find (shapeName);
|
||
|
|
||
|
// First time this shape is loaded ?
|
||
|
bool firstTime = wsmmIt == WaitingShapes.end();
|
||
|
|
||
|
if (firstTime)
|
||
|
wsmmIt = WaitingShapes.insert (TWaitingShapesMap::value_type(shapeName, CWaitingShape())).first;
|
||
|
|
||
|
// Add a reference to it
|
||
|
CWaitingShape &rWS = wsmmIt->second;
|
||
|
|
||
|
// Insert a new signal pointer
|
||
|
rWS.Signal.insert (bSignal);
|
||
|
|
||
|
// Add a signal
|
||
|
rWS.RefCnt += 1;
|
||
|
|
||
|
// Launch an async mesh loader the first time
|
||
|
if (firstTime)
|
||
|
CAsyncFileManager3D::getInstance().loadMesh (shapeName, &(wsmmIt->second.ShapePtr), pDriver, position, selectedTexture);
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::cancelLoadAsync (const std::string &shapeNameNotLwr)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
TWaitingShapesMap::iterator wsmmIt = WaitingShapes.find(shapeName);
|
||
|
if (wsmmIt != WaitingShapes.end())
|
||
|
{
|
||
|
wsmmIt->second.RefCnt -= 1;
|
||
|
if (wsmmIt->second.RefCnt == 0)
|
||
|
{
|
||
|
// nlinfo("unloadasync %s", shapeName);
|
||
|
CAsyncFileManager3D::getInstance().cancelLoadMesh (shapeName);
|
||
|
|
||
|
// If the state is not Delete, or Error, then the mesh has not been added to the map.
|
||
|
if(wsmmIt->second.State!=AsyncLoad_Delete && wsmmIt->second.State!=AsyncLoad_Error)
|
||
|
{
|
||
|
/* but it can still have been loaded:
|
||
|
- just ended in above cancelLoadMesh() because it was the current task
|
||
|
- ended in async far before this cancelLoadAsync() call, but processWaintingShape() still
|
||
|
not called on it
|
||
|
In this case we have to delete this shape, or even cancel texture upload!
|
||
|
NB: don't forget that the load can still be a fail too....
|
||
|
*/
|
||
|
IShape *shape= wsmmIt->second.ShapePtr;
|
||
|
if(shape!=NULL && shape!=(IShape*)-1)
|
||
|
{
|
||
|
// this ptr should not be added to the map
|
||
|
nlassert(ShapePtrToShapeInfo.find(shape)==ShapePtrToShapeInfo.end());
|
||
|
|
||
|
// Before deleting this shape, we must ensure it is not currently uploading texture
|
||
|
if(wsmmIt->second.State==AsyncLoad_Texture)
|
||
|
{
|
||
|
/* if it was uploading a texture, then force him to end, else may have a bug
|
||
|
in this scenario (very improbable, but possible I think):
|
||
|
- a mesh is loaded asynchronously, and reference a texture "pipo.tga"
|
||
|
- mesh load async is ended, and the texture is not found in the driver
|
||
|
=> texture generate()-d too
|
||
|
- mesh state is AsyncLoad_Texture, and begin (but doesn't end) to upload the texture
|
||
|
- another mesh is created syncrhonously using also this texture (thus
|
||
|
found in driver, and so just referencing it, no generate)
|
||
|
- the async mesh is then canceled, while the texture has not end to load!
|
||
|
- the texture is still in memory (the sync mesh still point to it), but with
|
||
|
partialy uploaded data!
|
||
|
*/
|
||
|
// \todo yoyo: should be very rare, and don't know if really happens. must do tests
|
||
|
//forceEndUpLoadTexture(wsmmIt->second);
|
||
|
}
|
||
|
|
||
|
// then delete this shape
|
||
|
delete shape;
|
||
|
wsmmIt->second.ShapePtr= NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// erase this waiting shape
|
||
|
WaitingShapes.erase (wsmmIt); // Delete the waiting shape
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
bool CShapeBank::isShapeWaiting ()
|
||
|
{
|
||
|
if (WaitingShapes.size() == 0)
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::add (const string &shapeNameNotLwr, IShape* pShp)
|
||
|
{
|
||
|
nlassert(pShp);
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
// request a system mem geometry copy?
|
||
|
if(pShp && _ShapeNeedingSystemGeometryCopy.find(shapeName)!=_ShapeNeedingSystemGeometryCopy.end())
|
||
|
{
|
||
|
// make a copy of the geometry, in RAM
|
||
|
pShp->buildSystemGeometry();
|
||
|
}
|
||
|
|
||
|
// Is the shape name already used ?
|
||
|
TShapeMap::iterator smIt = ShapeMap.find( shapeName );
|
||
|
if( smIt == ShapeMap.end() )
|
||
|
{
|
||
|
// No ok so lets add the smart pointer
|
||
|
CSmartPtr<IShape> spShape = pShp;
|
||
|
ShapeMap[shapeName] = spShape;
|
||
|
|
||
|
// create the shape info
|
||
|
CShapeInfo siTemp;
|
||
|
siTemp.sShpName = shapeName;
|
||
|
siTemp.pShpCache = getShapeCachePtrFromShapeName( shapeName );
|
||
|
// Is the shape has a valid shape cache ?
|
||
|
if( siTemp.pShpCache == NULL )
|
||
|
{
|
||
|
// No -> link to default (which do the UpdateShapeInfo)
|
||
|
siTemp.pShpCache = getShapeCachePtrFromShapeCacheName( "default" );
|
||
|
// Add the shape to the default shape cache
|
||
|
ShapePtrToShapeInfo[pShp]= siTemp;
|
||
|
ShapeNameToShapeCacheName[shapeName]= "default";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Yes -> add or replace the shape info
|
||
|
ShapePtrToShapeInfo[pShp] = siTemp;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::addShapeCache(const string &shapeCacheName)
|
||
|
{
|
||
|
TShapeCacheMap::iterator scmIt = ShapeCacheNameToShapeCache.find( shapeCacheName );
|
||
|
if( scmIt == ShapeCacheNameToShapeCache.end() )
|
||
|
{
|
||
|
// Not found so add it
|
||
|
ShapeCacheNameToShapeCache.insert(TShapeCacheMap::value_type(shapeCacheName,CShapeCache()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::removeShapeCache(const std::string &shapeCacheName)
|
||
|
{
|
||
|
if( shapeCacheName == "default" )
|
||
|
return;
|
||
|
|
||
|
// Free the shape cache
|
||
|
CShapeCache *pShpCache = getShapeCachePtrFromShapeCacheName( shapeCacheName );
|
||
|
if( pShpCache == NULL )
|
||
|
return;
|
||
|
pShpCache->MaxSize = 0;
|
||
|
checkShapeCache( pShpCache );
|
||
|
|
||
|
// Remove it
|
||
|
ShapeCacheNameToShapeCache.erase( shapeCacheName );
|
||
|
|
||
|
// All links are redirected to the default cache
|
||
|
TShapeCacheNameMap::iterator scnIt = ShapeNameToShapeCacheName.begin();
|
||
|
while( scnIt != ShapeNameToShapeCacheName.end() )
|
||
|
{
|
||
|
if( scnIt->second == shapeCacheName )
|
||
|
scnIt->second = "default";
|
||
|
++scnIt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::reset()
|
||
|
{
|
||
|
// Parse map ShapeCacheNameToShapeCache to delete all caches
|
||
|
TShapeCacheMap::iterator scmIt = ShapeCacheNameToShapeCache.begin();
|
||
|
while( scmIt != ShapeCacheNameToShapeCache.end() )
|
||
|
{
|
||
|
CShapeCache *pShpCache = getShapeCachePtrFromShapeCacheName( scmIt->first );
|
||
|
if( pShpCache == NULL )
|
||
|
nlstop; // Should never happen
|
||
|
pShpCache->MaxSize = 0;
|
||
|
checkShapeCache( pShpCache );
|
||
|
|
||
|
++scmIt;
|
||
|
}
|
||
|
ShapeNameToShapeCacheName.clear();
|
||
|
ShapeCacheNameToShapeCache.clear();
|
||
|
addShapeCache( "default" );
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::setShapeCacheSize(const string &shapeCacheName, sint32 maxSize)
|
||
|
{
|
||
|
TShapeCacheMap::iterator scmIt = ShapeCacheNameToShapeCache.find( shapeCacheName );
|
||
|
if( scmIt != ShapeCacheNameToShapeCache.end() )
|
||
|
{
|
||
|
scmIt->second.MaxSize = maxSize;
|
||
|
checkShapeCache(getShapeCachePtrFromShapeCacheName(shapeCacheName));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
sint CShapeBank::getShapeCacheFreeSpace(const std::string &shapeCacheName) const
|
||
|
{
|
||
|
TShapeCacheMap::const_iterator scmIt = ShapeCacheNameToShapeCache.find( shapeCacheName );
|
||
|
if( scmIt != ShapeCacheNameToShapeCache.end() )
|
||
|
{
|
||
|
return scmIt->second.MaxSize - (sint)scmIt->second.Elements.size();
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::linkShapeToShapeCache(const string &shapeNameNotLwr, const string &shapeCacheName)
|
||
|
{
|
||
|
string shapeName= toLower(shapeNameNotLwr);
|
||
|
|
||
|
for(;;)
|
||
|
{
|
||
|
// Shape exist?
|
||
|
IShape *shapePtr= getShapePtrFromShapeName(shapeName);
|
||
|
if(shapePtr == NULL)
|
||
|
// No, but still link the shape name to the shapeCache name.
|
||
|
break;
|
||
|
// Is the shape cache exist ?
|
||
|
CShapeCache *shapeCachePtr = getShapeCachePtrFromShapeCacheName( shapeCacheName );
|
||
|
if( shapeCachePtr == NULL )
|
||
|
// abort, since cannot correctly link to a valid shapeCache
|
||
|
return;
|
||
|
|
||
|
// Try to set to the same shape Cache as before?
|
||
|
CShapeInfo &shapeInfo= ShapePtrToShapeInfo[shapePtr];
|
||
|
if( shapeCachePtr == shapeInfo.pShpCache)
|
||
|
// abort, since same cache name / cache ptr
|
||
|
return;
|
||
|
|
||
|
// If The shape is In the cache of another Shape Cache, abort.
|
||
|
if( shapeInfo.isAdded )
|
||
|
// Abort, because impossible.
|
||
|
return;
|
||
|
|
||
|
// Is the shape is present ?
|
||
|
// Yes -> Update the ShapeInfo
|
||
|
shapeInfo.pShpCache= shapeCachePtr;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// change the cache name of the shape
|
||
|
ShapeNameToShapeCacheName[shapeName] = shapeCacheName;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::CShapeCache* CShapeBank::getShapeCachePtrFromShapePtr(IShape* pShp)
|
||
|
{
|
||
|
TShapeInfoMap::iterator scfpmIt = ShapePtrToShapeInfo.find( pShp );
|
||
|
if( scfpmIt != ShapePtrToShapeInfo.end() )
|
||
|
{
|
||
|
return scfpmIt->second.pShpCache;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
IShape* CShapeBank::getShapePtrFromShapeName(const std::string &pShpName)
|
||
|
{
|
||
|
TShapeMap::iterator smIt = ShapeMap.find(pShpName);
|
||
|
if( smIt != ShapeMap.end() )
|
||
|
{
|
||
|
// TMP
|
||
|
IShape *ptr = (IShape*)(smIt->second);
|
||
|
return ptr;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::CShapeCache* CShapeBank::getShapeCachePtrFromShapeCacheName(const string &shapeCacheName)
|
||
|
{
|
||
|
TShapeCacheMap::iterator scmIt = ShapeCacheNameToShapeCache.find( shapeCacheName );
|
||
|
if( scmIt != ShapeCacheNameToShapeCache.end())
|
||
|
{
|
||
|
return &(scmIt->second);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
const string* CShapeBank::getShapeNameFromShapePtr(IShape* pShp) const
|
||
|
{
|
||
|
TShapeInfoMap::const_iterator scfpmIt = ShapePtrToShapeInfo.find( pShp );
|
||
|
if( scfpmIt != ShapePtrToShapeInfo.end() )
|
||
|
{
|
||
|
return &(scfpmIt->second.sShpName);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
CShapeBank::CShapeCache* CShapeBank::getShapeCachePtrFromShapeName(const std::string &shapeName)
|
||
|
{
|
||
|
TShapeCacheNameMap::iterator scnIt = ShapeNameToShapeCacheName.find( shapeName );
|
||
|
if( scnIt != ShapeNameToShapeCacheName.end() )
|
||
|
{
|
||
|
return getShapeCachePtrFromShapeCacheName(scnIt->second);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
|
||
|
void CShapeBank::checkShapeCache(CShapeCache* pShpCache)
|
||
|
{
|
||
|
if( pShpCache != NULL )
|
||
|
while( (sint)pShpCache->Elements.size() > pShpCache->MaxSize )
|
||
|
{
|
||
|
// Suppress the last shape of the cache
|
||
|
IShape *pShp = pShpCache->Elements.back();
|
||
|
// Physical suppression because we own the last smart pointer on the shape
|
||
|
ShapeMap.erase(*getShapeNameFromShapePtr(pShp));
|
||
|
// delete information associated with the shape
|
||
|
ShapePtrToShapeInfo.erase( pShp );
|
||
|
// remove from queue
|
||
|
pShpCache->Elements.pop_back();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// ***************************************************************************
|
||
|
bool CShapeBank::isShapeCache(const std::string &shapeCacheName) const
|
||
|
{
|
||
|
return ShapeCacheNameToShapeCache.find(shapeCacheName) != ShapeCacheNameToShapeCache.end();
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void CShapeBank::preLoadShapes(const std::string &shapeCacheName,
|
||
|
const std::vector<std::string> &listFile, const std::string &wildCardNotLwr, NLMISC::IProgressCallback *progress, bool flushTextures /*= false*/, IDriver *drv /*= NULL*/)
|
||
|
{
|
||
|
// Abort if cache don't exist.
|
||
|
if(!isShapeCache(shapeCacheName))
|
||
|
return;
|
||
|
|
||
|
// lower case
|
||
|
string wildCard= toLower(wildCardNotLwr);
|
||
|
|
||
|
// For all files
|
||
|
for(uint i=0;i<listFile.size();i++)
|
||
|
{
|
||
|
// Progress bar
|
||
|
if (progress)
|
||
|
progress->progress ((float)i/(float)listFile.size ());
|
||
|
|
||
|
string fileName= toLower(CFile::getFilename(listFile[i]));
|
||
|
// if the file is ok for the wildCard, process it
|
||
|
if( testWildCard(fileName.c_str(), wildCard.c_str()) )
|
||
|
{
|
||
|
// link the shape to the shapeCache
|
||
|
linkShapeToShapeCache(fileName, shapeCacheName);
|
||
|
|
||
|
// If !present in the shapeBank
|
||
|
if( getPresentState(fileName)==CShapeBank::NotPresent )
|
||
|
{
|
||
|
// Don't load it if no more space in the cache
|
||
|
if( getShapeCacheFreeSpace(shapeCacheName)>0 )
|
||
|
{
|
||
|
// load it.
|
||
|
load(fileName);
|
||
|
|
||
|
// If success
|
||
|
if( getPresentState(fileName)==CShapeBank::Present )
|
||
|
{
|
||
|
// When a shape is first added to the bank, it is not in the cache.
|
||
|
// add it and release it to force it to be in the cache.
|
||
|
IShape *shp= addRef(fileName);
|
||
|
if(shp)
|
||
|
{
|
||
|
//nlinfo("Loading %s", CPath::lookup(fileName.c_str(), false, false).c_str());
|
||
|
if (flushTextures && drv)
|
||
|
{
|
||
|
shp->flushTextures(*drv, 0);
|
||
|
}
|
||
|
release(shp);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void CShapeBank::buildSystemGeometryForshape(const std::string &shapeName)
|
||
|
{
|
||
|
_ShapeNeedingSystemGeometryCopy.insert(toLower(shapeName));
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|