diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9d435a6123..569f40483c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1481,9 +1481,15 @@ namespace MWRender { mTerrain->setActiveGrid(grid); } - void RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) + bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { + if (!ptr.isInCell() || !ptr.getCell()->isExterior()) + return false; if (mObjectPaging && mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getRefData().getPosition().asVec3(), enabled)) - mTerrain->clearCachedViews(ptr.getRefData().getPosition().asVec3()); + { + mTerrain->rebuildViews(); + return true; + } + return false; } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 36df09202d..b45e3c8dc8 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -241,7 +241,7 @@ namespace MWRender void setActiveGrid(const osg::Vec4i &grid); - void pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); + bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); private: void updateProjectionMatrix(); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 8f61673b34..4280e1e681 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -182,10 +182,12 @@ namespace MWWorld { } - void storeViews(double referenceTime) + bool storeViews(double referenceTime) { for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime); + if (!mWorld->storeView(mTerrainViews[i], referenceTime)) + return false; + return true; } virtual void doWork() @@ -244,6 +246,7 @@ namespace MWWorld , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) + , mStoreViewsFailCount(0) { } @@ -379,7 +382,17 @@ namespace MWWorld if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) { - mTerrainPreloadItem->storeViews(timestamp); + if (!mTerrainPreloadItem->storeViews(timestamp)) + { + if (++mStoreViewsFailCount > 100) + { + OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; + mStoreViewsFailCount = 0; + } + setTerrainPreloadPositions(std::vector()); + } + else + mStoreViewsFailCount = 0; mTerrainPreloadItem = nullptr; } } @@ -451,11 +464,31 @@ namespace MWWorld } } + bool contains(const std::vector& container, const std::vector& contained) + { + for (auto pos : contained) + { + bool found = false; + for (auto pos2 : container) + { + if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } + void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { - if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) + if (positions.empty()) + mTerrainPreloadPositions.clear(); + else if (contains(mTerrainPreloadPositions, positions)) return; - else if (positions == mTerrainPreloadPositions) + if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { @@ -472,7 +505,6 @@ namespace MWWorld } mTerrainPreloadPositions = positions; - if (!positions.empty()) { mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 386fee2932..c170787357 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -88,6 +88,7 @@ namespace MWWorld bool mPreloadInstances; double mLastResourceCacheUpdate; + int mStoreViewsFailCount; struct PreloadEntry { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f8c16ca370..36f877bee2 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1135,6 +1135,11 @@ namespace MWWorld mPreloader->setTerrainPreloadPositions(vec); } + void Scene::reloadTerrain() + { + mPreloader->setTerrainPreloadPositions(std::vector()); + } + struct ListFastTravelDestinationsVisitor { ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 349dce4239..5727f13919 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -115,6 +115,7 @@ namespace MWWorld void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); void preloadTerrain(const osg::Vec3f& pos); + void reloadTerrain(); void unloadCell (CellStoreCollection::iterator iter, bool test = false); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fdb54fb756..8392a827cf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -817,7 +817,8 @@ namespace MWWorld if (reference.getCellRef().getRefNum().hasContentFile()) { int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); - mRendering->pagingEnableObject(type, reference, true); + if (mRendering->pagingEnableObject(type, reference, true)) + mWorldScene->reloadTerrain(); } } } @@ -858,7 +859,8 @@ namespace MWWorld if (reference.getCellRef().getRefNum().hasContentFile()) { int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); - mRendering->pagingEnableObject(type, reference, false); + if (mRendering->pagingEnableObject(type, reference, false)) + mWorldScene->reloadTerrain(); } if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 74abea362f..147ca8c8a8 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -53,7 +53,6 @@ namespace Terrain virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; - class ViewDataMap; class ViewData; class QuadTreeNode : public osg::Group diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 92bd09bd0f..c7544dbe29 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -253,7 +253,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour QuadTreeWorld::~QuadTreeWorld() { - mViewDataMap->clear(); } /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. @@ -279,7 +278,7 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewData* vd) +unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -506,7 +505,7 @@ void QuadTreeWorld::enable(bool enabled) View* QuadTreeWorld::createView() { - return new ViewData; + return mViewDataMap->createIndependentView(); } void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, std::atomic &progress, int& progressTotal) @@ -533,14 +532,9 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: vd->markUnchanged(); } -void QuadTreeWorld::storeView(const View* view, double referenceTime) +bool QuadTreeWorld::storeView(const View* view, double referenceTime) { - osg::ref_ptr dummy = new osg::DummyObject; - const ViewData* vd = static_cast(view); - bool needsUpdate = false; - ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), vd->getActiveGrid(), needsUpdate); - stored->copyFrom(*vd); - stored->setLastUsageTimeStamp(referenceTime); + return mViewDataMap->storeView(static_cast(view), referenceTime); } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) @@ -574,11 +568,9 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); } -void QuadTreeWorld::clearCachedViews(const osg::Vec3f &pos) +void QuadTreeWorld::rebuildViews() { -//mViewDataMap->clear(); - osg::Vec3 pos_ = pos / mStorage->getCellWorldSize(); - mViewDataMap->clearCachedViews(pos_); + mViewDataMap->rebuildViews(); } } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 7c01f5c27e..0cd2526dec 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -39,8 +39,8 @@ namespace Terrain View* createView(); void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange); - void storeView(const View* view, double referenceTime); - void clearCachedViews(const osg::Vec3f& pos) override; + bool storeView(const View* view, double referenceTime); + void rebuildViews() override; void reportStats(unsigned int frameNumber, osg::Stats* stats); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index e5e856cadf..8a87690005 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -10,6 +10,7 @@ ViewData::ViewData() , mLastUsageTimeStamp(0.0) , mChanged(false) , mHasViewPoint(false) + , mWorldUpdateRevision(0) { } @@ -27,6 +28,7 @@ void ViewData::copyFrom(const ViewData& other) mHasViewPoint = other.mHasViewPoint; mViewPoint = other.mViewPoint; mActiveGrid = other.mActiveGrid; + mWorldUpdateRevision = other.mWorldUpdateRevision; } void ViewData::add(QuadTreeNode *node) @@ -93,7 +95,12 @@ void ViewData::clear() mHasViewPoint = false; } -bool ViewData::contains(QuadTreeNode *node) +bool ViewData::suitableToUse(const osg::Vec4i &activeGrid) const +{ + return hasViewPoint() && activeGrid == mActiveGrid && getNumEntries(); +} + +bool ViewData::contains(QuadTreeNode *node) const { for (unsigned int i=0; i= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize); -} - -void ViewData::clearCache(const osg::Vec3f &cellPos) -{ - for (Entry& entry : mEntries) - if (entry.mNode && intersects(entry.mNode->getCenter(), entry.mNode->getSize()/2.f, cellPos)) - entry.mRenderingNode = nullptr; -} - ViewData::Entry::Entry() : mNode(nullptr) , mLodFlags(0) @@ -133,86 +128,106 @@ bool ViewData::Entry::set(QuadTreeNode *node) } } -bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist, const osg::Vec4i& activeGrid) -{ - return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist && vd->getActiveGrid() == activeGrid; -} - ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate) { - Map::const_iterator found = mViews.find(viewer); + ViewerMap::const_iterator found = mViewers.find(viewer); ViewData* vd = nullptr; - if (found == mViews.end()) + if (found == mViewers.end()) { vd = createOrReuseView(); - mViews[viewer] = vd; + mViewers[viewer] = vd; } else vd = found->second; + needsUpdate = false; - if (!suitable(vd, viewPoint, mReuseDistance, activeGrid)) + if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other) + float shortestDist = std::numeric_limits::max(); + const ViewData* mostSuitableView = nullptr; + for (const ViewData& other : mViewVector) { - if (suitable(other->second, viewPoint, mReuseDistance, activeGrid) && other->second->getNumEntries()) + if (other.suitableToUse(activeGrid) && other.getWorldUpdateRevision() >= mWorldUpdateRevision) { - vd->copyFrom(*other->second); - needsUpdate = false; - return vd; + float dist = (viewPoint-other.getViewPoint()).length2(); + if (dist < shortestDist) + { + shortestDist = dist; + mostSuitableView = &other; + } } } + if (mostSuitableView && mostSuitableView != vd) + { + vd->copyFrom(*mostSuitableView); + return vd; + } + } + if (!vd->suitableToUse(activeGrid)) + { vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); needsUpdate = true; } - else - needsUpdate = false; - return vd; } +bool ViewDataMap::storeView(const ViewData* view, double referenceTime) +{ + if (view->getWorldUpdateRevision() < mWorldUpdateRevision) + return false; + ViewData* store = createOrReuseView(); + store->copyFrom(*view); + store->setLastUsageTimeStamp(referenceTime); + return true; +} + ViewData *ViewDataMap::createOrReuseView() { + ViewData* vd = nullptr; if (mUnusedViews.size()) { - ViewData* vd = mUnusedViews.front(); + vd = mUnusedViews.front(); mUnusedViews.pop_front(); - return vd; } else { mViewVector.push_back(ViewData()); - return &mViewVector.back(); + vd = &mViewVector.back(); } + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; +} + +ViewData *ViewDataMap::createIndependentView() const +{ + ViewData* vd = new ViewData; + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; } void ViewDataMap::clearUnusedViews(double referenceTime) { - for (Map::iterator it = mViews.begin(); it != mViews.end(); ) + for (ViewerMap::iterator it = mViewers.begin(); it != mViewers.end(); ) { - ViewData* vd = it->second; - if (vd->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) - { - vd->clear(); - mUnusedViews.push_back(vd); - mViews.erase(it++); - } + if (it->second->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + mViewers.erase(it++); else ++it; } + for (ViewData& vd : mViewVector) + { + if (vd.getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + { + vd.clear(); + mUnusedViews.push_back(&vd); + } + } } -void ViewDataMap::clearCachedViews(const osg::Vec3f &cellPos) +void ViewDataMap::rebuildViews() { - for (auto pair : mViews) - pair.second->clearCache(cellPos); -} - -void ViewDataMap::clear() -{ - mViews.clear(); - mUnusedViews.clear(); - mViewVector.clear(); + ++mWorldUpdateRevision; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index f21f56ae11..ba4fba3b2a 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -23,10 +23,11 @@ namespace Terrain void reset(); - void clear(); - void clearCache(const osg::Vec3f &cellPos); + bool suitableToUse(const osg::Vec4i& activeGrid) const; - bool contains(QuadTreeNode* node); + void clear(); + + bool contains(QuadTreeNode* node) const; void copyFrom(const ViewData& other); @@ -61,6 +62,9 @@ namespace Terrain void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} } const osg::Vec4i &getActiveGrid() const { return mActiveGrid;} + unsigned int getWorldUpdateRevision() const { return mWorldUpdateRevision; } + void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; } + private: std::vector mEntries; unsigned int mNumEntries; @@ -69,35 +73,39 @@ namespace Terrain osg::Vec3f mViewPoint; bool mHasViewPoint; osg::Vec4i mActiveGrid; + unsigned int mWorldUpdateRevision; }; class ViewDataMap : public osg::Referenced { public: ViewDataMap() - : mReuseDistance(300) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. + : mReuseDistance(150) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. // this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time. , mExpiryDelay(1.f) + , mWorldUpdateRevision(0) {} ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate); ViewData* createOrReuseView(); + ViewData* createIndependentView() const; void clearUnusedViews(double referenceTime); - void clearCachedViews(const osg::Vec3f &cellPos); - - void clear(); + void rebuildViews(); + bool storeView(const ViewData* view, double referenceTime); private: std::list mViewVector; - typedef std::map, ViewData*> Map; - Map mViews; + typedef std::map, ViewData*> ViewerMap; + ViewerMap mViewers; float mReuseDistance; float mExpiryDelay; // time in seconds for unused view to be removed + unsigned int mWorldUpdateRevision; + std::deque mUnusedViews; }; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 4ad4e5f0ac..9f08454c8c 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -151,9 +151,9 @@ namespace Terrain /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. - virtual void storeView(const View* view, double referenceTime) {} + virtual bool storeView(const View* view, double referenceTime) {return true;} - virtual void clearCachedViews(const osg::Vec3f& pos) {} + virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}