Added stats collection to new Texture Pool code, and improved TexturePool implementation.

The Texture Pool can be enabled by setting the env var OSG_TEXTURE_POOL_SIZE=size_in_bytes.
Note, setting a size of 1 will result in the TexturePool allocating the minimum number of
textures it can without having to reuse TextureObjects from within the same frame.
This commit is contained in:
Robert Osfield 2009-09-25 18:05:54 +00:00
parent 6bc9c626f4
commit 4d75729650
5 changed files with 307 additions and 54 deletions

View File

@ -998,6 +998,7 @@ class OSG_EXPORT Texture : public osg::StateAttribute
TextureObject* _next;
Texture* _texture;
bool _allocated;
unsigned int _frameLastUsed;
double _timeStamp;
};
@ -1013,6 +1014,7 @@ class OSG_EXPORT Texture : public osg::StateAttribute
void discardAllDeletedTextureObjects();
void flushDeletedTextureObjects(double currentTime, double& availableTime);
TextureObject* takeFromOrphans(Texture* texture);
TextureObject* takeOrGenerate(Texture* texture);
void moveToBack(TextureObject* to);
void addToBack(TextureObject* to);
@ -1048,7 +1050,18 @@ class OSG_EXPORT Texture : public osg::StateAttribute
unsigned int getContextID() const { return _contextID; }
void setNumberActiveTextureObjects(unsigned int size) { _numActiveTextureObjects = size; }
unsigned int& getNumberActiveTextureObjects() { return _numActiveTextureObjects; }
unsigned int getNumberActiveTextureObjects() const { return _numActiveTextureObjects; }
void setNumberOrphanedTextureObjects(unsigned int size) { _numOrphanedTextureObjects = size; }
unsigned int& getNumberOrphanedTextureObjects() { return _numOrphanedTextureObjects; }
unsigned int getNumberOrphanedTextureObjects() const { return _numOrphanedTextureObjects; }
void setCurrTexturePoolSize(unsigned int size) { _currTexturePoolSize = size; }
unsigned int& getCurrTexturePoolSize() { return _currTexturePoolSize; }
unsigned int getCurrTexturePoolSize() const { return _currTexturePoolSize; }
void setMaxTexturePoolSize(unsigned int size);
@ -1072,13 +1085,46 @@ class OSG_EXPORT Texture : public osg::StateAttribute
void flushDeletedTextureObjects(double currentTime, double& availableTime);
void releaseTextureObject(TextureObject* to);
void newFrame(osg::FrameStamp* fs);
void resetStats();
void reportStats();
unsigned int& getFrameNumber() { return _frameNumber; }
unsigned int& getNumberFrames() { return _numFrames; }
unsigned int& getNumberDeleted() { return _numDeleted; }
double& getDeleteTime() { return _deleteTime; }
unsigned int& getNumberGenerated() { return _numGenerated; }
double& getGenerateTime() { return _generateTime; }
unsigned int& getNumberApplied() { return _numApplied; }
double& getApplyTime() { return _applyTime; }
protected:
typedef std::map< TextureProfile, osg::ref_ptr<TextureObjectSet> > TextureSetMap;
unsigned int _contextID;
unsigned int _numActiveTextureObjects;
unsigned int _numOrphanedTextureObjects;
unsigned int _currTexturePoolSize;
unsigned int _maxTexturePoolSize;
TextureSetMap _textureSetMap;
unsigned int _frameNumber;
unsigned int _numFrames;
unsigned int _numDeleted;
double _deleteTime;
unsigned int _numGenerated;
double _generateTime;
unsigned int _numApplied;
double _applyTime;
};
static osg::ref_ptr<Texture::TextureObjectManager>& getTextureObjectManager(unsigned int contextID);

View File

@ -74,6 +74,59 @@ class OSG_EXPORT Timer {
Timer_t _startTick;
double _secsPerTick;
};
/** Helper class for timing sections of code. */
class ElapsedTime
{
public:
inline ElapsedTime(double* elapsedTime, osg::Timer* timer = 0):
_time(elapsedTime)
{
init(timer);
}
inline ElapsedTime(osg::Timer* timer = 0):
_time(0)
{
init(timer);
}
inline ~ElapsedTime()
{
finish();
}
inline void reset()
{
_startTick = _timer->tick();
}
inline double elapsedTime() const
{
return _timer->delta_s(_startTick, _timer->tick());
}
inline void finish()
{
Timer_t endTick = _timer->tick();
if (_time) *_time += _timer->delta_s(_startTick, endTick);
_startTick = endTick;
}
protected:
inline void init(osg::Timer* timer)
{
if (timer) _timer = timer;
else _timer = osg::Timer::instance();
_startTick = _timer->tick();
}
double* _time;
Timer* _timer;
Timer_t _startTick;
};

View File

@ -108,7 +108,7 @@ Texture::TextureObjectSet::~TextureObjectSet()
bool Texture::TextureObjectSet::checkConsistency() const
{
// return true;
return true;
// osg::notify(osg::NOTICE)<<"TextureObjectSet::checkConsistency()"<<std::endl;
// check consistency of linked list.
@ -158,6 +158,8 @@ void Texture::TextureObjectSet::handlePendingOrphandedTextureObjects()
if (_pendingOrphanedTextureObjects.empty()) return;
unsigned int numOrphaned = _pendingOrphanedTextureObjects.size();
for(TextureObjectList::iterator itr = _pendingOrphanedTextureObjects.begin();
itr != _pendingOrphanedTextureObjects.end();
++itr)
@ -199,6 +201,11 @@ void Texture::TextureObjectSet::handlePendingOrphandedTextureObjects()
}
// update the TextureObjectManager's running total of active + orphaned TextureObjects
_parent->getNumberOrphanedTextureObjects() += numOrphaned;
_parent->getNumberActiveTextureObjects() -= numOrphaned;
_pendingOrphanedTextureObjects.clear();
checkConsistency();
@ -213,24 +220,36 @@ void Texture::TextureObjectSet::flushAllDeletedTextureObjects()
GLuint id = (*itr)->id();
osg::notify(osg::NOTICE)<<"Deleting textureobject id="<<id<<std::endl;
// osg::notify(osg::NOTICE)<<"Deleting textureobject id="<<id<<std::endl;
glDeleteTextures( 1L, &id);
}
_numOfTextureObjects -= _orphanedTextureObjects.size();
unsigned int numDeleted = _orphanedTextureObjects.size();
_numOfTextureObjects -= numDeleted;
// update the TextureObjectManager's running total of current pool size
_parent->setCurrTexturePoolSize( _parent->getCurrTexturePoolSize() - _orphanedTextureObjects.size()*_profile._size );
_parent->getCurrTexturePoolSize() -= numDeleted*_profile._size;
_parent->getNumberOrphanedTextureObjects() -= numDeleted;
_parent->getNumberDeleted() += numDeleted;
_orphanedTextureObjects.clear();
_orphanedTextureObjects.clear();
}
void Texture::TextureObjectSet::discardAllDeletedTextureObjects()
{
_numOfTextureObjects -= _orphanedTextureObjects.size();
unsigned int numDiscarded = _orphanedTextureObjects.size();
_numOfTextureObjects -= numDiscarded;
// update the TextureObjectManager's running total of current pool size
_parent->setCurrTexturePoolSize( _parent->getCurrTexturePoolSize() - _orphanedTextureObjects.size()*_profile._size );
_parent->setCurrTexturePoolSize( _parent->getCurrTexturePoolSize() - numDiscarded*_profile._size );
// update the number of active and orphaned TextureOjects
_parent->getNumberOrphanedTextureObjects() -= 1;
_parent->getNumberActiveTextureObjects() += 1;
_parent->getNumberDeleted() += 1;
// just clear the list as there is nothing else we can do with them when discarding them
_orphanedTextureObjects.clear();
@ -240,7 +259,51 @@ void Texture::TextureObjectSet::flushDeletedTextureObjects(double currentTime, d
{
// osg::notify(osg::NOTICE)<<"Texture::TextureObjectSet::flushDeletedTextureObjects(..) Not properly implemented yet"<<std::endl;
flushAllDeletedTextureObjects();
// if nothing to delete return
if (_orphanedTextureObjects.empty()) return;
// if no time available don't try to flush objects.
if (availableTime<=0.0) return;
// if we don't have too many orphaned texture objects then don't bother deleting them, as we can potentially reuse them later.
if (_parent->getNumberOrphanedTextureObjects()<=s_minimumNumberOfTextureObjectsToRetainInCache) return;
unsigned int numDeleted = 0;
unsigned int maxNumObjectsToDelete = _parent->getNumberOrphanedTextureObjects()-s_minimumNumberOfTextureObjectsToRetainInCache;
if (maxNumObjectsToDelete>4) maxNumObjectsToDelete = 4;
ElapsedTime timer;
TextureObjectList::iterator itr = _orphanedTextureObjects.begin();
for(;
itr != _orphanedTextureObjects.end() && timer.elapsedTime()<availableTime && numDeleted<maxNumObjectsToDelete;
++itr)
{
GLuint id = (*itr)->id();
// osg::notify(osg::NOTICE)<<"Deleting textureobject id="<<id<<std::endl;
glDeleteTextures( 1L, &id);
++numDeleted;
}
osg::notify(osg::NOTICE)<<"Size before = "<<_orphanedTextureObjects.size();
_orphanedTextureObjects.erase(_orphanedTextureObjects.begin(), itr);
osg::notify(osg::NOTICE)<<", after = "<<_orphanedTextureObjects.size()<<" numDeleted = "<<numDeleted<<std::endl;
// update the number of TO's in this TextureObjectSet
_numOfTextureObjects -= numDeleted;
_parent->setCurrTexturePoolSize( _parent->getCurrTexturePoolSize() - numDeleted*_profile._size );
// update the number of active and orphaned TextureOjects
_parent->getNumberOrphanedTextureObjects() -= numDeleted;
_parent->getNumberActiveTextureObjects() += numDeleted;
_parent->getNumberDeleted() += numDeleted;
availableTime -= timer.elapsedTime();
}
bool Texture::TextureObjectSet::makeSpace(unsigned int& size)
@ -252,45 +315,61 @@ bool Texture::TextureObjectSet::makeSpace(unsigned int& size)
else size = 0;
flushAllDeletedTextureObjects();
}
return size==0;
}
Texture::TextureObject* Texture::TextureObjectSet::takeFromOrphans(Texture* texture)
{
// take front of orphaned list.
ref_ptr<TextureObject> to = _orphanedTextureObjects.front();
// remove from orphan list.
_orphanedTextureObjects.pop_front();
// assign to new texture
to->setTexture(texture);
// update the number of active and orphaned TextureOjects
_parent->getNumberOrphanedTextureObjects() -= 1;
_parent->getNumberActiveTextureObjects() += 1;
// place at back of active list
addToBack(to.get());
// osg::notify(osg::NOTICE)<<"Reusing orhpahned TextureObject, _numOfTextureObjects="<<_numOfTextureObjects<<std::endl;
return to.release();
}
Texture::TextureObject* Texture::TextureObjectSet::takeOrGenerate(Texture* texture)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
if (!_pendingOrphanedTextureObjects.empty()) handlePendingOrphandedTextureObjects();
// see if we can recyle TextureObject from the orphane list
if (!_orphanedTextureObjects.empty())
if (!_pendingOrphanedTextureObjects.empty())
{
// take front of orphaned list.
ref_ptr<TextureObject> to = _orphanedTextureObjects.front();
// remove from orphan list.
_orphanedTextureObjects.pop_front();
// assign to new texture
to->setTexture(texture);
// place at back of active list
addToBack(to.get());
osg::notify(osg::NOTICE)<<"Reusing orhpahned TextureObject, _numOfTextureObjects="<<_numOfTextureObjects<<std::endl;
return to.release();
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
handlePendingOrphandedTextureObjects();
return takeFromOrphans(texture);
}
else if (!_orphanedTextureObjects.empty())
{
return takeFromOrphans(texture);
}
unsigned int minFrameNumber = _parent->getFrameNumber();
// see if we can reuse TextureObject by taking the least recently used active TextureObject
if ((_parent->getMaxTexturePoolSize()!=0) &&
(!_parent->hasSpace(_profile._size)) &&
(_numOfTextureObjects>1) &&
(_head != 0))
(_head != 0) &&
(_head->_frameLastUsed<minFrameNumber))
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
ref_ptr<TextureObject> to = _head;
ref_ptr<Texture> original_texture = to->getTexture();
@ -298,24 +377,21 @@ Texture::TextureObject* Texture::TextureObjectSet::takeOrGenerate(Texture* textu
if (original_texture.valid())
{
original_texture->setTextureObject(_contextID,0);
osg::notify(osg::NOTICE)<<"Reusing an active TextureObject "<<to.get()<<std::endl;
// osg::notify(osg::NOTICE)<<"TextureObjectSet="<<this<<": Reusing an active TextureObject "<<to.get()<<" _numOfTextureObjects="<<_numOfTextureObjects<<" width="<<_profile._width<<" height="<<_profile._height<<std::endl;
}
else
{
osg::notify(osg::NOTICE)<<"Reusing a recently orphaned active TextureObject "<<to.get()<<std::endl;
// osg::notify(osg::NOTICE)<<"Reusing a recently orphaned active TextureObject "<<to.get()<<std::endl;
}
moveToBack(to.get());
// assign to new texture
to->setTexture(texture);
return to.release();
}
//
// no TextureObjects available to recyle so have to create one from scratch
//
@ -327,11 +403,12 @@ Texture::TextureObject* Texture::TextureObjectSet::takeOrGenerate(Texture* textu
++_numOfTextureObjects;
// update the current texture pool size
_parent->setCurrTexturePoolSize( _parent->getCurrTexturePoolSize() + _profile._size );
_parent->getCurrTexturePoolSize() += _profile._size;
_parent->getNumberActiveTextureObjects() += 1;
addToBack(to);
osg::notify(osg::NOTICE)<<"Created new TextureObject, _numOfTextureObjects "<<_numOfTextureObjects<<std::endl;
// osg::notify(osg::NOTICE)<<"Created new TextureObject, _numOfTextureObjects "<<_numOfTextureObjects<<std::endl;
return to;
}
@ -346,6 +423,8 @@ void Texture::TextureObjectSet::moveToBack(Texture::TextureObject* to)
osg::notify(osg::NOTICE)<<" before to->_next = "<<to->_next<<std::endl;
#endif
to->_frameLastUsed = _parent->getFrameNumber();
// nothing to do if we are already tail
if (to==_tail) return;
@ -412,6 +491,8 @@ void Texture::TextureObjectSet::addToBack(Texture::TextureObject* to)
}
else
{
to->_frameLastUsed = _parent->getFrameNumber();
if (_tail) _tail->_next = to;
to->_previous = _tail;
@ -451,8 +532,18 @@ void Texture::TextureObjectSet::orphan(Texture::TextureObject* to)
Texture::TextureObjectManager::TextureObjectManager(unsigned int contextID):
_contextID(contextID),
_numActiveTextureObjects(0),
_numOrphanedTextureObjects(0),
_currTexturePoolSize(0),
_maxTexturePoolSize(0)
_maxTexturePoolSize(0),
_frameNumber(0),
_numFrames(0),
_numDeleted(0),
_deleteTime(0.0),
_numGenerated(0),
_generateTime(0.0),
_numApplied(0),
_applyTime(0.0)
{
}
@ -495,6 +586,9 @@ Texture::TextureObject* Texture::TextureObjectManager::generateTextureObject(con
GLsizei depth,
GLint border)
{
ElapsedTime elapsedTime(&(getGenerateTime()));
++getNumberGenerated();
Texture::TextureProfile profile(target,numMipmapLevels,internalFormat,width,height,depth,border);
osg::ref_ptr<Texture::TextureObjectSet>& tos = _textureSetMap[profile];
if (!tos) tos = new Texture::TextureObjectSet(this, profile);
@ -513,7 +607,7 @@ void Texture::TextureObjectManager::handlePendingOrphandedTextureObjects()
void Texture::TextureObjectManager::flushAllDeletedTextureObjects()
{
return;
ElapsedTime elapsedTime(&(getDeleteTime()));
for(TextureSetMap::iterator itr = _textureSetMap.begin();
itr != _textureSetMap.end();
@ -535,6 +629,8 @@ void Texture::TextureObjectManager::discardAllDeletedTextureObjects()
void Texture::TextureObjectManager::flushDeletedTextureObjects(double currentTime, double& availableTime)
{
ElapsedTime elapsedTime(&(getDeleteTime()));
for(TextureSetMap::iterator itr = _textureSetMap.begin();
(itr != _textureSetMap.end()) && (availableTime > 0.0);
++itr)
@ -550,6 +646,39 @@ void Texture::TextureObjectManager::releaseTextureObject(Texture::TextureObject*
}
void Texture::TextureObjectManager::newFrame(osg::FrameStamp* fs)
{
if (fs) _frameNumber = fs->getFrameNumber();
else ++_frameNumber;
++_numFrames;
}
void Texture::TextureObjectManager::reportStats()
{
double numFrames(_numFrames==0 ? 1.0 : _numFrames);
osg::notify(osg::NOTICE)<<"TextureObjectMananger::reportStats()"<<std::endl;
osg::notify(osg::NOTICE)<<" total _numOfTextureObjects="<<_numActiveTextureObjects<<", _numOrphanedTextureObjects="<<_numOrphanedTextureObjects<<" _currTexturePoolSize="<<_currTexturePoolSize<<std::endl;
osg::notify(osg::NOTICE)<<" total _numGenerated="<<_numGenerated<<", _generateTime="<<_generateTime<<", averagePerFrame="<<_generateTime/numFrames*1000.0<<"ms"<<std::endl;
osg::notify(osg::NOTICE)<<" total _numDeleted="<<_numDeleted<<", _deleteTime="<<_deleteTime<<", averagePerFrame="<<_deleteTime/numFrames*1000.0<<"ms"<<std::endl;
osg::notify(osg::NOTICE)<<" total _numApplied="<<_numApplied<<", _applyTime="<<_applyTime<<", averagePerFrame="<<_applyTime/numFrames*1000.0<<"ms"<<std::endl;
}
void Texture::TextureObjectManager::resetStats()
{
_numFrames = 0;
_numDeleted = 0;
_deleteTime = 0;
_numGenerated = 0;
_generateTime = 0;
_numApplied = 0;
_applyTime = 0;
}
osg::ref_ptr<Texture::TextureObjectManager>& Texture::getTextureObjectManager(unsigned int contextID)
{
typedef osg::buffered_object< ref_ptr<Texture::TextureObjectManager> > TextureObjectManagerBuffer;
@ -681,8 +810,12 @@ unsigned int Texture::s_numberDeletedTextureInLastFrame = 0;
static ref_ptr<OriginalTextureObjectManager> s_textureObjectManager = new OriginalTextureObjectManager;
Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Texture* texture, unsigned int /*contextID*/,GLenum target)
Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Texture* texture, unsigned int contextID,GLenum target)
{
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime elapsedTime(&(tom->getGenerateTime()));
tom->getNumberGenerated()++;
GLuint id;
glGenTextures( 1L, &id );
@ -691,7 +824,7 @@ Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Text
static int s_number = 0;
Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Texture* texture, unsigned int /*contextID*/,
Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Texture* texture, unsigned int contextID,
GLenum target,
GLint numMipmapLevels,
GLenum internalFormat,
@ -700,6 +833,10 @@ Texture::TextureObject* OriginalTextureObjectManager::generateTextureObject(Text
GLsizei depth,
GLint border)
{
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime elapsedTime(&(tom->getGenerateTime()));
tom->getNumberGenerated()++;
++s_number;
++Texture::s_numberNewTextureInLastFrame;
// notify(NOTICE)<<"creating new texture object "<<s_number<<std::endl;
@ -720,6 +857,10 @@ Texture::TextureObject* OriginalTextureObjectManager::reuseTextureObject(Texture
GLsizei depth,
GLint border)
{
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime elapsedTime(&(tom->getGenerateTime()));
tom->getNumberGenerated()++;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
Texture::TextureObjectList& tol = _textureObjectListMap[contextID];
@ -748,6 +889,9 @@ Texture::TextureObject* OriginalTextureObjectManager::reuseTextureObject(Texture
void OriginalTextureObjectManager::flushAllTextureObjects(unsigned int contextID)
{
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime elapsedTime(&(tom->getDeleteTime()));
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
Texture::TextureObjectList& tol = _textureObjectListMap[contextID];
@ -761,6 +905,8 @@ void OriginalTextureObjectManager::flushAllTextureObjects(unsigned int contextID
// osg::notify(osg::NOTICE)<<" deleting texture object "<<(*itr)->_id<<std::endl;
GLuint id = (*itr)->id();
glDeleteTextures( 1L, &id);
tom->getNumberDeleted()++;
}
tol.clear();
}
@ -775,17 +921,16 @@ void OriginalTextureObjectManager::discardAllTextureObjects(unsigned int context
void OriginalTextureObjectManager::flushTextureObjects(unsigned int contextID,double currentTime, double& availableTime)
{
// if no time available don't try to flush objects.
if (availableTime<=0.0) return;
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime timer(&(tom->getDeleteTime()));
unsigned int numObjectsDeleted = 0;
unsigned int maxNumObjectsToDelete = 4;
const osg::Timer& timer = *osg::Timer::instance();
osg::Timer_t start_tick = timer.tick();
double elapsedTime = 0.0;
unsigned int numTexturesDeleted = 0;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
@ -803,7 +948,7 @@ void OriginalTextureObjectManager::flushTextureObjects(unsigned int contextID,do
double expiryTime = currentTime-_expiryDelay;
for(itr=tol.begin();
itr!=tol.end() && elapsedTime<availableTime && tol.size()>s_minimumNumberOfTextureObjectsToRetainInCache && numObjectsDeleted<maxNumObjectsToDelete;
itr!=tol.end() && timer.elapsedTime()<availableTime && tol.size()>s_minimumNumberOfTextureObjectsToRetainInCache && numObjectsDeleted<maxNumObjectsToDelete;
)
{
if ((*itr)->getTimeStamp()<=expiryTime)
@ -814,20 +959,18 @@ void OriginalTextureObjectManager::flushTextureObjects(unsigned int contextID,do
GLuint id = (*itr)->id();
glDeleteTextures( 1L, &id);
itr = tol.erase(itr);
++numTexturesDeleted;
++numObjectsDeleted;
}
else
{
++itr;
}
elapsedTime = timer.delta_s(start_tick,timer.tick());
}
}
elapsedTime = timer.delta_s(start_tick,timer.tick());
// if (numTexturesDeleted) notify(osg::NOTICE)<<"Number of Texture's deleted = "<<numTexturesDeleted<<" new total "<<s_number<<" elapsed time"<<elapsedTime<<std::endl;
availableTime -= elapsedTime;
tom->getNumberDeleted()+=numObjectsDeleted;
availableTime -= timer.elapsedTime();
}

View File

@ -137,6 +137,10 @@ void Texture2D::apply(State& state) const
// current OpenGL context.
const unsigned int contextID = state.getContextID();
Texture::TextureObjectManager* tom = Texture::getTextureObjectManager(contextID);
ElapsedTime elapsedTime(&(tom->getApplyTime()));
tom->getNumberApplied()++;
// get the texture object for the current contextID.
TextureObject* textureObject = getTextureObject(contextID);

View File

@ -1022,6 +1022,9 @@ void SceneView::draw()
osg::State* state = _renderInfo.getState();
state->initializeExtensionProcs();
osg::Texture::TextureObjectManager* tom = osg::Texture::getTextureObjectManager(state->getContextID());
tom->newFrame(state->getFrameStamp());
if (!_initCalled) init();
// note, to support multi-pipe systems the deletion of OpenGL display list
@ -1560,6 +1563,10 @@ void SceneView::draw()
}
}
#ifdef REPORT_TEXTURE_MANAGER_STATS
tom->reportStats();
#endif
// osg::notify(osg::NOTICE)<<"SceneView draw() DynamicObjectCount"<<getState()->getDynamicObjectCount()<<std::endl;
}