diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be3caaeb4..177cee4ea 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -258,16 +258,21 @@ namespace MWRender return mResourceSystem; } - SceneUtil::WorkQueue *RenderingManager::getWorkQueue() + SceneUtil::WorkQueue* RenderingManager::getWorkQueue() { return mWorkQueue.get(); } - SceneUtil::UnrefQueue *RenderingManager::getUnrefQueue() + SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() { return mUnrefQueue.get(); } + Terrain::World* RenderingManager::getTerrain() + { + return mTerrain.get(); + } + void RenderingManager::preloadCommonAssets() { osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); @@ -279,12 +284,6 @@ namespace MWRender mWorkQueue->addWorkItem(workItem); } - void RenderingManager::clearCache() - { - if (mTerrain.get()) - mTerrain->clearCache(); - } - double RenderingManager::getReferenceTime() const { return mViewer->getFrameStamp()->getReferenceTime(); @@ -838,7 +837,7 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { if (mTerrain.get()) - mTerrain->clearCache(); + mTerrain->updateCache(); mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4c850de12..4dda6f273 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -73,11 +73,10 @@ namespace MWRender SceneUtil::WorkQueue* getWorkQueue(); SceneUtil::UnrefQueue* getUnrefQueue(); + Terrain::World* getTerrain(); void preloadCommonAssets(); - void clearCache(); - double getReferenceTime() const; osg::Group* getLightRoot(); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 047c1e99b..e72c36c68 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -44,10 +45,14 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager) - : mSceneManager(sceneManager) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain) + : mIsExterior(cell->getCell()->isExterior()) + , mX(cell->getCell()->getGridX()) + , mY(cell->getCell()->getGridY()) + , mSceneManager(sceneManager) , mBulletShapeManager(bulletShapeManager) , mKeyframeManager(keyframeManager) + , mTerrain(terrain) { osg::Timer timer; ListModelsVisitor visitor (mMeshes); @@ -86,8 +91,8 @@ namespace MWWorld //std::cout << "preloading " << mesh << std::endl; - mPreloadedNodes.push_back(mSceneManager->cacheInstance(mesh)); - mPreloadedShapes.push_back(mBulletShapeManager->cacheInstance(mesh)); + mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); + mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) @@ -99,7 +104,7 @@ namespace MWWorld if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); - mPreloadedKeyframes.push_back(mKeyframeManager->get(kfname)); + mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); } } @@ -111,29 +116,44 @@ namespace MWWorld // error will be shown when visiting the cell } } - std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; + + if (mIsExterior) + { + try + { + mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY)); + } + catch(std::exception& e) + { + } + } + + std::cout << "preloaded " << mPreloadedObjects.size() << " objects in " << preloadTimer.time_m() << std::endl; } private: typedef std::vector MeshList; + bool mIsExterior; + int mX; + int mY; MeshList mMeshes; Resource::SceneManager* mSceneManager; Resource::BulletShapeManager* mBulletShapeManager; Resource::KeyframeManager* mKeyframeManager; + Terrain::World* mTerrain; - // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state - std::vector > mPreloadedNodes; - std::vector > mPreloadedShapes; - std::vector > mPreloadedKeyframes; + // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state + std::vector > mPreloadedObjects; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. class UpdateCacheItem : public SceneUtil::WorkItem { public: - UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime) + UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime) : mReferenceTime(referenceTime) , mResourceSystem(resourceSystem) + , mTerrain(terrain) { } @@ -141,17 +161,21 @@ namespace MWWorld { osg::Timer timer; mResourceSystem->updateCache(mReferenceTime); + + mTerrain->updateCache(); std::cout << "cleared cache in " << timer.time_m() << std::endl; } private: double mReferenceTime; Resource::ResourceSystem* mResourceSystem; + Terrain::World* mTerrain; }; - CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager) + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) + , mTerrain(terrain) , mExpiryDelay(0.0) { } @@ -177,7 +201,7 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager())); + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain)); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); @@ -201,7 +225,7 @@ namespace MWWorld } // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations - mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, timestamp)); + mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp)); } void CellPreloader::setExpiryDelay(double expiryDelay) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 55e40dcbd..32ea194c6 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -11,6 +11,11 @@ namespace Resource class BulletShapeManager; } +namespace Terrain +{ + class World; +} + namespace MWWorld { class CellStore; @@ -18,7 +23,7 @@ namespace MWWorld class CellPreloader { public: - CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager); + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. @@ -37,6 +42,7 @@ namespace MWWorld private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; + Terrain::World* mTerrain; osg::ref_ptr mWorkQueue; double mExpiryDelay; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b0e384493..0be26c7b6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -416,7 +416,6 @@ namespace MWWorld mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); - mRendering.clearCache(); std::cout << "changeCellGrid took " << timer.time_m() << std::endl; } @@ -463,7 +462,7 @@ namespace MWWorld , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) { - mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); + mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); mPhysics->setUnrefQueue(rendering.getUnrefQueue()); @@ -549,7 +548,6 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); - mRendering.clearCache(); std::cout << "change to interior took " << timer.time_m() << std::endl; } diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index e5f6d9f43..0a9784e6b 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -19,11 +19,6 @@ namespace Resource mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); } - void ResourceManager::clearCache() - { - mCache->clear(); - } - void ResourceManager::setExpiryDelay(double expiryDelay) { mExpiryDelay = expiryDelay; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index d2a1d1023..34fce0145 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -22,9 +22,6 @@ namespace Resource /// Clear cache entries that have not been referenced for longer than expiryDelay. virtual void updateCache(double referenceTime); - /// Clear all cache entries regardless of having external references. - virtual void clearCache(); - /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay); diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index f9c860d47..2a72ea59b 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include "defs.hpp" @@ -178,6 +180,7 @@ namespace Terrain osg::ref_ptr BufferCache::getUVBuffer() { + OpenThreads::ScopedLock lock(mUvBufferMutex); if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) { return mUvBufferMap[mNumVerts]; @@ -206,6 +209,7 @@ namespace Terrain osg::ref_ptr BufferCache::getIndexBuffer(unsigned int flags) { + OpenThreads::ScopedLock lock(mIndexBufferMutex); unsigned int verts = mNumVerts; if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index ca210f238..d1a57f811 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -17,8 +17,10 @@ namespace Terrain /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + /// @note Thread safe. osg::ref_ptr getIndexBuffer (unsigned int flags); + /// @note Thread safe. osg::ref_ptr getUVBuffer(); // TODO: add releaseGLObjects() for our vertex/element buffer objects @@ -27,8 +29,10 @@ namespace Terrain // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. std::map > mIndexBufferMap; + OpenThreads::Mutex mIndexBufferMutex; std::map > mUvBufferMap; + OpenThreads::Mutex mUvBufferMutex; unsigned int mNumVerts; }; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4d2f421e6..5b1854abc 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include #include @@ -50,9 +52,9 @@ namespace Terrain TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) + , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) , mUnrefQueue(unrefQueue) { - mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() @@ -63,6 +65,21 @@ TerrainGrid::~TerrainGrid() } } +osg::ref_ptr TerrainGrid::cacheCell(int x, int y) +{ + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + return found->second; + } + osg::ref_ptr node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5)); + + OpenThreads::ScopedLock lock(mGridCacheMutex); + mGridCache.insert(std::make_pair(std::make_pair(x,y), node)); + return node; +} + osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { if (chunkSize * mNumSplits > 1.f) @@ -131,19 +148,22 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu unsigned int dummyTextureCounter = 0; std::vector > layerTextures; - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; - if (!texture) + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(texture); - mTextureCache[it->mDiffuseMap] = texture; + osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mDiffuseMap] = texture; + } + layerTextures.push_back(texture); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } - layerTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } std::vector > blendmapTextures; @@ -196,11 +216,27 @@ void TerrainGrid::loadCell(int x, int y) if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded - osg::Vec2f center(x+0.5f, y+0.5f); + // try to get it from the cache + osg::ref_ptr terrainNode; + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::const_iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + { + terrainNode = found->second; + if (!terrainNode) + return; // no terrain defined + } + } - osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + // didn't find in cache, build it if (!terrainNode) - return; // no terrain defined + { + osg::Vec2f center(x+0.5f, y+0.5f); + terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) + return; // no terrain defined + } mTerrainRoot->addChild(terrainNode); @@ -213,7 +249,7 @@ void TerrainGrid::unloadCell(int x, int y) if (it == mGrid.end()) return; - osg::Node* terrainNode = it->second; + osg::ref_ptr terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); if (mUnrefQueue.get()) @@ -222,18 +258,28 @@ void TerrainGrid::unloadCell(int x, int y) mGrid.erase(it); } -void TerrainGrid::clearCache() +void TerrainGrid::updateCache() { - for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { - if (it->second->referenceCount() <= 1) + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { - if (mUnrefQueue.get()) - mUnrefQueue->push(it->second); - mTextureCache.erase(it++); + if (it->second->referenceCount() <= 1) + mTextureCache.erase(it++); + else + ++it; + } + } + + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();) + { + if (it->second->referenceCount() <= 1) + mGridCache.erase(it++); + else + ++it; } - else - ++it; } } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 4e6a78abd..46a95e817 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -21,11 +21,20 @@ namespace Terrain TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); + /// Load a terrain cell and store it in cache for later use. + /// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed. + /// @note Thread safe. + virtual osg::ref_ptr cacheCell(int x, int y); + + /// @note Not thread safe. virtual void loadCell(int x, int y); + + /// @note Not thread safe. virtual void unloadCell(int x, int y); - /// Clear cached textures that are no longer referenced - void clearCache(); + /// Clear cached objects that are no longer referenced + /// @note Thread safe. + void updateCache(); private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); @@ -35,10 +44,16 @@ namespace Terrain typedef std::map > TextureCache; TextureCache mTextureCache; + OpenThreads::Mutex mTextureCacheMutex; typedef std::map, osg::ref_ptr > Grid; Grid mGrid; + Grid mGridCache; + OpenThreads::Mutex mGridCacheMutex; + + BufferCache mCache; + osg::ref_ptr mUnrefQueue; }; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2250b593d..b56e87e1d 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -11,7 +11,6 @@ namespace Terrain World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask) : mStorage(storage) - , mCache(storage->getCellVertices()) , mParent(parent) , mResourceSystem(resourceSystem) , mIncrementalCompileOperation(ico) diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index bd5d0a466..992438a6a 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,10 +39,12 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); - virtual void clearCache() {} + virtual void updateCache() {} float getHeightAt (const osg::Vec3f& worldPos); + virtual osg::ref_ptr cacheCell(int x, int y) {return NULL;} + // This is only a hint and may be ignored by the implementation. virtual void loadCell(int x, int y) {} virtual void unloadCell(int x, int y) {} @@ -52,8 +54,6 @@ namespace Terrain protected: Storage* mStorage; - BufferCache mCache; - osg::ref_ptr mParent; osg::ref_ptr mTerrainRoot;