diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h
index 838d7d34c069271a4a81a13f4967e439e6eaaf93..0d70da6178c1a80ef8ad07976e14845c6c886cec 100644
--- a/indra/llcommon/llmutex.h
+++ b/indra/llcommon/llmutex.h
@@ -36,7 +36,8 @@
 
 //============================================================================
 
-#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
+//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
+#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
 
 #if MUTEX_DEBUG
 #include <map>
@@ -61,7 +62,7 @@ class LL_COMMON_API LLMutex
 	mutable LLThread::id_t	mLockingThread;
 	
 #if MUTEX_DEBUG
-	std::map<LLThread::id_t, BOOL> mIsLocked;
+	std::unordered_map<LLThread::id_t, BOOL> mIsLocked;
 #endif
 };
 
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index 2a906c8d4118c004fbeeaf1d309743de429f69a2..b6cdcb2736ce8a31a34d1a09416a4257d5296d57 100644
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -3351,12 +3351,12 @@ BOOL LLVolume::isFlat(S32 face)
 
 bool LLVolumeParams::isSculpt() const
 {
-	return mSculptID.notNull();
+    return (mSculptType & LL_SCULPT_TYPE_MASK) != LL_SCULPT_TYPE_NONE;
 }
 
 bool LLVolumeParams::isMeshSculpt() const
 {
-	return isSculpt() && ((mSculptType & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH);
+	return (mSculptType & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH;
 }
 
 bool LLVolumeParams::operator==(const LLVolumeParams &params) const
@@ -3771,6 +3771,7 @@ bool LLVolumeParams::validate(U8 prof_curve, F32 prof_begin, F32 prof_end, F32 h
 void LLVolume::getLoDTriangleCounts(const LLVolumeParams& params, S32* counts)
 { //attempt to approximate the number of triangles that will result from generating a volume LoD set for the 
 	//supplied LLVolumeParams -- inaccurate, but a close enough approximation for determining streaming cost
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
 	F32 detail[] = {1.f, 1.5f, 2.5f, 4.f};	
 	for (S32 i = 0; i < 4; i++)
 	{
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index 0bd65fadef40646fafe5452339ae740d261e989d..ad7784f6d1f41ca6874faf99b393f6f3f30ad89b 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -167,6 +167,7 @@ class LLGLTFMaterial : public LLRefCount
     
     // set the contents of this LLGLTFMaterial from the given json
     // returns true if successful
+    // if unsuccessful, the contents of this LLGLTFMaterial should be left unchanged and false is returned
     // json - the json text to load from
     // warn_msg - warning message from TinyGLTF if any
     // error_msg - error_msg from TinyGLTF if any
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index 151d7fa96943c22130284895b0bd54db484e122f..ed16a1cf7ac40ef1e7cc9780d214e69f226a632d 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -219,40 +219,36 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
         struct ReturnData
         {
         public:
-            LLPointer<LLGLTFMaterial> mMaterial;
+            LLGLTFMaterial mMaterial;
             S32 mSide;
             bool mSuccess;
         };
 
-        // fromJson() is performance heavy offload to a thread.
-        main_queue->postTo(
-            general_queue,
-            [object_override]() // Work done on general queue
+        if (!object_override.mSides.empty())
         {
-            std::vector<ReturnData> results;
-
-            if (!object_override.mSides.empty())
+            // fromJson() is performance heavy offload to a thread.
+            main_queue->postTo(
+                general_queue,
+                [sides=object_override.mSides]() // Work done on general queue
             {
-                results.reserve(object_override.mSides.size());
+                std::vector<ReturnData> results;
+
+                results.reserve(sides.size());
                 // parse json
-                std::unordered_map<S32, std::string>::const_iterator iter = object_override.mSides.begin();
-                std::unordered_map<S32, std::string>::const_iterator end = object_override.mSides.end();
+                std::unordered_map<S32, std::string>::const_iterator iter = sides.begin();
+                std::unordered_map<S32, std::string>::const_iterator end = sides.end();
                 while (iter != end)
                 {
-                    LLPointer<LLGLTFMaterial> override_data = new LLGLTFMaterial();
                     std::string warn_msg, error_msg;
 
-                    bool success = override_data->fromJSON(iter->second, warn_msg, error_msg);
-
                     ReturnData result;
+
+                    bool success = result.mMaterial.fromJSON(iter->second, warn_msg, error_msg);
+
                     result.mSuccess = success;
                     result.mSide = iter->first;
 
-                    if (success)
-                    {
-                        result.mMaterial = override_data;
-                    }
-                    else
+                    if (!success)
                     {
                         LL_WARNS("GLTF") << "failed to parse GLTF override data.  errors: " << error_msg << " | warnings: " << warn_msg << LL_ENDL;
                     }
@@ -260,64 +256,68 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
                     results.push_back(result);
                     iter++;
                 }
-            }
-            return results;
-        },
-            [object_override, this](std::vector<ReturnData> results) // Callback to main thread
+                return results;
+            },
+            [object_id=object_override.mObjectId, this](std::vector<ReturnData> results) // Callback to main thread
             {
-            LLViewerObject * obj = gObjectList.findObject(object_override.mObjectId);
+                LLViewerObject * obj = gObjectList.findObject(object_id);
 
-            if (results.size() > 0 )
-            {
-                std::unordered_set<S32> side_set;
-
-                for (int i = 0; i < results.size(); ++i)
+                if (results.size() > 0 )
                 {
-                    if (results[i].mSuccess)
+                    std::unordered_set<S32> side_set;
+
+                    for (auto const & result : results)
                     {
-                        // flag this side to not be nulled out later
-                        side_set.insert(results[i].mSide);
+                        S32 side = result.mSide;
+                        if (result.mSuccess)
+                        {
+                            // copy to heap here because LLTextureEntry is going to take ownership with an LLPointer
+                            LLGLTFMaterial * material = new LLGLTFMaterial(result.mMaterial);
+
+                            // flag this side to not be nulled out later
+                            side_set.insert(side);
 
-                        if (obj)
+                            if (obj)
+                            {
+                                obj->setTEGLTFMaterialOverride(side, material);
+                            }
+                        }
+
+                        // unblock material editor
+                        if (obj && obj->getTE(side) && obj->getTE(side)->isSelected())
                         {
-                            obj->setTEGLTFMaterialOverride(results[i].mSide, results[i].mMaterial);
+                            doSelectionCallbacks(object_id, side);
                         }
                     }
-                    
-                    // unblock material editor
-                    if (obj && obj->getTE(results[i].mSide) && obj->getTE(results[i].mSide)->isSelected())
-                    {
-                        doSelectionCallbacks(object_override.mObjectId, results[i].mSide);
-                    }
-                }
 
-                if (obj && side_set.size() != obj->getNumTEs())
-                { // object exists and at least one texture entry needs to have its override data nulled out
-                    for (int i = 0; i < obj->getNumTEs(); ++i)
-                    {
-                        if (side_set.find(i) == side_set.end())
+                    if (obj && side_set.size() != obj->getNumTEs())
+                    { // object exists and at least one texture entry needs to have its override data nulled out
+                        for (int i = 0; i < obj->getNumTEs(); ++i)
                         {
-                            obj->setTEGLTFMaterialOverride(i, nullptr);
-                            if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                            if (side_set.find(i) == side_set.end())
                             {
-                                doSelectionCallbacks(object_override.mObjectId, i);
+                                obj->setTEGLTFMaterialOverride(i, nullptr);
+                                if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                                {
+                                    doSelectionCallbacks(object_id, i);
+                                }
                             }
                         }
                     }
                 }
-            }
-            else if (obj)
-            { // override list was empty or an error occurred, null out all overrides for this object
-                for (int i = 0; i < obj->getNumTEs(); ++i)
-                {
-                    obj->setTEGLTFMaterialOverride(i, nullptr);
-                    if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                else if (obj)
+                { // override list was empty or an error occurred, null out all overrides for this object
+                    for (int i = 0; i < obj->getNumTEs(); ++i)
                     {
-                        doSelectionCallbacks(obj->getID(), i);
+                        obj->setTEGLTFMaterialOverride(i, nullptr);
+                        if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                        {
+                            doSelectionCallbacks(obj->getID(), i);
+                        }
                     }
                 }
-            }
-        });
+            });
+        }
     }
 
 private:
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index d301e14e1098e046f4dacb33116327670287ee0c..67bf6827ad1a8dda51d26ca034d2f668798b50a3 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -1342,10 +1342,11 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)
 	
 	if (header_size > 0)
 	{
-		const LLSD& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["skin"]["offset"].asInteger();
-		S32 size = header["skin"]["size"].asInteger();
+		const LLMeshHeader& header = header_it->second.second;
+
+        S32 version = header.mVersion;
+		S32 offset = header_size + header.mSkinOffset;
+		S32 size = header.mSkinSize;
 
 		mHeaderMutex->unlock();
 
@@ -1456,9 +1457,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["physics_convex"]["offset"].asInteger();
-		S32 size = header["physics_convex"]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mPhysicsConvexOffset;
+        S32 size = header.mPhysicsConvexSize;
 
 		mHeaderMutex->unlock();
 
@@ -1555,9 +1556,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["physics_mesh"]["offset"].asInteger();
-		S32 size = header["physics_mesh"]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mPhysicsMeshOffset;
+		S32 size = header.mPhysicsMeshSize;
 
 		mHeaderMutex->unlock();
 
@@ -1753,9 +1754,9 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header[header_lod[lod]]["offset"].asInteger();
-		S32 size = header[header_lod[lod]]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mLodOffset[lod];
+        S32 size = header.mLodSize[lod];
 		mHeaderMutex->unlock();
 				
 		if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)
@@ -1857,8 +1858,10 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
 EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
 {
 	const LLUUID mesh_id = mesh_params.getSculptID();
-	LLSD header;
+	LLSD header_data;
 	
+    LLMeshHeader header;
+
 	U32 header_size = 0;
 	if (data_size > 0)
 	{
@@ -1869,23 +1872,25 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 
 		boost::iostreams::stream<boost::iostreams::array_source> stream(result_ptr, data_size);
 
-		if (!LLSDSerialize::fromBinary(header, stream, data_size))
+		if (!LLSDSerialize::fromBinary(header_data, stream, data_size))
 		{
 			LL_WARNS(LOG_MESH) << "Mesh header parse error.  Not a valid mesh asset!  ID:  " << mesh_id
 							   << LL_ENDL;
 			return MESH_PARSE_FAILURE;
 		}
 
-		if (!header.isMap())
+		if (!header_data.isMap())
 		{
 			LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL;
 			return MESH_INVALID;
 		}
 
-		if (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION)
+        header.fromLLSD(header_data);
+
+		if (header.mVersion > MAX_MESH_VERSION)
 		{
 			LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL;
-			header["404"] = 1;
+			header.m404 = true;
 		}
 		// make sure there is at least one lod, function returns -1 and marks as 404 otherwise
 		else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0)
@@ -1897,7 +1902,7 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 	{
 		LL_INFOS(LOG_MESH) << "Non-positive data size.  Marking header as non-existent, will not retry.  ID:  " << mesh_id
 						   << LL_ENDL;
-		header["404"] = 1;
+		header.m404 = 1;
 	}
 
 	{
@@ -1907,7 +1912,6 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 			mMeshHeader[mesh_id] = { header_size, header };
             LLMeshRepository::sCacheBytesHeaders += header_size;
 		}
-
 		
 		LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.
 
@@ -2977,7 +2981,7 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
 
 	if (iter != mMeshHeader.end())
 	{
-		LLSD& header = iter->second.second;
+		auto& header = iter->second.second;
 
 		return LLMeshRepository::getActualMeshLOD(header, lod);
 	}
@@ -2986,23 +2990,23 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
 }
 
 //static
-S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
+S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod)
 {
 	lod = llclamp(lod, 0, 3);
 
-	if (header.has("404"))
+	if (header.m404)
 	{
 		return -1;
 	}
 
-	S32 version = header["version"];
+	S32 version = header.mVersion;
 
 	if (version > MAX_MESH_VERSION)
 	{
 		return -1;
 	}
 
-	if (header[header_lod[lod]]["size"].asInteger() > 0)
+	if (header.mLodSize[lod] > 0)
 	{
 		return lod;
 	}
@@ -3010,7 +3014,7 @@ S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
 	//search down to find the next available lower lod
 	for (S32 i = lod-1; i >= 0; --i)
 	{
-		if (header[header_lod[i]]["size"].asInteger() > 0)
+		if (header.mLodSize[i] > 0)
 		{
 			return i;
 		}
@@ -3019,15 +3023,16 @@ S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
 	//search up to find then ext available higher lod
 	for (S32 i = lod+1; i < 4; ++i)
 	{
-		if (header[header_lod[i]]["size"].asInteger() > 0)
+		if (header.mLodSize[i] > 0)
 		{
 			return i;
 		}
 	}
 
 	//header exists and no good lod found, treat as 404
-	header["404"] = 1;
-	return -1;
+    header.m404 = true;
+
+    return -1;
 }
 
 // Handle failed or successful requests for mesh assets.
@@ -3216,7 +3221,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 	{
 		// header was successfully retrieved from sim and parsed and is in cache
 		S32 header_bytes = 0;
-		LLSD header;
+		LLMeshHeader header;
 
 		gMeshRepo.mThread->mHeaderMutex->lock();
 		LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id);
@@ -3227,8 +3232,8 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 		}
 
 		if (header_bytes > 0
-			&& !header.has("404")
-			&& (!header.has("version") || header["version"].asInteger() <= MAX_MESH_VERSION))
+			&& !header.m404
+			&& (header.mVersion <= MAX_MESH_VERSION))
 		{
 			std::stringstream str;
 
@@ -3237,13 +3242,12 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 			for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
 			{
 				// figure out how many bytes we'll need to reserve in the file
-				const std::string & lod_name = header_lod[i];
-				lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
+				lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]);
 			}
 		
 			// just in case skin info or decomposition is at the end of the file (which it shouldn't be)
-			lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
-			lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
+			lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize);
+            lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize);
 
             // Do not unlock mutex untill we are done with LLSD.
             // LLSD is smart and can work like smart pointer, is not thread safe.
@@ -4257,8 +4261,8 @@ bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id)
     mesh_header_map::iterator iter = mMeshHeader.find(mesh_id);
     if (iter != mMeshHeader.end() && iter->second.first > 0)
     {
-        LLSD &mesh = iter->second.second;
-        if (mesh.has("physics_mesh") && mesh["physics_mesh"].has("size") && (mesh["physics_mesh"]["size"].asInteger() > 0))
+        LLMeshHeader &mesh = iter->second.second;
+        if (mesh.mPhysicsMeshSize > 0)
         {
             return true;
         }
@@ -4281,20 +4285,21 @@ void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3
 
 S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
 	if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod)
 	{
 		LLMutexLock lock(mThread->mHeaderMutex);
 		LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
 		if (iter != mThread->mMeshHeader.end() && iter->second.first > 0)
 		{
-			const LLSD& header = iter->second.second;
+			const LLMeshHeader& header = iter->second.second;
 
-			if (header.has("404"))
+			if (header.m404)
 			{
 				return -1;
 			}
 
-			S32 size = header[header_lod[lod]]["size"].asInteger();
+            S32 size = header.mLodSize[lod];
 			return size;
 		}
 
@@ -4430,11 +4435,11 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* by
 
 // FIXME replace with calc based on LLMeshCostData
 //static
-F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
+F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
 {
-	if (header.has("404")
-		|| !header.has("lowest_lod")
-		|| (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION))
+	if (header.m404
+		|| header.mLodSize[0] <= 0
+		|| (header.mVersion > MAX_MESH_VERSION))
 	{
 		return 0.f;
 	}
@@ -4453,10 +4458,10 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 	F32 minimum_size = (F32)minimum_size_ch;
 	F32 bytes_per_triangle = (F32)bytes_per_triangle_ch;
 
-	S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
-	S32 bytes_low = header["low_lod"]["size"].asInteger();
-	S32 bytes_mid = header["medium_lod"]["size"].asInteger();
-	S32 bytes_high = header["high_lod"]["size"].asInteger();
+	S32 bytes_lowest = header.mLodSize[0];
+	S32 bytes_low = header.mLodSize[1];
+	S32 bytes_mid = header.mLodSize[2];
+	S32 bytes_high = header.mLodSize[3];
 
 	if (bytes_high == 0)
 	{
@@ -4486,10 +4491,10 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 	if (bytes)
 	{
 		*bytes = 0;
-		*bytes += header["lowest_lod"]["size"].asInteger();
-		*bytes += header["low_lod"]["size"].asInteger();
-		*bytes += header["medium_lod"]["size"].asInteger();
-		*bytes += header["high_lod"]["size"].asInteger();
+		*bytes += header.mLodSize[0];
+		*bytes += header.mLodSize[1];
+		*bytes += header.mLodSize[2];
+		*bytes += header.mLodSize[3];
 	}
 
 	if (bytes_visible)
@@ -4497,7 +4502,7 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 		lod = LLMeshRepository::getActualMeshLOD(header, lod);
 		if (lod >= 0 && lod <= 3)
 		{
-			*bytes_visible = header[header_lod[lod]]["size"].asInteger();
+			*bytes_visible = header.mLodSize[lod];
 		}
 	}
 
@@ -4539,34 +4544,29 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 
 LLMeshCostData::LLMeshCostData()
 {
-    mSizeByLOD.resize(4);
-    mEstTrisByLOD.resize(4);
-
     std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
     std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
 }
 
-bool LLMeshCostData::init(const LLSD& header)
+bool LLMeshCostData::init(const LLMeshHeader& header)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
-    mSizeByLOD.resize(4);
-    mEstTrisByLOD.resize(4);
-
+    
     std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
     std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
     
-    S32 bytes_high = header["high_lod"]["size"].asInteger();
-    S32 bytes_med = header["medium_lod"]["size"].asInteger();
+    S32 bytes_high = header.mLodSize[3];
+    S32 bytes_med = header.mLodSize[2];
     if (bytes_med == 0)
     {
         bytes_med = bytes_high;
     }
-    S32 bytes_low = header["low_lod"]["size"].asInteger();
+    S32 bytes_low = header.mLodSize[1];
     if (bytes_low == 0)
     {
         bytes_low = bytes_med;
     }
-    S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
+    S32 bytes_lowest = header.mLodSize[0];
     if (bytes_lowest == 0)
     {
         bytes_lowest = bytes_low;
@@ -4701,6 +4701,7 @@ F32 LLMeshCostData::getTriangleBasedStreamingCost()
 
 bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
     data = LLMeshCostData();
     
     if (mThread && mesh_id.notNull())
@@ -4709,11 +4710,11 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
         LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
         if (iter != mThread->mMeshHeader.end() && iter->second.first > 0)
         {
-            LLSD& header = iter->second.second;
+            LLMeshHeader& header = iter->second.second;
 
-            bool header_invalid = (header.has("404")
-                                   || !header.has("lowest_lod")
-                                   || (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION));
+            bool header_invalid = (header.m404
+                                   || header.mLodSize[0] <= 0
+                                   || header.mVersion > MAX_MESH_VERSION);
             if (!header_invalid)
             {
                 return getCostData(header, data);
@@ -4725,7 +4726,7 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
     return false;
 }
 
-bool LLMeshRepository::getCostData(LLSD& header, LLMeshCostData& data)
+bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data)
 {
     data = LLMeshCostData();
 
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 6922367ff7e5ac8565002e1aaa0982a4e1ce2ce5..619e076fa6c63b8229f497221b9dc2f2dae5b6ed 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -194,6 +194,63 @@ class RequestStats
     LLFrameTimer mTimer;
 };
 
+class LLMeshHeader
+{
+public:
+
+    LLMeshHeader() {}
+
+    explicit LLMeshHeader(const LLSD& header)
+    {
+        fromLLSD(header);
+    }
+
+    void fromLLSD(const LLSD& header)
+    {
+        const char* lod[] =
+        {
+            "lowest_lod",
+            "low_lod",
+            "medium_lod",
+            "high_lod"
+        };
+
+        mVersion = header["version"].asInteger();
+
+        for (U32 i = 0; i < 4; ++i)
+        {
+            mLodOffset[i] = header[lod[i]]["offset"].asInteger();
+            mLodSize[i] = header[lod[i]]["size"].asInteger();
+        }
+
+        mSkinOffset = header["skin"]["offset"].asInteger();
+        mSkinSize = header["skin"]["size"].asInteger();
+
+        mPhysicsConvexOffset = header["physics_convex"]["offset"].asInteger();
+        mPhysicsConvexSize = header["physics_convex"]["size"].asInteger();
+
+        mPhysicsMeshOffset = header["physics_mesh"]["offset"].asInteger();
+        mPhysicsMeshSize = header["physics_mesh"]["size"].asInteger();
+
+        m404 = header.has("404");
+    }
+
+    S32 mVersion = -1;
+    S32 mSkinOffset = -1;
+    S32 mSkinSize = -1;
+
+    S32 mPhysicsConvexOffset = -1;
+    S32 mPhysicsConvexSize = -1;
+
+    S32 mPhysicsMeshOffset = -1;
+    S32 mPhysicsMeshSize = -1;
+
+    S32 mLodOffset[4] = { -1 };
+    S32 mLodSize[4] = { -1 };
+
+    bool m404 = false;
+};
+
 class LLMeshRepoThread : public LLThread
 {
 public:
@@ -210,7 +267,7 @@ class LLMeshRepoThread : public LLThread
 	LLCondition* mSignal;
 
 	//map of known mesh headers
-	typedef boost::unordered_map<LLUUID, std::pair<U32, LLSD>> mesh_header_map; // pair is header_size and data
+	typedef boost::unordered_map<LLUUID, std::pair<U32, LLMeshHeader>> mesh_header_map; // pair is header_size and data
 	mesh_header_map mMeshHeader;
 
 	class HeaderRequest : public RequestStats
@@ -497,7 +554,7 @@ class LLMeshCostData
 public:
     LLMeshCostData();
 
-    bool init(const LLSD& header);
+    bool init(const LLMeshHeader& header);
     
     // Size for given LOD
     S32 getSizeByLOD(S32 lod);
@@ -532,10 +589,10 @@ class LLMeshCostData
 
 private:
     // From the "size" field of the mesh header. LOD 0=lowest, 3=highest.
-    std::vector<S32> mSizeByLOD;
+    std::array<S32,4> mSizeByLOD;
 
     // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest.
-    std::vector<F32> mEstTrisByLOD;
+    std::array<F32,4> mEstTrisByLOD;
 };
 
 class LLMeshRepository
@@ -566,9 +623,9 @@ class LLMeshRepository
     F32 getEstTrianglesMax(LLUUID mesh_id);
     F32 getEstTrianglesStreamingCost(LLUUID mesh_id);
 	F32 getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
-	static F32 getStreamingCostLegacy(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
+	static F32 getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
     bool getCostData(LLUUID mesh_id, LLMeshCostData& data);
-    bool getCostData(LLSD& header, LLMeshCostData& data);
+    bool getCostData(LLMeshHeader& header, LLMeshCostData& data);
 
 	LLMeshRepository();
 
@@ -588,7 +645,7 @@ class LLMeshRepository
 	void notifyDecompositionReceived(LLModel::Decomposition* info);
 
 	S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
-	static S32 getActualMeshLOD(LLSD& header, S32 lod);
+	static S32 getActualMeshLOD(LLMeshHeader& header, S32 lod);
 	const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj = nullptr);
 	LLModel::Decomposition* getDecomposition(const LLUUID& mesh_id);
 	void fetchPhysicsShape(const LLUUID& mesh_id);
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index 22c1176b05e0392354f1802b7d960a8ad402451a..045972b7a8761b0045727bb69b9de7496a82b951 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -7833,7 +7833,7 @@ S32 LLObjectSelection::getSelectedObjectRenderCost()
 				   for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
 				   {
 					   // add the cost of each individual texture in the linkset
-					   cost += iter->second;
+					   cost += LLVOVolume::getTextureCost(*iter);
 				   }
 
 				   textures.clear();
@@ -7855,7 +7855,7 @@ S32 LLObjectSelection::getSelectedObjectRenderCost()
 			for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
 			{
 				// add the cost of each individual texture in the linkset
-				cost += iter->second;
+				cost += LLVOVolume::getTextureCost(*iter);
 			}
 
 			textures.clear();
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index 4d99ee13864bccf0e18f2c4112cb6136c6c28696..f7df4286fe8f5e22ee77d87b83f52bf6ce22c03d 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -1826,116 +1826,6 @@ void renderUpdateType(LLDrawable* drawablep)
 	}
 }
 
-void renderComplexityDisplay(LLDrawable* drawablep)
-{
-	LLViewerObject* vobj = drawablep->getVObj();
-	if (!vobj)
-	{
-		return;
-	}
-
-	LLVOVolume *voVol = dynamic_cast<LLVOVolume*>(vobj);
-
-	if (!voVol)
-	{
-		return;
-	}
-
-	if (!voVol->isRoot())
-	{
-		return;
-	}
-
-	LLVOVolume::texture_cost_t textures;
-	F32 cost = (F32) voVol->getRenderCost(textures);
-
-	// add any child volumes
-	LLViewerObject::const_child_list_t children = voVol->getChildren();
-	for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); iter != children.end(); ++iter)
-	{
-		const LLViewerObject *child = *iter;
-		const LLVOVolume *child_volume = dynamic_cast<const LLVOVolume*>(child);
-		if (child_volume)
-		{
-			cost += child_volume->getRenderCost(textures);
-		}
-	}
-
-	// add texture cost
-	for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
-	{
-		// add the cost of each individual texture in the linkset
-		cost += iter->second;
-	}
-
-	F32 cost_max = (F32) LLVOVolume::getRenderComplexityMax();
-
-
-
-	// allow user to set a static color scale
-	if (gSavedSettings.getS32("RenderComplexityStaticMax") > 0)
-	{
-		cost_max = gSavedSettings.getS32("RenderComplexityStaticMax");
-	}
-
-	F32 cost_ratio = cost / cost_max;
-	
-	// cap cost ratio at 1.0f in case cost_max is at a low threshold
-	cost_ratio = cost_ratio > 1.0f ? 1.0f : cost_ratio;
-	
-	LLGLEnable blend(GL_BLEND);
-
-	LLColor4 color;
-	const LLColor4 color_min = gSavedSettings.getColor4("RenderComplexityColorMin");
-	const LLColor4 color_mid = gSavedSettings.getColor4("RenderComplexityColorMid");
-	const LLColor4 color_max = gSavedSettings.getColor4("RenderComplexityColorMax");
-
-	if (cost_ratio < 0.5f)
-	{
-		color = color_min * (1 - cost_ratio * 2) + color_mid * (cost_ratio * 2);
-	}
-	else
-	{
-		color = color_mid * (1 - (cost_ratio - 0.5) * 2) + color_max * ((cost_ratio - 0.5) * 2);
-	}
-
-	LLSD color_val = color.getValue();
-
-	// don't highlight objects below the threshold
-	if (cost > gSavedSettings.getS32("RenderComplexityThreshold"))
-	{
-		glColor4f(color[0],color[1],color[2],0.5f);
-
-
-		S32 num_faces = drawablep->getNumFaces();
-		if (num_faces)
-		{
-			for (S32 i = 0; i < num_faces; ++i)
-			{
-				pushVerts(drawablep->getFace(i));
-			}
-		}
-		LLViewerObject::const_child_list_t children = voVol->getChildren();
-		for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); iter != children.end(); ++iter)
-		{
-			const LLViewerObject *child = *iter;
-			if (child)
-			{
-				num_faces = child->getNumFaces();
-				if (num_faces)
-				{
-					for (S32 i = 0; i < num_faces; ++i)
-					{
-						pushVerts(child->mDrawable->getFace(i));
-					}
-				}
-			}
-		}
-	}
-	
-	voVol->setDebugText(llformat("%4.0f", cost));	
-}
-
 void renderBoundingBox(LLDrawable* drawable, BOOL set_color = TRUE)
 {
 	if (set_color)
@@ -3261,10 +3151,6 @@ class LLOctreeRenderNonOccluded : public OctreeTraveler
 			{
 				renderUpdateType(drawable);
 			}
-			if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RENDER_COMPLEXITY))
-			{
-				renderComplexityDisplay(drawable);
-			}
 			if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY))
 			{
 				renderTexelDensity(drawable);
@@ -3575,7 +3461,6 @@ void LLSpatialPartition::renderDebug()
 									  LLPipeline::RENDER_DEBUG_AGENT_TARGET |
 									  //LLPipeline::RENDER_DEBUG_BUILD_QUEUE |
 									  LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA |
-									  LLPipeline::RENDER_DEBUG_RENDER_COMPLEXITY |
 									  LLPipeline::RENDER_DEBUG_TEXEL_DENSITY))
 	{
 		return;
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 8334ca329a618dfd1df1264c138d8489292c80f3..77b4804076b5fe02b69134423249620a59daccda 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -369,7 +369,7 @@ LLViewerObject::~LLViewerObject()
     }
 
 	// Delete memory associated with extra parameters.
-	std::map<U16, ExtraParameter*>::iterator iter;
+	std::unordered_map<U16, ExtraParameter*>::iterator iter;
 	for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 	{
 		if(iter->second != NULL)
@@ -1555,7 +1555,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
 				unpackParticleSource(block_num, owner_id);
 
 				// Mark all extra parameters not used
-				std::map<U16, ExtraParameter*>::iterator iter;
+				std::unordered_map<U16, ExtraParameter*>::iterator iter;
 				for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 				{
 					iter->second->in_use = FALSE;
@@ -1947,7 +1947,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
 				}
 				
 				// Mark all extra parameters not used
-				std::map<U16, ExtraParameter*>::iterator iter;
+				std::unordered_map<U16, ExtraParameter*>::iterator iter;
 				for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 				{
 					iter->second->in_use = FALSE;
@@ -6242,7 +6242,8 @@ LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 para
 
 LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntry(U16 param_type) const
 {
-	std::map<U16, ExtraParameter*>::const_iterator itor = mExtraParameterList.find(param_type);
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VIEWER;
+	std::unordered_map<U16, ExtraParameter*>::const_iterator itor = mExtraParameterList.find(param_type);
 	if (itor != mExtraParameterList.end())
 	{
 		return itor->second;
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index cd2363a1b90f5777de59fbc24c10cd00872993eb..72505528f0e78f49ffdb18be946b8d1ddb88b413 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -122,7 +122,7 @@ class LLViewerObject
 		BOOL in_use;
 		LLNetworkData *data;
 	};
-	std::map<U16, ExtraParameter*> mExtraParameterList;
+	std::unordered_map<U16, ExtraParameter*> mExtraParameterList;
 
 public:
 	typedef std::list<LLPointer<LLViewerObject> > child_list_t;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index a7d544f18478c747d63f00b556f5c167cfd885de..95e9321d6fd4287d7acaa8ef63e2d6e7bd5a6b37 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -1448,7 +1448,7 @@ void LLVOAvatar::calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax)
                                 continue;
                             }
                         }
-                        if (vol && vol->isRiggedMesh())
+                        if (vol && vol->isRiggedMeshFast())
                         {
                             continue;
                         }
@@ -10834,6 +10834,7 @@ void LLVOAvatar::updateVisualComplexity()
 	mVisualComplexityStale = true;
 }
 
+
 // Account for the complexity of a single top-level object associated
 // with an avatar. This will be either an attached object or an animated
 // object.
@@ -10847,143 +10848,144 @@ void LLVOAvatar::accountRenderComplexityForObject(
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
     if (attached_object && !attached_object->isHUDAttachment())
-		{
+    {
         mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount();
         mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax();
         mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
 
-					textures.clear();
-					const LLDrawable* drawable = attached_object->mDrawable;
-					if (drawable)
-					{
-						const LLVOVolume* volume = drawable->getVOVolume();
-						if (volume)
-						{
-                            F32 attachment_total_cost = 0;
-                            F32 attachment_volume_cost = 0;
-                            F32 attachment_texture_cost = 0;
-                            F32 attachment_children_cost = 0;
+        textures.clear();
+        const LLDrawable* drawable = attached_object->mDrawable;
+        if (drawable)
+        {
+            const LLVOVolume* volume = drawable->getVOVolume();
+            if (volume)
+            {
+                F32 attachment_total_cost = 0;
+                F32 attachment_volume_cost = 0;
+                F32 attachment_texture_cost = 0;
+                F32 attachment_children_cost = 0;
                 const F32 animated_object_attachment_surcharge = 1000;
 
-                if (attached_object->isAnimatedObject())
+                if (volume->isAnimatedObjectFast())
                 {
                     attachment_volume_cost += animated_object_attachment_surcharge;
                 }
-							attachment_volume_cost += volume->getRenderCost(textures);
+                attachment_volume_cost += volume->getRenderCost(textures);
 
-							const_child_list_t children = volume->getChildren();
-							for (const_child_list_t::const_iterator child_iter = children.begin();
-								  child_iter != children.end();
-								  ++child_iter)
-							{
-								LLViewerObject* child_obj = *child_iter;
-								LLVOVolume *child = dynamic_cast<LLVOVolume*>( child_obj );
-								if (child)
-								{
-									attachment_children_cost += child->getRenderCost(textures);
-								}
-							}
+                const_child_list_t children = volume->getChildren();
+                for (const_child_list_t::const_iterator child_iter = children.begin();
+                    child_iter != children.end();
+                    ++child_iter)
+                {
+                    LLViewerObject* child_obj = *child_iter;
+                    LLVOVolume* child = dynamic_cast<LLVOVolume*>(child_obj);
+                    if (child)
+                    {
+                        attachment_children_cost += child->getRenderCost(textures);
+                    }
+                }
 
-							for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
-								 volume_texture != textures.end();
-								 ++volume_texture)
-							{
-								// add the cost of each individual texture in the linkset
-								attachment_texture_cost += volume_texture->second;
-							}
-                            attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost;
-                            LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID()
-                                                   << " total: " << attachment_total_cost
-                                                   << ", volume: " << attachment_volume_cost
-                                                   << ", " << textures.size()
-                                                   << " textures: " << attachment_texture_cost
-                                                   << ", " << volume->numChildren()
-                                                   << " children: " << attachment_children_cost
-                                                   << LL_ENDL;
-                            // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI
-                            cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity);
-
-                            if (isSelf())
-                            {
-                                LLObjectComplexity object_complexity;
-                                object_complexity.objectName = attached_object->getAttachmentItemName();
-                                object_complexity.objectId = attached_object->getAttachmentItemID();
-                                object_complexity.objectCost = attachment_total_cost;
-                                object_complexity_list.push_back(object_complexity);
-                            }
-						}
-					}
-				}
-                if (isSelf()
-                    && attached_object
-                    && attached_object->isHUDAttachment()
-                    && !attached_object->isTempAttachment()
-                    && attached_object->mDrawable)
+                for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
+                    volume_texture != textures.end();
+                    ++volume_texture)
                 {
-                    textures.clear();
-                    BOOL is_rigged_mesh = attached_object->isRiggedMesh();
+                    // add the cost of each individual texture in the linkset
+                    attachment_texture_cost += LLVOVolume::getTextureCost(*volume_texture);
+                }
+                attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost;
+                LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID()
+                    << " total: " << attachment_total_cost
+                    << ", volume: " << attachment_volume_cost
+                    << ", " << textures.size()
+                    << " textures: " << attachment_texture_cost
+                    << ", " << volume->numChildren()
+                    << " children: " << attachment_children_cost
+                    << LL_ENDL;
+                // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI
+                cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity);
+
+                if (isSelf())
+                {
+                    LLObjectComplexity object_complexity;
+                    object_complexity.objectName = attached_object->getAttachmentItemName();
+                    object_complexity.objectId = attached_object->getAttachmentItemID();
+                    object_complexity.objectCost = attachment_total_cost;
+                    object_complexity_list.push_back(object_complexity);
+                }
+            }
+        }
+    }
+    if (isSelf()
+        && attached_object
+        && attached_object->isHUDAttachment()
+        && !attached_object->isTempAttachment()
+        && attached_object->mDrawable)
+    {
+        textures.clear();
         mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
 
-                    const LLVOVolume* volume = attached_object->mDrawable->getVOVolume();
-                    if (volume)
-                    {
-                        LLHUDComplexity hud_object_complexity;
-                        hud_object_complexity.objectName = attached_object->getAttachmentItemName();
-                        hud_object_complexity.objectId = attached_object->getAttachmentItemID();
-                        std::string joint_name;
-                        gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name);
-                        hud_object_complexity.jointName = joint_name;
-                        // get cost and individual textures
-                        hud_object_complexity.objectsCost += volume->getRenderCost(textures);
-                        hud_object_complexity.objectsCount++;
-
-                        LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
-                        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-                            iter != child_list.end(); ++iter)
-                        {
-                            LLViewerObject* childp = *iter;
-                            is_rigged_mesh |= childp->isRiggedMesh();
-                            const LLVOVolume* chld_volume = dynamic_cast<LLVOVolume*>(childp);
-                            if (chld_volume)
-                            {
-                                // get cost and individual textures
-                                hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures);
-                                hud_object_complexity.objectsCount++;
-                            }
-                        }
-                        if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned)
-                        {
-                            LLSD args;                            
-                            LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID());
-                            args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown");
-                            args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName());
-                            LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args);
+        const LLVOVolume* volume = attached_object->mDrawable->getVOVolume();
+        if (volume)
+        {
+            BOOL is_rigged_mesh = volume->isRiggedMeshFast();
+            LLHUDComplexity hud_object_complexity;
+            hud_object_complexity.objectName = attached_object->getAttachmentItemName();
+            hud_object_complexity.objectId = attached_object->getAttachmentItemID();
+            std::string joint_name;
+            gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name);
+            hud_object_complexity.jointName = joint_name;
+            // get cost and individual textures
+            hud_object_complexity.objectsCost += volume->getRenderCost(textures);
+            hud_object_complexity.objectsCount++;
+
+            LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+            for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+                iter != child_list.end(); ++iter)
+            {
+                LLViewerObject* childp = *iter;
+                const LLVOVolume* chld_volume = dynamic_cast<LLVOVolume*>(childp);
+                if (chld_volume)
+                {
+                    is_rigged_mesh = is_rigged_mesh || chld_volume->isRiggedMeshFast();
+                    // get cost and individual textures
+                    hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures);
+                    hud_object_complexity.objectsCount++;
+                }
+            }
+            if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned)
+            {
+                LLSD args;
+                LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID());
+                args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown");
+                args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName());
+                LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args);
 
-                            attached_object->mRiggedAttachedWarned = true;
-                        }
+                attached_object->mRiggedAttachedWarned = true;
+            }
 
-                        hud_object_complexity.texturesCount += textures.size();
+            hud_object_complexity.texturesCount += textures.size();
 
-                        for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
-                            volume_texture != textures.end();
-                            ++volume_texture)
-                        {
-                            // add the cost of each individual texture (ignores duplicates)
-                            hud_object_complexity.texturesCost += volume_texture->second;
-                            LLViewerFetchedTexture *tex = LLViewerTextureManager::getFetchedTexture(volume_texture->first);
-                            if (tex)
-                            {
-                                // Note: Texture memory might be incorect since texture might be still loading.
-                                hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory();
-                                if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE)
-                                {
-                                    hud_object_complexity.largeTexturesCount++;
-                                }
-                            }
-                        }
-                        hud_complexity_list.push_back(hud_object_complexity);
+            for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
+                volume_texture != textures.end();
+                ++volume_texture)
+            {
+                // add the cost of each individual texture (ignores duplicates)
+                hud_object_complexity.texturesCost += LLVOVolume::getTextureCost(*volume_texture);
+                const LLViewerTexture* img = *volume_texture;
+                if (img->getType() == LLViewerTexture::FETCHED_TEXTURE)
+                {
+                    LLViewerFetchedTexture* tex = (LLViewerFetchedTexture*)img;
+                    // Note: Texture memory might be incorect since texture might be still loading.
+                    hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory();
+                    if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE)
+                    {
+                        hud_object_complexity.largeTexturesCount++;
                     }
                 }
+            }
+            hud_complexity_list.push_back(hud_object_complexity);
+        }
+    }
 }
 
 // Calculations for mVisualComplexity value
@@ -11005,7 +11007,7 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
         max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY);
 
         // Diagnostic list of all textures on our avatar
-        static std::set<LLUUID> all_textures;
+        static std::unordered_set<const LLViewerTexture*> all_textures;
 
 		U32 cost = VISUAL_COMPLEXITY_UNKNOWN;
 		LLVOVolume::texture_cost_t textures;
@@ -11074,46 +11076,6 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
 			}
 		}
 
-		// Diagnostic output to identify all avatar-related textures.
-		// Does not affect rendering cost calculation.
-		if (isSelf())
-		{
-			LL_DEBUGS("ARCdetail");
-			// print any attachment textures we didn't already know about.
-			for (LLVOVolume::texture_cost_t::iterator it = textures.begin(); it != textures.end(); ++it)
-			{
-				LLUUID image_id = it->first;
-				if( ! (image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
-				   && (all_textures.find(image_id) == all_textures.end()))
-				{
-					// attachment texture not previously seen.
-					LL_CONT << "attachment_texture: " << image_id.asString() << '\n';
-					all_textures.insert(image_id);
-				}
-			}
-
-			// print any avatar textures we didn't already know about
-			for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin();
-			 iter != LLAvatarAppearance::getDictionary()->getTextures().end();
-				 ++iter)
-			{
-				const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
-				// TODO: MULTI-WEARABLE: handle multiple textures for self
-				const LLViewerTexture* te_image = getImage(iter->first,0);
-				if (!te_image)
-					continue;
-				LLUUID image_id = te_image->getID();
-				if( image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
-					continue;
-				if (all_textures.find(image_id) == all_textures.end())
-				{
-					LL_CONT << "local_texture: " << texture_dict->mName << ": " << image_id << '\n';
-					all_textures.insert(image_id);
-				}
-			}
-			LL_ENDL;
-		}
-
         if ( cost != mVisualComplexity )
         {
             LL_DEBUGS("AvatarRender") << "Avatar "<< getID()
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 585c98bace66b831275515e72f3f3f20f0721548..aa60578cee75940cf063901f7b665a3b269799d7 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -3190,7 +3190,13 @@ void LLVOVolume::setLightCutoff(F32 cutoff)
 
 BOOL LLVOVolume::getIsLight() const
 {
-	return getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT);
+    mIsLight = getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT);
+    return mIsLight;
+}
+
+bool LLVOVolume::getIsLightFast() const
+{
+    return mIsLight;
 }
 
 LLColor3 LLVOVolume::getLightSRGBBaseColor() const
@@ -3576,6 +3582,31 @@ BOOL LLVOVolume::hasLightTexture() const
 	return FALSE;
 }
 
+bool LLVOVolume::isFlexibleFast() const
+{
+    return mVolumep && mVolumep->getParams().getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE;
+}
+
+bool LLVOVolume::isSculptedFast() const
+{
+    return mVolumep && mVolumep->getParams().isSculpt();
+}
+
+bool LLVOVolume::isMeshFast() const
+{
+    return mVolumep && mVolumep->getParams().isMeshSculpt();
+}
+
+bool LLVOVolume::isRiggedMeshFast() const
+{
+    return mSkinInfo.notNull();
+}
+
+bool LLVOVolume::isAnimatedObjectFast() const
+{
+    return mIsAnimatedObject;
+}
+
 BOOL LLVOVolume::isVolumeGlobal() const
 {
 	if (mVolumeImpl)
@@ -3736,8 +3767,8 @@ bool LLVOVolume::canBeAnimatedObject() const
 bool LLVOVolume::isAnimatedObject() const
 {
     LLVOVolume *root_vol = (LLVOVolume*)getRootEdit();
-    bool root_is_animated_flag = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
-    return root_is_animated_flag;
+    mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
+    return mIsAnimatedObject;
 }
 
 // Called any time parenting changes for a volume. Update flags and
@@ -3924,6 +3955,34 @@ const LLMatrix4 LLVOVolume::getRenderMatrix() const
 	return mDrawable->getWorldMatrix();
 }
 
+//static 
+S32 LLVOVolume::getTextureCost(const LLViewerTexture* img)
+{
+    static const U32 ARC_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested
+
+    S32 texture_cost = 0;
+    S8 type = img->getType();
+    if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE)
+    {
+        const LLViewerFetchedTexture* fetched_texturep = static_cast<const LLViewerFetchedTexture*>(img);
+        if (fetched_texturep
+            && fetched_texturep->getFTType() == FTT_LOCAL_FILE
+            && (img->getID() == IMG_ALPHA_GRAD_2D || img->getID() == IMG_ALPHA_GRAD)
+            )
+        {
+            // These two textures appear to switch between each other, but are of different sizes (4x256 and 256x256).
+            // Hardcode cost from larger one to not cause random complexity changes
+            texture_cost = 320;
+        }
+    }
+    if (texture_cost == 0)
+    {
+        texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f));
+    }
+
+    return texture_cost;
+}
+
 // Returns a base cost and adds textures to passed in set.
 // total cost is returned value + 5 * size of the resulting set.
 // Cannot include cost of textures, as they may be re-used in linked
@@ -3940,17 +3999,16 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 
 	// Get access to params we'll need at various points.  
 	// Skip if this is object doesn't have a volume (e.g. is an avatar).
-	BOOL has_volume = (getVolume() != NULL);
-	LLVolumeParams volume_params;
-	LLPathParams path_params;
-	LLProfileParams profile_params;
+    if (getVolume() == NULL)
+    {
+        return 0;
+    }
 
 	U32 num_triangles = 0;
 
 	// per-prim costs
 	static const U32 ARC_PARTICLE_COST = 1; // determined experimentally
 	static const U32 ARC_PARTICLE_MAX = 2048; // default values
-	static const U32 ARC_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested
 	static const U32 ARC_LIGHT_COST = 500; // static cost for light-producing prims 
 	static const U32 ARC_MEDIA_FACE_COST = 1500; // static cost per media-enabled face 
 
@@ -3985,45 +4043,41 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 	const LLDrawable* drawablep = mDrawable;
 	U32 num_faces = drawablep->getNumFaces();
 
-	if (has_volume)
-	{
-		volume_params = getVolume()->getParams();
-		path_params = volume_params.getPathParams();
-		profile_params = volume_params.getProfileParams();
+	const LLVolumeParams& volume_params = getVolume()->getParams();
 
-        LLMeshCostData costs;
-		if (getCostData(costs))
-		{
-            if (isAnimatedObject() && isRiggedMesh())
-            {
-                // Scaling here is to make animated object vs
-                // non-animated object ARC proportional to the
-                // corresponding calculations for streaming cost.
-                num_triangles = (ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * costs.getEstTrisForStreamingCost())/0.06;
-            }
-            else
-            {
-                F32 radius = getScale().length()*0.5f;
-                num_triangles = costs.getRadiusWeightedTris(radius);
-            }
-		}
+    LLMeshCostData costs;
+	if (getCostData(costs))
+	{
+        if (isAnimatedObjectFast() && isRiggedMeshFast())
+        {
+            // Scaling here is to make animated object vs
+            // non-animated object ARC proportional to the
+            // corresponding calculations for streaming cost.
+            num_triangles = (ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * costs.getEstTrisForStreamingCost())/0.06;
+        }
+        else
+        {
+            F32 radius = getScale().length()*0.5f;
+            num_triangles = costs.getRadiusWeightedTris(radius);
+        }
 	}
+	
 
 	if (num_triangles <= 0)
 	{
 		num_triangles = 4;
 	}
 
-	if (isSculpted())
+	if (isSculptedFast())
 	{
-		if (isMesh())
+		if (isMeshFast())
 		{
 			// base cost is dependent on mesh complexity
 			// note that 3 is the highest LOD as of the time of this coding.
 			S32 size = gMeshRepo.getMeshSize(volume_params.getSculptID(), getLOD());
 			if ( size > 0)
 			{
-				if (isRiggedMesh())
+				if (isRiggedMeshFast())
 				{
 					// weighted attachment - 1 point for every 3 bytes
 					weighted_mesh = 1;
@@ -4037,21 +4091,15 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 		}
 		else
 		{
-			const LLSculptParams *sculpt_params = (LLSculptParams *) getParameterEntry(LLNetworkData::PARAMS_SCULPT);
-			LLUUID sculpt_id = sculpt_params->getSculptTexture();
-			if (textures.find(sculpt_id) == textures.end())
+            LLViewerFetchedTexture* texture = mSculptTexture;
+			if (texture && textures.find(texture) == textures.end())
 			{
-				LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(sculpt_id);
-				if (texture)
-				{
-					S32 texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (texture->getFullHeight() / 128.f + texture->getFullWidth() / 128.f));
-					textures.insert(texture_cost_t::value_type(sculpt_id, texture_cost));
-				}
+                textures.insert(texture);
 			}
 		}
 	}
 
-	if (isFlexible())
+	if (isFlexibleFast())
 	{
 		flexi = 1;
 	}
@@ -4060,85 +4108,66 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 		particles = 1;
 	}
 
-	if (getIsLight())
+	if (getIsLightFast())
 	{
 		produces_light = 1;
 	}
 
-	for (S32 i = 0; i < num_faces; ++i)
-	{
-		const LLFace* face = drawablep->getFace(i);
-		if (!face) continue;
-		const LLTextureEntry* te = face->getTextureEntry();
-		const LLViewerTexture* img = face->getTexture();
+    {
+        LL_PROFILE_ZONE_NAMED_CATEGORY_VOLUME("ARC - face list");
+        for (S32 i = 0; i < num_faces; ++i)
+        {
+            const LLFace* face = drawablep->getFace(i);
+            if (!face) continue;
+            const LLTextureEntry* te = face->getTextureEntry();
+            const LLViewerTexture* img = face->getTexture();
 
-		if (img)
-		{
-			if (textures.find(img->getID()) == textures.end())
-			{
-                S32 texture_cost = 0;
-                S8 type = img->getType();
-                if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE)
+            if (img)
+            {
+                textures.insert(img);
+            }
+
+            if (face->isInAlphaPool())
+            {
+                alpha = 1;
+            }
+            else if (img && img->getPrimaryFormat() == GL_ALPHA)
+            {
+                invisi = 1;
+            }
+            if (face->hasMedia())
+            {
+                media_faces++;
+            }
+
+            if (te)
+            {
+                if (te->getBumpmap())
                 {
-                    const LLViewerFetchedTexture* fetched_texturep = static_cast<const LLViewerFetchedTexture*>(img);
-                    if (fetched_texturep
-                        && fetched_texturep->getFTType() == FTT_LOCAL_FILE
-                        && (img->getID() == IMG_ALPHA_GRAD_2D || img->getID() == IMG_ALPHA_GRAD)
-                        )
-                    {
-                        // These two textures appear to switch between each other, but are of different sizes (4x256 and 256x256).
-                        // Hardcode cost from larger one to not cause random complexity changes
-                        texture_cost = 320;
-                    }
+                    // bump is a multiplier, don't add per-face
+                    bump = 1;
                 }
-                if (texture_cost == 0)
+                if (te->getShiny())
                 {
-                    texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f));
+                    // shiny is a multiplier, don't add per-face
+                    shiny = 1;
                 }
-				textures.insert(texture_cost_t::value_type(img->getID(), texture_cost));
-			}
-		}
-
-		if (face->isInAlphaPool())
-		{
-			alpha = 1;
-		}
-		else if (img && img->getPrimaryFormat() == GL_ALPHA)
-		{
-			invisi = 1;
-		}
-		if (face->hasMedia())
-		{
-			media_faces++;
-		}
-
-		if (te)
-		{
-			if (te->getBumpmap())
-			{
-				// bump is a multiplier, don't add per-face
-				bump = 1;
-			}
-			if (te->getShiny())
-			{
-				// shiny is a multiplier, don't add per-face
-				shiny = 1;
-			}
-			if (te->getGlow() > 0.f)
-			{
-				// glow is a multiplier, don't add per-face
-				glow = 1;
-			}
-			if (face->mTextureMatrix != NULL)
-			{
-				animtex = 1;
-			}
-			if (te->getTexGen())
-			{
-				planar = 1;
-			}
-		}
-	}
+                if (te->getGlow() > 0.f)
+                {
+                    // glow is a multiplier, don't add per-face
+                    glow = 1;
+                }
+                if (face->mTextureMatrix != NULL)
+                {
+                    animtex = 1;
+                }
+                if (te->getTexGen())
+                {
+                    planar = 1;
+                }
+            }
+        }
+    }
 
 	// shame currently has the "base" cost of 1 point per 15 triangles, min 2.
 	shame = num_triangles  * 5.f;
@@ -4217,7 +4246,7 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
     // Streaming cost for animated objects includes a fixed cost
     // per linkset. Add a corresponding charge here translated into
     // triangles, but not weighted by any graphics properties.
-    if (isAnimatedObject() && isRootEdit())
+    if (isAnimatedObjectFast() && isRootEdit())
     {
         shame += (ANIMATED_OBJECT_BASE_COST/0.06) * 5.0f;
     }
@@ -4232,7 +4261,7 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 
 F32 LLVOVolume::getEstTrianglesMax() const
 {
-	if (isMesh() && getVolume())
+	if (isMeshFast() && getVolume())
 	{
 		return gMeshRepo.getEstTrianglesMax(getVolume()->getParams().getSculptID());
 	}
@@ -4241,7 +4270,7 @@ F32 LLVOVolume::getEstTrianglesMax() const
 
 F32 LLVOVolume::getEstTrianglesStreamingCost() const
 {
-	if (isMesh() && getVolume())
+	if (isMeshFast() && getVolume())
 	{
 		return gMeshRepo.getEstTrianglesStreamingCost(getVolume()->getParams().getSculptID());
 	}
@@ -4256,7 +4285,7 @@ F32 LLVOVolume::getStreamingCost() const
     LLMeshCostData costs;
     if (getCostData(costs))
     {
-        if (isAnimatedObject() && isRootEdit())
+        if (isRootEdit() && isAnimatedObject())
         {
             // Root object of an animated object has this to account for skeleton overhead.
             linkset_base_cost = ANIMATED_OBJECT_BASE_COST;
@@ -4286,7 +4315,9 @@ F32 LLVOVolume::getStreamingCost() const
 // virtual
 bool LLVOVolume::getCostData(LLMeshCostData& costs) const
 {
-    if (isMesh())
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+
+    if (isMeshFast())
     {
         return gMeshRepo.getCostData(getVolume()->getParams().getSculptID(), costs);
     }
@@ -4296,11 +4327,11 @@ bool LLVOVolume::getCostData(LLMeshCostData& costs) const
 		S32 counts[4];
 		LLVolume::getLoDTriangleCounts(volume->getParams(), counts);
 
-		LLSD header;
-		header["lowest_lod"]["size"] = counts[0] * 10;
-		header["low_lod"]["size"] = counts[1] * 10;
-		header["medium_lod"]["size"] = counts[2] * 10;
-		header["high_lod"]["size"] = counts[3] * 10;
+        LLMeshHeader header;
+		header.mLodSize[0] = counts[0] * 10;
+		header.mLodSize[1] = counts[1] * 10;
+		header.mLodSize[2] = counts[2] * 10;
+		header.mLodSize[3] = counts[3] * 10;
 
 		return gMeshRepo.getCostData(header, costs);
     }
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index 9cfd90a940fbf3c8346e6d637a32c46aefad62f8..d509a7e2ab7764637fcf328d78030d87f3e33690 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -34,8 +34,8 @@
 #include "lllocalbitmaps.h"
 #include "m3math.h"		// LLMatrix3
 #include "m4math.h"		// LLMatrix4
-#include <map>
-#include <set>
+#include <unordered_map>
+#include <unordered_set>
 
 
 class LLViewerTextureAnim;
@@ -146,7 +146,8 @@ class LLVOVolume : public LLViewerObject
 	const LLMatrix4&	getRelativeXform() const				{ return mRelativeXform; }
 	const LLMatrix3&	getRelativeXformInvTrans() const		{ return mRelativeXformInvTrans; }
 	/*virtual*/	const LLMatrix4	getRenderMatrix() const override;
-				typedef std::map<LLUUID, S32> texture_cost_t;
+				typedef std::unordered_set<const LLViewerTexture*> texture_cost_t;
+                static S32 getTextureCost(const LLViewerTexture* img);
 				U32 	getRenderCost(texture_cost_t &textures) const;
     /*virtual*/	F32		getEstTrianglesMax() const override;
     /*virtual*/	F32		getEstTrianglesStreamingCost() const override;
@@ -267,6 +268,7 @@ class LLVOVolume : public LLViewerObject
 	void setSpotLightParams(LLVector3 params);
 
 	BOOL getIsLight() const;
+    bool getIsLightFast() const;
 
 
     // Get the light color in sRGB color space NOT scaled by intensity.
@@ -315,7 +317,15 @@ class LLVOVolume : public LLViewerObject
 	virtual BOOL isRiggedMesh() const override;
 	virtual BOOL hasLightTexture() const override;
 
-    
+    // fast variants above that use state that is filled in later
+    //  not reliable early in the life of an object, but should be used after
+    //  object is loaded
+    bool isFlexibleFast() const;
+    bool isSculptedFast() const;
+    bool isMeshFast() const;
+    bool isRiggedMeshFast() const;
+    bool isAnimatedObjectFast() const;
+
 	BOOL isVolumeGlobal() const;
 	BOOL canBeFlexible() const;
 	BOOL setIsFlexible(BOOL is_flexible);
@@ -461,6 +471,13 @@ class LLVOVolume : public LLViewerObject
 	S32 mIndexInTex[LLRender::NUM_VOLUME_TEXTURE_CHANNELS];
 	S32 mMDCImplCount;
 
+    // cached value of getIsLight to avoid redundant map lookups
+    // accessed by getIsLightFast
+    mutable bool mIsLight = false;
+
+    // cached value of getIsAnimatedObject to avoid redundant map lookups
+    // accessed by getIsAnimatedObjectFast
+    mutable bool mIsAnimatedObject = false;
 	bool mResetDebugText;
 
 	LLPointer<LLRiggedVolume> mRiggedVolume;
@@ -475,7 +492,6 @@ class LLVOVolume : public LLViewerObject
 
 	static LLPointer<LLObjectMediaDataClient> sObjectMediaClient;
 	static LLPointer<LLObjectMediaNavigateClient> sObjectMediaNavigateClient;
-
 protected:
 	static S32 sNumLODChanges;
 
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 8a0a9a7172d936017931c9a8dd8c1a5c4306b578..e92fa32fc6156559cfe539907f579c5524f7fd4a 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -603,7 +603,6 @@ class LLPipeline
 		RENDER_DEBUG_PHYSICS_SHAPES     =  0x02000000,
 		RENDER_DEBUG_NORMALS	        =  0x04000000,
 		RENDER_DEBUG_LOD_INFO	        =  0x08000000,
-		RENDER_DEBUG_RENDER_COMPLEXITY  =  0x10000000,
 		RENDER_DEBUG_ATTACHMENT_BYTES	=  0x20000000, // not used
 		RENDER_DEBUG_TEXEL_DENSITY		=  0x40000000,
 		RENDER_DEBUG_TRIANGLE_COUNT		=  0x80000000,