diff --git a/include/osgDB/DatabasePager b/include/osgDB/DatabasePager index ae019282f..a8965386e 100644 --- a/include/osgDB/DatabasePager +++ b/include/osgDB/DatabasePager @@ -31,6 +31,8 @@ #include #include +#include +#include namespace osgDB { @@ -275,7 +277,7 @@ class OSGDB_EXPORT DatabasePager : public osg::NodeVisitor::DatabaseRequestHandl friend struct DatabaseRequest; - + struct DatabaseRequest : public osg::Referenced { DatabaseRequest(): @@ -294,8 +296,13 @@ class OSGDB_EXPORT DatabasePager : public osg::NodeVisitor::DatabaseRequestHandl osg::ref_ptr _loadedModel; DataToCompileMap _dataToCompileMap; osg::ref_ptr _loadOptions; + + bool isRequestCurrent (int frameNumber) const + { + return frameNumber - _frameNumberLastRequest <= 1; + } }; - + typedef std::vector< osg::ref_ptr > DatabaseRequestList; typedef std::vector< osg::ref_ptr > ObjectList; @@ -321,8 +328,86 @@ class OSGDB_EXPORT DatabasePager : public osg::NodeVisitor::DatabaseRequestHandl _databasePagerThreadBlock->set( (!_fileRequestList.empty() || !_childrenToDeleteList.empty()) && !_databasePagerThreadPaused); } - + // Helper functions for determining if objects need to be + // compiled. + inline static bool isCompiled(const osg::Texture* texture, + unsigned int contextID) + { + return texture->getTextureObject(contextID); + } + // Is texture compiled for all active contexts? + inline bool isCompiled(osg::Texture* texture) const + { + using namespace std; + return (count_if(_activeGraphicsContexts.begin(), + _activeGraphicsContexts.end(), + bind1st(mem_fun(&osg::Texture::getTextureObject), + texture)) + == _activeGraphicsContexts.size()); + } + + inline static bool isCompiled(const osg::StateSet* stateSet, + unsigned int contextID) + { + for (unsigned i = 0; + i < stateSet->getTextureAttributeList().size(); + ++i) + { + const osg::Texture* texture + = dynamic_cast(stateSet->getTextureAttribute(i,osg::StateAttribute::TEXTURE)); + if (texture && !isCompiled(texture, contextID)) + return false; + } + return true; + } + + inline bool isCompiled(osg::StateSet* stateSet) + { + using namespace std; + for (unsigned i = 0; + i < stateSet->getTextureAttributeList().size(); + ++i) + { + osg::Texture* texture + = dynamic_cast(stateSet->getTextureAttribute(i,osg::StateAttribute::TEXTURE)); + if (texture + && (count_if(_activeGraphicsContexts.begin(), + _activeGraphicsContexts.end(), + bind1st(mem_fun(&osg::Texture::getTextureObject), + texture)) + != _activeGraphicsContexts.size())) + return false; + } + return true; + } + + inline static bool isCompiled(const osg::Drawable* drawable, + unsigned int contextID) + { + // Worry about vbos later + if (drawable->getUseDisplayList()) + { + return drawable->getDisplayList(contextID) != 0; + } + return true; + } + + inline bool isCompiled(const osg::Drawable* drawable) const + { + using namespace std; + if (drawable->getUseDisplayList()) + { + return (count_if(_activeGraphicsContexts.begin(), + _activeGraphicsContexts.end(), + bind1st(mem_fun(&osg::Drawable::getDisplayList), + drawable)) + == _activeGraphicsContexts.size()); + } + return true; + } + + /** Iterate through the active PagedLOD nodes children removing * children which havn't been visited since specified expiryTime. * note, should be only be called from the update thread. */ diff --git a/include/osgDB/SharedStateManager b/include/osgDB/SharedStateManager index 6a3a853c3..9e5e8ef4b 100644 --- a/include/osgDB/SharedStateManager +++ b/include/osgDB/SharedStateManager @@ -42,8 +42,8 @@ namespace osgDB { SharedStateManager(); - void setShareMode(unsigned int mode) { shareMode = mode; } - unsigned int getShareMode() { return shareMode; } + void setShareMode(unsigned int mode) { _shareMode = mode; } + unsigned int getShareMode() { return _shareMode; } // Call right after each unload and before Registry cache prune. void prune(); @@ -53,7 +53,10 @@ namespace osgDB { void apply(osg::Node& node); void apply(osg::Geode& geode); - + // Answers the question "Will this state set be eliminated by + // the SharedStateManager because an equivalent one has been + // seen already?" Safe to call from the pager thread. + bool isShared(osg::StateSet* stateSet); protected: void process(osg::StateSet* ss, osg::Object* parent); @@ -62,24 +65,46 @@ namespace osgDB { void setStateSet(osg::StateSet* ss, osg::Object* object); void shareTextures(osg::StateSet* ss); + struct CompareStateAttributes + { + bool operator()(const osg::ref_ptr& lhs, + const osg::ref_ptr& rhs) + { + return *lhs < *rhs; + } + }; + + struct CompareStateSets + { + bool operator()(const osg::ref_ptr& lhs, + const osg::ref_ptr& rhs) + { + return lhs->compare(*rhs, true) < 0; + } + }; + // Lists of shared objects - typedef std::set< osg::ref_ptr > TextureSet; + typedef std::set< osg::ref_ptr, CompareStateAttributes > TextureSet; TextureSet _sharedTextureList; - typedef std::set< osg::ref_ptr > StateSetSet; + + typedef std::set< osg::ref_ptr, CompareStateSets > StateSetSet; StateSetSet _sharedStateSetList; // Temporary lists just to avoid unnecessary find calls typedef std::pair TextureSharePair; typedef std::map TextureTextureSharePairMap; TextureTextureSharePairMap tmpSharedTextureList; + typedef std::pair StateSetSharePair; typedef std::map StateSetStateSetSharePairMap; StateSetStateSetSharePairMap tmpSharedStateSetList; // Share connection mutex - unsigned int shareMode; + unsigned int _shareMode; - OpenThreads::Mutex *mutex; + OpenThreads::Mutex *_mutex; + // Mutex for doing isShared queries from other threads + mutable OpenThreads::Mutex _listMutex; }; } diff --git a/src/osgDB/DatabasePager.cpp b/src/osgDB/DatabasePager.cpp index 6deef8329..ff3037679 100644 --- a/src/osgDB/DatabasePager.cpp +++ b/src/osgDB/DatabasePager.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #ifdef WIN32 #include @@ -28,6 +30,57 @@ static osg::ApplicationUsageProxy DatabasePager_e4(osg::ApplicationUsage::ENVIRO static osg::ApplicationUsageProxy DatabasePager_e5(osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_DATABASE_PAGER_CHILDREN_TO_REMOVE_PER_FRAME ", "Set the maximum number of PagedLOD child to remove per frame."); static osg::ApplicationUsageProxy DatabasePager_e6(osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_DATABASE_PAGER_MINIMUM_INACTIVE_PAGEDLOD ", "Set the minimum number of inactive PagedLOD child to keep."); +// Convert function objects that take pointer args into functions that a +// reference to an osg::ref_ptr. This is quite useful for doing STL +// operations on lists of ref_ptr. This code assumes that a function +// with an argument const Foo* should be composed into a function of +// argument type ref_ptr&, not ref_ptr&. Some support +// for that should be added to make this more general. + +namespace +{ +template +struct PointerTraits +{ + typedef class NullType {} PointeeType; +}; + +template +struct PointerTraits +{ + typedef U PointeeType; +}; + +template +struct PointerTraits +{ + typedef U PointeeType; +}; + +template +class RefPtrAdapter + : public std::unary_function::PointeeType>, + typename FuncObj::result_type> +{ +public: + typedef typename PointerTraits::PointeeType PointeeType; + typedef osg::ref_ptr RefPtrType; + explicit RefPtrAdapter(const FuncObj& funcObj) : _func(funcObj) {} + typename FuncObj::result_type operator()(const RefPtrType& refPtr) const + { + return _func(refPtr.get()); + } +protected: + FuncObj _func; +}; + +template +RefPtrAdapter refPtrAdapt(const FuncObj& func) +{ + return RefPtrAdapter(func); +} +} + DatabasePager::DatabasePager() { //osg::notify(osg::INFO)<<"Constructing DatabasePager()"<getTextureAttributeList().size();++i) { osg::Texture* texture = dynamic_cast(stateset->getTextureAttribute(i,osg::StateAttribute::TEXTURE)); - if (texture) + // Has this texture already been encountered? + if (texture && !_textureSet.count(texture)) { + _textureSet.insert(texture); if (_changeAutoUnRef) texture->setUnRefImageDataAfterApply(_valueAutoUnRef); - if (_changeAnisotropy) texture->setMaxAnisotropy(_valueAnisotropy); - foundTextureState = true; + if ((_changeAnisotropy + && texture->getMaxAnisotropy() != _valueAnisotropy)) + { + if (_changeAnisotropy) + texture->setMaxAnisotropy(_valueAnisotropy); + } + + if (!_pager->isCompiled(texture)) + { + compileStateSet = true; + if (osg::getNotifyLevel() >= osg::DEBUG_INFO) + { + osg::notify(osg::DEBUG_INFO) + <<"Found compilable texture " << texture << " "; + osg::Image* image = texture->getImage(0); + if (image) osg::notify(osg::DEBUG_INFO) << image->getFileName(); + osg::notify(osg::DEBUG_INFO) << std:: endl; + } + break; + } } } - - // if texture object attributes exist add the state to the list for later compilation. - if (foundTextureState) + if (compileStateSet) { - //osg::notify(osg::DEBUG_INFO)<<"Found compilable texture state"<getStateSet()); switch(_drawablePolicy) @@ -495,8 +574,12 @@ public: // osg::notify(osg::NOTICE)<<"USE_VERTEX_ARRAYS"<getUseDisplayList() || drawable->getUseVertexBufferObjects()) + // Don't compile if already compiled. This can happen if the + // subgraph is shared with already-loaded nodes. + // + // XXX This "compiles" VBOs too, but compilation doesn't do + // anything for VBOs, does it? + if (drawable->getUseDisplayList() && _pager->isCompiled(drawable)) { // osg::notify(osg::NOTICE)<<" Found compilable drawable"< > _textureSet; + std::set > _drawableSet; }; @@ -631,7 +717,7 @@ void DatabasePager::run() FindCompileableGLObjectsVisitor frov(dtc, _changeAutoUnRef, _valueAutoUnRef, _changeAnisotropy, _valueAnisotropy, - _drawablePolicy); + _drawablePolicy, this); databaseRequest->_loadedModel->accept(frov); @@ -676,33 +762,51 @@ void DatabasePager::run() updateDatabasePagerThreadBlock(); } - + // Prepare and prune the to-be-compiled list here in + // the pager thread rather than in the draw or + // graphics context thread(s). if (loadedObjectsNeedToBeCompiled) { - for(ActiveGraphicsContexts::iterator itr = _activeGraphicsContexts.begin(); - itr != _activeGraphicsContexts.end(); - ++itr) + OpenThreads::ScopedLock lock(_dataToCompileListMutex); + sort(_dataToCompileList.begin(), + _dataToCompileList.end(), SortFileRequestFunctor()); + // Prune all the old entries. + DatabaseRequestList::iterator tooOld + = find_if(_dataToCompileList.begin(), + _dataToCompileList.end(), + refPtrAdapt(std::not1(std::bind2nd(std::mem_fun(&DatabaseRequest + ::isRequestCurrent), + _frameNumber)))); + // This is the database thread, so just delete + _dataToCompileList.erase(tooOld, _dataToCompileList.end()); + + if (!_dataToCompileList.empty()) { - osg::GraphicsContext* gc = osg::GraphicsContext::getCompileContext(*itr); - if (gc) - { - osg::GraphicsThread* gt = gc->getGraphicsThread(); - if (gt) - { - gt->add(new DatabasePager::CompileOperation(this)); - } - else - { - gc->makeCurrent(); + for(ActiveGraphicsContexts::iterator itr = _activeGraphicsContexts.begin(); + itr != _activeGraphicsContexts.end(); + ++itr) + { + osg::GraphicsContext* gc = osg::GraphicsContext::getCompileContext(*itr); + if (gc) + { + osg::GraphicsThread* gt = gc->getGraphicsThread(); + if (gt) + { + gt->add(new DatabasePager::CompileOperation(this)); + } + else + { + gc->makeCurrent(); - compileAllGLObjects(*(gc->getState())); + compileAllGLObjects(*(gc->getState())); - gc->releaseContext(); + gc->releaseContext(); + } } } - } - // osg::notify(osg::NOTICE)<<"Done compiling in paging thread"<getSharedStateManager(); osg::RenderInfo renderInfo; renderInfo.setState(&state); @@ -1045,40 +1151,9 @@ void DatabasePager::compileGLObjects(osg::State& state, double& availableTime) // get the first compilable entry. { OpenThreads::ScopedLock lock(_dataToCompileListMutex); - if (!_dataToCompileList.empty()) - { - std::sort(_dataToCompileList.begin(),_dataToCompileList.end(),SortFileRequestFunctor()); - - // prune all the old entries. - DatabaseRequestList::iterator litr; - int i=0; - for(litr = _dataToCompileList.begin(); - (litr != _dataToCompileList.end()) && (_frameNumber-(*litr)->_frameNumberLastRequest)<=1; - ++litr,i++) - { - //osg::notify(osg::NOTICE)<<"Compile "<<_frameNumber-(*litr)->_frameNumberLastRequest< lock(_childrenToDeleteListMutex); - - for(DatabaseRequestList::iterator ditr=litr; - ditr!=_dataToCompileList.end(); - ++ditr) - { - _childrenToDeleteList.push_back((*ditr)->_loadedModel.get()); - } - } - //osg::notify(osg::NOTICE)<<"Pruning "<<_dataToCompileList.size()-i<get(), state.getContextID()) + || (sharedManager && sharedManager->isShared(itr->get()))) + { + elapsedTime = timer.delta_s(start_tick,timer.tick()); + continue; + } double startCompileTime = timer.delta_s(start_tick,timer.tick()); (*itr)->compileGLObjects(state); @@ -1119,7 +1200,12 @@ void DatabasePager::compileGLObjects(osg::State& state, double& availableTime) ++numObjectsCompiled; } - // remove the compiled stateset from the list. + if (osg::getNotifyLevel() >= osg::DEBUG_INFO + && numObjectsCompiled > objTemp) + osg::notify(osg::DEBUG_INFO)<< _frameNumber << " compiled " + << numObjectsCompiled - objTemp + << " StateSets" << std::endl; + // remove the compiled statesets from the list. sslist.erase(sslist.begin(),itr); } @@ -1129,11 +1215,17 @@ void DatabasePager::compileGLObjects(osg::State& state, double& availableTime) //osg::notify(osg::INFO)<<"Compiling drawables"<get(), state.getContextID())) + { + elapsedTime = timer.delta_s(start_tick,timer.tick()); + continue; + } double startCompileTime = timer.delta_s(start_tick,timer.tick()); (*itr)->compileGLObjects(renderInfo); elapsedTime = timer.delta_s(start_tick,timer.tick()); @@ -1144,6 +1236,11 @@ void DatabasePager::compileGLObjects(osg::State& state, double& availableTime) ++numObjectsCompiled; } + if (osg::getNotifyLevel() >= osg::DEBUG_INFO + && numObjectsCompiled > objTemp) + osg::notify(osg::DEBUG_INFO)<< _frameNumber << " compiled " + << numObjectsCompiled - objTemp + << " Drawables" << std::endl; // remove the compiled drawables from the list. dwlist.erase(dwlist.begin(),itr); } @@ -1151,7 +1248,9 @@ void DatabasePager::compileGLObjects(osg::State& state, double& availableTime) //osg::notify(osg::INFO)<<"Checking if compiled"<second.second.empty())) allCompiled=false; } - + //if (numObjectsCompiled > 0) + //osg::notify(osg::NOTICE)<< _frameNumber << "compiled " << numObjectsCompiled << " objects" << std::endl; + if (allCompiled) { - // we've compile all of the current databaseRequest so we can now pop it off the + // we've compiled all of the current databaseRequest so we can now pop it off the + // to compile list and place it on the merge list. // osg::notify(osg::NOTICE)<<"All compiled"< lock(_dataToCompileListMutex); + // The request might have been removed from the + // _dataToCompile list by another graphics thread, in + // which case it's already on the merge list, or by + // the pager, which means that the request has + // expired. Also, the compile list might have been + // shuffled by the pager, so the current request is + // not guaranteed to still be at the beginning of the + // list. + DatabaseRequestList::iterator requestIter + = find(_dataToCompileList.begin(), _dataToCompileList.end(), + databaseRequest); + if (requestIter != _dataToCompileList.end()) { - OpenThreads::ScopedLock lock(_dataToMergeListMutex); - _dataToMergeList.push_back(databaseRequest); - } - - if (!_dataToCompileList.empty()) _dataToCompileList.erase(_dataToCompileList.begin()); - - if (!_dataToCompileList.empty()) - { - std::sort(_dataToCompileList.begin(),_dataToCompileList.end(),SortFileRequestFunctor()); - databaseRequest = _dataToCompileList.front(); + { + OpenThreads::ScopedLock lock(_dataToMergeListMutex); + _dataToMergeList.push_back(databaseRequest); + } + _dataToCompileList.erase(requestIter); } + + if (!_dataToCompileList.empty()) databaseRequest = _dataToCompileList.front(); else databaseRequest = 0; } diff --git a/src/osgDB/SharedStateManager.cpp b/src/osgDB/SharedStateManager.cpp index 4aff29ec6..3e28f8888 100644 --- a/src/osgDB/SharedStateManager.cpp +++ b/src/osgDB/SharedStateManager.cpp @@ -19,9 +19,9 @@ using namespace osgDB; SharedStateManager::SharedStateManager(): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { - shareMode = SHARE_TEXTURES; - //shareMode = SHARE_STATESETS; - mutex=0; + _shareMode = SHARE_TEXTURES; + //_shareMode = SHARE_STATESETS; + _mutex=0; } //---------------------------------------------------------------- @@ -29,18 +29,23 @@ SharedStateManager::SharedStateManager(): //---------------------------------------------------------------- void SharedStateManager::prune() { - StateSetSet::iterator sitr, sitr1; - for(sitr=_sharedStateSetList.begin(); sitr!=_sharedStateSetList.end(); sitr=sitr1) + StateSetSet::iterator sitr; + OpenThreads::ScopedLock lock(_listMutex); + for(sitr=_sharedStateSetList.begin(); sitr!=_sharedStateSetList.end();) { - sitr1=sitr; ++sitr1; - if((*sitr)->referenceCount()<=1) _sharedStateSetList.erase(sitr); + if ((*sitr)->referenceCount()<=1) + _sharedStateSetList.erase(sitr++); + else + ++sitr; } - TextureSet::iterator titr, titr1; - for(titr=_sharedTextureList.begin(); titr!=_sharedTextureList.end(); titr=titr1) + TextureSet::iterator titr; + for(titr=_sharedTextureList.begin(); titr!=_sharedTextureList.end();) { - titr1=titr; ++titr1; - if((*titr)->referenceCount()<=1) _sharedTextureList.erase(titr); + if ((*titr)->referenceCount()<=1) + _sharedTextureList.erase(titr++); + else + ++titr; } } @@ -54,11 +59,11 @@ void SharedStateManager::share(osg::Node *node, OpenThreads::Mutex *mt) // const osg::Timer& timer = *osg::Timer::instance(); // osg::Timer_t start_tick = timer.tick(); - mutex = mt; + _mutex = mt; apply(*node); tmpSharedTextureList.clear(); tmpSharedStateSetList.clear(); - mutex = 0; + _mutex = 0; // osg::Timer_t end_tick = timer.tick(); // std::cout << "SHARING TIME = "< lock(_listMutex); + return find(ss) != 0; + } + else + return false; +} //---------------------------------------------------------------- // SharedStateManager::find //---------------------------------------------------------------- +// +// The find methods don't need to lock _listMutex because the thread +// from which they are called is doing the writing to the lists. osg::StateSet *SharedStateManager::find(osg::StateSet *ss) { - for(StateSetSet::iterator itr=_sharedStateSetList.begin(); - itr!=_sharedStateSetList.end(); - ++itr) - { - if(ss->compare(*(itr->get()),true)==0) - return (osg::StateSet *)itr->get(); - } - return NULL; + StateSetSet::iterator result + = _sharedStateSetList.find(osg::ref_ptr(ss)); + if (result == _sharedStateSetList.end()) + return NULL; + else + return result->get(); } osg::StateAttribute *SharedStateManager::find(osg::StateAttribute *sa) { - for(TextureSet::iterator itr=_sharedTextureList.begin(); - itr!=_sharedTextureList.end(); - ++itr) - { - if(sa->compare(*(itr->get()))==0) - return (osg::StateAttribute *)itr->get(); - } - return NULL; + TextureSet::iterator result + = _sharedTextureList.find(osg::ref_ptr(sa)); + if (result == _sharedTextureList.end()) + return NULL; + else + return result->get(); } @@ -163,15 +177,17 @@ void SharedStateManager::shareTextures(osg::StateSet* ss) { // Texture is in sharedAttributeList: // Share now. Required to be shared all next times - if(mutex) mutex->lock(); + if(_mutex) _mutex->lock(); ss->setTextureAttributeAndModes(unit, textureFromSharedList, osg::StateAttribute::ON); - if(mutex) mutex->unlock(); + if(_mutex) _mutex->unlock(); tmpSharedTextureList[texture] = TextureSharePair(textureFromSharedList, true); } else { // Texture is not in _sharedAttributeList: - // Add to _sharedAttributeList. Not needed to be shared all next times. + // Add to _sharedAttributeList. Not needed to be + // shared all next times. + OpenThreads::ScopedLock lock(_listMutex); _sharedTextureList.insert(texture); tmpSharedTextureList[texture] = TextureSharePair(texture, false); } @@ -180,9 +196,9 @@ void SharedStateManager::shareTextures(osg::StateSet* ss) { // Texture is in tmpSharedAttributeList and share flag is on: // It should be shared - if(mutex) mutex->lock(); + if(_mutex) _mutex->lock(); ss->setTextureAttributeAndModes(unit, titr->second.first, osg::StateAttribute::ON); - if(mutex) mutex->unlock(); + if(_mutex) _mutex->unlock(); } } } @@ -194,7 +210,7 @@ void SharedStateManager::shareTextures(osg::StateSet* ss) //---------------------------------------------------------------- void SharedStateManager::process(osg::StateSet* ss, osg::Object* parent) { - if(shareMode & SHARE_STATESETS) + if(_shareMode & SHARE_STATESETS) { // Valid StateSet to be shared if(ss->getDataVariance()==osg::Object::STATIC) @@ -209,20 +225,23 @@ void SharedStateManager::process(osg::StateSet* ss, osg::Object* parent) { // StateSet is in sharedStateSetList: // Share now. Required to be shared all next times - if(mutex) mutex->lock(); + if(_mutex) _mutex->lock(); setStateSet(ssFromSharedList, parent); - if(mutex) mutex->unlock(); + if(_mutex) _mutex->unlock(); tmpSharedStateSetList[ss] = StateSetSharePair(ssFromSharedList, true); } else { // StateSet is not in sharedStateSetList: // Add to sharedStateSetList. Not needed to be shared all next times. - _sharedStateSetList.insert(ss); - tmpSharedStateSetList[ss] = StateSetSharePair(ss, false); - + { + OpenThreads::ScopedLock lock(_listMutex); + _sharedStateSetList.insert(ss); + tmpSharedStateSetList[ss] + = StateSetSharePair(ss, false); + } // Only in this case sharing textures is also required - if(shareMode & SHARE_TEXTURES) + if(_shareMode & SHARE_TEXTURES) { shareTextures(ss); } @@ -232,14 +251,14 @@ void SharedStateManager::process(osg::StateSet* ss, osg::Object* parent) { // StateSet is in tmpSharedStateSetList and share flag is on: // It should be shared - if(mutex) mutex->lock(); + if(_mutex) _mutex->lock(); setStateSet(sitr->second.first, parent); - if(mutex) mutex->unlock(); + if(_mutex) _mutex->unlock(); } } } - else if(shareMode & SHARE_TEXTURES) + else if(_shareMode & SHARE_TEXTURES) { shareTextures(ss); }