diff options
| author | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2025-03-11 22:44:49 -0400 | 
|---|---|---|
| committer | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2025-03-11 22:44:49 -0400 | 
| commit | e0d14e02e152b4e75ff8bdd974677f9669163d68 (patch) | |
| tree | 3379d14c9c5d8c188d2fb716e61edd09fc1219a9 /indra/newview/llmeshrepository.cpp | |
| parent | 179b29252d8bb28e11686a1852c8e8ffcd98ecc0 (diff) | |
| parent | b50ad90febda24d2296541f46ea1a129232aad70 (diff) | |
Merge branch 'release/2025.03' into rye/forevermac
Diffstat (limited to 'indra/newview/llmeshrepository.cpp')
| -rw-r--r-- | indra/newview/llmeshrepository.cpp | 1205 | 
1 files changed, 883 insertions, 322 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 93453f507c..e1fa84b4d8 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -255,12 +255,12 @@  //     mSkinInfoQ               mMutex        rw.repo.mMutex, rw.main.mMutex [5] (was:  [0])  //     mDecompositionRequests   mMutex        rw.repo.mMutex, ro.repo.none [5]  //     mPhysicsShapeRequests    mMutex        rw.repo.mMutex, ro.repo.none [5] -//     mDecompositionQ          mMutex        rw.repo.mMutex, rw.main.mMutex [5] (was:  [0]) +//     mDecompositionQ          mMutex        rw.repo.mLoadedMutex, rw.main.mLoadedMutex [5] (was:  [0])  //     mHeaderReqQ              mMutex        ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex  //     mLODReqQ                 mMutex        ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex -//     mUnavailableQ            mMutex        rw.repo.none [0], ro.main.none [5], rw.main.mMutex -//     mLoadedQ                 mMutex        rw.repo.mMutex, ro.main.none [5], rw.main.mMutex -//     mPendingLOD              mMutex        rw.repo.mMutex, rw.any.mMutex +//     mUnavailableQ            mMutex        rw.repo.none [0], ro.main.none [5], rw.main.mLoadedMutex +//     mLoadedQ                 mMutex        rw.repo.mLoadedMutex, ro.main.none [5], rw.main.mLoadedMutex +//     mPendingLOD              mMutex        rw.repo.mPendingMutex, rw.any.mPendingMutex  //     mGetMeshCapability       mMutex        rw.main.mMutex, ro.repo.mMutex (was:  [0])  //     mGetMesh2Capability      mMutex        rw.main.mMutex, ro.repo.mMutex (was:  [0])  //     mGetMeshVersion          mMutex        rw.main.mMutex, ro.repo.mMutex @@ -343,20 +343,22 @@ static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch");  LLMeshRepository gMeshRepo; -const S32 MESH_HEADER_SIZE = 4096;                      // Important:  assumption is that headers fit in this space +constexpr U32 CACHE_PREAMBLE_VERSION = 1; +constexpr S32 CACHE_PREAMBLE_SIZE = sizeof(U32) * 3; //version, header_size, flags +constexpr S32 MESH_HEADER_SIZE = 4096;                      // Important:  assumption is that headers fit in this space -const S32 REQUEST2_HIGH_WATER_MIN = 32;                 // Limits for GetMesh2 regions -const S32 REQUEST2_HIGH_WATER_MAX = 100; -const S32 REQUEST2_LOW_WATER_MIN = 16; -const S32 REQUEST2_LOW_WATER_MAX = 50; +constexpr S32 REQUEST2_HIGH_WATER_MIN = 32;                 // Limits for GetMesh2 regions +constexpr S32 REQUEST2_HIGH_WATER_MAX = 100; +constexpr S32 REQUEST2_LOW_WATER_MIN = 16; +constexpr S32 REQUEST2_LOW_WATER_MAX = 50; -const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21;        // Size at which requests goes to narrow/slow queue -const long SMALL_MESH_XFER_TIMEOUT = 120L;              // Seconds to complete xfer, small mesh downloads -const long LARGE_MESH_XFER_TIMEOUT = 600L;              // Seconds to complete xfer, large downloads +constexpr U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21;        // Size at which requests goes to narrow/slow queue +constexpr long SMALL_MESH_XFER_TIMEOUT = 120L;              // Seconds to complete xfer, small mesh downloads +constexpr long LARGE_MESH_XFER_TIMEOUT = 600L;              // Seconds to complete xfer, large downloads -const U32 DOWNLOAD_RETRY_LIMIT = 8; -const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds +constexpr U32 DOWNLOAD_RETRY_LIMIT = 8; +constexpr F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds  // Would normally like to retry on uploads as some  // retryable failures would be recoverable.  Unfortunately, @@ -366,7 +368,7 @@ const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds  // cap which then produces a 404 on retry destroying some  // (occasionally) useful error information.  We'll leave  // upload retries to the user as in the past.  SH-4667. -const long UPLOAD_RETRY_LIMIT = 0L; +constexpr long UPLOAD_RETRY_LIMIT = 0L;  // Maximum mesh version to support.  Three least significant digits are reserved for the minor version,  // with major version changes indicating a format change that is not backwards compatible and should not @@ -374,7 +376,7 @@ const long UPLOAD_RETRY_LIMIT = 0L;  // present, the version is 0.001. A viewer that can parse version 0.001 can also parse versions up to 0.999,  // but not 1.0 (integer 1000).  // See wiki at https://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format -const S32 MAX_MESH_VERSION = 999; +constexpr S32 MAX_MESH_VERSION = 999;  U32 LLMeshRepository::sBytesReceived = 0;  U32 LLMeshRepository::sMeshRequestCount = 0; @@ -386,12 +388,12 @@ U32 LLMeshRepository::sLODProcessing = 0;  U32 LLMeshRepository::sLODPending = 0;  U32 LLMeshRepository::sCacheBytesRead = 0; -U32 LLMeshRepository::sCacheBytesWritten = 0; +std::atomic<U32> LLMeshRepository::sCacheBytesWritten = 0;  U32 LLMeshRepository::sCacheBytesHeaders = 0;  U32 LLMeshRepository::sCacheBytesSkins = 0;  U32 LLMeshRepository::sCacheBytesDecomps = 0;  U32 LLMeshRepository::sCacheReads = 0; -U32 LLMeshRepository::sCacheWrites = 0; +std::atomic<U32> LLMeshRepository::sCacheWrites = 0;  U32 LLMeshRepository::sMaxLockHoldoffs = 0;  LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false);  // true -> gather cpu metrics @@ -550,6 +552,7 @@ LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMate  std::atomic<S32> LLMeshRepoThread::sActiveHeaderRequests = 0;  std::atomic<S32> LLMeshRepoThread::sActiveLODRequests = 0; +std::atomic<S32> LLMeshRepoThread::sActiveSkinRequests = 0;  U32 LLMeshRepoThread::sMaxConcurrentRequests = 1;  S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN;  S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; @@ -584,6 +587,7 @@ public:          : LLCore::HttpHandler(),            mMeshParams(),            mProcessed(false), +          mHasDataOwnership(true),            mHttpHandle(LLCORE_HTTP_HANDLE_INVALID),            mOffset(offset),            mRequestedBytes(requested_bytes) @@ -607,6 +611,9 @@ public:      LLCore::HttpHandle mHttpHandle;      U32 mOffset;      U32 mRequestedBytes; + +protected: +    bool mHasDataOwnership = true;  }; @@ -659,6 +666,9 @@ public:      virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size);      virtual void processFailure(LLCore::HttpStatus status); +private: +    void processLod(U8* data, S32 data_size); +  public:      S32 mLOD;  }; @@ -674,13 +684,17 @@ public:      LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes)          : LLMeshHandlerBase(offset, requested_bytes),            mMeshID(id) -    {} +    { +        LLMeshRepoThread::incActiveSkinRequests(); +    }      virtual ~LLMeshSkinInfoHandler();  protected:      LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &);       // Not defined      void operator=(const LLMeshSkinInfoHandler &);              // Not defined +    void processSkin(U8* data, S32 data_size); +  public:      virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size);      virtual void processFailure(LLCore::HttpStatus status); @@ -823,6 +837,14 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content,      gMeshRepo.uploadError(args);  } +void write_preamble(LLFileSystem &file, S32 header_bytes, S32 flags) +{ +    LLMeshRepository::sCacheBytesWritten += CACHE_PREAMBLE_SIZE; +    file.write((U8*)&CACHE_PREAMBLE_VERSION, sizeof(U32)); +    file.write((U8*)&header_bytes, sizeof(U32)); +    file.write((U8*)&flags, sizeof(U32)); +} +  LLMeshRepoThread::LLMeshRepoThread()  : LLThread("mesh repo"),    mHttpRequest(NULL), @@ -837,6 +859,9 @@ LLMeshRepoThread::LLMeshRepoThread()      mMutex = new LLMutex();      mHeaderMutex = new LLMutex(); +    mLoadedMutex = new LLMutex(); +    mPendingMutex = new LLMutex(); +    mSkinMapMutex = new LLMutex();      mSignal = new LLCondition();      mHttpRequest = new LLCore::HttpRequest;      mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); @@ -849,6 +874,11 @@ LLMeshRepoThread::LLMeshRepoThread()      mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_VND_LL_MESH);      mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_MESH2);      mHttpLargePolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_LARGE_MESH); + +    // Lod processing is expensive due to the number of requests +    // and a need to do expensive cacheOptimize(). +    mMeshThreadPool.reset(new LL::ThreadPool("MeshLodProcessing", 2)); +    mMeshThreadPool->start();  } @@ -875,13 +905,21 @@ LLMeshRepoThread::~LLMeshRepoThread()      }      delete mHttpRequest; -    mHttpRequest = NULL; +    mHttpRequest = nullptr;      delete mMutex; -    mMutex = NULL; +    mMutex = nullptr;      delete mHeaderMutex; -    mHeaderMutex = NULL; +    mHeaderMutex = nullptr; +    delete mLoadedMutex; +    mLoadedMutex = nullptr; +    delete mPendingMutex; +    mPendingMutex = nullptr; +    delete mSkinMapMutex; +    mSkinMapMutex = nullptr;      delete mSignal; -    mSignal = NULL; +    mSignal = nullptr; +    delete[] mDiskCacheBuffer; +    mDiskCacheBuffer = nullptr;  }  void LLMeshRepoThread::run() @@ -908,6 +946,7 @@ void LLMeshRepoThread::run()          // On the other hand, this may actually be an effective and efficient scheme...          mSignal->wait(); +        LL_PROFILE_ZONE_NAMED("mesh_thread_loop")          if (LLApp::isExiting())          { @@ -925,11 +964,55 @@ void LLMeshRepoThread::run()          }          sRequestWaterLevel = static_cast<S32>(mHttpRequestSet.size());            // Stats data update -        // NOTE: order of queue processing intentionally favors LOD requests over header requests +        // NOTE: order of queue processing intentionally favors LOD and Skin requests over header requests          // Todo: we are processing mLODReqQ, mHeaderReqQ, mSkinRequests, mDecompositionRequests and mPhysicsShapeRequests          // in relatively similar manners, remake code to simplify/unify the process,          // like processRequests(&requestQ, fetchFunction); which does same thing for each element +        if (mHttpRequestSet.size() < sRequestHighWater +            && !mSkinRequests.empty()) +        { +            if (!mSkinRequests.empty()) +            { +                std::list<UUIDBasedRequest> incomplete; +                while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) +                { + +                    mMutex->lock(); +                    auto req = mSkinRequests.front(); +                    mSkinRequests.pop_front(); +                    mMutex->unlock(); +                    if (req.isDelayed()) +                    { +                        incomplete.emplace_back(req); +                    } +                    else if (!fetchMeshSkinInfo(req.mId)) +                    { +                        if (req.canRetry()) +                        { +                            req.updateTime(); +                            incomplete.emplace_back(req); +                        } +                        else +                        { +                            LLMutexLock locker(mLoadedMutex); +                            mSkinUnavailableQ.push_back(req); +                            LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; +                        } +                    } +                } + +                if (!incomplete.empty()) +                { +                    LLMutexLock locker(mMutex); +                    for (const auto& req : incomplete) +                    { +                        mSkinRequests.push_back(req); +                    } +                } +            } +        } +          if (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)          {              std::list<LODRequest> incomplete; @@ -950,7 +1033,7 @@ void LLMeshRepoThread::run()                      // failed to load before, wait a bit                      incomplete.push_front(req);                  } -                else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) +                else if (!fetchMeshLOD(req.mMeshParams, req.mLOD))                  {                      if (req.canRetry())                      { @@ -961,7 +1044,7 @@ void LLMeshRepoThread::run()                      else                      {                          // too many fails -                        LLMutexLock lock(mMutex); +                        LLMutexLock lock(mLoadedMutex);                          mUnavailableQ.push_back(req);                          LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL;                      } @@ -998,7 +1081,7 @@ void LLMeshRepoThread::run()                      // failed to load before, wait a bit                      incomplete.push_front(req);                  } -                else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) +                else if (!fetchMeshHeader(req.mMeshParams))                  {                      if (req.canRetry())                      { @@ -1028,54 +1111,13 @@ void LLMeshRepoThread::run()          // performing long-duration actions.          if (mHttpRequestSet.size() < sRequestHighWater -            && (!mSkinRequests.empty() -            || !mDecompositionRequests.empty() +            && (!mDecompositionRequests.empty()              || !mPhysicsShapeRequests.empty()))          {              // Something to do probably, lock and double-check.  We don't want              // to hold the lock long here.  That will stall main thread activities              // so we bounce it. -            if (!mSkinRequests.empty()) -            { -                std::list<UUIDBasedRequest> incomplete; -                while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) -                { - -                    mMutex->lock(); -                    auto req = mSkinRequests.front(); -                    mSkinRequests.pop_front(); -                    mMutex->unlock(); -                    if (req.isDelayed()) -                    { -                        incomplete.emplace_back(req); -                    } -                    else if (!fetchMeshSkinInfo(req.mId, req.canRetry())) -                    { -                        if (req.canRetry()) -                        { -                            req.updateTime(); -                            incomplete.emplace_back(req); -                        } -                        else -                        { -                            LLMutexLock locker(mMutex); -                            mSkinUnavailableQ.push_back(req); -                            LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; -                        } -                    } -                } - -                if (!incomplete.empty()) -                { -                    LLMutexLock locker(mMutex); -                    for (const auto& req : incomplete) -                    { -                        mSkinRequests.push_back(req); -                    } -                } -            } -              // holding lock, try next list              // *TODO:  For UI/debug-oriented lists, we might drop the fine-              // grained locking as there's a lowered expectation of smoothness @@ -1103,7 +1145,7 @@ void LLMeshRepoThread::run()                          }                          else                          { -                            LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; +                            LL_DEBUGS(LOG_MESH) << "mDecompositionRequests failed: " << req.mId << LL_ENDL;                          }                      }                  } @@ -1139,7 +1181,7 @@ void LLMeshRepoThread::run()                          }                          else                          { -                            LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; +                            LL_DEBUGS(LOG_MESH) << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL;                          }                      }                  } @@ -1195,40 +1237,91 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32      }  } -  void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)  { //could be called from any thread      const LLUUID& mesh_id = mesh_params.getSculptID(); -    LLMutexLock lock(mMutex); -    LLMutexLock header_lock(mHeaderMutex); -    mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); -    if (iter != mMeshHeader.end()) +    loadMeshLOD(mesh_id, mesh_params, lod); +} + +void LLMeshRepoThread::loadMeshLOD(const LLUUID& mesh_id, const LLVolumeParams& mesh_params, S32 lod) +{ +    if (hasHeader(mesh_id))      { //if we have the header, request LOD byte range          LODRequest req(mesh_params, lod);          { +            LLMutexLock lock(mMutex);              mLODReqQ.push(req);              LLMeshRepository::sLODProcessing++;          }      }      else      { +        LLMutexLock lock(mPendingMutex);          HeaderRequest req(mesh_params);          pending_lod_map::iterator pending = mPendingLOD.find(mesh_id);          if (pending != mPendingLOD.end()) -        { //append this lod request to existing header request -            pending->second.push_back(lod); -            llassert(pending->second.size() <= LLModel::NUM_LODS); +        { +            //append this lod request to existing header request +            if (lod < LLModel::NUM_LODS && lod >= 0) +            { +                pending->second[lod]++; +            } +            else +            { +                LL_WARNS(LOG_MESH) << "Invalid LOD request: " << lod << "for mesh" << mesh_id << LL_ENDL; +            } +            llassert_msg(lod < LLModel::NUM_LODS, "Requested lod is out of bounds");          }          else -        { //if no header request is pending, fetch header +        { +            //if no header request is pending, fetch header +            auto& array = mPendingLOD[mesh_id]; +            std::fill(array.begin(), array.end(), 0); +            array[lod]++; + +            LLMutexLock lock(mMutex);              mHeaderReqQ.push(req); -            mPendingLOD[mesh_id].push_back(lod);          }      }  } +U8* LLMeshRepoThread::getDiskCacheBuffer(S32 size) +{ +    if (mDiskCacheBufferSize < size) +    { +        const S32 MINIMUM_BUFFER_SIZE = 8192; // a minimum to avoid frequent early reallocations +        size = llmax(MINIMUM_BUFFER_SIZE, size); +        delete[] mDiskCacheBuffer; +        try +        { +            mDiskCacheBuffer = new U8[size]; +        } +        catch (std::bad_alloc&) +        { +            LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh thread's buffer, size: " << size << LL_ENDL; +            mDiskCacheBuffer = NULL; + +            // Not sure what size is reasonable +            // but if 30MB allocation failed, we definitely have issues +            const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB +            if (size < MAX_SIZE) +            { +                LLAppViewer::instance()->outOfMemorySoftQuit(); +            } // else ignore failures for anomalously large data +        } +        mDiskCacheBufferSize = size; +    } +    else +    { +        // reusing old buffer, reset heading bytes to ensure +        // old content won't be parsable if something fails. +        memset(mDiskCacheBuffer, 0, 16); +    } +    return mDiskCacheBuffer; +} +  // Mutex:  must be holding mMutex when called  void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap)  { @@ -1327,7 +1420,7 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url,  } -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)  {      LL_PROFILE_ZONE_SCOPED;      if (!mHeaderMutex) @@ -1337,7 +1430,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)      mHeaderMutex->lock(); -    auto header_it = mMeshHeader.find(mesh_id); +    mesh_header_map::const_iterator header_it = mMeshHeader.find(mesh_id);      if (header_it == mMeshHeader.end())      { //we have no header info for this mesh, do nothing          mHeaderMutex->unlock(); @@ -1346,23 +1439,24 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)      ++LLMeshRepository::sMeshRequestCount;      bool ret = true; -    U32 header_size = header_it->second.first; +    const LLMeshHeader& header = header_it->second; +    U32 header_size = header.mHeaderSize;      if (header_size > 0)      { -        const LLMeshHeader& header = header_it->second.second; -          S32 version = header.mVersion;          S32 offset = header_size + header.mSkinOffset;          S32 size = header.mSkinSize; +        bool in_cache = header.mSkinInCache;          mHeaderMutex->unlock();          if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)          {              //check cache for mesh skin info +            S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE;              LLFileSystem file(mesh_id, LLAssetType::AT_MESH); -            if (file.getSize() >= offset + size) +            if (in_cache && file.getSize() >= disk_ofset + size)              {                  U8* buffer = new(std::nothrow) U8[size];                  if (!buffer) @@ -1370,19 +1464,19 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)                      LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL;                      // Not sure what size is reasonable for skin info, -                    // but if 20MB allocation failed, we definetely have issues +                    // but if 30MB allocation failed, we definitely have issues                      const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB                      if (size < MAX_SIZE)                      {                          LLAppViewer::instance()->outOfMemorySoftQuit();                      } // else ignore failures for anomalously large data -                    LLMutexLock locker(mMutex); +                    LLMutexLock locker(mLoadedMutex);                      mSkinUnavailableQ.emplace_back(mesh_id);                      return true;                  }                  LLMeshRepository::sCacheBytesRead += size;                  ++LLMeshRepository::sCacheReads; -                file.seek(offset); +                file.seek(disk_ofset);                  file.read(buffer, size);                  //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1393,14 +1487,67 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)                  }                  if (!zero) -                { //attempt to parse -                    if (skinInfoReceived(mesh_id, buffer, size)) +                { +                    //attempt to parse +                    bool posted = mMeshThreadPool->getQueue().post( +                        [mesh_id, buffer, size] +                        () +                    { +                        if (!gMeshRepo.mThread->skinInfoReceived(mesh_id, buffer, size)) +                        { +                            // either header is faulty or something else overwrote the cache +                            S32 header_size = 0; +                            U32 header_flags = 0; +                            { +                                LL_DEBUGS(LOG_MESH) << "Mesh header for ID " << mesh_id << " cache mismatch." << LL_ENDL; + +                                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                                auto header_it = gMeshRepo.mThread->mMeshHeader.find(mesh_id); +                                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                                { +                                    LLMeshHeader& header = header_it->second; +                                    // for safety just mark everything as missing +                                    header.mSkinInCache = false; +                                    header.mPhysicsConvexInCache = false; +                                    header.mPhysicsMeshInCache = false; +                                    for (S32 i = 0; i < LLModel::NUM_LODS; ++i) +                                    { +                                        header.mLodInCache[i] = false; +                                    } +                                    header_size = header.mHeaderSize; +                                    header_flags = header.getFlags(); +                                } +                            } + +                            if (header_size > 0) +                            { +                                LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); +                                if (file.getMaxSize() >= CACHE_PREAMBLE_SIZE) +                                { +                                    write_preamble(file, header_size, header_flags); +                                } +                            } + +                            { +                                LLMutexLock lock(gMeshRepo.mThread->mMutex); +                                UUIDBasedRequest req(mesh_id); +                                gMeshRepo.mThread->mSkinRequests.push_back(req); +                            } +                        } +                        delete[] buffer; +                    }); +                    if (posted) +                    { +                        // lambda owns buffer +                        return true; +                    } +                    else if (skinInfoReceived(mesh_id, buffer, size))                      {                          delete[] buffer;                          return true;                      }                  } -                  delete[] buffer;              } @@ -1420,26 +1567,21 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)                                         << LL_ENDL;                      ret = false;                  } -                else if(can_retry) +                else                  {                      handler->mHttpHandle = handle;                      mHttpRequestSet.insert(handler);                  } -                else -                { -                    LLMutexLock locker(mMutex); -                    mSkinUnavailableQ.emplace_back(mesh_id); -                }              }              else              { -                LLMutexLock locker(mMutex); +                LLMutexLock locker(mLoadedMutex);                  mSkinUnavailableQ.emplace_back(mesh_id);              }          }          else          { -            LLMutexLock locker(mMutex); +            LLMutexLock locker(mLoadedMutex);              mSkinUnavailableQ.emplace_back(mesh_id);          }      } @@ -1470,42 +1612,35 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)      }      ++LLMeshRepository::sMeshRequestCount; -    U32 header_size = header_it->second.first; +    const auto& header = header_it->second; +    U32 header_size = header.mHeaderSize;      bool ret = true;      if (header_size > 0)      { -        const auto& header = header_it->second.second;          S32 version = header.mVersion;          S32 offset = header_size + header.mPhysicsConvexOffset;          S32 size = header.mPhysicsConvexSize; +        bool in_cache = header.mPhysicsConvexInCache;          mHeaderMutex->unlock();          if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)          { -            //check cache for mesh skin info +            // check cache for mesh decomposition +            S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE;              LLFileSystem file(mesh_id, LLAssetType::AT_MESH); -            if (file.getSize() >= offset+size) +            if (in_cache && file.getSize() >= disk_ofset + size)              { -                U8* buffer = new(std::nothrow) U8[size]; +                U8* buffer = getDiskCacheBuffer(size);                  if (!buffer)                  { -                    LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - -                    // Not sure what size is reasonable for decomposition -                    // but if 20MB allocation failed, we definetely have issues -                    const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB -                    if (size < MAX_SIZE) -                    { -                        LLAppViewer::instance()->outOfMemorySoftQuit(); -                    } // else ignore failures for anomalously large decompositiions                      return true;                  }                  LLMeshRepository::sCacheBytesRead += size;                  ++LLMeshRepository::sCacheReads; -                file.seek(offset); +                file.seek(disk_ofset);                  file.read(buffer, size);                  //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1519,12 +1654,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)                  { //attempt to parse                      if (decompositionReceived(mesh_id, buffer, size))                      { -                        delete[] buffer;                          return true;                      }                  } - -                delete[] buffer;              }              //reading from cache failed for whatever reason, fetch from sim @@ -1578,41 +1710,36 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)      }      ++LLMeshRepository::sMeshRequestCount; -    U32 header_size = header_it->second.first; +    const auto& header = header_it->second; +    U32 header_size = header.mHeaderSize;      bool ret = true;      if (header_size > 0)      { -        const auto& header = header_it->second.second;          S32 version = header.mVersion;          S32 offset = header_size + header.mPhysicsMeshOffset;          S32 size = header.mPhysicsMeshSize; +        bool in_cache = header.mPhysicsMeshInCache;          mHeaderMutex->unlock(); +        // todo: check header.mHasPhysicsMesh          if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)          {              //check cache for mesh physics shape info +            S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE;              LLFileSystem file(mesh_id, LLAssetType::AT_MESH); -            if (file.getSize() >= offset+size) +            if (in_cache && file.getSize() >= disk_ofset +size)              {                  LLMeshRepository::sCacheBytesRead += size;                  ++LLMeshRepository::sCacheReads; -                file.seek(offset); -                U8* buffer = new(std::nothrow) U8[size]; + +                U8* buffer = getDiskCacheBuffer(size);                  if (!buffer)                  { -                    LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - -                    // Not sure what size is reasonable for physcis -                    // but if 20MB allocation failed, we definetely have issues -                    const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB -                    if (size < MAX_SIZE) -                    { -                        LLAppViewer::instance()->outOfMemorySoftQuit(); -                    } // else ignore failures for anomalously large data                      return true;                  } +                file.seek(disk_ofset);                  file.read(buffer, size);                  //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1626,12 +1753,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)                  { //attempt to parse                      if (physicsShapeReceived(mesh_id, buffer, size) == MESH_OK)                      { -                        delete[] buffer;                          return true;                      }                  } - -                delete[] buffer;              }              //reading from cache failed for whatever reason, fetch from sim @@ -1699,8 +1823,22 @@ void LLMeshRepoThread::decActiveHeaderRequests()      --LLMeshRepoThread::sActiveHeaderRequests;  } +//static +void LLMeshRepoThread::incActiveSkinRequests() +{ +    LLMutexLock lock(gMeshRepo.mThread->mMutex); +    ++LLMeshRepoThread::sActiveSkinRequests; +} + +//static +void LLMeshRepoThread::decActiveSkinRequests() +{ +    LLMutexLock lock(gMeshRepo.mThread->mMutex); +    --LLMeshRepoThread::sActiveSkinRequests; +} +  //return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params)  {      LL_PROFILE_ZONE_SCOPED;      ++LLMeshRepository::sMeshRequestCount; @@ -1714,17 +1852,34 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c          if (size > 0)          {              // *NOTE:  if the header size is ever more than 4KB, this will break -            U8 buffer[MESH_HEADER_SIZE]; -            S32 bytes = llmin(size, MESH_HEADER_SIZE); +            constexpr S32 DISK_MINIMAL_READ = 4096; +            U8 buffer[DISK_MINIMAL_READ * 2]; +            S32 bytes = llmin(size, DISK_MINIMAL_READ);              LLMeshRepository::sCacheBytesRead += bytes;              ++LLMeshRepository::sCacheReads; +              file.read(buffer, bytes); -            if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) + +            U32 version = 0; +            memcpy(&version, buffer, sizeof(U32)); +            if (version == CACHE_PREAMBLE_VERSION)              { -                LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mesh_params.getSculptID() << " - was retrieved from the cache." << LL_ENDL; +                S32 header_size = 0; +                memcpy(&header_size, buffer + sizeof(U32), sizeof(S32)); +                if (header_size + CACHE_PREAMBLE_SIZE > DISK_MINIMAL_READ) +                { +                    bytes = llmin(size , DISK_MINIMAL_READ * 2); +                    file.read(buffer + DISK_MINIMAL_READ, bytes - DISK_MINIMAL_READ); +                } +                U32 flags = 0; +                memcpy(&flags, buffer + 2 * sizeof(U32), sizeof(U32)); +                if (headerReceived(mesh_params, buffer + CACHE_PREAMBLE_SIZE, bytes - CACHE_PREAMBLE_SIZE, flags) == MESH_OK) +                { +                    LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mesh_params.getSculptID() << " - was retrieved from the cache." << LL_ENDL; -                // Found mesh in cache -                return true; +                    // Found mesh in cache +                    return true; +                }              }          }      } @@ -1753,7 +1908,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c                                 << LL_ENDL;              retval = false;          } -        else if (can_retry) +        else          {              handler->mHttpHandle = handle;              mHttpRequestSet.insert(handler); @@ -1764,7 +1919,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c  }  //return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod)  {      LL_PROFILE_ZONE_SCOPED;      if (!mHeaderMutex) @@ -1784,41 +1939,43 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,      ++LLMeshRepository::sMeshRequestCount;      bool retval = true; -    U32 header_size = header_it->second.first; +    const auto& header = header_it->second; +    U32 header_size = header.mHeaderSize;      if (header_size > 0)      { -        const auto& header = header_it->second.second;          S32 version = header.mVersion;          S32 offset = header_size + header.mLodOffset[lod];          S32 size = header.mLodSize[lod]; +        bool in_cache = header.mLodInCache[lod];          mHeaderMutex->unlock();          if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)          { - +            S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE;              //check cache for mesh asset              LLFileSystem file(mesh_id, LLAssetType::AT_MESH); -            if (file.getSize() >= offset+size) +            if (in_cache && (file.getSize() >= disk_ofset + size))              { -                U8* buffer = new(std::nothrow) U8[size]; +                U8* buffer = new(std::nothrow) U8[size]; // todo, make buffer thread local and read in thread?                  if (!buffer)                  {                      LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL;                      // Not sure what size is reasonable for a mesh, -                    // but if 20MB allocation failed, we definetely have issues +                    // but if 30MB allocation failed, we definitely have issues                      const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB                      if (size < MAX_SIZE)                      {                          LLAppViewer::instance()->outOfMemorySoftQuit();                      } // else ignore failures for anomalously large data -                    LLMutexLock lock(mMutex); + +                    LLMutexLock lock(mLoadedMutex);                      mUnavailableQ.push_back(LODRequest(mesh_params, lod));                      return true;                  }                  LLMeshRepository::sCacheBytesRead += size;                  ++LLMeshRepository::sCacheReads; -                file.seek(offset); +                file.seek(disk_ofset);                  file.read(buffer, size);                  //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1829,15 +1986,76 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,                  }                  if (!zero) -                { //attempt to parse -                    if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) +                { +                    //attempt to parse +                    const LLVolumeParams params(mesh_params); +                    bool posted = mMeshThreadPool->getQueue().post( +                        [params, mesh_id, lod, buffer, size] +                        ()                      { +                        if (gMeshRepo.mThread->lodReceived(params, lod, buffer, size) == MESH_OK) +                        { +                            LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mesh_id << " - was retrieved from the cache." << LL_ENDL; +                        } +                        else +                        { +                            // either header is faulty or something else overwrote the cache +                            S32 header_size = 0; +                            U32 header_flags = 0; +                            { +                                LL_DEBUGS(LOG_MESH) << "Mesh header for ID " << mesh_id << " cache mismatch." << LL_ENDL; + +                                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                                auto header_it = gMeshRepo.mThread->mMeshHeader.find(mesh_id); +                                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                                { +                                    LLMeshHeader& header = header_it->second; +                                    // for safety just mark everything as missing +                                    header.mSkinInCache = false; +                                    header.mPhysicsConvexInCache = false; +                                    header.mPhysicsMeshInCache = false; +                                    for (S32 i = 0; i < LLModel::NUM_LODS; ++i) +                                    { +                                        header.mLodInCache[i] = false; +                                    } +                                    header_size = header.mHeaderSize; +                                    header_flags = header.getFlags(); +                                } +                            } + +                            if (header_size > 0) +                            { +                                LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); +                                if (file.getMaxSize() >= CACHE_PREAMBLE_SIZE) +                                { +                                    write_preamble(file, header_size, header_flags); +                                } +                            } + +                            { +                                LLMutexLock lock(gMeshRepo.mThread->mMutex); +                                LODRequest req(params, lod); +                                gMeshRepo.mThread->mLODReqQ.push(req); +                                LLMeshRepository::sLODProcessing++; +                            } +                        }                          delete[] buffer; +                    }); +                    if (posted) +                    { +                        // now lambda owns buffer +                        return true; +                    } +                    else if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) +                    { +                        delete[] buffer;                          LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mesh_id << " - was retrieved from the cache." << LL_ENDL;                          return true;                      } +                  }                  delete[] buffer; @@ -1861,27 +2079,22 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,                                         << LL_ENDL;                      retval = false;                  } -                else if (can_retry) +                else                  { +                    // we already made a request, store the handle                      handler->mHttpHandle = handle;                      mHttpRequestSet.insert(handler); -                    // *NOTE:  Allowing a re-request, not marking as unavailable.  Is that correct? -                } -                else -                { -                    LLMutexLock lock(mMutex); -                    mUnavailableQ.push_back(LODRequest(mesh_params, lod));                  }              }              else              { -                LLMutexLock lock(mMutex); +                LLMutexLock lock(mLoadedMutex);                  mUnavailableQ.push_back(LODRequest(mesh_params, lod));              }          }          else          { -            LLMutexLock lock(mMutex); +            LLMutexLock lock(mLoadedMutex);              mUnavailableQ.push_back(LODRequest(mesh_params, lod));          }      } @@ -1893,14 +2106,19 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,      return retval;  } -EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) +EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size, U32 flags)  { +    LL_PROFILE_ZONE_SCOPED;      const LLUUID mesh_id = mesh_params.getSculptID();      LLSD header_data;      LLMeshHeader header;      llssize header_size = 0; +    S32 skin_offset = -1; +    S32 skin_size = -1; +    S32 lod_offset[LLModel::NUM_LODS] = { -1 }; +    S32 lod_size[LLModel::NUM_LODS] = { -1 };      if (data_size > 0)      {          llssize dsize = data_size; @@ -1933,7 +2151,40 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes          // make sure there is at least one lod, function returns -1 and marks as 404 otherwise          else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0)          { -            header_size += stream.tellg(); +            header.mHeaderSize = (S32)stream.tellg(); +            header_size += header.mHeaderSize; +            skin_offset = header.mSkinOffset; +            skin_size = header.mSkinSize; + +            memcpy(lod_offset, header.mLodOffset, sizeof(lod_offset)); +            memcpy(lod_size, header.mLodSize, sizeof(lod_size)); + +            if (flags != 0) +            { +                header.setFromFlags(flags); +            } +            else +            { +                if (header.mSkinSize > 0 && header_size + header.mSkinOffset + header.mSkinSize < data_size) +                { +                    header.mSkinInCache = true; +                } +                if (header.mPhysicsConvexSize > 0 && header_size + header.mPhysicsConvexOffset + header.mPhysicsConvexSize < data_size) +                { +                    header.mPhysicsConvexInCache = true; +                } +                if (header.mPhysicsMeshSize > 0 && header_size + header.mPhysicsMeshOffset + header.mPhysicsMeshSize < data_size) +                { +                    header.mPhysicsMeshInCache = true; +                } +                for (S32 i = 0; i < LLModel::NUM_LODS; ++i) +                { +                    if (lod_size[i] > 0 && header_size + lod_offset[i] + lod_size[i] < data_size) +                    { +                        header.mLodInCache[i] = true; +                    } +                } +            }          }      }      else @@ -1947,35 +2198,84 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes          {              LLMutexLock lock(mHeaderMutex); -            mMeshHeader[mesh_id] = { (U32)header_size, header }; +            mMeshHeader[mesh_id] = header;              LLMeshRepository::sCacheBytesHeaders += (U32)header_size;          }          // immediately request SkinInfo since we'll need it before we can render any LoD if it is present +        if (skin_offset >= 0 && skin_size > 0)          { -            LLMutexLock lock(gMeshRepo.mMeshMutex); +            { +                LLMutexLock lock(gMeshRepo.mMeshMutex); -            if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end()) +                if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end()) +                { +                    gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info +                } +            } + +            S32 offset = (S32)header_size + skin_offset; +            bool request_skin = true; +            if (offset + skin_size < data_size) +            { +                request_skin = !skinInfoReceived(mesh_id, data + offset, skin_size); +            } +            if (request_skin)              { -                gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info +                mSkinRequests.push_back(UUIDBasedRequest(mesh_id));              }          } -        fetchMeshSkinInfo(mesh_id); - -        LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. +        std::array<S32, LLModel::NUM_LODS> pending_lods; +        bool has_pending_lods = false; +        { +            LLMutexLock lock(mPendingMutex); // make sure only one thread access mPendingLOD at the same time. +            pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); +            if (iter != mPendingLOD.end()) +            { +                pending_lods = iter->second; +                mPendingLOD.erase(iter); +                has_pending_lods = true; +            } +        }          //check for pending requests -        pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); -        if (iter != mPendingLOD.end()) +        if (has_pending_lods)          { -            for (U32 i = 0; i < iter->second.size(); ++i) +            for (S32 i = 0; i < pending_lods.size(); ++i)              { -                LODRequest req(mesh_params, iter->second[i]); -                mLODReqQ.push(req); -                LLMeshRepository::sLODProcessing++; +                if (pending_lods[i] > 1) +                { +                    // mLoadingMeshes should be protecting from dupplciates, but looks +                    // like this is possible if object rezzes, unregisterMesh, then +                    // rezzes again before first request completes. +                    // mLoadingMeshes might need to change a bit to not rerequest if +                    // mesh is already pending. +                    // +                    // Todo: Improve mLoadingMeshes and once done turn this into an assert. +                    // Low priority since such situation should be relatively rare +                    LL_INFOS(LOG_MESH) << "Multiple dupplicate requests for mesd ID:  " << mesh_id << " LOD: " << i +                        << LL_ENDL; +                } +                if (pending_lods[i] > 0 && lod_size[i] > 0) +                { +                    // try to load from data we just received +                    bool request_lod = true; +                    S32 offset = (S32)header_size + lod_offset[i]; +                    if (offset + lod_size[i] <= data_size) +                    { +                        // initial request is 4096 bytes, it's big enough to fit this lod +                        request_lod = lodReceived(mesh_params, i, data + offset, lod_size[i]) != MESH_OK; +                    } +                    if (request_lod) +                    { +                        LLMutexLock lock(mMutex); +                        LODRequest req(mesh_params, i); +                        mLODReqQ.push(req); +                        LLMeshRepository::sLODProcessing++; +                    } +                }              } -            mPendingLOD.erase(iter);          }      } @@ -1995,8 +2295,16 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p          if (volume->getNumFaces() > 0)          {              // if we have a valid SkinInfo, cache per-joint bounding boxes for this LOD -            LLMeshSkinInfo* skin_info = mSkinMap[mesh_params.getSculptID()]; -            if (skin_info && isAgentAvatarValid()) +            LLPointer<LLMeshSkinInfo> skin_info = nullptr; +            { +                LLMutexLock lock(mSkinMapMutex); +                skin_map::iterator iter = mSkinMap.find(mesh_params.getSculptID()); +                if (iter != mSkinMap.end()) +                { +                    skin_info = iter->second; +                } +            } +            if (skin_info.notNull() && isAgentAvatarValid())              {                  for (S32 i = 0; i < volume->getNumFaces(); ++i)                  { @@ -2008,7 +2316,7 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p              LoadedMesh mesh(volume, mesh_params, lod);              { -                LLMutexLock lock(mMutex); +                LLMutexLock lock(mLoadedMutex);                  mLoadedQ.push_back(mesh);                  // LLPointer is not thread safe, since we added this pointer into                  // threaded list, make sure counter gets decreased inside mutex lock @@ -2026,6 +2334,7 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p  bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size)  { +    LL_PROFILE_ZONE_SCOPED;      LLSD skin;      if (data_size > 0) @@ -2060,12 +2369,15 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat          // copy the skin info for the background thread so we can use it          // to calculate per-joint bounding boxes when volumes are loaded -        mSkinMap[mesh_id] = new LLMeshSkinInfo(*info); +        { +            LLMutexLock lock(mSkinMapMutex); +            mSkinMap[mesh_id] = new LLMeshSkinInfo(*info); +        }          {              // Move the LLPointer in to the skin info queue to avoid reference              // count modification after we leave the lock -            LLMutexLock lock(mMutex); +            LLMutexLock lock(mLoadedMutex);              mSkinInfoQ.emplace_back(std::move(info));          }      } @@ -2075,6 +2387,7 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat  bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size)  { +    LL_PROFILE_ZONE_SCOPED;      LLSD decomp;      if (data_size > 0) @@ -2101,7 +2414,7 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3          LLModel::Decomposition* d = new LLModel::Decomposition(decomp);          d->mMeshID = mesh_id;          { -            LLMutexLock lock(mMutex); +            LLMutexLock lock(mLoadedMutex);              mDecompositionQ.push_back(d);          }      } @@ -2111,6 +2424,7 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3  EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size)  { +    LL_PROFILE_ZONE_SCOPED;      LLSD physics_shape;      LLModel::Decomposition* d = new LLModel::Decomposition(); @@ -2150,7 +2464,7 @@ EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_      }      { -        LLMutexLock lock(mMutex); +        LLMutexLock lock(mLoadedMutex);          mDecompositionQ.push_back(d);      }      return MESH_OK; @@ -2202,7 +2516,6 @@ LLMeshUploadThread::~LLMeshUploadThread()      mHttpRequest = NULL;      delete mMutex;      mMutex = NULL; -  }  LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) @@ -2943,15 +3256,17 @@ void LLMeshRepoThread::notifyLoadedMeshes()          return;      } +    LL_PROFILE_ZONE_SCOPED; +      if (!mLoadedQ.empty())      {          std::deque<LoadedMesh> loaded_queue; -        mMutex->lock(); +        mLoadedMutex->lock();          if (!mLoadedQ.empty())          {              loaded_queue.swap(mLoadedQ); -            mMutex->unlock(); +            mLoadedMutex->unlock();              update_metrics = true; @@ -2960,40 +3275,47 @@ void LLMeshRepoThread::notifyLoadedMeshes()              {                  if (mesh.mVolume->getNumVolumeFaces() > 0)                  { -                    gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); +                    gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume, mesh.mLOD);                  }                  else                  { -                    gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, -                        LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); +                    gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, mesh.mLOD, LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail()));                  }              }          } +        else +        { +            mLoadedMutex->unlock(); +        }      }      if (!mUnavailableQ.empty())      {          std::deque<LODRequest> unavil_queue; -        mMutex->lock(); +        mLoadedMutex->lock();          if (!mUnavailableQ.empty())          {              unavil_queue.swap(mUnavailableQ); -            mMutex->unlock(); +            mLoadedMutex->unlock();              update_metrics = true;              // Process the elements free of the lock              for (const auto& req : unavil_queue)              { -                gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); +                gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD, req.mLOD);              }          } +        else +        { +            mLoadedMutex->unlock(); +        }      }      if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty())      { -        if (mMutex->trylock()) +        if (mLoadedMutex->trylock())          {              std::deque<LLPointer<LLMeshSkinInfo>> skin_info_q;              std::deque<UUIDBasedRequest> skin_info_unavail_q; @@ -3014,7 +3336,7 @@ void LLMeshRepoThread::notifyLoadedMeshes()                  decomp_q.swap(mDecompositionQ);              } -            mMutex->unlock(); +            mLoadedMutex->unlock();              // Process the elements free of the lock              while (! skin_info_q.empty()) @@ -3051,9 +3373,11 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo      if (iter != mMeshHeader.end())      { -        auto& header = iter->second.second; - -        return LLMeshRepository::getActualMeshLOD(header, lod); +        auto& header = iter->second; +        if (header.mHeaderSize > 0) +        { +            return LLMeshRepository::getActualMeshLOD(header, lod); +        }      }      return lod; @@ -3220,7 +3544,10 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo          processData(body, body_offset, data, static_cast<S32>(data_size) - body_offset); -        delete [] data; +        if (mHasDataOwnership) +        { +            delete [] data; +        }      }      // Release handler @@ -3253,7 +3580,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)                         << LL_ENDL;      // Can't get the header so none of the LODs will be available -    LLMutexLock lock(gMeshRepo.mThread->mMutex); +    LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);      for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i)      {          gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3263,6 +3590,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status)  void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */,                                        U8 * data, S32 data_size)  { +    LL_PROFILE_ZONE_SCOPED;      LLUUID mesh_id = mMeshParams.getSculptID();      bool success = (!MESH_HEADER_PROCESS_FAILED)          && ((data != NULL) == (data_size > 0)); // if we have data but no size or have size but no data, something is wrong; @@ -3282,7 +3610,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b                             << LL_ENDL;          // Can't get the header so none of the LODs will be available -        LLMutexLock lock(gMeshRepo.mThread->mMutex); +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);          for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i)          {              gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3298,8 +3626,8 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b          LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id);          if (iter != gMeshRepo.mThread->mMeshHeader.end())          { -            header_bytes = (S32)iter->second.first; -            header = iter->second.second; +            header = iter->second; +            header_bytes = header.mHeaderSize;          }          if (header_bytes > 0 @@ -3324,7 +3652,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b              // LLSD is smart and can work like smart pointer, is not thread safe.              gMeshRepo.mThread->mHeaderMutex->unlock(); -            S32 bytes = lod_bytes + header_bytes; +            S32 bytes = lod_bytes + header_bytes + CACHE_PREAMBLE_SIZE;              // It's possible for the remote asset to have more data than is needed for the local cache @@ -3337,6 +3665,11 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b                  LLMeshRepository::sCacheBytesWritten += data_size;                  ++LLMeshRepository::sCacheWrites; +                // write preamble +                U32 flags = header.getFlags(); +                write_preamble(file, header_bytes, flags); + +                // write header                  file.write(data, data_size);                  S32 remaining = bytes - file.tell(); @@ -3359,7 +3692,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b              gMeshRepo.mThread->mHeaderMutex->unlock();              // headerReceived() parsed header, but header's data is invalid so none of the LODs will be available -            LLMutexLock lock(gMeshRepo.mThread->mMutex); +            LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);              for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i)              {                  gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3388,9 +3721,64 @@ void LLMeshLODHandler::processFailure(LLCore::HttpStatus status)                         << " (" << status.toTerseString() << ").  Not retrying."                         << LL_ENDL; -    LLMutexLock lock(gMeshRepo.mThread->mMutex); +    LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);      gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD));  } +void LLMeshLODHandler::processLod(U8* data, S32 data_size) +{ +    EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); +    if (result == MESH_OK) +    { +        // good fetch from sim, write to cache +        LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + +        S32 offset = mOffset + CACHE_PREAMBLE_SIZE; +        S32 size = mRequestedBytes; + +        if (file.getSize() >= offset + size) +        { +            S32 header_bytes = 0; +            U32 flags = 0; +            { +                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshParams.getSculptID()); +                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                { +                    LLMeshHeader& header = header_it->second; +                    // update header +                    if (!header.mLodInCache[mLOD]) +                    { +                        header.mLodInCache[mLOD] = true; +                        header_bytes = header.mHeaderSize; +                        flags = header.getFlags(); +                    } +                    // todo: handle else because we shouldn't have requested twice? +                } +            } +            if (flags > 0) +            { +                write_preamble(file, header_bytes, flags); +            } + +            file.seek(offset, 0); +            file.write(data, size); +            LLMeshRepository::sCacheBytesWritten += size; +            ++LLMeshRepository::sCacheWrites; +        } +    } +    else +    { +        LL_WARNS(LOG_MESH) << "Error during mesh LOD processing.  ID:  " << mMeshParams.getSculptID() +            << ", Reason: " << result +            << " LOD: " << mLOD +            << " Data size: " << data_size +            << " Not retrying." +            << LL_ENDL; +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); +        gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +    } +}  void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */,                                     U8 * data, S32 data_size) @@ -3399,33 +3787,26 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body      if ((!MESH_LOD_PROCESS_FAILED)          && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong      { -        EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); -        if (result == MESH_OK) +        LLMeshHandlerBase::ptr_t shrd_handler = shared_from_this(); +        bool posted = gMeshRepo.mThread->mMeshThreadPool->getQueue().post( +            [shrd_handler, data, data_size] +            ()          { -            // good fetch from sim, write to cache -            LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - -            S32 offset = mOffset; -            S32 size = mRequestedBytes; +            LLMeshLODHandler* handler = (LLMeshLODHandler * )shrd_handler.get(); +            handler->processLod(data, data_size); +            delete[] data; +        }); -            if (file.getSize() >= offset+size) -            { -                file.seek(offset); -                file.write(data, size); -                LLMeshRepository::sCacheBytesWritten += size; -                ++LLMeshRepository::sCacheWrites; -            } +        if (posted) +        { +            // ownership of data was passed to the lambda +            mHasDataOwnership = false;          }          else          { -            LL_WARNS(LOG_MESH) << "Error during mesh LOD processing.  ID:  " << mMeshParams.getSculptID() -                               << ", Reason: " << result -                               << " LOD: " << mLOD -                               << " Data size: " << data_size -                               << " Not retrying." -                               << LL_ENDL; -            LLMutexLock lock(gMeshRepo.mThread->mMutex); -            gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +            // mesh thread dies later than event queue, so this is normal +            LL_INFOS_ONCE(LOG_MESH) << "Failed to post work into mMeshThreadPool" << LL_ENDL; +            processLod(data, data_size);          }      }      else @@ -3435,7 +3816,7 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body                             << " LOD: " << mLOD                             << " Data size: " << data_size                             << LL_ENDL; -        LLMutexLock lock(gMeshRepo.mThread->mMutex); +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);          gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD));      }  } @@ -3446,6 +3827,7 @@ LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler()      {          LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL;      } +    LLMeshRepoThread::decActiveSkinRequests();  }  void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) @@ -3454,38 +3836,98 @@ void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status)                         << ", Reason:  " << status.toString()                         << " (" << status.toTerseString() << ").  Not retrying."                         << LL_ENDL; -        LLMutexLock lock(gMeshRepo.mThread->mMutex); +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);          gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID);  } -void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, -                                        U8 * data, S32 data_size) +void LLMeshSkinInfoHandler::processSkin(U8* data, S32 data_size)  { -    LL_PROFILE_ZONE_SCOPED; -    if ((!MESH_SKIN_INFO_PROCESS_FAILED) -        && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong -        && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) +    if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))      {          // good fetch from sim, write to cache          LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); -        S32 offset = mOffset; +        S32 offset = mOffset + CACHE_PREAMBLE_SIZE;          S32 size = mRequestedBytes; -        if (file.getSize() >= offset+size) +        if (file.getSize() >= offset + size)          {              LLMeshRepository::sCacheBytesWritten += size;              ++LLMeshRepository::sCacheWrites; -            file.seek(offset); + +            S32 header_bytes = 0; +            U32 flags = 0; +            { +                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); +                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                { +                    LLMeshHeader& header = header_it->second; +                    // update header +                    if (!header.mSkinInCache) +                    { +                        header.mSkinInCache = true; +                        header_bytes = header.mHeaderSize; +                        flags = header.getFlags(); +                    } +                    // todo: handle else because we shouldn't have requested twice? +                } +            } +            if (flags > 0) +            { +                write_preamble(file, header_bytes, flags); +            } + +            file.seek(offset, 0);              file.write(data, size);          }      }      else      {          LL_WARNS(LOG_MESH) << "Error during mesh skin info processing.  ID:  " << mMeshID +            << ", Unknown reason.  Not retrying." +            << LL_ENDL; +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); +        gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); +    } +} + +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, +                                        U8 * data, S32 data_size) +{ +    LL_PROFILE_ZONE_SCOPED; +    if ((!MESH_SKIN_INFO_PROCESS_FAILED) +        && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong +    { +        LLMeshHandlerBase::ptr_t shrd_handler = shared_from_this(); +        bool posted = gMeshRepo.mThread->mMeshThreadPool->getQueue().post( +            [shrd_handler, data, data_size] +            () +        { +            LLMeshSkinInfoHandler* handler = (LLMeshSkinInfoHandler*)shrd_handler.get(); +            handler->processSkin(data, data_size); +            delete[] data; +        }); + +        if (posted) +        { +            // ownership of data was passed to the lambda +            mHasDataOwnership = false; +        } +        else +        { +            // mesh thread dies later than event queue, so this is normal +            LL_INFOS_ONCE(LOG_MESH) << "Failed to post work into mMeshThreadPool" << LL_ENDL; +            processSkin(data, data_size); +        } +    } +    else +    { +        LL_WARNS(LOG_MESH) << "Error during mesh skin info processing.  ID:  " << mMeshID                             << ", Unknown reason.  Not retrying."                             << LL_ENDL; -        LLMutexLock lock(gMeshRepo.mThread->mMutex); +        LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex);          gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID);      }  } @@ -3519,14 +3961,39 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S          // good fetch from sim, write to cache          LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); -        S32 offset = mOffset; +        S32 offset = mOffset + CACHE_PREAMBLE_SIZE;          S32 size = mRequestedBytes;          if (file.getSize() >= offset+size)          {              LLMeshRepository::sCacheBytesWritten += size;              ++LLMeshRepository::sCacheWrites; -            file.seek(offset); + +            S32 header_bytes = 0; +            U32 flags = 0; +            { +                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); +                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                { +                    LLMeshHeader& header = header_it->second; +                    // update header +                    if (!header.mPhysicsConvexInCache) +                    { +                        header.mPhysicsConvexInCache = true; +                        header_bytes = header.mHeaderSize; +                        flags = header.getFlags(); +                    } +                    // todo: handle else because we shouldn't have requested twice? +                } +            } +            if (flags > 0) +            { +                write_preamble(file, header_bytes, flags); +            } + +            file.seek(offset, 0);              file.write(data, size);          }      } @@ -3567,14 +4034,39 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S3          // good fetch from sim, write to cache for caching          LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); -        S32 offset = mOffset; +        S32 offset = mOffset + CACHE_PREAMBLE_SIZE;          S32 size = mRequestedBytes;          if (file.getSize() >= offset+size)          {              LLMeshRepository::sCacheBytesWritten += size;              ++LLMeshRepository::sCacheWrites; -            file.seek(offset); + +            S32 header_bytes = 0; +            U32 flags = 0; +            { +                LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + +                LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); +                if (header_it != gMeshRepo.mThread->mMeshHeader.end()) +                { +                    LLMeshHeader& header = header_it->second; +                    // update header +                    if (!header.mPhysicsMeshInCache) +                    { +                        header.mPhysicsMeshInCache = true; +                        header_bytes = header.mHeaderSize; +                        flags = header.getFlags(); +                    } +                    // todo: handle else because we shouldn't have requested twice? +                } +            } +            if (flags > 0) +            { +                write_preamble(file, header_bytes, flags); +            } + +            file.seek(offset, 0);              file.write(data, size);          }      } @@ -3636,6 +4128,7 @@ void LLMeshRepository::shutdown()      }      mThread->mSignal->broadcast(); +    mThread->mMeshThreadPool->close();      while (!mThread->isStopped())      { @@ -3710,24 +4203,24 @@ void LLMeshRepository::unregisterMesh(LLVOVolume* vobj)      }  } -S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) +S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 new_lod, S32 last_lod)  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH);      // Manage time-to-load metrics for mesh download operations.      metricsProgress(1); -    if (detail < 0 || detail >= LLVolumeLODGroup::NUM_LODS) +    if (new_lod < 0 || new_lod >= LLVolumeLODGroup::NUM_LODS)      { -        return detail; +        return new_lod;      }      {          LLMutexLock lock(mMeshMutex);          //add volume to list of loading meshes          const auto& mesh_id = mesh_params.getSculptID(); -        mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_id); -        if (iter != mLoadingMeshes[detail].end()) +        mesh_load_map::iterator iter = mLoadingMeshes[new_lod].find(mesh_id); +        if (iter != mLoadingMeshes[new_lod].end())          { //request pending for this mesh, append volume id to list              auto it = std::find(iter->second.begin(), iter->second.end(), vobj);              if (it == iter->second.end()) { @@ -3737,8 +4230,8 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para          else          {              //first request for this mesh -            mLoadingMeshes[detail][mesh_id].push_back(vobj); -            mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); +            mLoadingMeshes[new_lod][mesh_id].push_back(vobj); +            mPendingRequests.emplace_back(new PendingRequestLOD(mesh_params, new_lod));              LLMeshRepository::sLODPending++;          }      } @@ -3767,7 +4260,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para              }              //next, see what the next lowest LOD available might be -            for (S32 i = detail-1; i >= 0; --i) +            for (S32 i = new_lod -1; i >= 0; --i)              {                  LLVolume* lod = group->refLOD(i);                  if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) @@ -3780,7 +4273,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para              }              //no lower LOD is a available, is a higher lod available? -            for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) +            for (S32 i = new_lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i)              {                  LLVolume* lod = group->refLOD(i);                  if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) @@ -3794,7 +4287,51 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para          }      } -    return detail; +    return new_lod; +} + +F32 calculate_score(LLVOVolume* object) +{ +    if (!object) +    { +        return -1.f; +    } +    LLDrawable* drawable = object->mDrawable; +    if (!drawable) +    { +        return -1; +    } +    if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment()) +    { +        LLVOAvatar* avatar = object->getAvatar(); +        LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr; +        if (avatar && av_drawable) +        { +            // See LLVOVolume::calcLOD() +            F32 radius; +            if (avatar->isControlAvatar()) +            { +                const LLVector3* box = avatar->getLastAnimExtents(); +                LLVector3 diag = box[1] - box[0]; +                radius = diag.magVec() * 0.5f; +            } +            else +            { +                // Volume in a rigged mesh attached to a regular avatar. +                const LLVector3* box = avatar->getLastAnimExtents(); +                LLVector3 diag = box[1] - box[0]; +                radius = diag.magVec(); + +                if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData()) +                { +                    // slightly deprioritize avatars that are still receiving data +                    radius *= 0.9f; +                } +            } +            return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f); +        } +    } +    return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f);  }  void LLMeshRepository::notifyLoadedMeshes() @@ -3918,6 +4455,7 @@ void LLMeshRepository::notifyLoadedMeshes()              // erase from background thread              mThread->mWorkQueue.post([=, this]()                  { +                    LLMutexLock(mThread->mSkinMapMutex);                      mThread->mSkinMap.erase(id);                  });          } @@ -3974,8 +4512,12 @@ void LLMeshRepository::notifyLoadedMeshes()              mUploadErrorQ.pop();          } -        S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; -        if (active_count < LLMeshRepoThread::sRequestLowWater) +        // mPendingRequests go into queues, queues go into active http requests. +        // Checking sRequestHighWater to keep queues at least somewhat populated +        // for faster transition into http +        S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests + LLMeshRepoThread::sActiveSkinRequests; +        active_count += (S32)(mThread->mLODReqQ.size() + mThread->mHeaderReqQ.size() + mThread->mSkinInfoQ.size()); +        if (active_count < LLMeshRepoThread::sRequestHighWater)          {              S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; @@ -3996,50 +4538,69 @@ void LLMeshRepository::notifyLoadedMeshes()                          F32 max_score = 0.f;                          for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)                          { -                            LLVOVolume* object = *obj_iter; -                            if (object) +                            F32 cur_score = calculate_score(*obj_iter); +                            if (cur_score >= 0.f)                              { -                                LLDrawable* drawable = object->mDrawable; -                                if (drawable) -                                { -                                    F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); -                                    max_score = llmax(max_score, cur_score); -                                } +                                max_score = llmax(max_score, cur_score);                              }                          }                          score_map[iter->first] = max_score;                      }                  } +                for (mesh_load_map::iterator iter = mLoadingSkins.begin(); iter != mLoadingSkins.end(); ++iter) +                { +                    F32 max_score = 0.f; +                    for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) +                    { +                        F32 cur_score = calculate_score(*obj_iter); +                        if (cur_score >= 0.f) +                        { +                            max_score = llmax(max_score, cur_score); +                        } +                    } + +                    score_map[iter->first] = max_score; +                }                  //set "score" for pending requests -                for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) +                for (std::unique_ptr<PendingRequestBase>& req_p : mPendingRequests)                  { -                    iter->mScore = score_map[iter->mMeshParams.getSculptID()]; +                    req_p->setScore(score_map[req_p->getId()]);                  }                  //sort by "score"                  std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, -                                  mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); +                                  mPendingRequests.end(), PendingRequestBase::CompareScoreGreater());              } -              while (!mPendingRequests.empty() && push_count > 0)              { -                LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); -                mThread->loadMeshLOD(request.mMeshParams, request.mLOD); +                std::unique_ptr<PendingRequestBase>& req_p = mPendingRequests.front(); +                switch (req_p->getRequestType()) +                { +                case MESH_REQUEST_LOD: +                    { +                        PendingRequestLOD* lod = (PendingRequestLOD*)req_p.get(); +                        mThread->loadMeshLOD(lod->mMeshParams, lod->mLOD); +                        LLMeshRepository::sLODPending--; +                        break; +                    } +                case MESH_REQUEST_SKIN: +                    { +                        PendingRequestUUID* skin = (PendingRequestUUID*)req_p.get(); +                        mThread->loadMeshSkinInfo(skin->getId()); +                        break; +                    } + +                default: +                    LL_ERRS() << "Unknown request type in LLMeshRepository::notifyLoadedMeshes" << LL_ENDL; +                    break; +                }                  mPendingRequests.erase(mPendingRequests.begin()); -                LLMeshRepository::sLODPending--;                  push_count--;              }          } -        //send skin info requests -        while (!mPendingSkinRequests.empty()) -        { -            mThread->loadMeshSkinInfo(mPendingSkinRequests.front()); -            mPendingSkinRequests.pop(); -        } -          //send decomposition requests          while (!mPendingDecompositionRequests.empty())          { @@ -4116,15 +4677,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom      }  } -void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) +void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume, S32 lod)  { //called from main thread -    S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail());      //get list of objects waiting to be notified this mesh is loaded      const auto& mesh_id = mesh_params.getSculptID(); -    mesh_load_map::iterator obj_iter = mLoadingMeshes[detail].find(mesh_id); +    mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); -    if (volume && obj_iter != mLoadingMeshes[detail].end()) +    if (volume && obj_iter != mLoadingMeshes[lod].end())      {          //make sure target volume is still valid          if (volume->getNumVolumeFaces() <= 0) @@ -4134,6 +4694,7 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol          }          { //update system volume +            S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail());              LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail);              if (sys_volume)              { @@ -4157,22 +4718,22 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol              }          } -        mLoadingMeshes[detail].erase(obj_iter); +        mLoadingMeshes[lod].erase(obj_iter);          LLViewerStatsRecorder::instance().meshLoaded();      }  } -void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) +void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 request_lod, S32 volume_lod)  { //called from main thread      //get list of objects waiting to be notified this mesh is loaded      const auto& mesh_id = mesh_params.getSculptID(); -    mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); -    if (obj_iter != mLoadingMeshes[lod].end()) +    mesh_load_map::iterator obj_iter = mLoadingMeshes[request_lod].find(mesh_id); +    if (obj_iter != mLoadingMeshes[request_lod].end())      { -        F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); +        F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(volume_lod); -        LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); +        LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, volume_lod);          if (sys_volume)          {              sys_volume->setMeshAssetUnavaliable(true); @@ -4189,12 +4750,12 @@ void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params,                      obj_volume->getDetail() == detail &&                      obj_volume->getParams() == mesh_params)                  { //should force volume to find most appropriate LOD -                    vobj->setVolume(obj_volume->getParams(), lod); +                    vobj->setVolume(obj_volume->getParams(), volume_lod);                  }              }          } -        mLoadingMeshes[lod].erase(obj_iter); +        mLoadingMeshes[request_lod].erase(obj_iter);      }  } @@ -4231,7 +4792,7 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOV              {                  //first request for this mesh                  mLoadingSkins[mesh_id].push_back(requesting_obj); -                mPendingSkinRequests.push(mesh_id); +                mPendingRequests.emplace_back(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN));              }          }      } @@ -4358,7 +4919,7 @@ bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id)      return false;  } -bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) +bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) const  {      if (mesh_id.isNull())      { @@ -4368,13 +4929,13 @@ bool LLMeshRepository::hasHeader(const LLUUID& mesh_id)      return mThread->hasHeader(mesh_id);  } -bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) const  {      LLMutexLock lock(mHeaderMutex); -    mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); -    if (iter != mMeshHeader.end() && iter->second.first > 0) +    mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id); +    if (iter != mMeshHeader.end() && iter->second.mHeaderSize > 0)      { -        LLMeshHeader &mesh = iter->second.second; +        const LLMeshHeader &mesh = iter->second;          if (mesh.mPhysicsMeshSize > 0)          {              return true; @@ -4384,13 +4945,13 @@ bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id)      return false;  } -bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) const  {      LLMutexLock lock(mHeaderMutex); -    mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); -    if (iter != mMeshHeader.end() && iter->second.first > 0) +    mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id); +    if (iter != mMeshHeader.end() && iter->second.mHeaderSize > 0)      { -        LLMeshHeader& mesh = iter->second.second; +        const LLMeshHeader& mesh = iter->second;          if (mesh.mSkinOffset >= 0              && mesh.mSkinSize > 0)          { @@ -4401,10 +4962,10 @@ bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id)      return false;  } -bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) const  {      LLMutexLock lock(mHeaderMutex); -    mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); +    mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id);      return iter != mMeshHeader.end();  } @@ -4419,16 +4980,16 @@ void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3      mUploadWaitList.push_back(thread);  } -S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) +S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) const  {      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) +        LLMeshRepoThread::mesh_header_map::const_iterator iter = mThread->mMeshHeader.find(mesh_id); +        if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0)          { -            const LLMeshHeader& header = iter->second.second; +            const LLMeshHeader& header = iter->second;              if (header.m404)              { @@ -4532,9 +5093,9 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* by      {          LLMutexLock lock(mThread->mHeaderMutex);          LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); -        if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) +        if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0)          { -            result  = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); +            result  = getStreamingCostLegacy(iter->second, radius, bytes, bytes_visible, lod, unscaled_value);          }      }      if (result > 0.f) @@ -4726,7 +5287,7 @@ bool LLMeshCostData::init(const LLMeshHeader& header)  } -S32 LLMeshCostData::getSizeByLOD(S32 lod) +S32 LLMeshCostData::getSizeByLOD(S32 lod) const  {      if (llclamp(lod,0,3) != lod)      { @@ -4735,12 +5296,12 @@ S32 LLMeshCostData::getSizeByLOD(S32 lod)      return mSizeByLOD[lod];  } -S32 LLMeshCostData::getSizeTotal() +S32 LLMeshCostData::getSizeTotal() const  {      return mSizeByLOD[0] + mSizeByLOD[1] + mSizeByLOD[2] + mSizeByLOD[3];  } -F32 LLMeshCostData::getEstTrisByLOD(S32 lod) +F32 LLMeshCostData::getEstTrisByLOD(S32 lod) const  {      if (llclamp(lod,0,3) != lod)      { @@ -4749,12 +5310,12 @@ F32 LLMeshCostData::getEstTrisByLOD(S32 lod)      return mEstTrisByLOD[lod];  } -F32 LLMeshCostData::getEstTrisMax() +F32 LLMeshCostData::getEstTrisMax() const  {      return llmax(mEstTrisByLOD[0], mEstTrisByLOD[1], mEstTrisByLOD[2], mEstTrisByLOD[3]);  } -F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) +F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) const  {      F32 max_distance = 512.f; @@ -4798,7 +5359,7 @@ F32 LLMeshCostData::getRadiusWeightedTris(F32 radius)      return weighted_avg;  } -F32 LLMeshCostData::getEstTrisForStreamingCost() +F32 LLMeshCostData::getEstTrisForStreamingCost() const  {      LL_DEBUGS("StreamingCost") << "tris_by_lod: "                                 << mEstTrisByLOD[0] << ", " @@ -4808,7 +5369,7 @@ F32 LLMeshCostData::getEstTrisForStreamingCost()      F32 charged_tris = mEstTrisByLOD[3];      F32 allowed_tris = mEstTrisByLOD[3]; -    const F32 ENFORCE_FLOOR = 64.0f; +    constexpr 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? @@ -4825,13 +5386,13 @@ F32 LLMeshCostData::getEstTrisForStreamingCost()      return charged_tris;  } -F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) +F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) const  {      static LLCachedControl<U32> mesh_triangle_budget(gSavedSettings, "MeshTriangleBudget");      return getRadiusWeightedTris(radius)/mesh_triangle_budget*15000.f;  } -F32 LLMeshCostData::getTriangleBasedStreamingCost() +F32 LLMeshCostData::getTriangleBasedStreamingCost() const  {      F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001f * getEstTrisForStreamingCost();      return result; @@ -4846,9 +5407,9 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)      {          LLMutexLock lock(mThread->mHeaderMutex);          LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); -        if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) +        if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0)          { -            LLMeshHeader& header = iter->second.second; +            LLMeshHeader& header = iter->second;              bool header_invalid = (header.m404                                     || header.mLodSize[0] <= 0  | 
