From bc773adf618eb531fdccacd95f4cd51f6b87497a Mon Sep 17 00:00:00 2001
From: "Brad Payne (Vir Linden)" <vir@lindenlab.com>
Date: Tue, 24 Apr 2018 16:55:55 +0100
Subject: [PATCH] MAINT-8549 - refactoring of streaming cost and related
 calculations

---
 indra/llcharacter/lljoint.cpp      |   2 +-
 indra/newview/llmeshrepository.cpp | 290 +++++++++++++++++++++--------
 indra/newview/llmeshrepository.h   |  47 ++++-
 indra/newview/llvovolume.cpp       |   4 -
 4 files changed, 255 insertions(+), 88 deletions(-)

diff --git a/indra/llcharacter/lljoint.cpp b/indra/llcharacter/lljoint.cpp
index abc5a95c8d..9d10f53bed 100644
--- a/indra/llcharacter/lljoint.cpp
+++ b/indra/llcharacter/lljoint.cpp
@@ -440,7 +440,7 @@ void LLJoint::addAttachmentPosOverride( const LLVector3& pos, const LLUUID& mesh
                                           llclamp(pos[2],-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET));
     if (constrained_pos != pos)
     {
-        LL_DEBUGS("Avatar") << "attachment pos override constrained to " 
+        LL_DEBUGS("Avatar") << mesh_id << " joint " << getName() << " attachment pos override constrained to " 
                             << constrained_pos << " was " << pos << LL_ENDL;
     }
     
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index b27fdefb83..9687376cff 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -4062,7 +4062,7 @@ void LLMeshRepository::uploadError(LLSD& args)
 	mUploadErrorQ.push(args);
 }
 
-bool LLMeshRepository::getLODSizes(LLUUID mesh_id, std::vector<S32>& lod_byte_sizes, std::vector<F32>& lod_tri_counts)
+bool LLMeshRepository::getLODSizes(LLSD& header, std::vector<S32>& lod_byte_sizes, std::vector<F32>& lod_tri_counts)
 {
     lod_byte_sizes.resize(4);
     lod_tri_counts.resize(4);
@@ -4070,120 +4070,111 @@ bool LLMeshRepository::getLODSizes(LLUUID mesh_id, std::vector<S32>& lod_byte_si
     std::fill(lod_byte_sizes.begin(), lod_byte_sizes.end(), 0);
     std::fill(lod_tri_counts.begin(), lod_tri_counts.end(), 0.f);
     
-    if (mThread && mesh_id.notNull())
+    S32 bytes_high = header["high_lod"]["size"].asInteger();
+    S32 bytes_med = header["medium_lod"]["size"].asInteger();
+    if (bytes_med == 0)
     {
-        LLMutexLock lock(mThread->mHeaderMutex);
-        LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
-        if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
-        {
-            LLSD& header = iter->second;
-            if (header.has("404")
-                || !header.has("lowest_lod")
-                || (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION))
-            {
-                return false;
-            }
-
-            S32 bytes_high = header["high_lod"]["size"].asInteger();
-            S32 bytes_med = header["medium_lod"]["size"].asInteger();
-            if (bytes_med == 0)
-            {
-                bytes_med = bytes_high;
-            }
-            S32 bytes_low = header["low_lod"]["size"].asInteger();
-            if (bytes_low == 0)
-            {
-                bytes_low = bytes_med;
-            }
-            S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
-            if (bytes_lowest == 0)
-            {
-                bytes_lowest = bytes_low;
-            }
-            lod_byte_sizes[0] = bytes_high;
-            lod_byte_sizes[1] = bytes_med;
-            lod_byte_sizes[2] = bytes_low;
-            lod_byte_sizes[3] = bytes_lowest;
-
-            F32 METADATA_DISCOUNT = (F32) gSavedSettings.getU32("MeshMetaDataDiscount");  //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
-            F32 MINIMUM_SIZE = (F32) gSavedSettings.getU32("MeshMinimumByteSize"); //make sure nothing is "free"
-            F32 bytes_per_triangle = (F32) gSavedSettings.getU32("MeshBytesPerTriangle");
+        bytes_med = bytes_high;
+    }
+    S32 bytes_low = header["low_lod"]["size"].asInteger();
+    if (bytes_low == 0)
+    {
+        bytes_low = bytes_med;
+    }
+    S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
+    if (bytes_lowest == 0)
+    {
+        bytes_lowest = bytes_low;
+    }
+    lod_byte_sizes[0] = bytes_lowest;
+    lod_byte_sizes[1] = bytes_low;
+    lod_byte_sizes[2] = bytes_med;
+    lod_byte_sizes[3] = bytes_high;
 
-            for (S32 i=0; i<4; i++)
-            {
-                lod_tri_counts[i] = llmax((F32) lod_byte_sizes[i]-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle; 
-            }
+    F32 METADATA_DISCOUNT = (F32) gSavedSettings.getU32("MeshMetaDataDiscount");  //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
+    F32 MINIMUM_SIZE = (F32) gSavedSettings.getU32("MeshMinimumByteSize"); //make sure nothing is "free"
+    F32 bytes_per_triangle = (F32) gSavedSettings.getU32("MeshBytesPerTriangle");
 
-            return true;
-        }
+    for (S32 i=0; i<4; i++)
+    {
+        lod_tri_counts[i] = llmax((F32) lod_byte_sizes[i]-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle; 
     }
-    return false;
+
+    return true;
 }
 
 F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id)
 {
-    std::vector<S32> lod_byte_sizes;
-    std::vector<F32> lod_tri_counts;
-    bool succ = getLODSizes(mesh_id, lod_byte_sizes, lod_tri_counts);
-    if (!succ)
+    LLMeshCostData costs;
+    if (getCostData(mesh_id, costs))
+    {
+        return costs.mEstTrisMax;
+    }
+    else
     {
         return 0.f;
     }
-    
-    return llmax(lod_tri_counts[0], lod_tri_counts[1], lod_tri_counts[2], lod_tri_counts[3]);
 }
 
 F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id)
 {
-    std::vector<S32> lod_byte_sizes;
-    std::vector<F32> tris_by_lod;
-    bool succ = getLODSizes(mesh_id, lod_byte_sizes, tris_by_lod);
-
-    if (!succ)
+    LLMeshCostData costs;
+    if (getCostData(mesh_id, costs))
     {
-        LL_DEBUGS("StreamingCost") << "couldn't get tris_by_lod" << LL_ENDL;
-        return 0.f;
+        return costs.computeEstTrisForStreamingCost();
     }
-
-    LL_DEBUGS("StreamingCost") << "tris_by_lod: "
-                               << tris_by_lod[0] << ", "
-                               << tris_by_lod[1] << ", "
-                               << tris_by_lod[2] << ", "
-                               << tris_by_lod[3] << LL_ENDL;
-
-    F32 charged_tris = tris_by_lod[0];
-    F32 allowed_tris = tris_by_lod[0];
-    const F32 ENFORCE_FLOOR = 64.0f;
-    for (S32 i=1; i<4; i++)
+    else
     {
-        // How many tris can we have in this LOD without affecting land impact?
-        // - normally an LOD should be at most half the size of the previous one.
-        // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller.
-        allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,tris_by_lod[i]);
-        F32 excess_tris = tris_by_lod[i]-allowed_tris;
-        if (excess_tris>0.f)
-        {
-            LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris <<  LL_ENDL;
-            charged_tris += excess_tris;
-        }
+        return 0.f;
     }
-    return charged_tris;
 }
 
+// FIXME replace with calc based on LLMeshCostData
 F32 LLMeshRepository::getStreamingCost(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
 {
+	F32 result = 0.f;
     if (mThread && mesh_id.notNull())
     {
         LLMutexLock lock(mThread->mHeaderMutex);
         LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
         if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
         {
-            return getStreamingCost(iter->second, radius, bytes, bytes_visible, lod, unscaled_value);
+            result  = getStreamingCost(iter->second, radius, bytes, bytes_visible, lod, unscaled_value);
         }
     }
-    return 0.f;
+    if (result > 0.f)
+    {
+        LLMeshCostData data;
+        if (getCostData(mesh_id, data))
+        {
+            F32 ref_streaming_cost = data.computeRadiusBasedStreamingCost(radius);
+            F32 ref_weighted_tris = data.computeRadiusWeightedTris(radius);
+            if (!is_approx_equal(ref_streaming_cost,result))
+            {
+                LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL;
+            }
+            if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value))
+            {
+                LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL;
+            }
+            if (bytes && (*bytes != data.mSizeTotal))
+            {
+                LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.mSizeTotal << LL_ENDL;
+            }
+            if (bytes_visible && (lod >=0) && (lod < 4) && (*bytes_visible != data.mSizeByLOD[lod]))
+            {
+                LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.mSizeByLOD[lod] << LL_ENDL;
+            }
+        }
+        else
+        {
+            LL_WARNS() << "getCostData failed!!!" << LL_ENDL;
+        }
+    }
+    return result;
 }
 
+// FIXME replace with calc based on LLMeshCostData
 //static
 F32 LLMeshRepository::getStreamingCost(LLSD& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
 {
@@ -4289,6 +4280,141 @@ F32 LLMeshRepository::getStreamingCost(LLSD& header, F32 radius, S32* bytes, S32
 	return weighted_avg/gSavedSettings.getU32("MeshTriangleBudget")*15000.f;
 }
 
+LLMeshCostData::LLMeshCostData()
+{
+    mSizeByLOD.resize(4);
+    mEstTrisByLOD.resize(4);
+
+    std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
+    std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
+    
+    mSizeTotal = 0;
+    mEstTrisMax = 0;
+}
+
+F32 LLMeshCostData::computeRadiusWeightedTris(F32 radius)
+{
+	F32 max_distance = 512.f;
+
+	F32 dlowest = llmin(radius/0.03f, max_distance);
+	F32 dlow = llmin(radius/0.06f, max_distance);
+	F32 dmid = llmin(radius/0.24f, max_distance);
+	
+	F32 triangles_lowest = mEstTrisByLOD[0];
+	F32 triangles_low = mEstTrisByLOD[1];
+	F32 triangles_mid = mEstTrisByLOD[2];
+	F32 triangles_high = mEstTrisByLOD[3];
+
+	F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559)
+	F32 min_area = 1.f;
+
+	F32 high_area = llmin(F_PI*dmid*dmid, max_area);
+	F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
+	F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
+	F32 lowest_area = max_area;
+
+	lowest_area -= low_area;
+	low_area -= mid_area;
+	mid_area -= high_area;
+
+	high_area = llclamp(high_area, min_area, max_area);
+	mid_area = llclamp(mid_area, min_area, max_area);
+	low_area = llclamp(low_area, min_area, max_area);
+	lowest_area = llclamp(lowest_area, min_area, max_area);
+
+	F32 total_area = high_area + mid_area + low_area + lowest_area;
+	high_area /= total_area;
+	mid_area /= total_area;
+	low_area /= total_area;
+	lowest_area /= total_area;
+
+	F32 weighted_avg = triangles_high*high_area +
+					   triangles_mid*mid_area +
+					   triangles_low*low_area +
+					   triangles_lowest*lowest_area;
+
+    return weighted_avg;
+}
+
+F32 LLMeshCostData::computeEstTrisForStreamingCost()
+{
+    LL_DEBUGS("StreamingCost") << "tris_by_lod: "
+                               << mEstTrisByLOD[0] << ", "
+                               << mEstTrisByLOD[1] << ", "
+                               << mEstTrisByLOD[2] << ", "
+                               << mEstTrisByLOD[3] << LL_ENDL;
+
+    F32 charged_tris = mEstTrisByLOD[3];
+    F32 allowed_tris = mEstTrisByLOD[3];
+    const F32 ENFORCE_FLOOR = 64.0f;
+    for (S32 i=2; i>=0; i--)
+    {
+        // How many tris can we have in this LOD without affecting land impact?
+        // - normally an LOD should be at most half the size of the previous one.
+        // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller.
+        allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]);
+        F32 excess_tris = mEstTrisByLOD[i]-allowed_tris;
+        if (excess_tris>0.f)
+        {
+            LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris <<  LL_ENDL;
+            charged_tris += excess_tris;
+        }
+    }
+    return charged_tris;
+}
+
+F32 LLMeshCostData::computeRadiusBasedStreamingCost(F32 radius)
+{
+	return computeRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f;
+}
+
+F32 LLMeshCostData::computeTriangleBasedStreamingCost()
+{
+    F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * computeEstTrisForStreamingCost()/0.06;
+    return result;
+}
+
+bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
+{
+    data = LLMeshCostData();
+    
+    if (mThread && mesh_id.notNull())
+    {
+        LLMutexLock lock(mThread->mHeaderMutex);
+        LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
+        if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
+        {
+            LLSD& header = iter->second;
+
+            bool header_invalid = (header.has("404")
+                                   || !header.has("lowest_lod")
+                                   || (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION));
+            if (!header_invalid)
+            {
+                return getCostData(header, mesh_id, data);
+            }
+
+            return true;
+        }
+    }
+    return false;
+}
+
+bool LLMeshRepository::getCostData(LLSD& header, LLUUID mesh_id, LLMeshCostData& data)
+{
+    data = LLMeshCostData();
+
+    if (!getLODSizes(header, data.mSizeByLOD, data.mEstTrisByLOD))
+    {
+        return false;
+    }
+    
+    data.mEstTrisMax = llmax(data.mEstTrisByLOD[0], data.mEstTrisByLOD[1], data.mEstTrisByLOD[2], data.mEstTrisByLOD[3]);
+
+    data.mSizeTotal = data.mSizeByLOD[0] + data.mSizeByLOD[1] + data.mSizeByLOD[2] + data.mSizeByLOD[3];
+
+    return true;
+}
 
 LLPhysicsDecomp::LLPhysicsDecomp()
 : LLThread("Physics Decomp")
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index bae3f66a72..e43f719471 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -451,6 +451,45 @@ private:
 	LLCore::HttpRequest::priority_t		mHttpPriority;
 };
 
+// Params related to streaming cost, render cost, and scene complexity tracking.
+struct LLMeshCostData
+{
+    LLMeshCostData();
+
+    // From the "size" field of the mesh header. LOD 0=lowest, 3=highest.
+    std::vector<S32> mSizeByLOD;
+
+    // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest.
+    std::vector<F32> mEstTrisByLOD;
+
+    // Estimated triangle counts for the largest LOD. Typically this
+    // is also the "high" LOD, but not necessarily.
+    F32 mEstTrisMax;
+
+    // Sum of all LOD sizes.
+    S32 mSizeTotal;
+
+    // Helper functions for building data
+
+    // Triangle count as computed by original streaming cost
+    // formula. Triangles in each LOD are weighted based on how
+    // frequently they will be seen.
+    // This was called "unscaled_value" in the original getStreamingCost() functions.
+    F32 computeRadiusWeightedTris(F32 radius);
+
+    // Triangle count used by triangle-based cost formula. Based on
+    // triangles in highest LOD plus potentially partial charges for
+    // lower LODs depending on complexity.
+    F32 computeEstTrisForStreamingCost();
+
+    // Streaming cost. This should match the server-side calculation
+    // for the corresponding volume.
+    F32 computeRadiusBasedStreamingCost(F32 radius);
+
+    // New streaming cost formula, currently only used for animated objects.
+    F32 computeTriangleBasedStreamingCost();
+};
+
 class LLMeshRepository
 {
 public:
@@ -472,12 +511,14 @@ public:
 	
 	static LLDeadmanTimer sQuiescentTimer;		// Time-to-complete-mesh-downloads after significant events
 
-    bool getLODSizes(LLUUID mesh_id, std::vector<S32>& lod_byte_sizes, std::vector<F32>& lod_tri_counts);
+    bool getLODSizes(LLSD& header, std::vector<S32>& lod_byte_sizes, std::vector<F32>& lod_tri_counts);
     // Estimated triangle count of the largest LOD
     F32 getEstTrianglesMax(LLUUID mesh_id);
     F32 getEstTrianglesStreamingCost(LLUUID mesh_id);
 	F32 getStreamingCost(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
 	static F32 getStreamingCost(LLSD& 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, LLUUID mesh_id, LLMeshCostData& data);
 
 	LLMeshRepository();
 
@@ -588,5 +629,9 @@ public:
 
 extern LLMeshRepository gMeshRepo;
 
+// AXON make sure this is consistent with the final simulator-side values.
+const F32 ANIMATED_OBJECT_BASE_COST = 15.0f;
+const F32 ANIMATED_OBJECT_COST_PER_KTRI = 1.5f;
+
 #endif
 
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index bf646eb44b..9db4b3de0c 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -3918,10 +3918,6 @@ F32 LLVOVolume::getStreamingCost(S32* bytes, S32* visible_bytes, F32* unscaled_v
 {
 	F32 radius = getScale().length()*0.5f;
 
-    // AXON make sure this is consistent with the final simulator-side values.
-    const F32 ANIMATED_OBJECT_BASE_COST = 15.0f;
-    const F32 ANIMATED_OBJECT_COST_PER_KTRI = 1.5f;
-
     F32 linkset_base_cost = 0.f;
     if (isAnimatedObject() && isRootEdit())
     {
-- 
GitLab