diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index a3c26aeb59..8345df9265 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include @@ -17,8 +19,137 @@ namespace MWRender { - NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) + struct NavMesh::LessByTilePosition + { + bool operator()(const DetourNavigator::TilePosition& lhs, + const std::pair& rhs) const + { + return lhs < rhs.first; + } + + bool operator()(const std::pair& lhs, + const DetourNavigator::TilePosition& rhs) const + { + return lhs.first < rhs; + } + }; + + struct NavMesh::CreateNavMeshTileGroups final : SceneUtil::WorkItem + { + std::size_t mId; + DetourNavigator::Version mVersion; + const std::weak_ptr mNavMesh; + const osg::ref_ptr mGroupStateSet; + const osg::ref_ptr mDebugDrawStateSet; + const DetourNavigator::Settings mSettings; + std::map mTiles; + std::atomic_bool mAborted {false}; + std::mutex mMutex; + bool mStarted = false; + std::vector> mUpdatedTiles; + std::vector mRemovedTiles; + + explicit CreateNavMeshTileGroups(std::size_t id, DetourNavigator::Version version, + std::weak_ptr navMesh, + const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, + const DetourNavigator::Settings& settings, const std::map& tiles) + : mId(id) + , mVersion(version) + , mNavMesh(navMesh) + , mGroupStateSet(groupStateSet) + , mDebugDrawStateSet(debugDrawStateSet) + , mSettings(settings) + , mTiles(tiles) + { + } + + void doWork() final + { + using DetourNavigator::TilePosition; + using DetourNavigator::Version; + + const std::lock_guard lock(mMutex); + mStarted = true; + + if (mAborted.load(std::memory_order_acquire)) + return; + + const auto navMeshPtr = mNavMesh.lock(); + if (navMeshPtr == nullptr) + return; + + std::vector> existingTiles; + + navMeshPtr->lockConst()->forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& /*meshTile*/) + { + existingTiles.emplace_back(position, version); + }); + + if (mAborted.load(std::memory_order_acquire)) + return; + + std::sort(existingTiles.begin(), existingTiles.end()); + + std::vector removedTiles; + + for (const auto& [position, tile] : mTiles) + if (!std::binary_search(existingTiles.begin(), existingTiles.end(), position, LessByTilePosition {})) + removedTiles.push_back(position); + + std::vector> updatedTiles; + + for (const auto& [position, version] : existingTiles) + { + const auto it = mTiles.find(position); + if (it != mTiles.end() && it->second.mGroup != nullptr && it->second.mVersion == version) + continue; + + osg::ref_ptr group; + { + const auto navMesh = navMeshPtr->lockConst(); + const dtMeshTile* meshTile = DetourNavigator::getTile(navMesh->getImpl(), position); + if (meshTile == nullptr) + continue; + + if (mAborted.load(std::memory_order_acquire)) + return; + + group = SceneUtil::createNavMeshTileGroup(navMesh->getImpl(), *meshTile, mSettings, mGroupStateSet, mDebugDrawStateSet); + } + if (group == nullptr) + { + removedTiles.push_back(position); + continue; + } + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); + group->setNodeMask(Mask_Debug); + updatedTiles.emplace_back(position, Tile {version, std::move(group)}); + } + + if (mAborted.load(std::memory_order_acquire)) + return; + + mUpdatedTiles = std::move(updatedTiles); + mRemovedTiles = std::move(removedTiles); + } + + void abort() final + { + mAborted.store(true, std::memory_order_release); + } + }; + + struct NavMesh::DeallocateCreateNavMeshTileGroups final : SceneUtil::WorkItem + { + osg::ref_ptr mWorkItem; + + explicit DeallocateCreateNavMeshTileGroups(osg::ref_ptr&& workItem) + : mWorkItem(std::move(workItem)) {} + }; + + NavMesh::NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, bool enabled) : mRootNode(root) + , mWorkQueue(workQueue) , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) , mDebugDrawStateSet(SceneUtil::DebugDraw::makeStateSet()) , mEnabled(enabled) @@ -30,6 +161,8 @@ namespace MWRender { if (mEnabled) disable(); + for (const auto& workItem : mWorkItems) + workItem->abort(); } bool NavMesh::toggle() @@ -42,13 +175,69 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, + void NavMesh::update(const std::shared_ptr& navMesh, std::size_t id, const DetourNavigator::Settings& settings) { using DetourNavigator::TilePosition; using DetourNavigator::Version; - if (!mEnabled || (!mTiles.empty() && mId == id && mVersion == navMesh.getVersion())) + if (!mEnabled) + return; + + { + std::pair lastest {0, Version {}}; + osg::ref_ptr latestCandidate; + for (auto it = mWorkItems.begin(); it != mWorkItems.end();) + { + if (!(*it)->isDone()) + { + ++it; + continue; + } + const std::pair order {(*it)->mId, (*it)->mVersion}; + if (lastest < order) + { + lastest = order; + std::swap(latestCandidate, *it); + } + if (*it != nullptr) + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(*it))); + it = mWorkItems.erase(it); + } + + if (latestCandidate != nullptr) + { + for (const TilePosition& position : latestCandidate->mRemovedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + continue; + mRootNode->removeChild(it->second.mGroup); + mTiles.erase(it); + } + + for (auto& [position, tile] : latestCandidate->mUpdatedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + { + mRootNode->addChild(tile.mGroup); + mTiles.emplace_hint(it, position, std::move(tile)); + } + else + { + mRootNode->replaceChild(it->second.mGroup, tile.mGroup); + std::swap(it->second, tile); + } + } + + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(latestCandidate))); + } + } + + const auto version = navMesh->lock()->getVersion(); + + if (!mTiles.empty() && mId == id && mVersion == version) return; if (mId != id) @@ -57,40 +246,35 @@ namespace MWRender mId = id; } - mVersion = navMesh.getVersion(); + mVersion = version; - std::vector updated; - navMesh.forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& meshTile) + for (auto& workItem : mWorkItems) { - updated.push_back(position); - Tile& tile = mTiles[position]; - if (tile.mGroup != nullptr && tile.mVersion == version) - return; - if (tile.mGroup != nullptr) - mRootNode->removeChild(tile.mGroup); - tile.mGroup = SceneUtil::createNavMeshTileGroup(navMesh.getImpl(), meshTile, settings, - mGroupStateSet, mDebugDrawStateSet); - if (tile.mGroup == nullptr) - return; - MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(tile.mGroup, "debug"); - tile.mGroup->setNodeMask(Mask_Debug); - mRootNode->addChild(tile.mGroup); - }); - std::sort(updated.begin(), updated.end()); - for (auto it = mTiles.begin(); it != mTiles.end();) - { - if (!std::binary_search(updated.begin(), updated.end(), it->first)) - { - mRootNode->removeChild(it->second.mGroup); - it = mTiles.erase(it); - } - else - ++it; + const std::unique_lock lock(workItem->mMutex, std::try_to_lock); + + if (!lock.owns_lock()) + continue; + + if (workItem->mStarted) + continue; + + workItem->mId = id; + workItem->mVersion = version; + workItem->mTiles = mTiles; + + return; } + + osg::ref_ptr workItem = new CreateNavMeshTileGroups(id, version, navMesh, mGroupStateSet, mDebugDrawStateSet, settings, mTiles); + mWorkQueue->addWorkItem(workItem); + mWorkItems.push_back(std::move(workItem)); } void NavMesh::reset() { + for (auto& workItem : mWorkItems) + workItem->abort(); + mWorkItems.clear(); for (auto& [position, tile] : mTiles) mRootNode->removeChild(tile.mGroup); mTiles.clear(); @@ -98,15 +282,12 @@ namespace MWRender void NavMesh::enable() { - for (const auto& [position, tile] : mTiles) - mRootNode->addChild(tile.mGroup); mEnabled = true; } void NavMesh::disable() { - for (const auto& [position, tile] : mTiles) - mRootNode->removeChild(tile.mGroup); + reset(); mEnabled = false; } } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index fd69a3e487..1c2cfbd323 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -3,11 +3,14 @@ #include #include +#include #include #include #include +#include +#include class dtNavMesh; @@ -24,18 +27,24 @@ namespace DetourNavigator struct Settings; } +namespace SceneUtil +{ + class WorkQueue; +} + namespace MWRender { class NavMesh { public: - NavMesh(const osg::ref_ptr& root, bool enabled); + explicit NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, + bool enabled); ~NavMesh(); bool toggle(); - void update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, - const DetourNavigator::Settings& settings); + void update(const std::shared_ptr>& navMesh, + std::size_t id, const DetourNavigator::Settings& settings); void reset(); @@ -55,13 +64,19 @@ namespace MWRender osg::ref_ptr mGroup; }; + struct LessByTilePosition; + struct CreateNavMeshTileGroups; + struct DeallocateCreateNavMeshTileGroups; + osg::ref_ptr mRootNode; + osg::ref_ptr mWorkQueue; osg::ref_ptr mGroupStateSet; osg::ref_ptr mDebugDrawStateSet; bool mEnabled; std::size_t mId; DetourNavigator::Version mVersion; std::map mTiles; + std::vector> mWorkItems; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a504a42faf..384580adb8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -384,7 +384,7 @@ namespace MWRender // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); - mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); + mNavMesh.reset(new NavMesh(mRootNode, mWorkQueue, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -1395,7 +1395,7 @@ namespace MWRender { try { - mNavMesh->update(*it->second->lockConst(), mNavMeshNumber, mNavigator.getSettings()); + mNavMesh->update(it->second, mNavMeshNumber, mNavigator.getSettings()); } catch (const std::exception& e) {