summaryrefslogtreecommitdiff
path: root/indra/newview/llmeshrepository.cpp
diff options
context:
space:
mode:
authorJonathan "Geenz" Goodman <geenz@lindenlab.com>2025-04-15 09:50:42 -0400
committerGitHub <noreply@github.com>2025-04-15 09:50:42 -0400
commitcf2b4dbfb280986cf859b12fd55158d7b9e0ac3d (patch)
treee7af9b048afdb57799259b7ae7767457b79115ca /indra/newview/llmeshrepository.cpp
parentb7dd677933797a72175a95f2945b2ca8363e09b5 (diff)
parent632a8648ca5456448499a96dcc58c40f4ff80d95 (diff)
Merge pull request #3706 from secondlife/release/2025.03
Release/2025.03
Diffstat (limited to 'indra/newview/llmeshrepository.cpp')
-rw-r--r--indra/newview/llmeshrepository.cpp1207
1 files changed, 885 insertions, 322 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 93453f507c..a8c6f69425 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,71 @@ 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());
}
-
+ LLMutexTrylock lock3(mThread->mHeaderMutex);
+ LLMutexTrylock lock4(mThread->mPendingMutex);
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 +4679,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 +4696,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 +4720,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 +4752,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 +4794,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 +4921,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 +4931,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 +4947,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 +4964,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 +4982,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 +5095,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 +5289,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 +5298,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 +5312,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 +5361,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 +5371,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 +5388,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 +5409,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