summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCosmic Linden <cosmic@lindenlab.com>2026-01-02 21:34:34 -0500
committerAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-01-05 20:45:57 +0200
commit4f70d8c830c6213bafcbf40f5ff7eb72840739cc (patch)
treeedc9d8f247c1de858ef5c1d3c2e7eb26c1661af1
parent11e16d5ae73ec7dd0a738cb5ee2a8d2457176409 (diff)
secondlife/viewer#2462: Optimize unloading of prims
Signed-off-by: Rye <rye@alchemyviewer.org>
-rw-r--r--indra/newview/llmeshrepository.cpp81
-rw-r--r--indra/newview/llmeshrepository.h7
-rw-r--r--indra/newview/llviewerobjectlist.cpp3
-rw-r--r--indra/newview/llvovolume.cpp27
-rw-r--r--indra/newview/llvovolume.h1
5 files changed, 114 insertions, 5 deletions
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<S32>(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)
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 01b51e753e..886ef70a07 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -862,10 +862,15 @@ public:
LLMeshRepository();
void init();
+ void unregisterAllMeshes();
void shutdown();
S32 update();
- void unregisterMesh(LLVOVolume* volume);
+#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
S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 new_lod = 0, S32 last_lod = -1);
diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp
index 1b38fed3bb..50edb408c7 100644
--- a/indra/newview/llviewerobjectlist.cpp
+++ b/indra/newview/llviewerobjectlist.cpp
@@ -65,6 +65,7 @@
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "llkeyboard.h"
+#include "llmeshrepository.h"
#include "u64.h"
#include "llviewertexturelist.h"
#include "lldatapacker.h"
@@ -1398,6 +1399,8 @@ void LLViewerObjectList::killAllObjects()
llassert((objectp == gAgentAvatarp) || objectp->isDead());
}
+ gMeshRepo.unregisterAllMeshes();
+
cleanDeadObjects(false);
if(!mObjects.empty())
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index d1318018c9..c68ed01cc1 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -250,10 +250,13 @@ LLVOVolume::~LLVOVolume()
delete mVolumeImpl;
mVolumeImpl = NULL;
- gMeshRepo.unregisterMesh(this);
+ unregisterOldMeshAndSkin();
+ llassert(!gMeshRepo.forceUnregisterMesh(this));
if(!mMediaImplList.empty())
{
+ LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("delete volume media list");
+
for(U32 i = 0 ; i < mMediaImplList.size() ; i++)
{
if(mMediaImplList[i].notNull())
@@ -1048,6 +1051,28 @@ LLDrawable *LLVOVolume::createDrawable(LLPipeline *pipeline)
return mDrawable;
}
+// Inverse of gMeshRepo.loadMesh and gMeshRepo.getSkinInfo, combined into one function
+// Assume a Collada mesh never changes after being set.
+void LLVOVolume::unregisterOldMeshAndSkin()
+{
+ if (mVolumep)
+ {
+ const LLVolumeParams& params = mVolumep->getParams();
+ if ((params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH)
+ {
+ // object is being deleted, so it will no longer need to request
+ // meshes.
+ for (S32 lod = 0; lod != LLVolumeLODGroup::NUM_LODS; ++lod)
+ {
+ gMeshRepo.unregisterMesh(this, params, lod);
+ }
+ // This volume may or may not have a skin
+ gMeshRepo.unregisterSkinInfo(params.getSculptID(), this);
+ }
+ }
+}
+
+
bool LLVOVolume::setVolume(const LLVolumeParams &params_in, const S32 detail, bool unique_volume)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index 48f999c5d3..b6044bc319 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -227,6 +227,7 @@ public:
void setTexture(const S32 face);
S32 getIndexInTex(U32 ch) const {return mIndexInTex[ch];}
+ void unregisterOldMeshAndSkin();
/*virtual*/ bool setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume = false) override;
void updateSculptTexture();
void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;}