From 4f70d8c830c6213bafcbf40f5ff7eb72840739cc Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Fri, 2 Jan 2026 21:34:34 -0500 Subject: secondlife/viewer#2462: Optimize unloading of prims Signed-off-by: Rye --- indra/newview/llmeshrepository.cpp | 81 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index c0b1a5326a..c3513229c6 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -4271,20 +4271,95 @@ S32 LLMeshRepository::update() return static_cast(size); } -void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) +#ifdef SHOW_ASSERT +// Brute-force remove the object from all loading queues. Returns true if +// something was removed. +// This function is used in a debug assert to ensure unregisterMesh and +// unregisterSkinInfo are called as intended. +// *TODO: Consider removing at some point if we feel confident about the code +// working as intended. +bool LLMeshRepository::forceUnregisterMesh(LLVOVolume* vobj) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + bool found = false; + for (auto& lod : mLoadingMeshes) { for (auto& param : lod) { - vector_replace_with_last(param.second.mVolumes, vobj); + llassert(std::find(param.second.mVolumes.begin(), param.second.mVolumes.end(), vobj) == param.second.mVolumes.end()); + found = found || vector_replace_with_last(param.second.mVolumes, vobj); } } for (auto& skin_pair : mLoadingSkins) { - vector_replace_with_last(skin_pair.second.mVolumes, vobj); + llassert(std::find(skin_pair.mVolumes.second.begin(), skin_pair.mVolumes.second.end(), vobj) == skin_pair.mVolumes.second.end()); + found = found || vector_replace_with_last(skin_pair.second.mVolumes, vobj); + } + + return found; +} +#endif + +void LLMeshRepository::unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + llassert((mesh_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH); + llassert(mesh_params.getSculptID().notNull()); + auto& lod = mLoadingMeshes[detail]; + auto param_iter = lod.find(mesh_params.getSculptID()); + if (param_iter != lod.end()) + { + vector_replace_with_last(param_iter->second.mVolumes, vobj); + llassert(!vector_replace_with_last(param_iter->second.mVolumes, vobj)); + if (param_iter->second.mVolumes.empty()) + { + lod.erase(param_iter); + } + } +} + +void LLMeshRepository::unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vobj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + llassert(mesh_id.notNull()); + auto skin_pair_iter = mLoadingSkins.find(mesh_id); + if (skin_pair_iter != mLoadingSkins.end()) + { + vector_replace_with_last(skin_pair_iter->second.mVolumes, vobj); + llassert(!vector_replace_with_last(skin_pair_iter->second.mVolumes, vobj)); + if (skin_pair_iter->second.mVolumes.empty()) + { + mLoadingSkins.erase(skin_pair_iter); + } + } +} + +// Lots of dead objects make expensive calls to +// LLMeshRepository::unregisterMesh which may delay shutdown. Avoid this by +// preemptively unregistering all meshes. +// We can also do this safely if all objects are confirmed dead for some other +// reason. +void LLMeshRepository::unregisterAllMeshes() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + // The size of mLoadingMeshes and mLoadingSkins may be large and thus + // expensive to iterate over in LLVOVolume::~LLVOVolume. + // This is unnecessary during shutdown, so we ignore the referenced objects in the + // least expensive way which is still safe: by clearing these containers. + // Clear now and not in LLMeshRepository::shutdown because + // LLMeshRepository::notifyLoadedMeshes could (depending on invocation + // order) reference a pointer to an object after it has been deleted. + for (auto& lod : mLoadingMeshes) + { + lod.clear(); } + mLoadingSkins.clear(); } S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 new_lod, S32 last_lod) -- cgit v1.3 From 24dccc4015a291b819c2a4bb0451c8898e5a3cc2 Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Thu, 19 Sep 2024 17:34:28 -0700 Subject: secondlife/viewer#2623: Remove assert Signed-off-by: Rye --- indra/newview/llmeshrepository.cpp | 32 -------------------------------- indra/newview/llmeshrepository.h | 3 --- indra/newview/llvovolume.cpp | 1 - 3 files changed, 36 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index c3513229c6..8ee5a4f028 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -4271,38 +4271,6 @@ S32 LLMeshRepository::update() return static_cast(size); } -#ifdef SHOW_ASSERT -// Brute-force remove the object from all loading queues. Returns true if -// something was removed. -// This function is used in a debug assert to ensure unregisterMesh and -// unregisterSkinInfo are called as intended. -// *TODO: Consider removing at some point if we feel confident about the code -// working as intended. -bool LLMeshRepository::forceUnregisterMesh(LLVOVolume* vobj) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - bool found = false; - - for (auto& lod : mLoadingMeshes) - { - for (auto& param : lod) - { - llassert(std::find(param.second.mVolumes.begin(), param.second.mVolumes.end(), vobj) == param.second.mVolumes.end()); - found = found || vector_replace_with_last(param.second.mVolumes, vobj); - } - } - - for (auto& skin_pair : mLoadingSkins) - { - llassert(std::find(skin_pair.mVolumes.second.begin(), skin_pair.mVolumes.second.end(), vobj) == skin_pair.mVolumes.second.end()); - found = found || vector_replace_with_last(skin_pair.second.mVolumes, vobj); - } - - return found; -} -#endif - void LLMeshRepository::unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 886ef70a07..b0dd8d7ba1 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -866,9 +866,6 @@ public: void shutdown(); S32 update(); -#ifdef SHOW_ASSERT - bool forceUnregisterMesh(LLVOVolume* volume); -#endif void unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail); void unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vobj); //mesh management functions diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index c68ed01cc1..b328d3a414 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -251,7 +251,6 @@ LLVOVolume::~LLVOVolume() mVolumeImpl = NULL; unregisterOldMeshAndSkin(); - llassert(!gMeshRepo.forceUnregisterMesh(this)); if(!mMediaImplList.empty()) { -- cgit v1.3 From 23bfecf0b1dd92efcd743ec00637cd1122220fb9 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 2 Jan 2026 09:50:34 -0500 Subject: Further optimize unregistration of loading meshes via std::unordered_set This container type is better suited to the repeated find and erase patterns used here. Signed-off-by: Rye --- indra/newview/llmeshrepository.cpp | 12 ++++++------ indra/newview/llmeshrepository.h | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 8ee5a4f028..ed342935ad 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -4281,8 +4281,8 @@ void LLMeshRepository::unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& me auto param_iter = lod.find(mesh_params.getSculptID()); if (param_iter != lod.end()) { - vector_replace_with_last(param_iter->second.mVolumes, vobj); - llassert(!vector_replace_with_last(param_iter->second.mVolumes, vobj)); + param_iter->second.mVolumes.erase(vobj); + llassert(!param_iter->second.mVolumes.contains(vobj)); if (param_iter->second.mVolumes.empty()) { lod.erase(param_iter); @@ -4298,8 +4298,8 @@ void LLMeshRepository::unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vob auto skin_pair_iter = mLoadingSkins.find(mesh_id); if (skin_pair_iter != mLoadingSkins.end()) { - vector_replace_with_last(skin_pair_iter->second.mVolumes, vobj); - llassert(!vector_replace_with_last(skin_pair_iter->second.mVolumes, vobj)); + skin_pair_iter->second.mVolumes.erase(vobj); + llassert(!skin_pair_iter->second.mVolumes.contains(vobj)); if (skin_pair_iter->second.mVolumes.empty()) { mLoadingSkins.erase(skin_pair_iter); @@ -4349,7 +4349,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para mesh_load_map::iterator iter = mLoadingMeshes[new_lod].find(mesh_id); if (iter != mLoadingMeshes[new_lod].end()) { //request pending for this mesh, append volume id to list - auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), vobj); + auto it = iter->second.mVolumes.find(vobj); if (it == iter->second.mVolumes.end()) { iter->second.addVolume(vobj); } @@ -4847,7 +4847,7 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOV skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); if (iter != mLoadingSkins.end()) { //request pending for this mesh, append volume id to list - auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), requesting_obj); + auto it = iter->second.mVolumes.find(requesting_obj); if (it == iter->second.mVolumes.end()) { iter->second.addVolume(requesting_obj); } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index b0dd8d7ba1..56ad8297c7 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -290,7 +290,7 @@ private: class MeshLoadData { public: - MeshLoadData() {} + MeshLoadData() = default; ~MeshLoadData() { if (std::shared_ptr request = mRequest.lock()) @@ -300,19 +300,19 @@ public: } void initData(LLVOVolume* vol, std::shared_ptr& request) { - mVolumes.push_back(vol); + mVolumes.insert(vol); request->trackData(this); mRequest = request; } void addVolume(LLVOVolume* vol) { - mVolumes.push_back(vol); + mVolumes.insert(vol); if (std::shared_ptr request = mRequest.lock()) { request->setScoreDirty(); } } - std::vector mVolumes; + std::unordered_set mVolumes; private: std::weak_ptr mRequest; }; -- cgit v1.3 From b9fd20501e2cff8831832f00d7ca9fcfc0d9e801 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:37:46 +0200 Subject: #5356 Fix fast texture cache's mutex stall #2 --- indra/newview/llmeshrepository.cpp | 5 +++++ indra/newview/lltexturecache.cpp | 6 ++++-- indra/newview/lltexturecache.h | 4 ++-- indra/newview/llviewertexturelist.cpp | 20 ++++++++++++-------- 4 files changed, 23 insertions(+), 12 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index ed342935ad..20bda5039d 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -3331,6 +3331,8 @@ void LLMeshRepoThread::notifyLoadedMeshes() loaded_queue.swap(mLoadedQ); mLoadedMutex->unlock(); + LL_PROFILE_ZONE_NAMED("notify loaded meshes"); + update_metrics = true; // Process the elements free of the lock @@ -3362,6 +3364,8 @@ void LLMeshRepoThread::notifyLoadedMeshes() unavil_queue.swap(mUnavailableQ); mLoadedMutex->unlock(); + LL_PROFILE_ZONE_NAMED("notify unavail meshes"); + update_metrics = true; // Process the elements free of the lock @@ -3380,6 +3384,7 @@ void LLMeshRepoThread::notifyLoadedMeshes() { if (mLoadedMutex->trylock()) { + LL_PROFILE_ZONE_NAMED("notify misc meshes"); std::deque> skin_info_q; std::deque skin_info_unavail_q; std::list decomp_q; diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 2e7f353e03..8c8734b52f 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -2048,7 +2048,7 @@ LLPointer LLTextureCache::readFromFastCache(const LLUUID& id, S32& d LL_PROFILE_ZONE_NAMED("Read fast cache"); LLMutexLock lock(&mFastCacheMutex); - openFastCache(); + openFastCache(); // only reopens if needed, lasts 10 seconds mFastCachep->seek(APR_SET, offset); @@ -2079,7 +2079,9 @@ LLPointer LLTextureCache::readFromFastCache(const LLUUID& id, S32& d closeFastCache(); } - LLPointer raw = new LLImageRaw(data, head[0], head[1], head[2], true); + + // directly construct image from new buffer. + LLPointer raw = new LLImageRaw(data, head[0], head[1], head[2], true /*take ownership*/); return raw; } diff --git a/indra/newview/lltexturecache.h b/indra/newview/lltexturecache.h index 42894383fd..a09bcc1572 100644 --- a/indra/newview/lltexturecache.h +++ b/indra/newview/lltexturecache.h @@ -148,7 +148,7 @@ public: U32 getMaxEntries() { return sCacheMaxEntries; }; bool isInCache(const LLUUID& id) ; bool isInLocal(const LLUUID& id) ; //not thread safe at the moment - + LLMutex* getFastCacheMutex() { return &mFastCacheMutex; } protected: // Accessed by LLTextureCacheWorker std::string getLocalFileName(const LLUUID& id); @@ -194,7 +194,7 @@ private: // Internal LLMutex mWorkersMutex; LLMutex mHeaderMutex; - LLMutex mHeaderIDMapMutex; + LLMutex mHeaderIDMapMutex; // To avoid deadlocks, never lock mFastCacheMutex after mHeaderIDMapMutex. LLMutex mListMutex; LLMutex mFastCacheMutex; LLAPRFile* mHeaderAPRFile; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 11ca3098fd..96962bbeae 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1191,15 +1191,19 @@ F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) LLTimer timer; image_list_t::iterator enditer = mFastCacheList.begin(); - for (image_list_t::iterator iter = mFastCacheList.begin(); - iter != mFastCacheList.end();) { - image_list_t::iterator curiter = iter++; - enditer = iter; - LLViewerFetchedTexture *imagep = *curiter; - imagep->loadFromFastCache(); - if (timer.getElapsedTimeF32() > max_time) - break; + // prelock fast cache mutex to avoid waiting multiple times. + LLMutexLock cache_lock(LLAppViewer::getTextureCache()->getFastCacheMutex()); + for (image_list_t::iterator iter = mFastCacheList.begin(); + iter != mFastCacheList.end();) + { + image_list_t::iterator curiter = iter++; + enditer = iter; + LLViewerFetchedTexture* imagep = *curiter; + imagep->loadFromFastCache(); + if (timer.getElapsedTimeF32() > max_time) + break; + } } mFastCacheList.erase(mFastCacheList.begin(), enditer); return timer.getElapsedTimeF32(); -- cgit v1.3