From 74c8b028d42a8c5b080bb861e427f38cedd4ad7c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 15 Dec 2023 18:26:14 +0100 Subject: SL-20743 Use LLMutex in LLImageBase for internal data thread-safety --- indra/newview/llmeshrepository.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 01d6469010..2622c23314 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2360,17 +2360,19 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) std::stringstream texture_str; if (texture != NULL && include_textures && mUploadTextures) { - if(texture->hasSavedRawImage()) - { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + LLPointer upload_file = LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); if (!upload_file.isNull() && upload_file->getDataSize()) { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } } } - } if (texture != NULL && mUploadTextures && @@ -2514,17 +2516,19 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) std::stringstream texture_str; if (texture != NULL && include_textures && mUploadTextures) { - if(texture->hasSavedRawImage()) - { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + LLPointer upload_file = LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); if (!upload_file.isNull() && upload_file->getDataSize()) { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } } } - } if (texture != NULL && mUploadTextures && -- cgit v1.2.3 From 60d3dd98a44230c21803c1606552ee098ed9fa7c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 21 Feb 2024 21:05:14 +0100 Subject: Convert remaining BOOL to bool --- indra/newview/llmeshrepository.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2622c23314..03a560a090 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2293,8 +2293,8 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - FALSE, - FALSE, + false, + false, data.mBaseModel->mSubmodelID); data.mAssetData = ostr.str(); @@ -2450,8 +2450,8 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - FALSE, - FALSE, + false, + false, data.mBaseModel->mSubmodelID); data.mAssetData = ostr.str(); @@ -4382,7 +4382,7 @@ void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, LLVector3& result_scale) { // check for reflection - BOOL reflected = (transformation.determinant() < 0); + bool reflected = (transformation.determinant() < 0); // compute position LLVector3 position = LLVector3(0, 0, 0) * transformation; @@ -5497,7 +5497,7 @@ void on_new_single_inventory_upload_complete( // Show the preview panel for textures and sounds to let // user know that the image (or snapshot) arrived intact. - LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); + LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); if (panel) { @@ -5507,7 +5507,7 @@ void on_new_single_inventory_upload_complete( } else { - LLInventoryPanel::openInventoryPanelAndSetSelection(TRUE, server_response["new_inventory_item"].asUUID(), TRUE, TAKE_FOCUS_NO, TRUE); + LLInventoryPanel::openInventoryPanelAndSetSelection(true, server_response["new_inventory_item"].asUUID(), true, TAKE_FOCUS_NO, true); } // restore keyboard focus -- cgit v1.2.3 From f9473e8afcb624cc1b101195bf15943ec372b56f Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 6 May 2024 16:52:34 +0200 Subject: secondlife/viewer#1333 BOOL to bool conversion leftovers: ternaries --- indra/newview/llmeshrepository.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 20274888df..9a2f47cc8d 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1355,7 +1355,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) { //check cache for mesh skin info LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) + if (file.getSize() >= offset + size) { U8* buffer = new(std::nothrow) U8[size]; if (!buffer) @@ -1372,7 +1372,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) bool zero = true; for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) { - zero = buffer[i] > 0 ? false : true; + zero = buffer[i] == 0; } if (!zero) @@ -1486,7 +1486,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) bool zero = true; for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) { - zero = buffer[i] > 0 ? false : true; + zero = buffer[i] == 0; } if (!zero) @@ -1584,7 +1584,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) bool zero = true; for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) { - zero = buffer[i] > 0 ? false : true; + zero = buffer[i] == 0; } if (!zero) @@ -1784,7 +1784,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool zero = true; for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) { - zero = buffer[i] > 0 ? false : true; + zero = buffer[i] == 0; } if (!zero) -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/newview/llmeshrepository.cpp | 11062 +++++++++++++++++------------------ 1 file changed, 5531 insertions(+), 5531 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 869d1a6ac8..f0fd731fb5 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1,5531 +1,5531 @@ -/** - * @file llmeshrepository.cpp - * @brief Mesh repository implementation. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llapr.h" -#include "apr_portable.h" -#include "apr_pools.h" -#include "apr_dso.h" -#include "llhttpconstants.h" -#include "llmeshrepository.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llbufferstream.h" -#include "llcallbacklist.h" -#include "lldatapacker.h" -#include "lldeadmantimer.h" -#include "llfloatermodelpreview.h" -#include "llfloaterperms.h" -#include "llimagej2c.h" -#include "llhost.h" -#include "llmath.h" -#include "llnotificationsutil.h" -#include "llsd.h" -#include "llsdutil_math.h" -#include "llsdserialize.h" -#include "llthread.h" -#include "llfilesystem.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewermenufile.h" -#include "llviewermessage.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerstatsrecorder.h" -#include "llviewertexturelist.h" -#include "llvolume.h" -#include "llvolumemgr.h" -#include "llvovolume.h" -#include "llworld.h" -#include "material_codes.h" -#include "pipeline.h" -#include "llinventorymodel.h" -#include "llfoldertype.h" -#include "llviewerparcelmgr.h" -#include "lluploadfloaterobservers.h" -#include "bufferarray.h" -#include "bufferstream.h" -#include "llfasttimer.h" -#include "llcorehttputil.h" -#include "lltrans.h" -#include "llstatusbar.h" -#include "llinventorypanel.h" -#include "lluploaddialog.h" -#include "llfloaterreg.h" - -#include "boost/iostreams/device/array.hpp" -#include "boost/iostreams/stream.hpp" -#include "boost/lexical_cast.hpp" - -#ifndef LL_WINDOWS -#include "netdb.h" -#endif - - -// Purpose -// -// The purpose of this module is to provide access between the viewer -// and the asset system as regards to mesh objects. -// -// * High-throughput download of mesh assets from servers while -// following best industry practices for network profile. -// * Reliable expensing and upload of new mesh assets. -// * Recovery and retry from errors when appropriate. -// * Decomposition of mesh assets for preview and uploads. -// * And most important: all of the above without exposing the -// main thread to stalls due to deep processing or thread -// locking actions. In particular, the following operations -// on LLMeshRepository are very averse to any stalls: -// * loadMesh -// * search in mMeshHeader (For structural details, see: -// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) -// * notifyLoadedMeshes -// * getSkinInfo -// -// Threads -// -// main Main rendering thread, very sensitive to locking and other stalls -// repo Overseeing worker thread associated with the LLMeshRepoThread class -// decom Worker thread for mesh decomposition requests -// core HTTP worker thread: does the work but doesn't intrude here -// uploadN 0-N temporary mesh upload threads (0-1 in practice) -// -// Sequence of Operations -// -// What follows is a description of the retrieval of one LOD for -// a new mesh object. Work is performed by a series of short, quick -// actions distributed over a number of threads. Each is meant -// to proceed without stalling and the whole forms a deep request -// pipeline to achieve throughput. Ellipsis indicates a return -// or break in processing which is resumed elsewhere. -// -// main thread repo thread (run() method) -// -// loadMesh() invoked to request LOD -// append LODRequest to mPendingRequests -// ... -// other mesh requests may be made -// ... -// notifyLoadedMeshes() invoked to stage work -// append HeaderRequest to mHeaderReqQ -// ... -// scan mHeaderReqQ -// issue 4096-byte GET for header -// ... -// onCompleted() invoked for GET -// data copied -// headerReceived() invoked -// LLSD parsed -// mMeshHeader updated -// scan mPendingLOD for LOD request -// push LODRequest to mLODReqQ -// ... -// scan mLODReqQ -// fetchMeshLOD() invoked -// issue Byte-Range GET for LOD -// ... -// onCompleted() invoked for GET -// data copied -// lodReceived() invoked -// unpack data into LLVolume -// append LoadedMesh to mLoadedQ -// ... -// notifyLoadedMeshes() invoked again -// scan mLoadedQ -// notifyMeshLoaded() for LOD -// setMeshAssetLoaded() invoked for system volume -// notifyMeshLoaded() invoked for each interested object -// ... -// -// Mutexes -// -// LLMeshRepository::mMeshMutex -// LLMeshRepoThread::mMutex -// LLMeshRepoThread::mHeaderMutex -// LLMeshRepoThread::mSignal (LLCondition) -// LLPhysicsDecomp::mSignal (LLCondition) -// LLPhysicsDecomp::mMutex -// LLMeshUploadThread::mMutex -// -// Mutex Order Rules -// -// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex -// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex -// (There are more rules, haven't been extracted.) -// -// Data Member Access/Locking -// -// Description of how shared access to static and instance data -// members is performed. Each member is followed by the name of -// the mutex, if any, covering the data and then a list of data -// access models each of which is a triplet of the following form: -// -// {ro, wo, rw}.{main, repo, any}.{mutex, none} -// Type of access: read-only, write-only, read-write. -// Accessing thread or 'any' -// Relevant mutex held during access (several may be held) or 'none' -// -// A careful eye will notice some unsafe operations. Many of these -// have an alibi of some form. Several types of alibi are identified -// and listed here: -// -// [0] No alibi. Probably unsafe. -// [1] Single-writer, self-consistent readers. Old data must -// be tolerated by any reader but data will come true eventually. -// [2] Like [1] but provides a hint about thread state. These -// may be unsafe. -// [3] empty() check outside of lock. Can me made safish when -// done in double-check lock style. But this depends on -// std:: implementation and memory model. -// [4] Appears to be covered by a mutex but doesn't need one. -// [5] Read of a double-checked lock. -// -// So, in addition to documentation, take this as a to-do/review -// list and see if you can improve things. For porters to non-x86 -// architectures, the weaker memory models will make these platforms -// probabilistically more susceptible to hitting race conditions. -// True here and in other multi-thread code such as texture fetching. -// (Strong memory models make weak programmers. Weak memory models -// make strong programmers. Ref: arm, ppc, mips, alpha) -// -// LLMeshRepository: -// -// sBytesReceived none rw.repo.none, ro.main.none [1] -// sMeshRequestCount " -// sHTTPRequestCount " -// sHTTPLargeRequestCount " -// sHTTPRetryCount " -// sHTTPErrorCount " -// sLODPending mMeshMutex [4] rw.main.mMeshMutex -// sLODProcessing Repo::mMutex rw.any.Repo::mMutex -// sCacheBytesRead none rw.repo.none, ro.main.none [1] -// sCacheBytesWritten " -// sCacheReads " -// sCacheWrites " -// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex -// mSkinMap none rw.main.none -// mDecompositionMap none rw.main.none -// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex -// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex -// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex -// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex -// mUploads none rw.main.none (upload thread accessing objects) -// mUploadWaitList none rw.main.none (upload thread accessing objects) -// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] -// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex -// mGetMeshVersion none rw.main.none -// -// LLMeshRepoThread: -// -// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] -// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] -// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex -// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] -// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// 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]) -// 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 -// 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 -// mHttp* none rw.repo.none -// -// LLMeshUploadThread: -// -// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] -// ... more ... -// -// QA/Development Testing -// -// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will -// simulate an error on fee query or upload. Defined bits are: -// -// 0x01 Simulate application error on fee check reading -// response body from file "fake_upload_error.xml" -// 0x02 Same as 0x01 but for actual upload attempt. -// 0x04 Simulate a transport problem on fee check with a -// locally-generated 500 status. -// 0x08 As with 0x04 but for the upload operation. -// -// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and -// instructions for looking for frame stalls using fast timers. -// -// *TODO: Work list for followup actions: -// * Review anything marked as unsafe above, verify if there are real issues. -// * See if we can put ::run() into a hard sleep. May not actually perform better -// than the current scheme so be prepared for disappointment. You'll likely -// need to introduce a condition variable class that references a mutex in -// methods rather than derives from mutex which isn't correct. -// * On upload failures, make more information available to the alerting -// dialog. Get the structured information going into the log into a -// tree there. -// * Header parse failures come without much explanation. Elaborate. -// * Work queue for uploads? Any need for this or is the current scheme good -// enough? -// * Move data structures holding mesh data used by main thread into main- -// thread-only access so that no locking is needed. May require duplication -// of some data so that worker thread has a minimal data set to guide -// operations. -// -// -------------------------------------------------------------------------- -// Development/Debug/QA Tools -// -// Enable here or in build environment to get fasttimer data on mesh fetches. -// -// Typically, this is used to perform A/B testing using the -// fasttimer console (shift-ctrl-9). This is done by looking -// for stalls due to lock contention between the main thread -// and the repository and HTTP code. In a release viewer, -// these appear as ping-time or worse spikes in frame time. -// With this instrumentation enabled, a stall will appear -// under the 'Mesh Fetch' timer which will be either top-level -// or under 'Render' time. - -static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); - -// Random failure testing for development/QA. -// -// Set the MESH_*_FAILED macros to either 'false' or to -// an invocation of MESH_RANDOM_NTH_TRUE() with some -// suitable number. In production, all must be false. -// -// Example: -// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) - -// 1-in-N calls will test true -#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) - -#define MESH_HTTP_RESPONSE_FAILED false -#define MESH_HEADER_PROCESS_FAILED false -#define MESH_LOD_PROCESS_FAILED false -#define MESH_SKIN_INFO_PROCESS_FAILED false -#define MESH_DECOMP_PROCESS_FAILED false -#define MESH_PHYS_SHAPE_PROCESS_FAILED false - -// -------------------------------------------------------------------------- - - -LLMeshRepository gMeshRepo; - -const 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; - -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 - -const U32 DOWNLOAD_RETRY_LIMIT = 8; -const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds - -// Would normally like to retry on uploads as some -// retryable failures would be recoverable. Unfortunately, -// the mesh service is using 500 (retryable) rather than -// 400/bad request (permanent) for a bad payload and -// retrying that just leads to revocation of the one-shot -// 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; - -// 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 -// be parsed by viewers that don't specifically support that version. For example, if the integer "1" is -// 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; - -U32 LLMeshRepository::sBytesReceived = 0; -U32 LLMeshRepository::sMeshRequestCount = 0; -U32 LLMeshRepository::sHTTPRequestCount = 0; -U32 LLMeshRepository::sHTTPLargeRequestCount = 0; -U32 LLMeshRepository::sHTTPRetryCount = 0; -U32 LLMeshRepository::sHTTPErrorCount = 0; -U32 LLMeshRepository::sLODProcessing = 0; -U32 LLMeshRepository::sLODPending = 0; - -U32 LLMeshRepository::sCacheBytesRead = 0; -U32 LLMeshRepository::sCacheBytesWritten = 0; -U32 LLMeshRepository::sCacheBytesHeaders = 0; -U32 LLMeshRepository::sCacheBytesSkins = 0; -U32 LLMeshRepository::sCacheBytesDecomps = 0; -U32 LLMeshRepository::sCacheReads = 0; -U32 LLMeshRepository::sCacheWrites = 0; -U32 LLMeshRepository::sMaxLockHoldoffs = 0; - -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics - -namespace { - // The NoOpDeletor is used when passing certain objects (generally the LLMeshUploadThread) - // in a smart pointer below for passage into the LLCore::Http libararies. - // When the smart pointer is destroyed, no action will be taken since we - // do not in these cases want the object to be destroyed at the end of the call. - // - // *NOTE$: Yes! It is "Deletor" - // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb - // "delete" derives from Latin "deletus" - - void NoOpDeletor(LLCore::HttpHandler *) - { /*NoOp*/ } -} - -static S32 dump_num = 0; -std::string make_dump_name(std::string prefix, S32 num) -{ - return prefix + std::to_string(num) + std::string(".xml"); -} -void dump_llsd_to_file(const LLSD& content, std::string filename); -LLSD llsd_from_file(std::string filename); - -const std::string header_lod[] = -{ - "lowest_lod", - "low_lod", - "medium_lod", - "high_lod" -}; -const char * const LOG_MESH = "Mesh"; - -// Static data and functions to measure mesh load -// time metrics for a new region scene. -static unsigned int metrics_teleport_start_count = 0; -boost::signals2::connection metrics_teleport_started_signal; -static void teleport_started(); - -void on_new_single_inventory_upload_complete( - LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - const std::string inventory_type_string, - const LLUUID& item_folder_id, - const std::string& item_name, - const std::string& item_description, - const LLSD& server_response, - S32 upload_price); - - -//get the number of bytes resident in memory for given volume -U32 get_volume_memory_size(const LLVolume* volume) -{ - U32 indices = 0; - U32 vertices = 0; - - for (U32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - indices += face.mNumIndices; - vertices += face.mNumVertices; - } - - - return indices*2+vertices*11+sizeof(LLVolume)+sizeof(LLVolumeFace)*volume->getNumVolumeFaces(); -} - -void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f) -{ - res.mPositions.clear(); - res.mNormals.clear(); - - const F32* v = mesh.mVertexBase; - - if (mesh.mIndexType == LLCDMeshData::INT_16) - { - U16* idx = (U16*) mesh.mIndexBase; - for (S32 j = 0; j < mesh.mNumTriangles; ++j) - { - F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); - F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); - F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); - - idx = (U16*) (((U8*)idx)+mesh.mIndexStrideBytes); - - LLVector3 v0(mp0); - LLVector3 v1(mp1); - LLVector3 v2(mp2); - - LLVector3 n = (v1-v0)%(v2-v0); - n.normalize(); - - res.mPositions.push_back(v0*scale); - res.mPositions.push_back(v1*scale); - res.mPositions.push_back(v2*scale); - - res.mNormals.push_back(n); - res.mNormals.push_back(n); - res.mNormals.push_back(n); - } - } - else - { - U32* idx = (U32*) mesh.mIndexBase; - for (S32 j = 0; j < mesh.mNumTriangles; ++j) - { - F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); - F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); - F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); - - idx = (U32*) (((U8*)idx)+mesh.mIndexStrideBytes); - - LLVector3 v0(mp0); - LLVector3 v1(mp1); - LLVector3 v2(mp2); - - LLVector3 n = (v1-v0)%(v2-v0); - n.normalize(); - - res.mPositions.push_back(v0*scale); - res.mPositions.push_back(v1*scale); - res.mPositions.push_back(v2*scale); - - res.mNormals.push_back(n); - res.mNormals.push_back(n); - res.mNormals.push_back(n); - } - } -} - -void RequestStats::updateTime() -{ - U32 modifier = 1 << mRetries; // before ++ - mRetries++; - mTimer.reset(); - mTimer.setTimerExpirySec(DOWNLOAD_RETRY_DELAY * (F32)modifier); // up to 32s, 64 total wait -} - -bool RequestStats::canRetry() const -{ - return mRetries < DOWNLOAD_RETRY_LIMIT; -} - -bool RequestStats::isDelayed() const -{ - return mTimer.getStarted() && !mTimer.hasExpired(); -} - -LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material) -{ - LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData); - return ppTex ? (*ppTex).get() : NULL; -} - -volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; -volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; -U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; -S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; -S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; -S32 LLMeshRepoThread::sRequestWaterLevel = 0; - -// Base handler class for all mesh users of llcorehttp. -// This is roughly equivalent to a Responder class in -// traditional LL code. The base is going to perform -// common response/data handling in the inherited -// onCompleted() method. Derived classes, one for each -// type of HTTP action, define processData() and -// processFailure() methods to customize handling and -// error messages. -// -// LLCore::HttpHandler -// LLMeshHandlerBase -// LLMeshHeaderHandler -// LLMeshLODHandler -// LLMeshSkinInfoHandler -// LLMeshDecompositionHandler -// LLMeshPhysicsShapeHandler -// LLMeshUploadThread - -class LLMeshHandlerBase : public LLCore::HttpHandler, - public std::enable_shared_from_this -{ -public: - typedef std::shared_ptr ptr_t; - - LOG_CLASS(LLMeshHandlerBase); - LLMeshHandlerBase(U32 offset, U32 requested_bytes) - : LLCore::HttpHandler(), - mMeshParams(), - mProcessed(false), - mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), - mOffset(offset), - mRequestedBytes(requested_bytes) - {} - - virtual ~LLMeshHandlerBase() - {} - -protected: - LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined - void operator=(const LLMeshHandlerBase &); // Not defined - -public: - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size) = 0; - virtual void processFailure(LLCore::HttpStatus status) = 0; - -public: - LLVolumeParams mMeshParams; - bool mProcessed; - LLCore::HttpHandle mHttpHandle; - U32 mOffset; - U32 mRequestedBytes; -}; - - -// Subclass for header fetches. -// -// Thread: repo -class LLMeshHeaderHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshHeaderHandler); - LLMeshHeaderHandler(const LLVolumeParams & mesh_params, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes) - { - mMeshParams = mesh_params; - LLMeshRepoThread::incActiveHeaderRequests(); - } - virtual ~LLMeshHeaderHandler(); - -protected: - LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined - void operator=(const LLMeshHeaderHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); -}; - - -// Subclass for LOD fetches. -// -// Thread: repo -class LLMeshLODHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshLODHandler); - LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mLOD(lod) - { - mMeshParams = mesh_params; - LLMeshRepoThread::incActiveLODRequests(); - } - virtual ~LLMeshLODHandler(); - -protected: - LLMeshLODHandler(const LLMeshLODHandler &); // Not defined - void operator=(const LLMeshLODHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - S32 mLOD; -}; - - -// Subclass for skin info fetches. -// -// Thread: repo -class LLMeshSkinInfoHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshSkinInfoHandler); - LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshSkinInfoHandler(); - -protected: - LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined - void operator=(const LLMeshSkinInfoHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -// Subclass for decomposition fetches. -// -// Thread: repo -class LLMeshDecompositionHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshDecompositionHandler); - LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshDecompositionHandler(); - -protected: - LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined - void operator=(const LLMeshDecompositionHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -// Subclass for physics shape fetches. -// -// Thread: repo -class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshPhysicsShapeHandler); - LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshPhysicsShapeHandler(); - -protected: - LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined - void operator=(const LLMeshPhysicsShapeHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -void log_upload_error(LLCore::HttpStatus status, const LLSD& content, - const char * const stage, const std::string & model_name) -{ - // Add notification popup. - LLSD args; - std::string message = content["error"]["message"].asString(); - std::string identifier = content["error"]["identifier"].asString(); - args["MESSAGE"] = message; - args["IDENTIFIER"] = identifier; - args["LABEL"] = model_name; - - // Log details. - LL_WARNS(LOG_MESH) << "Error in stage: " << stage - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << ")" << LL_ENDL; - - std::ostringstream details; - typedef std::set mav_errors_set_t; - mav_errors_set_t mav_errors; - - if (content.has("error")) - { - const LLSD& err = content["error"]; - LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; - LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage - << "', error '" << err["error"].asString() - << "', message '" << err["message"].asString() - << "', id '" << err["identifier"].asString() - << "'" << LL_ENDL; - - if (err.has("errors")) - { - details << std::endl << std::endl; - - S32 error_num = 0; - const LLSD& err_list = err["errors"]; - for (LLSD::array_const_iterator it = err_list.beginArray(); - it != err_list.endArray(); - ++it) - { - const LLSD& err_entry = *it; - std::string message = err_entry["message"]; - - if (message.length() > 0) - { - mav_errors.insert(message); - } - - LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; - for (LLSD::map_const_iterator map_it = err_entry.beginMap(); - map_it != err_entry.endMap(); - ++map_it) - { - LL_WARNS(LOG_MESH) << " " << map_it->first << ": " - << map_it->second << LL_ENDL; - } - error_num++; - } - } - } - else - { - LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; - } - - mav_errors_set_t::iterator mav_errors_it = mav_errors.begin(); - for (; mav_errors_it != mav_errors.end(); ++mav_errors_it) - { - std::string mav_details = "Mav_Details_" + *mav_errors_it; - details << "Message: '" << *mav_errors_it << "': " << LLTrans::getString(mav_details) << std::endl << std::endl; - } - - std::string details_str = details.str(); - if (details_str.length() > 0) - { - args["DETAILS"] = details_str; - } - - gMeshRepo.uploadError(args); -} - -LLMeshRepoThread::LLMeshRepoThread() -: LLThread("mesh repo"), - mHttpRequest(NULL), - mHttpOptions(), - mHttpLargeOptions(), - mHttpHeaders(), - mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) -{ - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - - mMutex = new LLMutex(); - mHeaderMutex = new LLMutex(); - mSignal = new LLCondition(); - mHttpRequest = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); - mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpLargeOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); - mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - 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); -} - - -LLMeshRepoThread::~LLMeshRepoThread() -{ - LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount - << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount - << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs - << LL_ENDL; - - mHttpRequestSet.clear(); - mHttpHeaders.reset(); - - while (!mSkinInfoQ.empty()) - { - delete mSkinInfoQ.front(); - mSkinInfoQ.pop_front(); - } - - while (!mDecompositionQ.empty()) - { - delete mDecompositionQ.front(); - mDecompositionQ.pop_front(); - } - - delete mHttpRequest; - mHttpRequest = NULL; - delete mMutex; - mMutex = NULL; - delete mHeaderMutex; - mHeaderMutex = NULL; - delete mSignal; - mSignal = NULL; -} - -void LLMeshRepoThread::run() -{ - LLCDResult res = LLConvexDecomposition::initThread(); - if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) - { - LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; - } - - while (!LLApp::isExiting()) - { - // *TODO: Revise sleep/wake strategy and try to move away - // from polling operations in this thread. We can sleep - // this thread hard when: - // * All Http requests are serviced - // * LOD request queue empty - // * Header request queue empty - // * Skin info request queue empty - // * Decomposition request queue empty - // * Physics shape request queue empty - // We wake the thread when any of the above become untrue. - // Will likely need a correctly-implemented condition variable to do this. - // On the other hand, this may actually be an effective and efficient scheme... - - mSignal->wait(); - - if (LLApp::isExiting()) - { - break; - } - - if (! mHttpRequestSet.empty()) - { - // Dispatch all HttpHandler notifications - mHttpRequest->update(0L); - } - sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update - - // NOTE: order of queue processing intentionally favors LOD 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 (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - std::list incomplete; - while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - if (!mMutex) - { - break; - } - - mMutex->lock(); - LODRequest req = mLODReqQ.front(); - mLODReqQ.pop(); - LLMeshRepository::sLODProcessing--; - mMutex->unlock(); - if (req.isDelayed()) - { - // failed to load before, wait a bit - incomplete.push_front(req); - } - else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) - { - if (req.canRetry()) - { - // failed, resubmit - req.updateTime(); - incomplete.push_front(req); - } - else - { - // too many fails - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(req); - LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) - { - mLODReqQ.push(*iter); - ++LLMeshRepository::sLODProcessing; - } - } - } - - if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - std::list incomplete; - while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - if (!mMutex) - { - break; - } - - mMutex->lock(); - HeaderRequest req = mHeaderReqQ.front(); - mHeaderReqQ.pop(); - mMutex->unlock(); - if (req.isDelayed()) - { - // failed to load before, wait a bit - incomplete.push_front(req); - } - else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) - { - if (req.canRetry()) - { - //failed, resubmit - req.updateTime(); - incomplete.push_front(req); - } - else - { - LL_DEBUGS() << "mHeaderReqQ failed: " << req.mMeshParams << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) - { - mHeaderReqQ.push(*iter); - } - } - } - - // For the final three request lists, similar goal to above but - // slightly different queue structures. Stay off the mutex when - // performing long-duration actions. - - if (mHttpRequestSet.size() < sRequestHighWater - && (!mSkinRequests.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 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 - // in these cases. - if (!mDecompositionRequests.empty()) - { - std::set incomplete; - while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set::iterator iter = mDecompositionRequests.begin(); - UUIDBasedRequest req = *iter; - mDecompositionRequests.erase(iter); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.insert(req); - } - else if (!fetchMeshDecomposition(req.mId)) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.insert(req); - } - else - { - LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); - } - } - - // holding lock, final list - if (!mPhysicsShapeRequests.empty()) - { - std::set incomplete; - while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set::iterator iter = mPhysicsShapeRequests.begin(); - UUIDBasedRequest req = *iter; - mPhysicsShapeRequests.erase(iter); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.insert(req); - } - else if (!fetchMeshPhysicsShape(req.mId)) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.insert(req); - } - else - { - LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); - } - } - } - - // For dev purposes only. A dynamic change could make this false - // and that shouldn't assert. - // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); - } - - if (mSignal->isLocked()) - { //make sure to let go of the mutex associated with the given signal before shutting down - mSignal->unlock(); - } - - res = LLConvexDecomposition::quitThread(); - if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) - { - LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; - } -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) -{ - mSkinRequests.push_back(UUIDBasedRequest(mesh_id)); -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) -{ - mDecompositionRequests.insert(UUIDBasedRequest(mesh_id)); -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) -{ - mPhysicsShapeRequests.insert(UUIDBasedRequest(mesh_id)); -} - -void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ - if (!LLAppViewer::isExiting()) - { - loadMeshLOD(mesh_params, lod); - } -} - - -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()) - { //if we have the header, request LOD byte range - - LODRequest req(mesh_params, lod); - { - mLODReqQ.push(req); - LLMeshRepository::sLODProcessing++; - } - } - else - { - 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); - } - else - { //if no header request is pending, fetch header - mHeaderReqQ.push(req); - mPendingLOD[mesh_id].push_back(lod); - } - } -} - -// Mutex: must be holding mMutex when called -void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap) -{ - mGetMeshCapability = mesh_cap; -} - - -// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap -// over a GetMesh cap. -// -// Mutex: acquires mMutex -void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url) -{ - std::string res_url; - - if (gAgent.getRegion()) - { - { - LLMutexLock lock(mMutex); - res_url = mGetMeshCapability; - } - - if (!res_url.empty()) - { - res_url += "/?mesh_id="; - res_url += mesh_id.asString().c_str(); - } - else - { - LL_WARNS_ONCE(LOG_MESH) << "Current region does not have ViewerAsset capability! Cannot load meshes. Region id: " - << gAgent.getRegion()->getRegionID() << LL_ENDL; - LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; - } - } - else - { - LL_WARNS_ONCE(LOG_MESH) << "Current region is not loaded so there is no capability to load from! Cannot load meshes." << LL_ENDL; - LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; - } - - *url = res_url; -} - -// Issue an HTTP GET request with byte range using the right -// policy class. -// -// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. -// If the latter, actual status is found in -// mHttpStatus member which is valid until the -// next call to this method. -// -// Thread: repo -LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, - size_t offset, size_t len, - const LLCore::HttpHandler::ptr_t &handler) -{ - // Also used in lltexturefetch.cpp - static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); - - LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); - - if (len < LARGE_MESH_FETCH_THRESHOLD) - { - handle = mHttpRequest->requestGetByteRange( mHttpPolicyClass, - url, - (disable_range_req ? size_t(0) : offset), - (disable_range_req ? size_t(0) : len), - mHttpOptions, - mHttpHeaders, - handler); - if (LLCORE_HTTP_HANDLE_INVALID != handle) - { - ++LLMeshRepository::sHTTPRequestCount; - } - } - else - { - handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, - url, - (disable_range_req ? size_t(0) : offset), - (disable_range_req ? size_t(0) : len), - mHttpLargeOptions, - mHttpHeaders, - handler); - if (LLCORE_HTTP_HANDLE_INVALID != handle) - { - ++LLMeshRepository::sHTTPLargeRequestCount; - } - } - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - // Something went wrong, capture the error code for caller. - mHttpStatus = mHttpRequest->getStatus(); - } - return handle; -} - - -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) -{ - - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - bool ret = true; - U32 header_size = header_it->second.first; - - 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; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset + size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL; - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (skinInfoReceived(mesh_id, buffer, size)) - { - delete[] buffer; - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshSkinInfoHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else if(can_retry) - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; - 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; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //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 - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshDecompositionHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; - 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; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh physics shape info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for physics shape, size: " << size << LL_ENDL; - return false; - } - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //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 - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshPhysicsShapeHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - } - else - { //no physics shape whatsoever, report back NULL - physicsShapeReceived(mesh_id, NULL, 0); - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -//static -void LLMeshRepoThread::incActiveLODRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - ++LLMeshRepoThread::sActiveLODRequests; -} - -//static -void LLMeshRepoThread::decActiveLODRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - --LLMeshRepoThread::sActiveLODRequests; -} - -//static -void LLMeshRepoThread::incActiveHeaderRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - ++LLMeshRepoThread::sActiveHeaderRequests; -} - -//static -void LLMeshRepoThread::decActiveHeaderRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - --LLMeshRepoThread::sActiveHeaderRequests; -} - -//return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) -{ - ++LLMeshRepository::sMeshRequestCount; - - { - //look for mesh in asset in cache - LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); - - S32 size = file.getSize(); - - 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); - LLMeshRepository::sCacheBytesRead += bytes; - ++LLMeshRepository::sCacheReads; - file.read(buffer, bytes); - if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) - { - std::string mid; - mesh_params.getSculptID().toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; - - // Found mesh in cache - return true; - } - } - } - - //either cache entry doesn't exist or is corrupt, request header from simulator - bool retval = true; - std::string http_url; - constructUrl(mesh_params.getSculptID(), &http_url); - - - if (!http_url.empty()) - { - std::string mid; - mesh_params.getSculptID().toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; - - //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits - //within the first 4KB - //NOTE -- this will break of headers ever exceed 4KB - - LLMeshHandlerBase::ptr_t handler(new LLMeshHeaderHandler(mesh_params, 0, MESH_HEADER_SIZE)); - LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - retval = false; - } - else if (can_retry) - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - - return retval; -} - -//return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) -{ - if (!mHeaderMutex) - { - return false; - } - - const LLUUID& mesh_id = mesh_params.getSculptID(); - - mHeaderMutex->lock(); - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - ++LLMeshRepository::sMeshRequestCount; - bool retval = true; - - U32 header_size = header_it->second.first; - 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]; - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - - //check cache for mesh asset - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL; - // todo: for now it will result in indefinite constant retries, should result in timeout - // or in retry-count and disabling mesh. (but usually viewer is beyond saving at this point) - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) - { - delete[] buffer; - - std::string mid; - mesh_id.toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; - - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - std::string mid; - mesh_id.toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; - - LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - retval = false; - } - else if (can_retry) - { - 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); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); - } - } - else - { - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); - } - } - else - { - mHeaderMutex->unlock(); - } - - return retval; -} - -EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) -{ - const LLUUID mesh_id = mesh_params.getSculptID(); - LLSD header_data; - - LLMeshHeader header; - - llssize header_size = 0; - if (data_size > 0) - { - llssize dsize = data_size; - char* result_ptr = strip_deprecated_header((char*)data, dsize, &header_size); - - data_size = dsize; - - boost::iostreams::stream stream(result_ptr, data_size); - - if (!LLSDSerialize::fromBinary(header_data, stream, data_size)) - { - LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id - << LL_ENDL; - return MESH_PARSE_FAILURE; - } - - if (!header_data.isMap()) - { - LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL; - return MESH_INVALID; - } - - header.fromLLSD(header_data); - - if (header.mVersion > MAX_MESH_VERSION) - { - LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL; - header.m404 = true; - } - // make sure there is at least one lod, function returns -1 and marks as 404 otherwise - else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0) - { - header_size += stream.tellg(); - } - } - else - { - LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id - << LL_ENDL; - header.m404 = 1; - } - - { - - { - LLMutexLock lock(mHeaderMutex); - mMeshHeader[mesh_id] = { header_size, header }; - LLMeshRepository::sCacheBytesHeaders += header_size; - } - - LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. - - //check for pending requests - pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); - if (iter != mPendingLOD.end()) - { - for (U32 i = 0; i < iter->second.size(); ++i) - { - LODRequest req(mesh_params, iter->second[i]); - mLODReqQ.push(req); - LLMeshRepository::sLODProcessing++; - } - mPendingLOD.erase(iter); - } - } - - return MESH_OK; -} - -EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size) -{ - if (data == NULL || data_size == 0) - { - return MESH_NO_DATA; - } - - LLPointer volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod)); - if (volume->unpackVolumeFaces(data, data_size)) - { - if (volume->getNumFaces() > 0) - { - LoadedMesh mesh(volume, mesh_params, lod); - { - LLMutexLock lock(mMutex); - 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 - // and won't affect mLoadedQ processing - volume = NULL; - // might be good idea to turn mesh into pointer to avoid making a copy - mesh.mVolume = NULL; - } - return MESH_OK; - } - } - - return MESH_UNKNOWN; -} - -bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD skin; - - if (data_size > 0) - { - try - { - U32 uzip_result = LLUZipHelper::unzip_llsd(skin, data, data_size); - if (uzip_result != LLUZipHelper::ZR_OK) - { - LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id - << " uzip result" << uzip_result - << LL_ENDL; - return false; - } - } - catch (std::bad_alloc&) - { - LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; - return false; - } - } - - { - LLMeshSkinInfo* info = nullptr; - try - { - info = new LLMeshSkinInfo(mesh_id, skin); - } - catch (const std::bad_alloc& ex) - { - LL_WARNS() << "Failed to allocate skin info with exception: " << ex.what() << LL_ENDL; - return false; - } - - // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; - { - LLMutexLock lock(mMutex); - mSkinInfoQ.push_back(info); - } - } - - return true; -} - -bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD decomp; - - if (data_size > 0) - { - try - { - U32 uzip_result = LLUZipHelper::unzip_llsd(decomp, data, data_size); - if (uzip_result != LLUZipHelper::ZR_OK) - { - LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id - << " uzip result: " << uzip_result - << LL_ENDL; - return false; - } - } - catch (const std::bad_alloc&) - { - LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; - return false; - } - } - - { - LLModel::Decomposition* d = new LLModel::Decomposition(decomp); - d->mMeshID = mesh_id; - { - LLMutexLock lock(mMutex); - mDecompositionQ.push_back(d); - } - } - - return true; -} - -EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD physics_shape; - - LLModel::Decomposition* d = new LLModel::Decomposition(); - d->mMeshID = mesh_id; - - if (data == NULL) - { //no data, no physics shape exists - d->mPhysicsShapeMesh.clear(); - } - else - { - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - volume_params.setSculptID(mesh_id, LL_SCULPT_TYPE_MESH); - LLPointer volume = new LLVolume(volume_params,0); - - if (volume->unpackVolumeFaces(data, data_size)) - { - d->mPhysicsShapeMesh.clear(); - - std::vector& pos = d->mPhysicsShapeMesh.mPositions; - std::vector& norm = d->mPhysicsShapeMesh.mNormals; - - for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - - for (S32 i = 0; i < face.mNumIndices; ++i) - { - U16 idx = face.mIndices[i]; - - pos.push_back(LLVector3(face.mPositions[idx].getF32ptr())); - norm.push_back(LLVector3(face.mNormals[idx].getF32ptr())); - } - } - } - } - - { - LLMutexLock lock(mMutex); - mDecompositionQ.push_back(d); - } - return MESH_OK; -} - -LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - const std::string & upload_url, bool do_upload, - LLHandle fee_observer, - LLHandle upload_observer) - : LLThread("mesh upload"), - LLCore::HttpHandler(), - mDiscarded(false), - mDoUpload(do_upload), - mWholeModelUploadURL(upload_url), - mFeeObserverHandle(fee_observer), - mUploadObserverHandle(upload_observer) -{ - mInstanceList = data; - mUploadTextures = upload_textures; - mUploadSkin = upload_skin; - mUploadJoints = upload_joints; - mLockScaleIfJointPosition = lock_scale_if_joint_position; - mMutex = new LLMutex(); - mPendingUploads = 0; - mFinished = false; - mOrigin = gAgent.getPositionAgent(); - mHost = gAgent.getRegionHost(); - - mWholeModelFeeCapability = gAgent.getRegionCapability("NewFileAgentInventory"); - - mOrigin += gAgent.getAtAxis() * scale.magVec(); - - mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); - - mHttpRequest = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); - mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); -} - -LLMeshUploadThread::~LLMeshUploadThread() -{ - delete mHttpRequest; - mHttpRequest = NULL; - delete mMutex; - mMutex = NULL; - -} - -LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) -{ - mStage = "single_hull"; - mModel = mdl; - mDecompID = &mdl->mDecompID; - mBaseModel = base_model; - mThread = thread; - - //copy out positions and indices - assignData(mdl) ; - - mThread->mFinalDecomp = this; - mThread->mPhysicsComplete = false; -} - -void LLMeshUploadThread::DecompRequest::completed() -{ - if (mThread->mFinalDecomp == this) - { - mThread->mPhysicsComplete = true; - } - - llassert(mHull.size() == 1); - - mThread->mHullMap[mBaseModel] = mHull[0]; -} - -//called in the main thread. -void LLMeshUploadThread::preStart() -{ - //build map of LLModel refs to instances for callbacks - for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter) - { - mInstance[iter->mModel].push_back(*iter); - } -} - -void LLMeshUploadThread::discard() -{ - LLMutexLock lock(mMutex); - mDiscarded = true; -} - -bool LLMeshUploadThread::isDiscarded() const -{ - LLMutexLock lock(mMutex); - return mDiscarded; -} - -void LLMeshUploadThread::run() -{ - if (mDoUpload) - { - doWholeModelUpload(); - } - else - { - requestWholeModelFee(); - } -} - -void dump_llsd_to_file(const LLSD& content, std::string filename) -{ - if (gSavedSettings.getBOOL("MeshUploadLogXML")) - { - llofstream of(filename.c_str()); - LLSDSerialize::toPrettyXML(content,of); - } -} - -LLSD llsd_from_file(std::string filename) -{ - llifstream ifs(filename.c_str()); - LLSD result; - LLSDSerialize::fromXML(result,ifs); - return result; -} - -void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) -{ - LLSD result; - - LLSD res; - result["folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_OBJECT); - result["texture_folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - result["asset_type"] = "mesh"; - result["inventory_type"] = "object"; - result["description"] = "(No Description)"; - result["next_owner_mask"] = LLSD::Integer(LLFloaterPerms::getNextOwnerPerms("Uploads")); - result["group_mask"] = LLSD::Integer(LLFloaterPerms::getGroupPerms("Uploads")); - result["everyone_mask"] = LLSD::Integer(LLFloaterPerms::getEveryonePerms("Uploads")); - - res["mesh_list"] = LLSD::emptyArray(); - res["texture_list"] = LLSD::emptyArray(); - res["instance_list"] = LLSD::emptyArray(); - S32 mesh_num = 0; - S32 texture_num = 0; - - std::set textures; - std::map texture_index; - - std::map mesh_index; - std::string model_name; - - S32 instance_num = 0; - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - if (data.mBaseModel->mSubmodelID) - { - // These are handled below to insure correct parenting order on creation - // due to map walking being based on model address (aka random) - continue; - } - - LLModelInstance& first_instance = *(iter->second.begin()); - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = first_instance.mLOD[i]; - } - - if (mesh_index.find(data.mBaseModel) == mesh_index.end()) - { - // Have not seen this model before - create a new mesh_list entry for it. - if (model_name.empty()) - { - model_name = data.mBaseModel->getName(); - } - - std::stringstream ostr; - - LLModel::Decomposition& decomp = - data.mModel[LLModel::LOD_PHYSICS].notNull() ? - data.mModel[LLModel::LOD_PHYSICS]->mPhysics : - data.mBaseModel->mPhysics; - - decomp.mBaseHull = mHullMap[data.mBaseModel]; - - LLSD mesh_header = LLModel::writeModel( - ostr, - data.mModel[LLModel::LOD_PHYSICS], - data.mModel[LLModel::LOD_HIGH], - data.mModel[LLModel::LOD_MEDIUM], - data.mModel[LLModel::LOD_LOW], - data.mModel[LLModel::LOD_IMPOSTOR], - decomp, - mUploadSkin, - mUploadJoints, - mLockScaleIfJointPosition, - false, - false, - data.mBaseModel->mSubmodelID); - - data.mAssetData = ostr.str(); - std::string str = ostr.str(); - - res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); - mesh_index[data.mBaseModel] = mesh_num; - mesh_num++; - } - - // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); - ++instance_iter) - { - - LLModelInstance& instance = *instance_iter; - - LLSD instance_entry; - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - LLVector3 pos, scale; - LLQuaternion rot; - LLMatrix4 transformation = instance.mTransform; - decomposeMeshMatrix(transformation,pos,rot,scale); - instance_entry["position"] = ll_sd_from_vector3(pos); - instance_entry["rotation"] = ll_sd_from_quaternion(rot); - instance_entry["scale"] = ll_sd_from_vector3(scale); - - instance_entry["material"] = LL_MCODE_WOOD; - instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); - instance_entry["mesh"] = mesh_index[data.mBaseModel]; - instance_entry["mesh_name"] = instance.mLabel; - - instance_entry["face_list"] = LLSD::emptyArray(); - - // We want to be able to allow more than 8 materials... - // - S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; - - for (S32 face_num = 0; face_num < end; face_num++) - { - LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; - LLSD face_entry = LLSD::emptyMap(); - - LLViewerFetchedTexture *texture = NULL; - - if (material.mDiffuseMapFilename.size()) - { - texture = FindViewerTexture(material); - } - - if ((texture != NULL) && - (textures.find(texture) == textures.end())) - { - textures.insert(texture); - } - - std::stringstream texture_str; - if (texture != NULL && include_textures && mUploadTextures) - { - if (texture->hasSavedRawImage()) - { - LLImageDataLock lock(texture->getSavedRawImage()); - - LLPointer upload_file = - LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); - - if (!upload_file.isNull() && upload_file->getDataSize()) - { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); - } - } - } - - if (texture != NULL && - mUploadTextures && - texture_index.find(texture) == texture_index.end()) - { - texture_index[texture] = texture_num; - std::string str = texture_str.str(); - res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); - texture_num++; - } - - // Subset of TextureEntry fields. - if (texture != NULL && mUploadTextures) - { - face_entry["image"] = texture_index[texture]; - face_entry["scales"] = 1.0; - face_entry["scalet"] = 1.0; - face_entry["offsets"] = 0.0; - face_entry["offsett"] = 0.0; - face_entry["imagerot"] = 0.0; - } - face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); - face_entry["fullbright"] = material.mFullbright; - instance_entry["face_list"][face_num] = face_entry; - } - - res["instance_list"][instance_num] = instance_entry; - instance_num++; - } - } - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - if (!data.mBaseModel->mSubmodelID) - { - // These were handled above already... - // - continue; - } - - LLModelInstance& first_instance = *(iter->second.begin()); - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = first_instance.mLOD[i]; - } - - if (mesh_index.find(data.mBaseModel) == mesh_index.end()) - { - // Have not seen this model before - create a new mesh_list entry for it. - if (model_name.empty()) - { - model_name = data.mBaseModel->getName(); - } - - std::stringstream ostr; - - LLModel::Decomposition& decomp = - data.mModel[LLModel::LOD_PHYSICS].notNull() ? - data.mModel[LLModel::LOD_PHYSICS]->mPhysics : - data.mBaseModel->mPhysics; - - decomp.mBaseHull = mHullMap[data.mBaseModel]; - - LLSD mesh_header = LLModel::writeModel( - ostr, - data.mModel[LLModel::LOD_PHYSICS], - data.mModel[LLModel::LOD_HIGH], - data.mModel[LLModel::LOD_MEDIUM], - data.mModel[LLModel::LOD_LOW], - data.mModel[LLModel::LOD_IMPOSTOR], - decomp, - mUploadSkin, - mUploadJoints, - mLockScaleIfJointPosition, - false, - false, - data.mBaseModel->mSubmodelID); - - data.mAssetData = ostr.str(); - std::string str = ostr.str(); - - res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); - mesh_index[data.mBaseModel] = mesh_num; - mesh_num++; - } - - // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); - ++instance_iter) - { - - LLModelInstance& instance = *instance_iter; - - LLSD instance_entry; - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - LLVector3 pos, scale; - LLQuaternion rot; - LLMatrix4 transformation = instance.mTransform; - decomposeMeshMatrix(transformation,pos,rot,scale); - instance_entry["position"] = ll_sd_from_vector3(pos); - instance_entry["rotation"] = ll_sd_from_quaternion(rot); - instance_entry["scale"] = ll_sd_from_vector3(scale); - - instance_entry["material"] = LL_MCODE_WOOD; - instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE); - instance_entry["mesh"] = mesh_index[data.mBaseModel]; - - instance_entry["face_list"] = LLSD::emptyArray(); - - // We want to be able to allow more than 8 materials... - // - S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; - - for (S32 face_num = 0; face_num < end; face_num++) - { - LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; - LLSD face_entry = LLSD::emptyMap(); - - LLViewerFetchedTexture *texture = NULL; - - if (material.mDiffuseMapFilename.size()) - { - texture = FindViewerTexture(material); - } - - if ((texture != NULL) && - (textures.find(texture) == textures.end())) - { - textures.insert(texture); - } - - std::stringstream texture_str; - if (texture != NULL && include_textures && mUploadTextures) - { - if (texture->hasSavedRawImage()) - { - LLImageDataLock lock(texture->getSavedRawImage()); - - LLPointer upload_file = - LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); - - if (!upload_file.isNull() && upload_file->getDataSize()) - { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); - } - } - } - - if (texture != NULL && - mUploadTextures && - texture_index.find(texture) == texture_index.end()) - { - texture_index[texture] = texture_num; - std::string str = texture_str.str(); - res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); - texture_num++; - } - - // Subset of TextureEntry fields. - if (texture != NULL && mUploadTextures) - { - face_entry["image"] = texture_index[texture]; - face_entry["scales"] = 1.0; - face_entry["scalet"] = 1.0; - face_entry["offsets"] = 0.0; - face_entry["offsett"] = 0.0; - face_entry["imagerot"] = 0.0; - } - face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); - face_entry["fullbright"] = material.mFullbright; - instance_entry["face_list"][face_num] = face_entry; - } - - res["instance_list"][instance_num] = instance_entry; - instance_num++; - } - } - - if (model_name.empty()) model_name = "mesh model"; - result["name"] = model_name; - res["metric"] = "MUT_Unspecified"; - result["asset_resources"] = res; - dump_llsd_to_file(result,make_dump_name("whole_model_",dump_num)); - - dest = result; -} - -void LLMeshUploadThread::generateHulls() -{ - bool has_valid_requests = false ; - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - LLModelInstance& instance = *(iter->second.begin()); - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - //queue up models for hull generation - LLModel* physics = NULL; - - if (data.mModel[LLModel::LOD_PHYSICS].notNull()) - { - physics = data.mModel[LLModel::LOD_PHYSICS]; - } - else if (data.mModel[LLModel::LOD_LOW].notNull()) - { - physics = data.mModel[LLModel::LOD_LOW]; - } - else if (data.mModel[LLModel::LOD_MEDIUM].notNull()) - { - physics = data.mModel[LLModel::LOD_MEDIUM]; - } - else - { - physics = data.mModel[LLModel::LOD_HIGH]; - } - - llassert(physics != NULL); - - DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this); - if(request->isValid()) - { - gMeshRepo.mDecompThread->submitRequest(request); - has_valid_requests = true ; - } - } - - if (has_valid_requests) - { - // *NOTE: Interesting livelock condition on shutdown. If there - // is an upload request in generateHulls() when shutdown starts, - // the main thread isn't available to manage communication between - // the decomposition thread and the upload thread and this loop - // wouldn't complete in turn stalling the main thread. The check - // on isDiscarded() prevents that. - while (! mPhysicsComplete && ! isDiscarded()) - { - apr_sleep(100); - } - } -} - -void LLMeshUploadThread::doWholeModelUpload() -{ - LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; - - if (mWholeModelUploadURL.empty()) - { - LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." - << LL_ENDL; - } - else - { - generateHulls(); - LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; - - mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, true); - LLSD body = mModelData["asset_resources"]; - - dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); - - LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, - mHttpPolicyClass, - mWholeModelUploadURL, - body, - mHttpOptions, - mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - mHttpStatus = mHttpRequest->getStatus(); - - LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - } - else - { - U32 sleep_time(10); - - LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; - - mHttpRequest->update(0); - while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) - { - ms_sleep(sleep_time); - sleep_time = llmin(250U, sleep_time + sleep_time); - mHttpRequest->update(0); - } - - if (isDiscarded()) - { - LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; - } - else - { - LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; - } - } - } -} - -void LLMeshUploadThread::requestWholeModelFee() -{ - dump_num++; - - generateHulls(); - - mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, false); - dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); - LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, - mHttpPolicyClass, - mWholeModelFeeCapability, - mModelData, - mHttpOptions, - mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - mHttpStatus = mHttpRequest->getStatus(); - - LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - } - else - { - U32 sleep_time(10); - - mHttpRequest->update(0); - while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) - { - ms_sleep(sleep_time); - sleep_time = llmin(250U, sleep_time + sleep_time); - mHttpRequest->update(0); - } - if (isDiscarded()) - { - LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; - } - } -} - - -// Does completion duty for both fee queries and actual uploads. -void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check - const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); - LLCore::HttpStatus status(response->getStatus()); - if (fake_error) - { - status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); - } - std::string reason(status.toString()); - LLSD body; - - mFinished = true; - - if (mDoUpload) - { - // model upload case - LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); - - if (! status) - { - LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason - << " (" << status.toTerseString() << ")" - << LL_ENDL; - - // Build a fake body for the alert generator - body["error"] = LLSD::emptyMap(); - body["error"]["message"] = reason; - body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "upload", mModelData["name"].asString()); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } - else - { - if (fake_error & 0x2) - { - body = llsd_from_file("fake_upload_error.xml"); - } - else - { - // *TODO: handle error in conversion process - LLCoreHttpUtil::responseToLLSD(response, true, body); - } - dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); - - if (body["state"].asString() == "complete") - { - // requested "mesh" asset type isn't actually the type - // of the resultant object, fix it up here. - mModelData["asset_type"] = "object"; - gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); - } - } - else - { - LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; - log_upload_error(status, body, "upload", mModelData["name"].asString()); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } - } - } - else - { - // model fee case - LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); - mWholeModelUploadURL.clear(); - - if (! status) - { - LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason - << " (" << status.toTerseString() << ")" - << LL_ENDL; - - // Build a fake body for the alert generator - body["error"] = LLSD::emptyMap(); - body["error"]["message"] = reason; - body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "fee", mModelData["name"].asString()); - - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); - } - } - else - { - if (fake_error & 0x1) - { - body = llsd_from_file("fake_upload_error.xml"); - } - else - { - // *TODO: handle error in conversion process - LLCoreHttpUtil::responseToLLSD(response, true, body); - } - dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); - - if (body["state"].asString() == "upload") - { - mWholeModelUploadURL = body["uploader"].asString(); - - if (observer) - { - body["data"]["upload_price"] = body["upload_price"]; - observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); - } - } - else - { - LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; - log_upload_error(status, body, "fee", mModelData["name"].asString()); - - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); - } - } - } - } -} - - -void LLMeshRepoThread::notifyLoadedMeshes() -{ - bool update_metrics(false); - - if (!mMutex) - { - return; - } - - if (!mLoadedQ.empty()) - { - std::deque loaded_queue; - - mMutex->lock(); - if (!mLoadedQ.empty()) - { - loaded_queue.swap(mLoadedQ); - mMutex->unlock(); - - update_metrics = true; - - // Process the elements free of the lock - for (const auto& mesh : loaded_queue) - { - if (mesh.mVolume->getNumVolumeFaces() > 0) - { - gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); - } - else - { - gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, - LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); - } - } - } - } - - if (!mUnavailableQ.empty()) - { - std::deque unavil_queue; - - mMutex->lock(); - if (!mUnavailableQ.empty()) - { - unavil_queue.swap(mUnavailableQ); - mMutex->unlock(); - - update_metrics = true; - - // Process the elements free of the lock - for (const auto& req : unavil_queue) - { - gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); - } - } - } - - if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty()) - { - if (mMutex->trylock()) - { - std::deque skin_info_q; - std::deque skin_info_unavail_q; - std::list decomp_q; - - if (! mSkinInfoQ.empty()) - { - skin_info_q.swap(mSkinInfoQ); - } - - if (! mSkinUnavailableQ.empty()) - { - skin_info_unavail_q.swap(mSkinUnavailableQ); - } - - if (! mDecompositionQ.empty()) - { - decomp_q.swap(mDecompositionQ); - } - - mMutex->unlock(); - - // Process the elements free of the lock - while (! skin_info_q.empty()) - { - gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); - skin_info_q.pop_front(); - } - while (! skin_info_unavail_q.empty()) - { - gMeshRepo.notifySkinInfoUnavailable(skin_info_unavail_q.front().mId); - skin_info_unavail_q.pop_front(); - } - - while (! decomp_q.empty()) - { - gMeshRepo.notifyDecompositionReceived(decomp_q.front()); - decomp_q.pop_front(); - } - } - } - - if (update_metrics) - { - // Ping time-to-load metrics for mesh download operations. - LLMeshRepository::metricsProgress(0); - } - -} - -S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ //only ever called from main thread - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID()); - - if (iter != mMeshHeader.end()) - { - auto& header = iter->second.second; - - return LLMeshRepository::getActualMeshLOD(header, lod); - } - - return lod; -} - -//static -S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod) -{ - lod = llclamp(lod, 0, 3); - - if (header.m404) - { - return -1; - } - - S32 version = header.mVersion; - - if (version > MAX_MESH_VERSION) - { - return -1; - } - - if (header.mLodSize[lod] > 0) - { - return lod; - } - - //search down to find the next available lower lod - for (S32 i = lod-1; i >= 0; --i) - { - if (header.mLodSize[i] > 0) - { - return i; - } - } - - //search up to find then ext available higher lod - for (S32 i = lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - if (header.mLodSize[i] > 0) - { - return i; - } - } - - //header exists and no good lod found, treat as 404 - header.m404 = true; - - return -1; -} - -// Handle failed or successful requests for mesh assets. -// -// Support for 200 responses was added for several reasons. One, -// a service or cache can ignore range headers and give us a -// 200 with full asset should it elect to. We also support -// a debug flag which disables range requests for those very -// few users that have some sort of problem with their networking -// services. But the 200 response handling is suboptimal: rather -// than cache the whole asset, we just extract the part that would -// have been sent in a 206 and process that. Inefficient but these -// are cases far off the norm. -void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - mProcessed = true; - - unsigned int retries(0U); - response->getRetries(NULL, &retries); - LLMeshRepository::sHTTPRetryCount += retries; - - LLCore::HttpStatus status(response->getStatus()); - if (! status || MESH_HTTP_RESPONSE_FAILED) - { - processFailure(status); - ++LLMeshRepository::sHTTPErrorCount; - } - else - { - // From texture fetch code and may apply here: - // - // A warning about partial (HTTP 206) data. Some grid services - // do *not* return a 'Content-Range' header in the response to - // Range requests with a 206 status. We're forced to assume - // we get what we asked for in these cases until we can fix - // the services. - // - // May also need to deal with 200 status (full asset returned - // rather than partial) and 416 (request completely unsatisfyable). - // Always been exposed to these but are less likely here where - // speculative loads aren't done. - LLCore::BufferArray * body(response->getBody()); - S32 body_offset(0); - U8 * data(NULL); - S32 data_size(body ? body->size() : 0); - - if (data_size > 0) - { - static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); - - unsigned int offset(0), length(0), full_length(0); - - if (par_status == status) - { - // 206 case - response->getRange(&offset, &length, &full_length); - if (! offset && ! length) - { - // This is the case where we receive a 206 status but - // there wasn't a useful Content-Range header in the response. - // This could be because it was badly formatted but is more - // likely due to capabilities services which scrub headers - // from responses. Assume we got what we asked for...` - // length = data_size; - offset = mOffset; - } - } - else - { - // 200 case, typically - offset = 0; - } - - // *DEBUG: To test validation below - // offset += 1; - - // Validate that what we think we received is consistent with - // what we've asked for. I.e. first byte we wanted lies somewhere - // in the response. - if (offset > mOffset - || (offset + data_size) <= mOffset - || (mOffset - offset) >= data_size) - { - // No overlap with requested range. Fail request with - // suitable error. Shouldn't happen unless server/cache/ISP - // is doing something awful. - LL_WARNS(LOG_MESH) << "Mesh response (bytes [" - << offset << ".." << (offset + length - 1) - << "]) didn't overlap with request's origin (bytes [" - << mOffset << ".." << (mOffset + mRequestedBytes - 1) - << "])." << LL_ENDL; - processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR)); - ++LLMeshRepository::sHTTPErrorCount; - goto common_exit; - } - - // *TODO: Try to get rid of data copying and add interfaces - // that support BufferArray directly. Introduce a two-phase - // handler, optional first that takes a body, fallback second - // that requires a temporary allocation and data copy. - body_offset = mOffset - offset; - data = new(std::nothrow) U8[data_size - body_offset]; - if (data) - { - body->read(body_offset, (char *) data, data_size - body_offset); - LLMeshRepository::sBytesReceived += data_size; - } - else - { - LL_WARNS(LOG_MESH) << "Failed to allocate " << data_size - body_offset << " memory for mesh response" << LL_ENDL; - processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_BAD_ALLOC)); - } - } - - processData(body, body_offset, data, data_size - body_offset); - - delete [] data; - } - - // Release handler -common_exit: - gMeshRepo.mThread->mHttpRequestSet.erase(this->shared_from_this()); -} - - -LLMeshHeaderHandler::~LLMeshHeaderHandler() -{ - if (!LLApp::isExiting()) - { - if (! mProcessed) - { - // something went wrong, retry - LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); - } - LLMeshRepoThread::decActiveHeaderRequests(); - } -} - -void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - - // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } -} - -void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - 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; - llassert(success); - EMeshProcessingResult res = MESH_UNKNOWN; - if (success) - { - res = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); - success = (res == MESH_OK); - } - if (! success) - { - // *TODO: Get real reason for parse failure here. Might we want to retry? - LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id - << ", Size: " << data_size - << ", Reason: " << res << " Not retrying." - << LL_ENDL; - - // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } - } - else if (data && data_size > 0) - { - // header was successfully retrieved from sim and parsed and is in cache - S32 header_bytes = 0; - LLMeshHeader header; - - gMeshRepo.mThread->mHeaderMutex->lock(); - 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; - } - - if (header_bytes > 0 - && !header.m404 - && (header.mVersion <= MAX_MESH_VERSION)) - { - std::stringstream str; - - S32 lod_bytes = 0; - - for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) - { - // figure out how many bytes we'll need to reserve in the file - lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]); - } - - // just in case skin info or decomposition is at the end of the file (which it shouldn't be) - lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize); - lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize); - - // Do not unlock mutex untill we are done with LLSD. - // LLSD is smart and can work like smart pointer, is not thread safe. - gMeshRepo.mThread->mHeaderMutex->unlock(); - - S32 bytes = lod_bytes + header_bytes; - - - // It's possible for the remote asset to have more data than is needed for the local cache - // only allocate as much space in the cache as is needed for the local cache - data_size = llmin(data_size, bytes); - - // Fix asset caching - //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - if (file.getMaxSize() >= bytes) - { - LLMeshRepository::sCacheBytesWritten += data_size; - ++LLMeshRepository::sCacheWrites; - - file.write(data, data_size); - - // Fix asset caching - S32 remaining = bytes - file.tell(); - if (remaining > 0) - { - U8* block = new(std::nothrow) U8[remaining]; - if (block) - { - memset(block, 0, remaining); - file.write(block, remaining); - delete[] block; - } - } - // - } - } - else - { - LL_WARNS(LOG_MESH) << "Trying to cache nonexistent mesh, mesh id: " << mesh_id << LL_ENDL; - - 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); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } - } - } -} - -LLMeshLODHandler::~LLMeshLODHandler() -{ - if (! LLApp::isExiting()) - { - if (! mProcessed) - { - LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; - gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); - } - LLMeshRepoThread::decActiveLODRequests(); - } -} - -void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); -} - -void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - 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) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - file.seek(offset); - 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->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() - << ", Unknown reason. Not retrying." - << " LOD: " << mLOD - << " Data size: " << data_size - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); - } -} - -LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); -} - -void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - 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)) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - 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->mMutex); - gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); - } -} - -LLMeshDecompositionHandler::~LLMeshDecompositionHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error. For now, simply leave - // request unfulfilled rather than retry forever. -} - -void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_DECOMP_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->decompositionReceived(mMeshID, data, data_size)) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - file.write(data, size); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID - << ", Unknown reason. Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error - } -} - -LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error -} - -void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_PHYS_SHAPE_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->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) - { - // good fetch from sim, write to cache for caching - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - file.write(data, size); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID - << ", Unknown reason. Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error - } -} - -LLMeshRepository::LLMeshRepository() -: mMeshMutex(NULL), - mDecompThread(NULL), - mMeshThreadCount(0), - mThread(NULL) -{ - mSkinInfoCullTimer.resetWithExpiry(10.f); -} - -void LLMeshRepository::init() -{ - mMeshMutex = new LLMutex(); - - LLConvexDecomposition::getInstance()->initSystem(); - - if (!LLConvexDecomposition::isFunctional()) - { - LL_INFOS(LOG_MESH) << "Using STUB for LLConvexDecomposition" << LL_ENDL; - } - - mDecompThread = new LLPhysicsDecomp(); - mDecompThread->start(); - - while (!mDecompThread->mInited) - { //wait for physics decomp thread to init - apr_sleep(100); - } - - metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); - - mThread = new LLMeshRepoThread(); - mThread->start(); -} - -void LLMeshRepository::shutdown() -{ - LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; - llassert(mThread != NULL); - llassert(mThread->mSignal != NULL); - - metrics_teleport_started_signal.disconnect(); - - for (U32 i = 0; i < mUploads.size(); ++i) - { - LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; - mUploads[i]->discard() ; //discard the uploading requests. - } - - mThread->mSignal->broadcast(); - - while (!mThread->isStopped()) - { - apr_sleep(10); - } - delete mThread; - mThread = NULL; - - for (U32 i = 0; i < mUploads.size(); ++i) - { - LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; - while (!mUploads[i]->isStopped()) - { - apr_sleep(10); - } - delete mUploads[i]; - } - - mUploads.clear(); - - delete mMeshMutex; - mMeshMutex = NULL; - - LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; - - if (mDecompThread) - { - mDecompThread->shutdown(); - delete mDecompThread; - mDecompThread = NULL; - } - - LLConvexDecomposition::quitSystem(); -} - -//called in the main thread. -S32 LLMeshRepository::update() -{ - // Conditionally log a mesh metrics event - metricsUpdate(); - - if(mUploadWaitList.empty()) - { - return 0 ; - } - - S32 size = mUploadWaitList.size() ; - for (S32 i = 0; i < size; ++i) - { - mUploads.push_back(mUploadWaitList[i]); - mUploadWaitList[i]->preStart() ; - mUploadWaitList[i]->start() ; - } - mUploadWaitList.clear() ; - - return size ; -} - -void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) -{ - for (auto& lod : mLoadingMeshes) - { - for (auto& param : lod) - { - vector_replace_with_last(param.second, vobj); - } - } - - for (auto& skin_pair : mLoadingSkins) - { - vector_replace_with_last(skin_pair.second, vobj); - } -} - -S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, 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) - { - return detail; - } - - { - 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()) - { //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()) { - iter->second.push_back(vobj); - } - } - else - { - //first request for this mesh - mLoadingMeshes[detail][mesh_id].push_back(vobj); - mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); - LLMeshRepository::sLODPending++; - } - } - - //do a quick search to see if we can't display something while we wait for this mesh to load - LLVolume* volume = vobj->getVolume(); - - if (volume) - { - LLVolumeParams params = volume->getParams(); - - LLVolumeLODGroup* group = LLPrimitive::getVolumeManager()->getGroup(params); - - if (group) - { - //first, see if last_lod is available (don't transition down to avoid funny popping a la SH-641) - if (last_lod >= 0) - { - LLVolume* lod = group->refLOD(last_lod); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return last_lod; - } - group->derefLOD(lod); - } - - //next, see what the next lowest LOD available might be - for (S32 i = detail-1; i >= 0; --i) - { - LLVolume* lod = group->refLOD(i); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return i; - } - - group->derefLOD(lod); - } - - //no lower LOD is a available, is a higher lod available? - for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - LLVolume* lod = group->refLOD(i); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return i; - } - - group->derefLOD(lod); - } - } - } - - return detail; -} - -void LLMeshRepository::notifyLoadedMeshes() -{ //called from main thread - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - // GetMesh2 operation with keepalives, etc. With pipelining, - // we'll increase this. See llappcorehttp and llcorehttp for - // discussion on connection strategies. - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - S32 scale(app_core_http.isPipelined(LLAppCoreHttp::AP_MESH2) - ? (2 * LLAppCoreHttp::PIPELINING_DEPTH) - : 5); - - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); - LLMeshRepoThread::sRequestHighWater = llclamp(scale * S32(LLMeshRepoThread::sMaxConcurrentRequests), - REQUEST2_HIGH_WATER_MIN, - REQUEST2_HIGH_WATER_MAX); - LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, - REQUEST2_LOW_WATER_MIN, - REQUEST2_LOW_WATER_MAX); - - //clean up completed upload threads - for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) - { - LLMeshUploadThread* thread = *iter; - - if (thread->isStopped() && thread->finished()) - { - iter = mUploads.erase(iter); - delete thread; - } - else - { - ++iter; - } - } - - //update inventory - if (!mInventoryQ.empty()) - { - LLMutexLock lock(mMeshMutex); - while (!mInventoryQ.empty()) - { - inventory_data& data = mInventoryQ.front(); - - LLAssetType::EType asset_type = LLAssetType::lookup(data.mPostData["asset_type"].asString()); - LLInventoryType::EType inventory_type = LLInventoryType::lookup(data.mPostData["inventory_type"].asString()); - - // Handle addition of texture, if any. - if ( data.mResponse.has("new_texture_folder_id") ) - { - const LLUUID& new_folder_id = data.mResponse["new_texture_folder_id"].asUUID(); - - if ( new_folder_id.notNull() ) - { - LLUUID parent_id = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - - std::string name; - // Check if the server built a different name for the texture folder - if ( data.mResponse.has("new_texture_folder_name") ) - { - name = data.mResponse["new_texture_folder_name"].asString(); - } - else - { - name = data.mPostData["name"].asString(); - } - - // Add the category to the internal representation - LLPointer cat = - new LLViewerInventoryCategory(new_folder_id, parent_id, - LLFolderType::FT_NONE, name, gAgent.getID()); - cat->setVersion(LLViewerInventoryCategory::VERSION_UNKNOWN); - - LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); - gInventory.accountForUpdate(update); - gInventory.updateCategory(cat); - } - } - - on_new_single_inventory_upload_complete( - asset_type, - inventory_type, - data.mPostData["asset_type"].asString(), - data.mPostData["folder_id"].asUUID(), - data.mPostData["name"], - data.mPostData["description"], - data.mResponse, - data.mResponse["upload_price"]); - //} - - mInventoryQ.pop(); - } - } - - //call completed callbacks on finished decompositions - mDecompThread->notifyCompleted(); - - if (mSkinInfoCullTimer.checkExpirationAndReset(10.f)) - { - //// Clean up dead skin info - //U64Bytes skinbytes(0); - for (auto iter = mSkinMap.begin(), ender = mSkinMap.end(); iter != ender;) - { - auto copy_iter = iter++; - - //skinbytes += U64Bytes(sizeof(LLMeshSkinInfo)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(std::string)); - //skinbytes += U64Bytes(copy_iter->second->mJointNums.size() * sizeof(S32)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4a)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4)); - - if (copy_iter->second->getNumRefs() == 1) - { - mSkinMap.erase(copy_iter); - } - } - //LL_INFOS() << "Skin info cache elements:" << mSkinMap.size() << " Memory: " << U64Kilobytes(skinbytes) << LL_ENDL; - } - - // For major operations, attempt to get the required locks - // without blocking and punt if they're not available. The - // longest run of holdoffs is kept in sMaxLockHoldoffs just - // to collect the data. In testing, I've never seen a value - // greater than 2 (written to log on exit). - { - LLMutexTrylock lock1(mMeshMutex); - LLMutexTrylock lock2(mThread->mMutex); - - static U32 hold_offs(0); - if (! lock1.isLocked() || ! lock2.isLocked()) - { - // If we can't get the locks, skip and pick this up later. - ++hold_offs; - sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); - return; - } - hold_offs = 0; - - if (gAgent.getRegion()) - { - // Update capability urls - static std::string region_name("never name a region this"); - - if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) - { - region_name = gAgent.getRegion()->getName(); - const std::string mesh_cap(gAgent.getRegion()->getViewerAssetUrl()); - mThread->setGetMeshCap(mesh_cap); - LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name - << "', ViewerAsset cap: " << mesh_cap - << LL_ENDL; - } - } - - //popup queued error messages from background threads - while (!mUploadErrorQ.empty()) - { - LLSD substitutions(mUploadErrorQ.front()); - if (substitutions.has("DETAILS")) - { - LLNotificationsUtil::add("MeshUploadErrorDetails", substitutions); - } - else - { - LLNotificationsUtil::add("MeshUploadError", substitutions); - } - mUploadErrorQ.pop(); - } - - S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; - if (active_count < LLMeshRepoThread::sRequestLowWater) - { - S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; - - if (mPendingRequests.size() > push_count) - { - // More requests than the high-water limit allows so - // sort and forward the most important. - - //calculate "score" for pending requests - - //create score map - std::map score_map; - - for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) - { - 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) - { - LLDrawable* drawable = object->mDrawable; - if (drawable) - { - F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); - max_score = llmax(max_score, cur_score); - } - } - } - - score_map[iter->first] = max_score; - } - } - - //set "score" for pending requests - for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) - { - iter->mScore = score_map[iter->mMeshParams.getSculptID()]; - } - - //sort by "score" - std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, - mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); - } - - while (!mPendingRequests.empty() && push_count > 0) - { - LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); - mThread->loadMeshLOD(request.mMeshParams, request.mLOD); - 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()) - { - mThread->loadMeshDecomposition(mPendingDecompositionRequests.front()); - mPendingDecompositionRequests.pop(); - } - - //send physics shapes decomposition requests - while (!mPendingPhysicsShapeRequests.empty()) - { - mThread->loadMeshPhysicsShape(mPendingPhysicsShapeRequests.front()); - mPendingPhysicsShapeRequests.pop(); - } - - mThread->notifyLoadedMeshes(); - } - - mThread->mSignal->signal(); -} - -void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info) -{ - mSkinMap[info->mMeshID] = info; // Cache into LLPointer - // Alternative: We can get skin size from header - sCacheBytesSkins += info->sizeBytes(); - - skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID); - if (iter != mLoadingSkins.end()) - { - for (LLVOVolume* vobj : iter->second) - { - if (vobj) - { - vobj->notifySkinInfoLoaded(info); - } - } - mLoadingSkins.erase(iter); - } -} - -void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id) -{ - skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); - if (iter != mLoadingSkins.end()) - { - for (LLVOVolume* vobj : iter->second) - { - if (vobj) - { - vobj->notifySkinInfoUnavailable(); - } - } - mLoadingSkins.erase(iter); - } -} - -void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) -{ - decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID); - if (iter == mDecompositionMap.end()) - { //just insert decomp into map - mDecompositionMap[decomp->mMeshID] = decomp; - mLoadingDecompositions.erase(decomp->mMeshID); - sCacheBytesDecomps += decomp->sizeBytes(); - } - else - { //merge decomp with existing entry - sCacheBytesDecomps -= iter->second->sizeBytes(); - iter->second->merge(decomp); - sCacheBytesDecomps += iter->second->sizeBytes(); - - mLoadingDecompositions.erase(decomp->mMeshID); - delete decomp; - } -} - -void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) -{ //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); - - if (volume && obj_iter != mLoadingMeshes[detail].end()) - { - //make sure target volume is still valid - if (volume->getNumVolumeFaces() <= 0) - { - LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_id - << LL_ENDL; - } - - { //update system volume - LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail); - if (sys_volume) - { - sys_volume->copyVolumeFaces(volume); - sys_volume->setMeshAssetLoaded(true); - LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); - } - else - { - LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_id - << LL_ENDL; - } - } - - //notify waiting LLVOVolume instances that their requested mesh is available - for (LLVOVolume* vobj : obj_iter->second) - { - if (vobj) - { - vobj->notifyMeshLoaded(); - } - } - - mLoadingMeshes[detail].erase(obj_iter); - - LLViewerStatsRecorder::instance().meshLoaded(); - } -} - -void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 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()) - { - F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); - - LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); - if (sys_volume) - { - sys_volume->setMeshAssetUnavaliable(true); - LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); - } - - for (LLVOVolume* vobj : obj_iter->second) - { - if (vobj) - { - LLVolume* obj_volume = vobj->getVolume(); - - if (obj_volume && - obj_volume->getDetail() == detail && - obj_volume->getParams() == mesh_params) - { //should force volume to find most appropriate LOD - vobj->setVolume(obj_volume->getParams(), lod); - } - } - } - - mLoadingMeshes[lod].erase(obj_iter); - } -} - -S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ - return mThread->getActualMeshLOD(mesh_params, lod); -} - -const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (mesh_id.notNull()) - { - skin_map::iterator iter = mSkinMap.find(mesh_id); - if (iter != mSkinMap.end()) - { - return iter->second; - } - - //no skin info known about given mesh, try to fetch it - if (requesting_obj != nullptr) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); - if (iter != mLoadingSkins.end()) - { //request pending for this mesh, append volume id to list - auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj); - if (it == iter->second.end()) { - iter->second.push_back(requesting_obj); - } - } - else - { - //first request for this mesh - mLoadingSkins[mesh_id].push_back(requesting_obj); - mPendingSkinRequests.push(mesh_id); - } - } - } - return nullptr; -} - -void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - if (mesh_id.notNull()) - { - LLModel::Decomposition* decomp = NULL; - decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); - if (iter != mDecompositionMap.end()) - { - decomp = iter->second; - } - - //decomposition block hasn't been fetched yet - if (!decomp || decomp->mPhysicsShapeMesh.empty()) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); - if (iter == mLoadingPhysicsShapes.end()) - { //no request pending for this skin info - // *FIXME: Nothing ever deletes entries, can't be right - mLoadingPhysicsShapes.insert(mesh_id); - mPendingPhysicsShapeRequests.push(mesh_id); - } - } - } -} - -LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - LLModel::Decomposition* ret = NULL; - - if (mesh_id.notNull()) - { - decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); - if (iter != mDecompositionMap.end()) - { - ret = iter->second; - } - - //decomposition block hasn't been fetched yet - if (!ret || ret->mBaseHullMesh.empty()) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - std::set::iterator iter = mLoadingDecompositions.find(mesh_id); - if (iter == mLoadingDecompositions.end()) - { //no request pending for this skin info - mLoadingDecompositions.insert(mesh_id); - mPendingDecompositionRequests.push(mesh_id); - } - } - } - - return ret; -} - -void LLMeshRepository::buildHull(const LLVolumeParams& params, S32 detail) -{ - LLVolume* volume = LLPrimitive::sVolumeManager->refVolume(params, detail); - - if (!volume->mHullPoints) - { - //all default params - //execute first stage - //set simplify mode to retain - //set retain percentage to zero - //run second stage - } - - LLPrimitive::sVolumeManager->unrefVolume(volume); -} - -bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - if (mThread->hasPhysicsShapeInHeader(mesh_id)) - { - return true; - } - - LLModel::Decomposition* decomp = getDecomposition(mesh_id); - if (decomp && !decomp->mHull.empty()) - { - return true; - } - - return false; -} - -bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - if (mThread->hasSkinInfoInHeader(mesh_id)) - { - return true; - } - - const LLMeshSkinInfo* skininfo = getSkinInfo(mesh_id); - if (skininfo) - { - return true; - } - - return false; -} - -bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - return mThread->hasHeader(mesh_id); -} - -bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader &mesh = iter->second.second; - if (mesh.mPhysicsMeshSize > 0) - { - return true; - } - } - - return false; -} - -bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader& mesh = iter->second.second; - if (mesh.mSkinOffset >= 0 - && mesh.mSkinSize > 0) - { - return true; - } - } - - return false; -} - -bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - return iter != mMeshHeader.end(); -} - -void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - std::string upload_url, bool do_upload, - LLHandle fee_observer, LLHandle upload_observer) -{ - LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, - upload_skin, upload_joints, lock_scale_if_joint_position, - upload_url, do_upload, fee_observer, upload_observer); - mUploadWaitList.push_back(thread); -} - -S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - const LLMeshHeader& header = iter->second.second; - - if (header.m404) - { - return -1; - } - - S32 size = header.mLodSize[lod]; - return size; - } - - } - - return -1; -} - -void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, - LLVector3& result_pos, - LLQuaternion& result_rot, - LLVector3& result_scale) -{ - // check for reflection - bool reflected = (transformation.determinant() < 0); - - // compute position - LLVector3 position = LLVector3(0, 0, 0) * transformation; - - // compute scale - LLVector3 x_transformed = LLVector3(1, 0, 0) * transformation - position; - LLVector3 y_transformed = LLVector3(0, 1, 0) * transformation - position; - LLVector3 z_transformed = LLVector3(0, 0, 1) * transformation - position; - F32 x_length = x_transformed.normalize(); - F32 y_length = y_transformed.normalize(); - F32 z_length = z_transformed.normalize(); - LLVector3 scale = LLVector3(x_length, y_length, z_length); - - // adjust for "reflected" geometry - LLVector3 x_transformed_reflected = x_transformed; - if (reflected) - { - x_transformed_reflected *= -1.0; - } - - // compute rotation - LLMatrix3 rotation_matrix; - rotation_matrix.setRows(x_transformed_reflected, y_transformed, z_transformed); - LLQuaternion quat_rotation = rotation_matrix.quaternion(); - quat_rotation.normalize(); // the rotation_matrix might not have been orthoginal. make it so here. - LLVector3 euler_rotation; - quat_rotation.getEulerAngles(&euler_rotation.mV[VX], &euler_rotation.mV[VY], &euler_rotation.mV[VZ]); - - result_pos = position + mOrigin; - result_scale = scale; - result_rot = quat_rotation; -} - -void LLMeshRepository::updateInventory(inventory_data data) -{ - LLMutexLock lock(mMeshMutex); - dump_llsd_to_file(data.mPostData,make_dump_name("update_inventory_post_data_",dump_num)); - dump_llsd_to_file(data.mResponse,make_dump_name("update_inventory_response_",dump_num)); - mInventoryQ.push(data); -} - -void LLMeshRepository::uploadError(LLSD& args) -{ - LLMutexLock lock(mMeshMutex); - mUploadErrorQ.push(args); -} - -F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id) -{ - LLMeshCostData costs; - if (getCostData(mesh_id, costs)) - { - return costs.getEstTrisMax(); - } - else - { - return 0.f; - } -} - -F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id) -{ - LLMeshCostData costs; - if (getCostData(mesh_id, costs)) - { - return costs.getEstTrisForStreamingCost(); - } - else - { - return 0.f; - } -} - -// FIXME replace with calc based on LLMeshCostData -F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) -{ - F32 result = 0.f; - if (mThread && mesh_id.notNull()) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - result = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); - } - } - if (result > 0.f) - { - LLMeshCostData data; - if (getCostData(mesh_id, data)) - { - F32 ref_streaming_cost = data.getRadiusBasedStreamingCost(radius); - F32 ref_weighted_tris = data.getRadiusWeightedTris(radius); - if (!is_approx_equal(ref_streaming_cost,result)) - { - LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL; - } - if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value)) - { - LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL; - } - if (bytes && (*bytes != data.getSizeTotal())) - { - LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.getSizeTotal() << LL_ENDL; - } - if (bytes_visible && (lod >=0) && (lod < LLVolumeLODGroup::NUM_LODS) && (*bytes_visible != data.getSizeByLOD(lod))) - { - LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.getSizeByLOD(lod) << LL_ENDL; - } - } - else - { - LL_WARNS() << "getCostData failed!!!" << LL_ENDL; - } - } - return result; -} - -// FIXME replace with calc based on LLMeshCostData -//static -F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) -{ - if (header.m404 - || header.mLodSize[0] <= 0 - || (header.mVersion > MAX_MESH_VERSION)) - { - return 0.f; - } - - F32 max_distance = 512.f; - - F32 dlowest = llmin(radius/0.03f, max_distance); - F32 dlow = llmin(radius/0.06f, max_distance); - F32 dmid = llmin(radius/0.24f, max_distance); - - static LLCachedControl metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl bytes_per_triangle_ch(gSavedSettings, "MeshBytesPerTriangle", 16); - - F32 metadata_discount = (F32)metadata_discount_ch; - F32 minimum_size = (F32)minimum_size_ch; - F32 bytes_per_triangle = (F32)bytes_per_triangle_ch; - - S32 bytes_lowest = header.mLodSize[0]; - S32 bytes_low = header.mLodSize[1]; - S32 bytes_mid = header.mLodSize[2]; - S32 bytes_high = header.mLodSize[3]; - - if (bytes_high == 0) - { - return 0.f; - } - - if (bytes_mid == 0) - { - bytes_mid = bytes_high; - } - - if (bytes_low == 0) - { - bytes_low = bytes_mid; - } - - if (bytes_lowest == 0) - { - bytes_lowest = bytes_low; - } - - F32 triangles_lowest = llmax((F32) bytes_lowest-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_low = llmax((F32) bytes_low-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_mid = llmax((F32) bytes_mid-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_high = llmax((F32) bytes_high-metadata_discount, minimum_size)/bytes_per_triangle; - - if (bytes) - { - *bytes = 0; - *bytes += header.mLodSize[0]; - *bytes += header.mLodSize[1]; - *bytes += header.mLodSize[2]; - *bytes += header.mLodSize[3]; - } - - if (bytes_visible) - { - lod = LLMeshRepository::getActualMeshLOD(header, lod); - if (lod >= 0 && lod <= 3) - { - *bytes_visible = header.mLodSize[lod]; - } - } - - F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559) - F32 min_area = 1.f; - - F32 high_area = llmin(F_PI*dmid*dmid, max_area); - F32 mid_area = llmin(F_PI*dlow*dlow, max_area); - F32 low_area = llmin(F_PI*dlowest*dlowest, max_area); - F32 lowest_area = max_area; - - lowest_area -= low_area; - low_area -= mid_area; - mid_area -= high_area; - - high_area = llclamp(high_area, min_area, max_area); - mid_area = llclamp(mid_area, min_area, max_area); - low_area = llclamp(low_area, min_area, max_area); - lowest_area = llclamp(lowest_area, min_area, max_area); - - F32 total_area = high_area + mid_area + low_area + lowest_area; - high_area /= total_area; - mid_area /= total_area; - low_area /= total_area; - lowest_area /= total_area; - - F32 weighted_avg = triangles_high*high_area + - triangles_mid*mid_area + - triangles_low*low_area + - triangles_lowest*lowest_area; - - if (unscaled_value) - { - *unscaled_value = weighted_avg; - } - - return weighted_avg/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; -} - -LLMeshCostData::LLMeshCostData() -{ - std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); - std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); -} - -bool LLMeshCostData::init(const LLMeshHeader& header) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); - std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); - - S32 bytes_high = header.mLodSize[3]; - S32 bytes_med = header.mLodSize[2]; - if (bytes_med == 0) - { - bytes_med = bytes_high; - } - S32 bytes_low = header.mLodSize[1]; - if (bytes_low == 0) - { - bytes_low = bytes_med; - } - S32 bytes_lowest = header.mLodSize[0]; - if (bytes_lowest == 0) - { - bytes_lowest = bytes_low; - } - mSizeByLOD[0] = bytes_lowest; - mSizeByLOD[1] = bytes_low; - mSizeByLOD[2] = bytes_med; - mSizeByLOD[3] = bytes_high; - - static LLCachedControl metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16); - - for (S32 i=0; i=0; i--) - { - // How many tris can we have in this LOD without affecting land impact? - // - normally an LOD should be at most half the size of the previous one. - // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller. - allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]); - F32 excess_tris = mEstTrisByLOD[i]-allowed_tris; - if (excess_tris>0.f) - { - LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL; - charged_tris += excess_tris; - } - } - return charged_tris; -} - -F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) -{ - return getRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; -} - -F32 LLMeshCostData::getTriangleBasedStreamingCost() -{ - F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * getEstTrisForStreamingCost(); - return result; -} - -bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - data = LLMeshCostData(); - - if (mThread && mesh_id.notNull()) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader& header = iter->second.second; - - bool header_invalid = (header.m404 - || header.mLodSize[0] <= 0 - || header.mVersion > MAX_MESH_VERSION); - if (!header_invalid) - { - return getCostData(header, data); - } - - return true; - } - } - return false; -} - -bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data) -{ - data = LLMeshCostData(); - - if (!data.init(header)) - { - return false; - } - - return true; -} - -LLPhysicsDecomp::LLPhysicsDecomp() -: LLThread("Physics Decomp") -{ - mInited = false; - mQuitting = false; - mDone = false; - - mSignal = new LLCondition(); - mMutex = new LLMutex(); -} - -LLPhysicsDecomp::~LLPhysicsDecomp() -{ - shutdown(); - - delete mSignal; - mSignal = NULL; - delete mMutex; - mMutex = NULL; -} - -void LLPhysicsDecomp::shutdown() -{ - if (mSignal) - { - mQuitting = true; - // There is only one wait(), but just in case 'broadcast' - mSignal->broadcast(); - - while (!isStopped()) - { - apr_sleep(10); - } - } -} - -void LLPhysicsDecomp::submitRequest(LLPhysicsDecomp::Request* request) -{ - LLMutexLock lock(mMutex); - mRequestQ.push(request); - mSignal->signal(); -} - -//static -S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2) -{ - if (gMeshRepo.mDecompThread && gMeshRepo.mDecompThread->mCurRequest.notNull()) - { - return gMeshRepo.mDecompThread->mCurRequest->statusCallback(status, p1, p2); - } - - return 1; -} - -void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) -{ - mesh.mVertexBase = mCurRequest->mPositions[0].mV; - mesh.mVertexStrideBytes = 12; - mesh.mNumVertices = mCurRequest->mPositions.size(); - - if(!vertex_based) - { - mesh.mIndexType = LLCDMeshData::INT_16; - mesh.mIndexBase = &(mCurRequest->mIndices[0]); - mesh.mIndexStrideBytes = 6; - - mesh.mNumTriangles = mCurRequest->mIndices.size()/3; - } - - if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2) - { - LLCDResult ret = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh, vertex_based); - } - - if (ret) - { - LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; - } - } -} - -void LLPhysicsDecomp::doDecomposition() -{ - LLCDMeshData mesh; - S32 stage = mStageID[mCurRequest->mStage]; - - if (LLConvexDecomposition::getInstance() == NULL) - { - // stub library. do nothing. - return; - } - - //load data intoLLCD - if (stage == 0) - { - setMeshData(mesh, false); - } - - //build parameter map - std::map param_map; - - static const LLCDParam* params = NULL; - static S32 param_count = 0; - if (!params) - { - param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); - } - - for (S32 i = 0; i < param_count; ++i) - { - param_map[params[i].mName] = params+i; - } - - U32 ret = LLCD_OK; - //set parameter values - for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) - { - const std::string& name = iter->first; - const LLSD& value = iter->second; - - const LLCDParam* param = param_map[name]; - - if (param == NULL) - { //couldn't find valid parameter - continue; - } - - - if (param->mType == LLCDParam::LLCD_FLOAT) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); - } - else if (param->mType == LLCDParam::LLCD_INTEGER || - param->mType == LLCDParam::LLCD_ENUM) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); - } - else if (param->mType == LLCDParam::LLCD_BOOLEAN) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); - } - } - - mCurRequest->setStatusMessage("Executing."); - - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->executeStage(stage); - } - - if (ret) - { - LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." - << LL_ENDL; - LLMutexLock lock(mMutex); - - mCurRequest->mHull.clear(); - mCurRequest->mHullMesh.clear(); - - mCurRequest->setStatusMessage("FAIL"); - - completeCurrent(); - } - else - { - mCurRequest->setStatusMessage("Reading results"); - - S32 num_hulls =0; - if (LLConvexDecomposition::getInstance() != NULL) - { - num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); - } - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull.clear(); - mCurRequest->mHull.resize(num_hulls); - - mCurRequest->mHullMesh.clear(); - mCurRequest->mHullMesh.resize(num_hulls); - } - - for (S32 i = 0; i < num_hulls; ++i) - { - std::vector p; - LLCDHull hull; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); - - const F32* v = hull.mVertexBase; - - for (S32 j = 0; j < hull.mNumVertices; ++j) - { - LLVector3 vert(v[0], v[1], v[2]); - p.push_back(vert); - v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); - } - - LLCDMeshData mesh; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); - - get_vertex_buffer_from_mesh(mesh, mCurRequest->mHullMesh[i]); - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull[i] = p; - } - } - - { - LLMutexLock lock(mMutex); - mCurRequest->setStatusMessage("FAIL"); - completeCurrent(); - } - } -} - -void LLPhysicsDecomp::completeCurrent() -{ - LLMutexLock lock(mMutex); - mCompletedQ.push(mCurRequest); - mCurRequest = NULL; -} - -void LLPhysicsDecomp::notifyCompleted() -{ - if (!mCompletedQ.empty()) - { - LLMutexLock lock(mMutex); - while (!mCompletedQ.empty()) - { - Request* req = mCompletedQ.front(); - req->completed(); - mCompletedQ.pop(); - } - } -} - - -void make_box(LLPhysicsDecomp::Request * request) -{ - LLVector3 min,max; - min = request->mPositions[0]; - max = min; - - for (U32 i = 0; i < request->mPositions.size(); ++i) - { - update_min_max(min, max, request->mPositions[i]); - } - - request->mHull.clear(); - - LLModel::hull box; - box.push_back(LLVector3(min[0],min[1],min[2])); - box.push_back(LLVector3(max[0],min[1],min[2])); - box.push_back(LLVector3(min[0],max[1],min[2])); - box.push_back(LLVector3(max[0],max[1],min[2])); - box.push_back(LLVector3(min[0],min[1],max[2])); - box.push_back(LLVector3(max[0],min[1],max[2])); - box.push_back(LLVector3(min[0],max[1],max[2])); - box.push_back(LLVector3(max[0],max[1],max[2])); - - request->mHull.push_back(box); -} - - -void LLPhysicsDecomp::doDecompositionSingleHull() -{ - LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); - - if (decomp == NULL) - { - //stub. do nothing. - return; - } - - LLCDMeshData mesh; - - setMeshData(mesh, true); - - LLCDResult ret = decomp->buildSingleHull() ; - if (ret) - { - LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; - make_box(mCurRequest); - } - else - { - { - LLMutexLock lock(mMutex); - mCurRequest->mHull.clear(); - mCurRequest->mHull.resize(1); - mCurRequest->mHullMesh.clear(); - } - - std::vector p; - LLCDHull hull; - - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - decomp->getSingleHull(&hull); - - const F32* v = hull.mVertexBase; - - for (S32 j = 0; j < hull.mNumVertices; ++j) - { - LLVector3 vert(v[0], v[1], v[2]); - p.push_back(vert); - v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); - } - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull[0] = p; - } - } - - { - completeCurrent(); - - } -} - - -void LLPhysicsDecomp::run() -{ - LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); - if (decomp == NULL) - { - // stub library. Set init to true so the main thread - // doesn't wait for this to finish. - mInited = true; - return; - } - - decomp->initThread(); - mInited = true; - - static const LLCDStageData* stages = NULL; - static S32 num_stages = 0; - - if (!stages) - { - num_stages = decomp->getStages(&stages); - } - - for (S32 i = 0; i < num_stages; i++) - { - mStageID[stages[i].mName] = i; - } - - while (!mQuitting) - { - mSignal->wait(); - while (!mQuitting && !mRequestQ.empty()) - { - { - LLMutexLock lock(mMutex); - mCurRequest = mRequestQ.front(); - mRequestQ.pop(); - } - - S32& id = *(mCurRequest->mDecompID); - if (id == -1) - { - decomp->genDecomposition(id); - } - decomp->bindDecomposition(id); - - if (mCurRequest->mStage == "single_hull") - { - doDecompositionSingleHull(); - } - else - { - doDecomposition(); - } - } - } - - decomp->quitThread(); - - if (mSignal->isLocked()) - { //let go of mSignal's associated mutex - mSignal->unlock(); - } - - mDone = true; -} - -void LLPhysicsDecomp::Request::assignData(LLModel* mdl) -{ - if (!mdl) - { - return ; - } - - U16 index_offset = 0; - U16 tri[3] ; - - mPositions.clear(); - mIndices.clear(); - mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ; - mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ; - - //queue up vertex positions and indices - for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = mdl->getVolumeFace(i); - if (mPositions.size() + face.mNumVertices > 65535) - { - continue; - } - - for (U32 j = 0; j < face.mNumVertices; ++j) - { - mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); - for(U32 k = 0 ; k < 3 ; k++) - { - mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ; - mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ; - } - } - - updateTriangleAreaThreshold() ; - - for (U32 j = 0; j+2 < face.mNumIndices; j += 3) - { - tri[0] = face.mIndices[j] + index_offset ; - tri[1] = face.mIndices[j + 1] + index_offset ; - tri[2] = face.mIndices[j + 2] + index_offset ; - - if(isValidTriangle(tri[0], tri[1], tri[2])) - { - mIndices.push_back(tri[0]); - mIndices.push_back(tri[1]); - mIndices.push_back(tri[2]); - } - } - - index_offset += face.mNumVertices; - } - - return ; -} - -void LLPhysicsDecomp::Request::updateTriangleAreaThreshold() -{ - F32 range = mBBox[1].mV[0] - mBBox[0].mV[0] ; - range = llmin(range, mBBox[1].mV[1] - mBBox[0].mV[1]) ; - range = llmin(range, mBBox[1].mV[2] - mBBox[0].mV[2]) ; - - mTriangleAreaThreshold = llmin(0.0002f, range * 0.000002f) ; -} - -//check if the triangle area is large enough to qualify for a valid triangle -bool LLPhysicsDecomp::Request::isValidTriangle(U16 idx1, U16 idx2, U16 idx3) -{ - LLVector3 a = mPositions[idx2] - mPositions[idx1] ; - LLVector3 b = mPositions[idx3] - mPositions[idx1] ; - F32 c = a * b ; - - return ((a*a) * (b*b) - c * c) > mTriangleAreaThreshold ; -} - -void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg) -{ - mStatusMessage = msg; -} - -void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) -{ - decomp.mMesh.resize(decomp.mHull.size()); - - for (U32 i = 0; i < decomp.mHull.size(); ++i) - { - LLCDHull hull; - hull.mNumVertices = decomp.mHull[i].size(); - hull.mVertexBase = decomp.mHull[i][0].mV; - hull.mVertexStrideBytes = 12; - - LLCDMeshData mesh; - LLCDResult res = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); - } - if (res == LLCD_OK) - { - get_vertex_buffer_from_mesh(mesh, decomp.mMesh[i]); - } - } - - if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) - { //get mesh for base hull - LLCDHull hull; - hull.mNumVertices = decomp.mBaseHull.size(); - hull.mVertexBase = decomp.mBaseHull[0].mV; - hull.mVertexStrideBytes = 12; - - LLCDMeshData mesh; - LLCDResult res = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); - } - if (res == LLCD_OK) - { - get_vertex_buffer_from_mesh(mesh, decomp.mBaseHullMesh); - } - } -} - - -bool LLMeshRepository::meshUploadEnabled() -{ - LLViewerRegion *region = gAgent.getRegion(); - if(gSavedSettings.getBOOL("MeshEnabled") && - region) - { - return region->meshUploadEnabled(); - } - return false; -} - -bool LLMeshRepository::meshRezEnabled() -{ - LLViewerRegion *region = gAgent.getRegion(); - if(gSavedSettings.getBOOL("MeshEnabled") && - region) - { - return region->meshRezEnabled(); - } - return false; -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsStart() -{ - ++metrics_teleport_start_count; - sQuiescentTimer.start(0); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsStop() -{ - sQuiescentTimer.stop(0); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsProgress(unsigned int this_count) -{ - static bool first_start(true); - - if (first_start) - { - metricsStart(); - first_start = false; - } - sQuiescentTimer.ringBell(0, this_count); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsUpdate() -{ - F64 started, stopped; - U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); - - if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) - { - LLSD metrics; - - metrics["reason"] = "Mesh Download Quiescent"; - metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; - metrics["start"] = started; - metrics["stop"] = stopped; - metrics["fetches"] = LLSD::Integer(total_count); - metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); - metrics["user_cpu"] = double(user_cpu) / 1.0e6; - metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; - LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; - } -} - -// Threading: main thread only -// static -void teleport_started() -{ - LLMeshRepository::metricsStart(); -} - - -void on_new_single_inventory_upload_complete( - LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - const std::string inventory_type_string, - const LLUUID& item_folder_id, - const std::string& item_name, - const std::string& item_description, - const LLSD& server_response, - S32 upload_price) -{ - bool success = false; - - if (upload_price > 0) - { - // this upload costed us L$, update our balance - // and display something saying that it cost L$ - LLStatusBar::sendMoneyBalanceRequest(); - - LLSD args; - args["AMOUNT"] = llformat("%d", upload_price); - LLNotificationsUtil::add("UploadPayment", args); - } - - if (item_folder_id.notNull()) - { - U32 everyone_perms = PERM_NONE; - U32 group_perms = PERM_NONE; - U32 next_owner_perms = PERM_ALL; - if (server_response.has("new_next_owner_mask")) - { - // The server provided creation perms so use them. - // Do not assume we got the perms we asked for in - // since the server may not have granted them all. - everyone_perms = server_response["new_everyone_mask"].asInteger(); - group_perms = server_response["new_group_mask"].asInteger(); - next_owner_perms = server_response["new_next_owner_mask"].asInteger(); - } - else - { - // The server doesn't provide creation perms - // so use old assumption-based perms. - if (inventory_type_string != "snapshot") - { - next_owner_perms = PERM_MOVE | PERM_TRANSFER; - } - } - - LLPermissions new_perms; - new_perms.init( - gAgent.getID(), - gAgent.getID(), - LLUUID::null, - LLUUID::null); - - new_perms.initMasks( - PERM_ALL, - PERM_ALL, - everyone_perms, - group_perms, - next_owner_perms); - - U32 inventory_item_flags = 0; - if (server_response.has("inventory_flags")) - { - inventory_item_flags = (U32)server_response["inventory_flags"].asInteger(); - if (inventory_item_flags != 0) - { - LL_INFOS() << "inventory_item_flags " << inventory_item_flags << LL_ENDL; - } - } - S32 creation_date_now = time_corrected(); - LLPointer item = new LLViewerInventoryItem( - server_response["new_inventory_item"].asUUID(), - item_folder_id, - new_perms, - server_response["new_asset"].asUUID(), - asset_type, - inventory_type, - item_name, - item_description, - LLSaleInfo::DEFAULT, - inventory_item_flags, - creation_date_now); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - success = true; - - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - - // Show the preview panel for textures and sounds to let - // user know that the image (or snapshot) arrived intact. - LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); - if (panel) - { - - panel->setSelection( - server_response["new_inventory_item"].asUUID(), - TAKE_FOCUS_NO); - } - else - { - LLInventoryPanel::openInventoryPanelAndSetSelection(true, server_response["new_inventory_item"].asUUID(), true, false, true); - } - - // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus); - } - else - { - LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; - } - - // Todo: This is mesh repository code, is following code really needed? - // remove the "Uploading..." message - LLUploadDialog::modalUploadFinished(); - - // Let the Snapshot floater know we have finished uploading a snapshot to inventory. - LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); - if (asset_type == LLAssetType::AT_TEXTURE && floater_snapshot) - { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); - } -} +/** + * @file llmeshrepository.cpp + * @brief Mesh repository implementation. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llapr.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "apr_dso.h" +#include "llhttpconstants.h" +#include "llmeshrepository.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llbufferstream.h" +#include "llcallbacklist.h" +#include "lldatapacker.h" +#include "lldeadmantimer.h" +#include "llfloatermodelpreview.h" +#include "llfloaterperms.h" +#include "llimagej2c.h" +#include "llhost.h" +#include "llmath.h" +#include "llnotificationsutil.h" +#include "llsd.h" +#include "llsdutil_math.h" +#include "llsdserialize.h" +#include "llthread.h" +#include "llfilesystem.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewermenufile.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerstatsrecorder.h" +#include "llviewertexturelist.h" +#include "llvolume.h" +#include "llvolumemgr.h" +#include "llvovolume.h" +#include "llworld.h" +#include "material_codes.h" +#include "pipeline.h" +#include "llinventorymodel.h" +#include "llfoldertype.h" +#include "llviewerparcelmgr.h" +#include "lluploadfloaterobservers.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llfasttimer.h" +#include "llcorehttputil.h" +#include "lltrans.h" +#include "llstatusbar.h" +#include "llinventorypanel.h" +#include "lluploaddialog.h" +#include "llfloaterreg.h" + +#include "boost/iostreams/device/array.hpp" +#include "boost/iostreams/stream.hpp" +#include "boost/lexical_cast.hpp" + +#ifndef LL_WINDOWS +#include "netdb.h" +#endif + + +// Purpose +// +// The purpose of this module is to provide access between the viewer +// and the asset system as regards to mesh objects. +// +// * High-throughput download of mesh assets from servers while +// following best industry practices for network profile. +// * Reliable expensing and upload of new mesh assets. +// * Recovery and retry from errors when appropriate. +// * Decomposition of mesh assets for preview and uploads. +// * And most important: all of the above without exposing the +// main thread to stalls due to deep processing or thread +// locking actions. In particular, the following operations +// on LLMeshRepository are very averse to any stalls: +// * loadMesh +// * search in mMeshHeader (For structural details, see: +// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) +// * notifyLoadedMeshes +// * getSkinInfo +// +// Threads +// +// main Main rendering thread, very sensitive to locking and other stalls +// repo Overseeing worker thread associated with the LLMeshRepoThread class +// decom Worker thread for mesh decomposition requests +// core HTTP worker thread: does the work but doesn't intrude here +// uploadN 0-N temporary mesh upload threads (0-1 in practice) +// +// Sequence of Operations +// +// What follows is a description of the retrieval of one LOD for +// a new mesh object. Work is performed by a series of short, quick +// actions distributed over a number of threads. Each is meant +// to proceed without stalling and the whole forms a deep request +// pipeline to achieve throughput. Ellipsis indicates a return +// or break in processing which is resumed elsewhere. +// +// main thread repo thread (run() method) +// +// loadMesh() invoked to request LOD +// append LODRequest to mPendingRequests +// ... +// other mesh requests may be made +// ... +// notifyLoadedMeshes() invoked to stage work +// append HeaderRequest to mHeaderReqQ +// ... +// scan mHeaderReqQ +// issue 4096-byte GET for header +// ... +// onCompleted() invoked for GET +// data copied +// headerReceived() invoked +// LLSD parsed +// mMeshHeader updated +// scan mPendingLOD for LOD request +// push LODRequest to mLODReqQ +// ... +// scan mLODReqQ +// fetchMeshLOD() invoked +// issue Byte-Range GET for LOD +// ... +// onCompleted() invoked for GET +// data copied +// lodReceived() invoked +// unpack data into LLVolume +// append LoadedMesh to mLoadedQ +// ... +// notifyLoadedMeshes() invoked again +// scan mLoadedQ +// notifyMeshLoaded() for LOD +// setMeshAssetLoaded() invoked for system volume +// notifyMeshLoaded() invoked for each interested object +// ... +// +// Mutexes +// +// LLMeshRepository::mMeshMutex +// LLMeshRepoThread::mMutex +// LLMeshRepoThread::mHeaderMutex +// LLMeshRepoThread::mSignal (LLCondition) +// LLPhysicsDecomp::mSignal (LLCondition) +// LLPhysicsDecomp::mMutex +// LLMeshUploadThread::mMutex +// +// Mutex Order Rules +// +// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex +// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex +// (There are more rules, haven't been extracted.) +// +// Data Member Access/Locking +// +// Description of how shared access to static and instance data +// members is performed. Each member is followed by the name of +// the mutex, if any, covering the data and then a list of data +// access models each of which is a triplet of the following form: +// +// {ro, wo, rw}.{main, repo, any}.{mutex, none} +// Type of access: read-only, write-only, read-write. +// Accessing thread or 'any' +// Relevant mutex held during access (several may be held) or 'none' +// +// A careful eye will notice some unsafe operations. Many of these +// have an alibi of some form. Several types of alibi are identified +// and listed here: +// +// [0] No alibi. Probably unsafe. +// [1] Single-writer, self-consistent readers. Old data must +// be tolerated by any reader but data will come true eventually. +// [2] Like [1] but provides a hint about thread state. These +// may be unsafe. +// [3] empty() check outside of lock. Can me made safish when +// done in double-check lock style. But this depends on +// std:: implementation and memory model. +// [4] Appears to be covered by a mutex but doesn't need one. +// [5] Read of a double-checked lock. +// +// So, in addition to documentation, take this as a to-do/review +// list and see if you can improve things. For porters to non-x86 +// architectures, the weaker memory models will make these platforms +// probabilistically more susceptible to hitting race conditions. +// True here and in other multi-thread code such as texture fetching. +// (Strong memory models make weak programmers. Weak memory models +// make strong programmers. Ref: arm, ppc, mips, alpha) +// +// LLMeshRepository: +// +// sBytesReceived none rw.repo.none, ro.main.none [1] +// sMeshRequestCount " +// sHTTPRequestCount " +// sHTTPLargeRequestCount " +// sHTTPRetryCount " +// sHTTPErrorCount " +// sLODPending mMeshMutex [4] rw.main.mMeshMutex +// sLODProcessing Repo::mMutex rw.any.Repo::mMutex +// sCacheBytesRead none rw.repo.none, ro.main.none [1] +// sCacheBytesWritten " +// sCacheReads " +// sCacheWrites " +// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex +// mSkinMap none rw.main.none +// mDecompositionMap none rw.main.none +// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex +// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex +// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex +// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex +// mUploads none rw.main.none (upload thread accessing objects) +// mUploadWaitList none rw.main.none (upload thread accessing objects) +// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] +// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex +// mGetMeshVersion none rw.main.none +// +// LLMeshRepoThread: +// +// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex +// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] +// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// 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]) +// 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 +// 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 +// mHttp* none rw.repo.none +// +// LLMeshUploadThread: +// +// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] +// ... more ... +// +// QA/Development Testing +// +// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will +// simulate an error on fee query or upload. Defined bits are: +// +// 0x01 Simulate application error on fee check reading +// response body from file "fake_upload_error.xml" +// 0x02 Same as 0x01 but for actual upload attempt. +// 0x04 Simulate a transport problem on fee check with a +// locally-generated 500 status. +// 0x08 As with 0x04 but for the upload operation. +// +// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and +// instructions for looking for frame stalls using fast timers. +// +// *TODO: Work list for followup actions: +// * Review anything marked as unsafe above, verify if there are real issues. +// * See if we can put ::run() into a hard sleep. May not actually perform better +// than the current scheme so be prepared for disappointment. You'll likely +// need to introduce a condition variable class that references a mutex in +// methods rather than derives from mutex which isn't correct. +// * On upload failures, make more information available to the alerting +// dialog. Get the structured information going into the log into a +// tree there. +// * Header parse failures come without much explanation. Elaborate. +// * Work queue for uploads? Any need for this or is the current scheme good +// enough? +// * Move data structures holding mesh data used by main thread into main- +// thread-only access so that no locking is needed. May require duplication +// of some data so that worker thread has a minimal data set to guide +// operations. +// +// -------------------------------------------------------------------------- +// Development/Debug/QA Tools +// +// Enable here or in build environment to get fasttimer data on mesh fetches. +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9). This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code. In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. + +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +// Random failure testing for development/QA. +// +// Set the MESH_*_FAILED macros to either 'false' or to +// an invocation of MESH_RANDOM_NTH_TRUE() with some +// suitable number. In production, all must be false. +// +// Example: +// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) + +// 1-in-N calls will test true +#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) + +#define MESH_HTTP_RESPONSE_FAILED false +#define MESH_HEADER_PROCESS_FAILED false +#define MESH_LOD_PROCESS_FAILED false +#define MESH_SKIN_INFO_PROCESS_FAILED false +#define MESH_DECOMP_PROCESS_FAILED false +#define MESH_PHYS_SHAPE_PROCESS_FAILED false + +// -------------------------------------------------------------------------- + + +LLMeshRepository gMeshRepo; + +const 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; + +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 + +const U32 DOWNLOAD_RETRY_LIMIT = 8; +const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds + +// Would normally like to retry on uploads as some +// retryable failures would be recoverable. Unfortunately, +// the mesh service is using 500 (retryable) rather than +// 400/bad request (permanent) for a bad payload and +// retrying that just leads to revocation of the one-shot +// 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; + +// 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 +// be parsed by viewers that don't specifically support that version. For example, if the integer "1" is +// 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; + +U32 LLMeshRepository::sBytesReceived = 0; +U32 LLMeshRepository::sMeshRequestCount = 0; +U32 LLMeshRepository::sHTTPRequestCount = 0; +U32 LLMeshRepository::sHTTPLargeRequestCount = 0; +U32 LLMeshRepository::sHTTPRetryCount = 0; +U32 LLMeshRepository::sHTTPErrorCount = 0; +U32 LLMeshRepository::sLODProcessing = 0; +U32 LLMeshRepository::sLODPending = 0; + +U32 LLMeshRepository::sCacheBytesRead = 0; +U32 LLMeshRepository::sCacheBytesWritten = 0; +U32 LLMeshRepository::sCacheBytesHeaders = 0; +U32 LLMeshRepository::sCacheBytesSkins = 0; +U32 LLMeshRepository::sCacheBytesDecomps = 0; +U32 LLMeshRepository::sCacheReads = 0; +U32 LLMeshRepository::sCacheWrites = 0; +U32 LLMeshRepository::sMaxLockHoldoffs = 0; + +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics + +namespace { + // The NoOpDeletor is used when passing certain objects (generally the LLMeshUploadThread) + // in a smart pointer below for passage into the LLCore::Http libararies. + // When the smart pointer is destroyed, no action will be taken since we + // do not in these cases want the object to be destroyed at the end of the call. + // + // *NOTE$: Yes! It is "Deletor" + // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb + // "delete" derives from Latin "deletus" + + void NoOpDeletor(LLCore::HttpHandler *) + { /*NoOp*/ } +} + +static S32 dump_num = 0; +std::string make_dump_name(std::string prefix, S32 num) +{ + return prefix + std::to_string(num) + std::string(".xml"); +} +void dump_llsd_to_file(const LLSD& content, std::string filename); +LLSD llsd_from_file(std::string filename); + +const std::string header_lod[] = +{ + "lowest_lod", + "low_lod", + "medium_lod", + "high_lod" +}; +const char * const LOG_MESH = "Mesh"; + +// Static data and functions to measure mesh load +// time metrics for a new region scene. +static unsigned int metrics_teleport_start_count = 0; +boost::signals2::connection metrics_teleport_started_signal; +static void teleport_started(); + +void on_new_single_inventory_upload_complete( + LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price); + + +//get the number of bytes resident in memory for given volume +U32 get_volume_memory_size(const LLVolume* volume) +{ + U32 indices = 0; + U32 vertices = 0; + + for (U32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + indices += face.mNumIndices; + vertices += face.mNumVertices; + } + + + return indices*2+vertices*11+sizeof(LLVolume)+sizeof(LLVolumeFace)*volume->getNumVolumeFaces(); +} + +void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f) +{ + res.mPositions.clear(); + res.mNormals.clear(); + + const F32* v = mesh.mVertexBase; + + if (mesh.mIndexType == LLCDMeshData::INT_16) + { + U16* idx = (U16*) mesh.mIndexBase; + for (S32 j = 0; j < mesh.mNumTriangles; ++j) + { + F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); + F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); + F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); + + idx = (U16*) (((U8*)idx)+mesh.mIndexStrideBytes); + + LLVector3 v0(mp0); + LLVector3 v1(mp1); + LLVector3 v2(mp2); + + LLVector3 n = (v1-v0)%(v2-v0); + n.normalize(); + + res.mPositions.push_back(v0*scale); + res.mPositions.push_back(v1*scale); + res.mPositions.push_back(v2*scale); + + res.mNormals.push_back(n); + res.mNormals.push_back(n); + res.mNormals.push_back(n); + } + } + else + { + U32* idx = (U32*) mesh.mIndexBase; + for (S32 j = 0; j < mesh.mNumTriangles; ++j) + { + F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); + F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); + F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); + + idx = (U32*) (((U8*)idx)+mesh.mIndexStrideBytes); + + LLVector3 v0(mp0); + LLVector3 v1(mp1); + LLVector3 v2(mp2); + + LLVector3 n = (v1-v0)%(v2-v0); + n.normalize(); + + res.mPositions.push_back(v0*scale); + res.mPositions.push_back(v1*scale); + res.mPositions.push_back(v2*scale); + + res.mNormals.push_back(n); + res.mNormals.push_back(n); + res.mNormals.push_back(n); + } + } +} + +void RequestStats::updateTime() +{ + U32 modifier = 1 << mRetries; // before ++ + mRetries++; + mTimer.reset(); + mTimer.setTimerExpirySec(DOWNLOAD_RETRY_DELAY * (F32)modifier); // up to 32s, 64 total wait +} + +bool RequestStats::canRetry() const +{ + return mRetries < DOWNLOAD_RETRY_LIMIT; +} + +bool RequestStats::isDelayed() const +{ + return mTimer.getStarted() && !mTimer.hasExpired(); +} + +LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material) +{ + LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData); + return ppTex ? (*ppTex).get() : NULL; +} + +volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; +volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; +U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; +S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; +S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; +S32 LLMeshRepoThread::sRequestWaterLevel = 0; + +// Base handler class for all mesh users of llcorehttp. +// This is roughly equivalent to a Responder class in +// traditional LL code. The base is going to perform +// common response/data handling in the inherited +// onCompleted() method. Derived classes, one for each +// type of HTTP action, define processData() and +// processFailure() methods to customize handling and +// error messages. +// +// LLCore::HttpHandler +// LLMeshHandlerBase +// LLMeshHeaderHandler +// LLMeshLODHandler +// LLMeshSkinInfoHandler +// LLMeshDecompositionHandler +// LLMeshPhysicsShapeHandler +// LLMeshUploadThread + +class LLMeshHandlerBase : public LLCore::HttpHandler, + public std::enable_shared_from_this +{ +public: + typedef std::shared_ptr ptr_t; + + LOG_CLASS(LLMeshHandlerBase); + LLMeshHandlerBase(U32 offset, U32 requested_bytes) + : LLCore::HttpHandler(), + mMeshParams(), + mProcessed(false), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), + mOffset(offset), + mRequestedBytes(requested_bytes) + {} + + virtual ~LLMeshHandlerBase() + {} + +protected: + LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined + void operator=(const LLMeshHandlerBase &); // Not defined + +public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size) = 0; + virtual void processFailure(LLCore::HttpStatus status) = 0; + +public: + LLVolumeParams mMeshParams; + bool mProcessed; + LLCore::HttpHandle mHttpHandle; + U32 mOffset; + U32 mRequestedBytes; +}; + + +// Subclass for header fetches. +// +// Thread: repo +class LLMeshHeaderHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshHeaderHandler); + LLMeshHeaderHandler(const LLVolumeParams & mesh_params, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes) + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveHeaderRequests(); + } + virtual ~LLMeshHeaderHandler(); + +protected: + LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined + void operator=(const LLMeshHeaderHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); +}; + + +// Subclass for LOD fetches. +// +// Thread: repo +class LLMeshLODHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshLODHandler); + LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mLOD(lod) + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveLODRequests(); + } + virtual ~LLMeshLODHandler(); + +protected: + LLMeshLODHandler(const LLMeshLODHandler &); // Not defined + void operator=(const LLMeshLODHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + S32 mLOD; +}; + + +// Subclass for skin info fetches. +// +// Thread: repo +class LLMeshSkinInfoHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshSkinInfoHandler); + LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshSkinInfoHandler(); + +protected: + LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined + void operator=(const LLMeshSkinInfoHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +// Subclass for decomposition fetches. +// +// Thread: repo +class LLMeshDecompositionHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshDecompositionHandler); + LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshDecompositionHandler(); + +protected: + LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined + void operator=(const LLMeshDecompositionHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +// Subclass for physics shape fetches. +// +// Thread: repo +class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshPhysicsShapeHandler); + LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshPhysicsShapeHandler(); + +protected: + LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined + void operator=(const LLMeshPhysicsShapeHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +void log_upload_error(LLCore::HttpStatus status, const LLSD& content, + const char * const stage, const std::string & model_name) +{ + // Add notification popup. + LLSD args; + std::string message = content["error"]["message"].asString(); + std::string identifier = content["error"]["identifier"].asString(); + args["MESSAGE"] = message; + args["IDENTIFIER"] = identifier; + args["LABEL"] = model_name; + + // Log details. + LL_WARNS(LOG_MESH) << "Error in stage: " << stage + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << ")" << LL_ENDL; + + std::ostringstream details; + typedef std::set mav_errors_set_t; + mav_errors_set_t mav_errors; + + if (content.has("error")) + { + const LLSD& err = content["error"]; + LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; + LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage + << "', error '" << err["error"].asString() + << "', message '" << err["message"].asString() + << "', id '" << err["identifier"].asString() + << "'" << LL_ENDL; + + if (err.has("errors")) + { + details << std::endl << std::endl; + + S32 error_num = 0; + const LLSD& err_list = err["errors"]; + for (LLSD::array_const_iterator it = err_list.beginArray(); + it != err_list.endArray(); + ++it) + { + const LLSD& err_entry = *it; + std::string message = err_entry["message"]; + + if (message.length() > 0) + { + mav_errors.insert(message); + } + + LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; + for (LLSD::map_const_iterator map_it = err_entry.beginMap(); + map_it != err_entry.endMap(); + ++map_it) + { + LL_WARNS(LOG_MESH) << " " << map_it->first << ": " + << map_it->second << LL_ENDL; + } + error_num++; + } + } + } + else + { + LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; + } + + mav_errors_set_t::iterator mav_errors_it = mav_errors.begin(); + for (; mav_errors_it != mav_errors.end(); ++mav_errors_it) + { + std::string mav_details = "Mav_Details_" + *mav_errors_it; + details << "Message: '" << *mav_errors_it << "': " << LLTrans::getString(mav_details) << std::endl << std::endl; + } + + std::string details_str = details.str(); + if (details_str.length() > 0) + { + args["DETAILS"] = details_str; + } + + gMeshRepo.uploadError(args); +} + +LLMeshRepoThread::LLMeshRepoThread() +: LLThread("mesh repo"), + mHttpRequest(NULL), + mHttpOptions(), + mHttpLargeOptions(), + mHttpHeaders(), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) +{ + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mMutex = new LLMutex(); + mHeaderMutex = new LLMutex(); + mSignal = new LLCondition(); + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpLargeOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); + mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + 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); +} + + +LLMeshRepoThread::~LLMeshRepoThread() +{ + LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount + << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount + << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs + << LL_ENDL; + + mHttpRequestSet.clear(); + mHttpHeaders.reset(); + + while (!mSkinInfoQ.empty()) + { + delete mSkinInfoQ.front(); + mSkinInfoQ.pop_front(); + } + + while (!mDecompositionQ.empty()) + { + delete mDecompositionQ.front(); + mDecompositionQ.pop_front(); + } + + delete mHttpRequest; + mHttpRequest = NULL; + delete mMutex; + mMutex = NULL; + delete mHeaderMutex; + mHeaderMutex = NULL; + delete mSignal; + mSignal = NULL; +} + +void LLMeshRepoThread::run() +{ + LLCDResult res = LLConvexDecomposition::initThread(); + if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) + { + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; + } + + while (!LLApp::isExiting()) + { + // *TODO: Revise sleep/wake strategy and try to move away + // from polling operations in this thread. We can sleep + // this thread hard when: + // * All Http requests are serviced + // * LOD request queue empty + // * Header request queue empty + // * Skin info request queue empty + // * Decomposition request queue empty + // * Physics shape request queue empty + // We wake the thread when any of the above become untrue. + // Will likely need a correctly-implemented condition variable to do this. + // On the other hand, this may actually be an effective and efficient scheme... + + mSignal->wait(); + + if (LLApp::isExiting()) + { + break; + } + + if (! mHttpRequestSet.empty()) + { + // Dispatch all HttpHandler notifications + mHttpRequest->update(0L); + } + sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update + + // NOTE: order of queue processing intentionally favors LOD 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 (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + std::list incomplete; + while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (!mMutex) + { + break; + } + + mMutex->lock(); + LODRequest req = mLODReqQ.front(); + mLODReqQ.pop(); + LLMeshRepository::sLODProcessing--; + mMutex->unlock(); + if (req.isDelayed()) + { + // failed to load before, wait a bit + incomplete.push_front(req); + } + else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) + { + if (req.canRetry()) + { + // failed, resubmit + req.updateTime(); + incomplete.push_front(req); + } + else + { + // too many fails + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(req); + LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) + { + mLODReqQ.push(*iter); + ++LLMeshRepository::sLODProcessing; + } + } + } + + if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + std::list incomplete; + while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (!mMutex) + { + break; + } + + mMutex->lock(); + HeaderRequest req = mHeaderReqQ.front(); + mHeaderReqQ.pop(); + mMutex->unlock(); + if (req.isDelayed()) + { + // failed to load before, wait a bit + incomplete.push_front(req); + } + else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) + { + if (req.canRetry()) + { + //failed, resubmit + req.updateTime(); + incomplete.push_front(req); + } + else + { + LL_DEBUGS() << "mHeaderReqQ failed: " << req.mMeshParams << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) + { + mHeaderReqQ.push(*iter); + } + } + } + + // For the final three request lists, similar goal to above but + // slightly different queue structures. Stay off the mutex when + // performing long-duration actions. + + if (mHttpRequestSet.size() < sRequestHighWater + && (!mSkinRequests.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 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 + // in these cases. + if (!mDecompositionRequests.empty()) + { + std::set incomplete; + while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + mMutex->lock(); + std::set::iterator iter = mDecompositionRequests.begin(); + UUIDBasedRequest req = *iter; + mDecompositionRequests.erase(iter); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.insert(req); + } + else if (!fetchMeshDecomposition(req.mId)) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.insert(req); + } + else + { + LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); + } + } + + // holding lock, final list + if (!mPhysicsShapeRequests.empty()) + { + std::set incomplete; + while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + mMutex->lock(); + std::set::iterator iter = mPhysicsShapeRequests.begin(); + UUIDBasedRequest req = *iter; + mPhysicsShapeRequests.erase(iter); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.insert(req); + } + else if (!fetchMeshPhysicsShape(req.mId)) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.insert(req); + } + else + { + LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); + } + } + } + + // For dev purposes only. A dynamic change could make this false + // and that shouldn't assert. + // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); + } + + if (mSignal->isLocked()) + { //make sure to let go of the mutex associated with the given signal before shutting down + mSignal->unlock(); + } + + res = LLConvexDecomposition::quitThread(); + if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) + { + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; + } +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) +{ + mSkinRequests.push_back(UUIDBasedRequest(mesh_id)); +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) +{ + mDecompositionRequests.insert(UUIDBasedRequest(mesh_id)); +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) +{ + mPhysicsShapeRequests.insert(UUIDBasedRequest(mesh_id)); +} + +void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ + if (!LLAppViewer::isExiting()) + { + loadMeshLOD(mesh_params, lod); + } +} + + +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()) + { //if we have the header, request LOD byte range + + LODRequest req(mesh_params, lod); + { + mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + } + else + { + 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); + } + else + { //if no header request is pending, fetch header + mHeaderReqQ.push(req); + mPendingLOD[mesh_id].push_back(lod); + } + } +} + +// Mutex: must be holding mMutex when called +void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap) +{ + mGetMeshCapability = mesh_cap; +} + + +// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap +// over a GetMesh cap. +// +// Mutex: acquires mMutex +void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url) +{ + std::string res_url; + + if (gAgent.getRegion()) + { + { + LLMutexLock lock(mMutex); + res_url = mGetMeshCapability; + } + + if (!res_url.empty()) + { + res_url += "/?mesh_id="; + res_url += mesh_id.asString().c_str(); + } + else + { + LL_WARNS_ONCE(LOG_MESH) << "Current region does not have ViewerAsset capability! Cannot load meshes. Region id: " + << gAgent.getRegion()->getRegionID() << LL_ENDL; + LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; + } + } + else + { + LL_WARNS_ONCE(LOG_MESH) << "Current region is not loaded so there is no capability to load from! Cannot load meshes." << LL_ENDL; + LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; + } + + *url = res_url; +} + +// Issue an HTTP GET request with byte range using the right +// policy class. +// +// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. +// If the latter, actual status is found in +// mHttpStatus member which is valid until the +// next call to this method. +// +// Thread: repo +LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, + size_t offset, size_t len, + const LLCore::HttpHandler::ptr_t &handler) +{ + // Also used in lltexturefetch.cpp + static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); + + LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + if (len < LARGE_MESH_FETCH_THRESHOLD) + { + handle = mHttpRequest->requestGetByteRange( mHttpPolicyClass, + url, + (disable_range_req ? size_t(0) : offset), + (disable_range_req ? size_t(0) : len), + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPRequestCount; + } + } + else + { + handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, + url, + (disable_range_req ? size_t(0) : offset), + (disable_range_req ? size_t(0) : len), + mHttpLargeOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPLargeRequestCount; + } + } + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // Something went wrong, capture the error code for caller. + mHttpStatus = mHttpRequest->getStatus(); + } + return handle; +} + + +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) +{ + + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + bool ret = true; + U32 header_size = header_it->second.first; + + 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; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset + size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL; + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (skinInfoReceived(mesh_id, buffer, size)) + { + delete[] buffer; + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshSkinInfoHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else if(can_retry) + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) +{ + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + U32 header_size = header_it->second.first; + 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; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //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 + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshDecompositionHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) +{ + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + U32 header_size = header_it->second.first; + 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; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh physics shape info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for physics shape, size: " << size << LL_ENDL; + return false; + } + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //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 + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshPhysicsShapeHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + } + else + { //no physics shape whatsoever, report back NULL + physicsShapeReceived(mesh_id, NULL, 0); + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +//static +void LLMeshRepoThread::incActiveLODRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + ++LLMeshRepoThread::sActiveLODRequests; +} + +//static +void LLMeshRepoThread::decActiveLODRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + --LLMeshRepoThread::sActiveLODRequests; +} + +//static +void LLMeshRepoThread::incActiveHeaderRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + ++LLMeshRepoThread::sActiveHeaderRequests; +} + +//static +void LLMeshRepoThread::decActiveHeaderRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + --LLMeshRepoThread::sActiveHeaderRequests; +} + +//return false if failed to get header +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) +{ + ++LLMeshRepository::sMeshRequestCount; + + { + //look for mesh in asset in cache + LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); + + S32 size = file.getSize(); + + 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); + LLMeshRepository::sCacheBytesRead += bytes; + ++LLMeshRepository::sCacheReads; + file.read(buffer, bytes); + if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) + { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + + // Found mesh in cache + return true; + } + } + } + + //either cache entry doesn't exist or is corrupt, request header from simulator + bool retval = true; + std::string http_url; + constructUrl(mesh_params.getSculptID(), &http_url); + + + if (!http_url.empty()) + { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + + //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits + //within the first 4KB + //NOTE -- this will break of headers ever exceed 4KB + + LLMeshHandlerBase::ptr_t handler(new LLMeshHeaderHandler(mesh_params, 0, MESH_HEADER_SIZE)); + LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + retval = false; + } + else if (can_retry) + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + + return retval; +} + +//return false if failed to get mesh lod. +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) +{ + if (!mHeaderMutex) + { + return false; + } + + const LLUUID& mesh_id = mesh_params.getSculptID(); + + mHeaderMutex->lock(); + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + ++LLMeshRepository::sMeshRequestCount; + bool retval = true; + + U32 header_size = header_it->second.first; + 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]; + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + + //check cache for mesh asset + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL; + // todo: for now it will result in indefinite constant retries, should result in timeout + // or in retry-count and disabling mesh. (but usually viewer is beyond saving at this point) + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) + { + delete[] buffer; + + std::string mid; + mesh_id.toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + std::string mid; + mesh_id.toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + + LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + retval = false; + } + else if (can_retry) + { + 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); + mUnavailableQ.push_back(LODRequest(mesh_params, lod)); + } + } + else + { + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(LODRequest(mesh_params, lod)); + } + } + else + { + mHeaderMutex->unlock(); + } + + return retval; +} + +EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) +{ + const LLUUID mesh_id = mesh_params.getSculptID(); + LLSD header_data; + + LLMeshHeader header; + + llssize header_size = 0; + if (data_size > 0) + { + llssize dsize = data_size; + char* result_ptr = strip_deprecated_header((char*)data, dsize, &header_size); + + data_size = dsize; + + boost::iostreams::stream stream(result_ptr, data_size); + + if (!LLSDSerialize::fromBinary(header_data, stream, data_size)) + { + LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; + return MESH_PARSE_FAILURE; + } + + if (!header_data.isMap()) + { + LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL; + return MESH_INVALID; + } + + header.fromLLSD(header_data); + + if (header.mVersion > MAX_MESH_VERSION) + { + LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL; + header.m404 = true; + } + // make sure there is at least one lod, function returns -1 and marks as 404 otherwise + else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0) + { + header_size += stream.tellg(); + } + } + else + { + LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id + << LL_ENDL; + header.m404 = 1; + } + + { + + { + LLMutexLock lock(mHeaderMutex); + mMeshHeader[mesh_id] = { header_size, header }; + LLMeshRepository::sCacheBytesHeaders += header_size; + } + + LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. + + //check for pending requests + pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); + if (iter != mPendingLOD.end()) + { + for (U32 i = 0; i < iter->second.size(); ++i) + { + LODRequest req(mesh_params, iter->second[i]); + mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + mPendingLOD.erase(iter); + } + } + + return MESH_OK; +} + +EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size) +{ + if (data == NULL || data_size == 0) + { + return MESH_NO_DATA; + } + + LLPointer volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod)); + if (volume->unpackVolumeFaces(data, data_size)) + { + if (volume->getNumFaces() > 0) + { + LoadedMesh mesh(volume, mesh_params, lod); + { + LLMutexLock lock(mMutex); + 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 + // and won't affect mLoadedQ processing + volume = NULL; + // might be good idea to turn mesh into pointer to avoid making a copy + mesh.mVolume = NULL; + } + return MESH_OK; + } + } + + return MESH_UNKNOWN; +} + +bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD skin; + + if (data_size > 0) + { + try + { + U32 uzip_result = LLUZipHelper::unzip_llsd(skin, data, data_size); + if (uzip_result != LLUZipHelper::ZR_OK) + { + LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id + << " uzip result" << uzip_result + << LL_ENDL; + return false; + } + } + catch (std::bad_alloc&) + { + LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; + return false; + } + } + + { + LLMeshSkinInfo* info = nullptr; + try + { + info = new LLMeshSkinInfo(mesh_id, skin); + } + catch (const std::bad_alloc& ex) + { + LL_WARNS() << "Failed to allocate skin info with exception: " << ex.what() << LL_ENDL; + return false; + } + + // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; + { + LLMutexLock lock(mMutex); + mSkinInfoQ.push_back(info); + } + } + + return true; +} + +bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD decomp; + + if (data_size > 0) + { + try + { + U32 uzip_result = LLUZipHelper::unzip_llsd(decomp, data, data_size); + if (uzip_result != LLUZipHelper::ZR_OK) + { + LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id + << " uzip result: " << uzip_result + << LL_ENDL; + return false; + } + } + catch (const std::bad_alloc&) + { + LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; + return false; + } + } + + { + LLModel::Decomposition* d = new LLModel::Decomposition(decomp); + d->mMeshID = mesh_id; + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } + } + + return true; +} + +EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD physics_shape; + + LLModel::Decomposition* d = new LLModel::Decomposition(); + d->mMeshID = mesh_id; + + if (data == NULL) + { //no data, no physics shape exists + d->mPhysicsShapeMesh.clear(); + } + else + { + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + volume_params.setSculptID(mesh_id, LL_SCULPT_TYPE_MESH); + LLPointer volume = new LLVolume(volume_params,0); + + if (volume->unpackVolumeFaces(data, data_size)) + { + d->mPhysicsShapeMesh.clear(); + + std::vector& pos = d->mPhysicsShapeMesh.mPositions; + std::vector& norm = d->mPhysicsShapeMesh.mNormals; + + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + + for (S32 i = 0; i < face.mNumIndices; ++i) + { + U16 idx = face.mIndices[i]; + + pos.push_back(LLVector3(face.mPositions[idx].getF32ptr())); + norm.push_back(LLVector3(face.mNormals[idx].getF32ptr())); + } + } + } + } + + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } + return MESH_OK; +} + +LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + const std::string & upload_url, bool do_upload, + LLHandle fee_observer, + LLHandle upload_observer) + : LLThread("mesh upload"), + LLCore::HttpHandler(), + mDiscarded(false), + mDoUpload(do_upload), + mWholeModelUploadURL(upload_url), + mFeeObserverHandle(fee_observer), + mUploadObserverHandle(upload_observer) +{ + mInstanceList = data; + mUploadTextures = upload_textures; + mUploadSkin = upload_skin; + mUploadJoints = upload_joints; + mLockScaleIfJointPosition = lock_scale_if_joint_position; + mMutex = new LLMutex(); + mPendingUploads = 0; + mFinished = false; + mOrigin = gAgent.getPositionAgent(); + mHost = gAgent.getRegionHost(); + + mWholeModelFeeCapability = gAgent.getRegionCapability("NewFileAgentInventory"); + + mOrigin += gAgent.getAtAxis() * scale.magVec(); + + mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); + + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); +} + +LLMeshUploadThread::~LLMeshUploadThread() +{ + delete mHttpRequest; + mHttpRequest = NULL; + delete mMutex; + mMutex = NULL; + +} + +LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) +{ + mStage = "single_hull"; + mModel = mdl; + mDecompID = &mdl->mDecompID; + mBaseModel = base_model; + mThread = thread; + + //copy out positions and indices + assignData(mdl) ; + + mThread->mFinalDecomp = this; + mThread->mPhysicsComplete = false; +} + +void LLMeshUploadThread::DecompRequest::completed() +{ + if (mThread->mFinalDecomp == this) + { + mThread->mPhysicsComplete = true; + } + + llassert(mHull.size() == 1); + + mThread->mHullMap[mBaseModel] = mHull[0]; +} + +//called in the main thread. +void LLMeshUploadThread::preStart() +{ + //build map of LLModel refs to instances for callbacks + for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter) + { + mInstance[iter->mModel].push_back(*iter); + } +} + +void LLMeshUploadThread::discard() +{ + LLMutexLock lock(mMutex); + mDiscarded = true; +} + +bool LLMeshUploadThread::isDiscarded() const +{ + LLMutexLock lock(mMutex); + return mDiscarded; +} + +void LLMeshUploadThread::run() +{ + if (mDoUpload) + { + doWholeModelUpload(); + } + else + { + requestWholeModelFee(); + } +} + +void dump_llsd_to_file(const LLSD& content, std::string filename) +{ + if (gSavedSettings.getBOOL("MeshUploadLogXML")) + { + llofstream of(filename.c_str()); + LLSDSerialize::toPrettyXML(content,of); + } +} + +LLSD llsd_from_file(std::string filename) +{ + llifstream ifs(filename.c_str()); + LLSD result; + LLSDSerialize::fromXML(result,ifs); + return result; +} + +void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) +{ + LLSD result; + + LLSD res; + result["folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_OBJECT); + result["texture_folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); + result["asset_type"] = "mesh"; + result["inventory_type"] = "object"; + result["description"] = "(No Description)"; + result["next_owner_mask"] = LLSD::Integer(LLFloaterPerms::getNextOwnerPerms("Uploads")); + result["group_mask"] = LLSD::Integer(LLFloaterPerms::getGroupPerms("Uploads")); + result["everyone_mask"] = LLSD::Integer(LLFloaterPerms::getEveryonePerms("Uploads")); + + res["mesh_list"] = LLSD::emptyArray(); + res["texture_list"] = LLSD::emptyArray(); + res["instance_list"] = LLSD::emptyArray(); + S32 mesh_num = 0; + S32 texture_num = 0; + + std::set textures; + std::map texture_index; + + std::map mesh_index; + std::string model_name; + + S32 instance_num = 0; + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + if (data.mBaseModel->mSubmodelID) + { + // These are handled below to insure correct parenting order on creation + // due to map walking being based on model address (aka random) + continue; + } + + LLModelInstance& first_instance = *(iter->second.begin()); + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = first_instance.mLOD[i]; + } + + if (mesh_index.find(data.mBaseModel) == mesh_index.end()) + { + // Have not seen this model before - create a new mesh_list entry for it. + if (model_name.empty()) + { + model_name = data.mBaseModel->getName(); + } + + std::stringstream ostr; + + LLModel::Decomposition& decomp = + data.mModel[LLModel::LOD_PHYSICS].notNull() ? + data.mModel[LLModel::LOD_PHYSICS]->mPhysics : + data.mBaseModel->mPhysics; + + decomp.mBaseHull = mHullMap[data.mBaseModel]; + + LLSD mesh_header = LLModel::writeModel( + ostr, + data.mModel[LLModel::LOD_PHYSICS], + data.mModel[LLModel::LOD_HIGH], + data.mModel[LLModel::LOD_MEDIUM], + data.mModel[LLModel::LOD_LOW], + data.mModel[LLModel::LOD_IMPOSTOR], + decomp, + mUploadSkin, + mUploadJoints, + mLockScaleIfJointPosition, + false, + false, + data.mBaseModel->mSubmodelID); + + data.mAssetData = ostr.str(); + std::string str = ostr.str(); + + res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); + mesh_index[data.mBaseModel] = mesh_num; + mesh_num++; + } + + // For all instances that use this model + for (instance_list::iterator instance_iter = iter->second.begin(); + instance_iter != iter->second.end(); + ++instance_iter) + { + + LLModelInstance& instance = *instance_iter; + + LLSD instance_entry; + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + LLVector3 pos, scale; + LLQuaternion rot; + LLMatrix4 transformation = instance.mTransform; + decomposeMeshMatrix(transformation,pos,rot,scale); + instance_entry["position"] = ll_sd_from_vector3(pos); + instance_entry["rotation"] = ll_sd_from_quaternion(rot); + instance_entry["scale"] = ll_sd_from_vector3(scale); + + instance_entry["material"] = LL_MCODE_WOOD; + instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); + instance_entry["mesh"] = mesh_index[data.mBaseModel]; + instance_entry["mesh_name"] = instance.mLabel; + + instance_entry["face_list"] = LLSD::emptyArray(); + + // We want to be able to allow more than 8 materials... + // + S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; + + for (S32 face_num = 0; face_num < end; face_num++) + { + LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; + LLSD face_entry = LLSD::emptyMap(); + + LLViewerFetchedTexture *texture = NULL; + + if (material.mDiffuseMapFilename.size()) + { + texture = FindViewerTexture(material); + } + + if ((texture != NULL) && + (textures.find(texture) == textures.end())) + { + textures.insert(texture); + } + + std::stringstream texture_str; + if (texture != NULL && include_textures && mUploadTextures) + { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + + LLPointer upload_file = + LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); + + if (!upload_file.isNull() && upload_file->getDataSize()) + { + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } + } + } + + if (texture != NULL && + mUploadTextures && + texture_index.find(texture) == texture_index.end()) + { + texture_index[texture] = texture_num; + std::string str = texture_str.str(); + res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); + texture_num++; + } + + // Subset of TextureEntry fields. + if (texture != NULL && mUploadTextures) + { + face_entry["image"] = texture_index[texture]; + face_entry["scales"] = 1.0; + face_entry["scalet"] = 1.0; + face_entry["offsets"] = 0.0; + face_entry["offsett"] = 0.0; + face_entry["imagerot"] = 0.0; + } + face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); + face_entry["fullbright"] = material.mFullbright; + instance_entry["face_list"][face_num] = face_entry; + } + + res["instance_list"][instance_num] = instance_entry; + instance_num++; + } + } + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + if (!data.mBaseModel->mSubmodelID) + { + // These were handled above already... + // + continue; + } + + LLModelInstance& first_instance = *(iter->second.begin()); + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = first_instance.mLOD[i]; + } + + if (mesh_index.find(data.mBaseModel) == mesh_index.end()) + { + // Have not seen this model before - create a new mesh_list entry for it. + if (model_name.empty()) + { + model_name = data.mBaseModel->getName(); + } + + std::stringstream ostr; + + LLModel::Decomposition& decomp = + data.mModel[LLModel::LOD_PHYSICS].notNull() ? + data.mModel[LLModel::LOD_PHYSICS]->mPhysics : + data.mBaseModel->mPhysics; + + decomp.mBaseHull = mHullMap[data.mBaseModel]; + + LLSD mesh_header = LLModel::writeModel( + ostr, + data.mModel[LLModel::LOD_PHYSICS], + data.mModel[LLModel::LOD_HIGH], + data.mModel[LLModel::LOD_MEDIUM], + data.mModel[LLModel::LOD_LOW], + data.mModel[LLModel::LOD_IMPOSTOR], + decomp, + mUploadSkin, + mUploadJoints, + mLockScaleIfJointPosition, + false, + false, + data.mBaseModel->mSubmodelID); + + data.mAssetData = ostr.str(); + std::string str = ostr.str(); + + res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); + mesh_index[data.mBaseModel] = mesh_num; + mesh_num++; + } + + // For all instances that use this model + for (instance_list::iterator instance_iter = iter->second.begin(); + instance_iter != iter->second.end(); + ++instance_iter) + { + + LLModelInstance& instance = *instance_iter; + + LLSD instance_entry; + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + LLVector3 pos, scale; + LLQuaternion rot; + LLMatrix4 transformation = instance.mTransform; + decomposeMeshMatrix(transformation,pos,rot,scale); + instance_entry["position"] = ll_sd_from_vector3(pos); + instance_entry["rotation"] = ll_sd_from_quaternion(rot); + instance_entry["scale"] = ll_sd_from_vector3(scale); + + instance_entry["material"] = LL_MCODE_WOOD; + instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE); + instance_entry["mesh"] = mesh_index[data.mBaseModel]; + + instance_entry["face_list"] = LLSD::emptyArray(); + + // We want to be able to allow more than 8 materials... + // + S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; + + for (S32 face_num = 0; face_num < end; face_num++) + { + LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; + LLSD face_entry = LLSD::emptyMap(); + + LLViewerFetchedTexture *texture = NULL; + + if (material.mDiffuseMapFilename.size()) + { + texture = FindViewerTexture(material); + } + + if ((texture != NULL) && + (textures.find(texture) == textures.end())) + { + textures.insert(texture); + } + + std::stringstream texture_str; + if (texture != NULL && include_textures && mUploadTextures) + { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + + LLPointer upload_file = + LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); + + if (!upload_file.isNull() && upload_file->getDataSize()) + { + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } + } + } + + if (texture != NULL && + mUploadTextures && + texture_index.find(texture) == texture_index.end()) + { + texture_index[texture] = texture_num; + std::string str = texture_str.str(); + res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); + texture_num++; + } + + // Subset of TextureEntry fields. + if (texture != NULL && mUploadTextures) + { + face_entry["image"] = texture_index[texture]; + face_entry["scales"] = 1.0; + face_entry["scalet"] = 1.0; + face_entry["offsets"] = 0.0; + face_entry["offsett"] = 0.0; + face_entry["imagerot"] = 0.0; + } + face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); + face_entry["fullbright"] = material.mFullbright; + instance_entry["face_list"][face_num] = face_entry; + } + + res["instance_list"][instance_num] = instance_entry; + instance_num++; + } + } + + if (model_name.empty()) model_name = "mesh model"; + result["name"] = model_name; + res["metric"] = "MUT_Unspecified"; + result["asset_resources"] = res; + dump_llsd_to_file(result,make_dump_name("whole_model_",dump_num)); + + dest = result; +} + +void LLMeshUploadThread::generateHulls() +{ + bool has_valid_requests = false ; + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + LLModelInstance& instance = *(iter->second.begin()); + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + //queue up models for hull generation + LLModel* physics = NULL; + + if (data.mModel[LLModel::LOD_PHYSICS].notNull()) + { + physics = data.mModel[LLModel::LOD_PHYSICS]; + } + else if (data.mModel[LLModel::LOD_LOW].notNull()) + { + physics = data.mModel[LLModel::LOD_LOW]; + } + else if (data.mModel[LLModel::LOD_MEDIUM].notNull()) + { + physics = data.mModel[LLModel::LOD_MEDIUM]; + } + else + { + physics = data.mModel[LLModel::LOD_HIGH]; + } + + llassert(physics != NULL); + + DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this); + if(request->isValid()) + { + gMeshRepo.mDecompThread->submitRequest(request); + has_valid_requests = true ; + } + } + + if (has_valid_requests) + { + // *NOTE: Interesting livelock condition on shutdown. If there + // is an upload request in generateHulls() when shutdown starts, + // the main thread isn't available to manage communication between + // the decomposition thread and the upload thread and this loop + // wouldn't complete in turn stalling the main thread. The check + // on isDiscarded() prevents that. + while (! mPhysicsComplete && ! isDiscarded()) + { + apr_sleep(100); + } + } +} + +void LLMeshUploadThread::doWholeModelUpload() +{ + LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; + + if (mWholeModelUploadURL.empty()) + { + LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." + << LL_ENDL; + } + else + { + generateHulls(); + LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, true); + LLSD body = mModelData["asset_resources"]; + + dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); + + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicyClass, + mWholeModelUploadURL, + body, + mHttpOptions, + mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; + + mHttpRequest->update(0); + while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } + + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; + } + else + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; + } + } + } +} + +void LLMeshUploadThread::requestWholeModelFee() +{ + dump_num++; + + generateHulls(); + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, false); + dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicyClass, + mWholeModelFeeCapability, + mModelData, + mHttpOptions, + mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + mHttpRequest->update(0); + while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; + } + } +} + + +// Does completion duty for both fee queries and actual uploads. +void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check + const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); + LLCore::HttpStatus status(response->getStatus()); + if (fake_error) + { + status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); + } + std::string reason(status.toString()); + LLSD body; + + mFinished = true; + + if (mDoUpload) + { + // model upload case + LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + else + { + if (fake_error & 0x2) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + // *TODO: handle error in conversion process + LLCoreHttpUtil::responseToLLSD(response, true, body); + } + dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); + + if (body["state"].asString() == "complete") + { + // requested "mesh" asset type isn't actually the type + // of the resultant object, fix it up here. + mModelData["asset_type"] = "object"; + gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + } + } + else + { + // model fee case + LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); + mWholeModelUploadURL.clear(); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); + } + } + else + { + if (fake_error & 0x1) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + // *TODO: handle error in conversion process + LLCoreHttpUtil::responseToLLSD(response, true, body); + } + dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); + + if (body["state"].asString() == "upload") + { + mWholeModelUploadURL = body["uploader"].asString(); + + if (observer) + { + body["data"]["upload_price"] = body["upload_price"]; + observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); + } + } + else + { + LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); + } + } + } + } +} + + +void LLMeshRepoThread::notifyLoadedMeshes() +{ + bool update_metrics(false); + + if (!mMutex) + { + return; + } + + if (!mLoadedQ.empty()) + { + std::deque loaded_queue; + + mMutex->lock(); + if (!mLoadedQ.empty()) + { + loaded_queue.swap(mLoadedQ); + mMutex->unlock(); + + update_metrics = true; + + // Process the elements free of the lock + for (const auto& mesh : loaded_queue) + { + if (mesh.mVolume->getNumVolumeFaces() > 0) + { + gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); + } + else + { + gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, + LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); + } + } + } + } + + if (!mUnavailableQ.empty()) + { + std::deque unavil_queue; + + mMutex->lock(); + if (!mUnavailableQ.empty()) + { + unavil_queue.swap(mUnavailableQ); + mMutex->unlock(); + + update_metrics = true; + + // Process the elements free of the lock + for (const auto& req : unavil_queue) + { + gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); + } + } + } + + if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty()) + { + if (mMutex->trylock()) + { + std::deque skin_info_q; + std::deque skin_info_unavail_q; + std::list decomp_q; + + if (! mSkinInfoQ.empty()) + { + skin_info_q.swap(mSkinInfoQ); + } + + if (! mSkinUnavailableQ.empty()) + { + skin_info_unavail_q.swap(mSkinUnavailableQ); + } + + if (! mDecompositionQ.empty()) + { + decomp_q.swap(mDecompositionQ); + } + + mMutex->unlock(); + + // Process the elements free of the lock + while (! skin_info_q.empty()) + { + gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); + skin_info_q.pop_front(); + } + while (! skin_info_unavail_q.empty()) + { + gMeshRepo.notifySkinInfoUnavailable(skin_info_unavail_q.front().mId); + skin_info_unavail_q.pop_front(); + } + + while (! decomp_q.empty()) + { + gMeshRepo.notifyDecompositionReceived(decomp_q.front()); + decomp_q.pop_front(); + } + } + } + + if (update_metrics) + { + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + } + +} + +S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ //only ever called from main thread + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID()); + + if (iter != mMeshHeader.end()) + { + auto& header = iter->second.second; + + return LLMeshRepository::getActualMeshLOD(header, lod); + } + + return lod; +} + +//static +S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod) +{ + lod = llclamp(lod, 0, 3); + + if (header.m404) + { + return -1; + } + + S32 version = header.mVersion; + + if (version > MAX_MESH_VERSION) + { + return -1; + } + + if (header.mLodSize[lod] > 0) + { + return lod; + } + + //search down to find the next available lower lod + for (S32 i = lod-1; i >= 0; --i) + { + if (header.mLodSize[i] > 0) + { + return i; + } + } + + //search up to find then ext available higher lod + for (S32 i = lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + if (header.mLodSize[i] > 0) + { + return i; + } + } + + //header exists and no good lod found, treat as 404 + header.m404 = true; + + return -1; +} + +// Handle failed or successful requests for mesh assets. +// +// Support for 200 responses was added for several reasons. One, +// a service or cache can ignore range headers and give us a +// 200 with full asset should it elect to. We also support +// a debug flag which disables range requests for those very +// few users that have some sort of problem with their networking +// services. But the 200 response handling is suboptimal: rather +// than cache the whole asset, we just extract the part that would +// have been sent in a 206 and process that. Inefficient but these +// are cases far off the norm. +void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + mProcessed = true; + + unsigned int retries(0U); + response->getRetries(NULL, &retries); + LLMeshRepository::sHTTPRetryCount += retries; + + LLCore::HttpStatus status(response->getStatus()); + if (! status || MESH_HTTP_RESPONSE_FAILED) + { + processFailure(status); + ++LLMeshRepository::sHTTPErrorCount; + } + else + { + // From texture fetch code and may apply here: + // + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + // + // May also need to deal with 200 status (full asset returned + // rather than partial) and 416 (request completely unsatisfyable). + // Always been exposed to these but are less likely here where + // speculative loads aren't done. + LLCore::BufferArray * body(response->getBody()); + S32 body_offset(0); + U8 * data(NULL); + S32 data_size(body ? body->size() : 0); + + if (data_size > 0) + { + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + unsigned int offset(0), length(0), full_length(0); + + if (par_status == status) + { + // 206 case + response->getRange(&offset, &length, &full_length); + if (! offset && ! length) + { + // This is the case where we receive a 206 status but + // there wasn't a useful Content-Range header in the response. + // This could be because it was badly formatted but is more + // likely due to capabilities services which scrub headers + // from responses. Assume we got what we asked for...` + // length = data_size; + offset = mOffset; + } + } + else + { + // 200 case, typically + offset = 0; + } + + // *DEBUG: To test validation below + // offset += 1; + + // Validate that what we think we received is consistent with + // what we've asked for. I.e. first byte we wanted lies somewhere + // in the response. + if (offset > mOffset + || (offset + data_size) <= mOffset + || (mOffset - offset) >= data_size) + { + // No overlap with requested range. Fail request with + // suitable error. Shouldn't happen unless server/cache/ISP + // is doing something awful. + LL_WARNS(LOG_MESH) << "Mesh response (bytes [" + << offset << ".." << (offset + length - 1) + << "]) didn't overlap with request's origin (bytes [" + << mOffset << ".." << (mOffset + mRequestedBytes - 1) + << "])." << LL_ENDL; + processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR)); + ++LLMeshRepository::sHTTPErrorCount; + goto common_exit; + } + + // *TODO: Try to get rid of data copying and add interfaces + // that support BufferArray directly. Introduce a two-phase + // handler, optional first that takes a body, fallback second + // that requires a temporary allocation and data copy. + body_offset = mOffset - offset; + data = new(std::nothrow) U8[data_size - body_offset]; + if (data) + { + body->read(body_offset, (char *) data, data_size - body_offset); + LLMeshRepository::sBytesReceived += data_size; + } + else + { + LL_WARNS(LOG_MESH) << "Failed to allocate " << data_size - body_offset << " memory for mesh response" << LL_ENDL; + processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_BAD_ALLOC)); + } + } + + processData(body, body_offset, data, data_size - body_offset); + + delete [] data; + } + + // Release handler +common_exit: + gMeshRepo.mThread->mHttpRequestSet.erase(this->shared_from_this()); +} + + +LLMeshHeaderHandler::~LLMeshHeaderHandler() +{ + if (!LLApp::isExiting()) + { + if (! mProcessed) + { + // something went wrong, retry + LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; + LLMeshRepoThread::HeaderRequest req(mMeshParams); + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mHeaderReqQ.push(req); + } + LLMeshRepoThread::decActiveHeaderRequests(); + } +} + +void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } +} + +void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + 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; + llassert(success); + EMeshProcessingResult res = MESH_UNKNOWN; + if (success) + { + res = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); + success = (res == MESH_OK); + } + if (! success) + { + // *TODO: Get real reason for parse failure here. Might we want to retry? + LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id + << ", Size: " << data_size + << ", Reason: " << res << " Not retrying." + << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } + } + else if (data && data_size > 0) + { + // header was successfully retrieved from sim and parsed and is in cache + S32 header_bytes = 0; + LLMeshHeader header; + + gMeshRepo.mThread->mHeaderMutex->lock(); + 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; + } + + if (header_bytes > 0 + && !header.m404 + && (header.mVersion <= MAX_MESH_VERSION)) + { + std::stringstream str; + + S32 lod_bytes = 0; + + for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) + { + // figure out how many bytes we'll need to reserve in the file + lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]); + } + + // just in case skin info or decomposition is at the end of the file (which it shouldn't be) + lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize); + lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize); + + // Do not unlock mutex untill we are done with LLSD. + // LLSD is smart and can work like smart pointer, is not thread safe. + gMeshRepo.mThread->mHeaderMutex->unlock(); + + S32 bytes = lod_bytes + header_bytes; + + + // It's possible for the remote asset to have more data than is needed for the local cache + // only allocate as much space in the cache as is needed for the local cache + data_size = llmin(data_size, bytes); + + // Fix asset caching + //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + if (file.getMaxSize() >= bytes) + { + LLMeshRepository::sCacheBytesWritten += data_size; + ++LLMeshRepository::sCacheWrites; + + file.write(data, data_size); + + // Fix asset caching + S32 remaining = bytes - file.tell(); + if (remaining > 0) + { + U8* block = new(std::nothrow) U8[remaining]; + if (block) + { + memset(block, 0, remaining); + file.write(block, remaining); + delete[] block; + } + } + // + } + } + else + { + LL_WARNS(LOG_MESH) << "Trying to cache nonexistent mesh, mesh id: " << mesh_id << LL_ENDL; + + 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); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } + } + } +} + +LLMeshLODHandler::~LLMeshLODHandler() +{ + if (! LLApp::isExiting()) + { + if (! mProcessed) + { + LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; + gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); + } + LLMeshRepoThread::decActiveLODRequests(); + } +} + +void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +} + +void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + 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) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + file.seek(offset); + 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->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Unknown reason. Not retrying." + << " LOD: " << mLOD + << " Data size: " << data_size + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } +} + +LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); +} + +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + 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)) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + 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->mMutex); + gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); + } +} + +LLMeshDecompositionHandler::~LLMeshDecompositionHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. +} + +void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_DECOMP_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->decompositionReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } +} + +LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error +} + +void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_PHYS_SHAPE_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->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) + { + // good fetch from sim, write to cache for caching + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } +} + +LLMeshRepository::LLMeshRepository() +: mMeshMutex(NULL), + mDecompThread(NULL), + mMeshThreadCount(0), + mThread(NULL) +{ + mSkinInfoCullTimer.resetWithExpiry(10.f); +} + +void LLMeshRepository::init() +{ + mMeshMutex = new LLMutex(); + + LLConvexDecomposition::getInstance()->initSystem(); + + if (!LLConvexDecomposition::isFunctional()) + { + LL_INFOS(LOG_MESH) << "Using STUB for LLConvexDecomposition" << LL_ENDL; + } + + mDecompThread = new LLPhysicsDecomp(); + mDecompThread->start(); + + while (!mDecompThread->mInited) + { //wait for physics decomp thread to init + apr_sleep(100); + } + + metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); + + mThread = new LLMeshRepoThread(); + mThread->start(); +} + +void LLMeshRepository::shutdown() +{ + LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; + llassert(mThread != NULL); + llassert(mThread->mSignal != NULL); + + metrics_teleport_started_signal.disconnect(); + + for (U32 i = 0; i < mUploads.size(); ++i) + { + LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; + mUploads[i]->discard() ; //discard the uploading requests. + } + + mThread->mSignal->broadcast(); + + while (!mThread->isStopped()) + { + apr_sleep(10); + } + delete mThread; + mThread = NULL; + + for (U32 i = 0; i < mUploads.size(); ++i) + { + LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; + while (!mUploads[i]->isStopped()) + { + apr_sleep(10); + } + delete mUploads[i]; + } + + mUploads.clear(); + + delete mMeshMutex; + mMeshMutex = NULL; + + LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; + + if (mDecompThread) + { + mDecompThread->shutdown(); + delete mDecompThread; + mDecompThread = NULL; + } + + LLConvexDecomposition::quitSystem(); +} + +//called in the main thread. +S32 LLMeshRepository::update() +{ + // Conditionally log a mesh metrics event + metricsUpdate(); + + if(mUploadWaitList.empty()) + { + return 0 ; + } + + S32 size = mUploadWaitList.size() ; + for (S32 i = 0; i < size; ++i) + { + mUploads.push_back(mUploadWaitList[i]); + mUploadWaitList[i]->preStart() ; + mUploadWaitList[i]->start() ; + } + mUploadWaitList.clear() ; + + return size ; +} + +void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) +{ + for (auto& lod : mLoadingMeshes) + { + for (auto& param : lod) + { + vector_replace_with_last(param.second, vobj); + } + } + + for (auto& skin_pair : mLoadingSkins) + { + vector_replace_with_last(skin_pair.second, vobj); + } +} + +S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, 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) + { + return detail; + } + + { + 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()) + { //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()) { + iter->second.push_back(vobj); + } + } + else + { + //first request for this mesh + mLoadingMeshes[detail][mesh_id].push_back(vobj); + mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); + LLMeshRepository::sLODPending++; + } + } + + //do a quick search to see if we can't display something while we wait for this mesh to load + LLVolume* volume = vobj->getVolume(); + + if (volume) + { + LLVolumeParams params = volume->getParams(); + + LLVolumeLODGroup* group = LLPrimitive::getVolumeManager()->getGroup(params); + + if (group) + { + //first, see if last_lod is available (don't transition down to avoid funny popping a la SH-641) + if (last_lod >= 0) + { + LLVolume* lod = group->refLOD(last_lod); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return last_lod; + } + group->derefLOD(lod); + } + + //next, see what the next lowest LOD available might be + for (S32 i = detail-1; i >= 0; --i) + { + LLVolume* lod = group->refLOD(i); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return i; + } + + group->derefLOD(lod); + } + + //no lower LOD is a available, is a higher lod available? + for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + LLVolume* lod = group->refLOD(i); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return i; + } + + group->derefLOD(lod); + } + } + } + + return detail; +} + +void LLMeshRepository::notifyLoadedMeshes() +{ //called from main thread + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + // GetMesh2 operation with keepalives, etc. With pipelining, + // we'll increase this. See llappcorehttp and llcorehttp for + // discussion on connection strategies. + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + S32 scale(app_core_http.isPipelined(LLAppCoreHttp::AP_MESH2) + ? (2 * LLAppCoreHttp::PIPELINING_DEPTH) + : 5); + + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); + LLMeshRepoThread::sRequestHighWater = llclamp(scale * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST2_HIGH_WATER_MIN, + REQUEST2_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST2_LOW_WATER_MIN, + REQUEST2_LOW_WATER_MAX); + + //clean up completed upload threads + for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) + { + LLMeshUploadThread* thread = *iter; + + if (thread->isStopped() && thread->finished()) + { + iter = mUploads.erase(iter); + delete thread; + } + else + { + ++iter; + } + } + + //update inventory + if (!mInventoryQ.empty()) + { + LLMutexLock lock(mMeshMutex); + while (!mInventoryQ.empty()) + { + inventory_data& data = mInventoryQ.front(); + + LLAssetType::EType asset_type = LLAssetType::lookup(data.mPostData["asset_type"].asString()); + LLInventoryType::EType inventory_type = LLInventoryType::lookup(data.mPostData["inventory_type"].asString()); + + // Handle addition of texture, if any. + if ( data.mResponse.has("new_texture_folder_id") ) + { + const LLUUID& new_folder_id = data.mResponse["new_texture_folder_id"].asUUID(); + + if ( new_folder_id.notNull() ) + { + LLUUID parent_id = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); + + std::string name; + // Check if the server built a different name for the texture folder + if ( data.mResponse.has("new_texture_folder_name") ) + { + name = data.mResponse["new_texture_folder_name"].asString(); + } + else + { + name = data.mPostData["name"].asString(); + } + + // Add the category to the internal representation + LLPointer cat = + new LLViewerInventoryCategory(new_folder_id, parent_id, + LLFolderType::FT_NONE, name, gAgent.getID()); + cat->setVersion(LLViewerInventoryCategory::VERSION_UNKNOWN); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + gInventory.accountForUpdate(update); + gInventory.updateCategory(cat); + } + } + + on_new_single_inventory_upload_complete( + asset_type, + inventory_type, + data.mPostData["asset_type"].asString(), + data.mPostData["folder_id"].asUUID(), + data.mPostData["name"], + data.mPostData["description"], + data.mResponse, + data.mResponse["upload_price"]); + //} + + mInventoryQ.pop(); + } + } + + //call completed callbacks on finished decompositions + mDecompThread->notifyCompleted(); + + if (mSkinInfoCullTimer.checkExpirationAndReset(10.f)) + { + //// Clean up dead skin info + //U64Bytes skinbytes(0); + for (auto iter = mSkinMap.begin(), ender = mSkinMap.end(); iter != ender;) + { + auto copy_iter = iter++; + + //skinbytes += U64Bytes(sizeof(LLMeshSkinInfo)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(std::string)); + //skinbytes += U64Bytes(copy_iter->second->mJointNums.size() * sizeof(S32)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4a)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4)); + + if (copy_iter->second->getNumRefs() == 1) + { + mSkinMap.erase(copy_iter); + } + } + //LL_INFOS() << "Skin info cache elements:" << mSkinMap.size() << " Memory: " << U64Kilobytes(skinbytes) << LL_ENDL; + } + + // For major operations, attempt to get the required locks + // without blocking and punt if they're not available. The + // longest run of holdoffs is kept in sMaxLockHoldoffs just + // to collect the data. In testing, I've never seen a value + // greater than 2 (written to log on exit). + { + LLMutexTrylock lock1(mMeshMutex); + LLMutexTrylock lock2(mThread->mMutex); + + static U32 hold_offs(0); + if (! lock1.isLocked() || ! lock2.isLocked()) + { + // If we can't get the locks, skip and pick this up later. + ++hold_offs; + sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); + return; + } + hold_offs = 0; + + if (gAgent.getRegion()) + { + // Update capability urls + static std::string region_name("never name a region this"); + + if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + { + region_name = gAgent.getRegion()->getName(); + const std::string mesh_cap(gAgent.getRegion()->getViewerAssetUrl()); + mThread->setGetMeshCap(mesh_cap); + LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name + << "', ViewerAsset cap: " << mesh_cap + << LL_ENDL; + } + } + + //popup queued error messages from background threads + while (!mUploadErrorQ.empty()) + { + LLSD substitutions(mUploadErrorQ.front()); + if (substitutions.has("DETAILS")) + { + LLNotificationsUtil::add("MeshUploadErrorDetails", substitutions); + } + else + { + LLNotificationsUtil::add("MeshUploadError", substitutions); + } + mUploadErrorQ.pop(); + } + + S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; + if (active_count < LLMeshRepoThread::sRequestLowWater) + { + S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; + + if (mPendingRequests.size() > push_count) + { + // More requests than the high-water limit allows so + // sort and forward the most important. + + //calculate "score" for pending requests + + //create score map + std::map score_map; + + for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) + { + 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) + { + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); + max_score = llmax(max_score, cur_score); + } + } + } + + score_map[iter->first] = max_score; + } + } + + //set "score" for pending requests + for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) + { + iter->mScore = score_map[iter->mMeshParams.getSculptID()]; + } + + //sort by "score" + std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, + mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + } + + while (!mPendingRequests.empty() && push_count > 0) + { + LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); + mThread->loadMeshLOD(request.mMeshParams, request.mLOD); + 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()) + { + mThread->loadMeshDecomposition(mPendingDecompositionRequests.front()); + mPendingDecompositionRequests.pop(); + } + + //send physics shapes decomposition requests + while (!mPendingPhysicsShapeRequests.empty()) + { + mThread->loadMeshPhysicsShape(mPendingPhysicsShapeRequests.front()); + mPendingPhysicsShapeRequests.pop(); + } + + mThread->notifyLoadedMeshes(); + } + + mThread->mSignal->signal(); +} + +void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info) +{ + mSkinMap[info->mMeshID] = info; // Cache into LLPointer + // Alternative: We can get skin size from header + sCacheBytesSkins += info->sizeBytes(); + + skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID); + if (iter != mLoadingSkins.end()) + { + for (LLVOVolume* vobj : iter->second) + { + if (vobj) + { + vobj->notifySkinInfoLoaded(info); + } + } + mLoadingSkins.erase(iter); + } +} + +void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id) +{ + skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); + if (iter != mLoadingSkins.end()) + { + for (LLVOVolume* vobj : iter->second) + { + if (vobj) + { + vobj->notifySkinInfoUnavailable(); + } + } + mLoadingSkins.erase(iter); + } +} + +void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) +{ + decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID); + if (iter == mDecompositionMap.end()) + { //just insert decomp into map + mDecompositionMap[decomp->mMeshID] = decomp; + mLoadingDecompositions.erase(decomp->mMeshID); + sCacheBytesDecomps += decomp->sizeBytes(); + } + else + { //merge decomp with existing entry + sCacheBytesDecomps -= iter->second->sizeBytes(); + iter->second->merge(decomp); + sCacheBytesDecomps += iter->second->sizeBytes(); + + mLoadingDecompositions.erase(decomp->mMeshID); + delete decomp; + } +} + +void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) +{ //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); + + if (volume && obj_iter != mLoadingMeshes[detail].end()) + { + //make sure target volume is still valid + if (volume->getNumVolumeFaces() <= 0) + { + LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_id + << LL_ENDL; + } + + { //update system volume + LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail); + if (sys_volume) + { + sys_volume->copyVolumeFaces(volume); + sys_volume->setMeshAssetLoaded(true); + LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); + } + else + { + LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_id + << LL_ENDL; + } + } + + //notify waiting LLVOVolume instances that their requested mesh is available + for (LLVOVolume* vobj : obj_iter->second) + { + if (vobj) + { + vobj->notifyMeshLoaded(); + } + } + + mLoadingMeshes[detail].erase(obj_iter); + + LLViewerStatsRecorder::instance().meshLoaded(); + } +} + +void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 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()) + { + F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); + + LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); + if (sys_volume) + { + sys_volume->setMeshAssetUnavaliable(true); + LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); + } + + for (LLVOVolume* vobj : obj_iter->second) + { + if (vobj) + { + LLVolume* obj_volume = vobj->getVolume(); + + if (obj_volume && + obj_volume->getDetail() == detail && + obj_volume->getParams() == mesh_params) + { //should force volume to find most appropriate LOD + vobj->setVolume(obj_volume->getParams(), lod); + } + } + } + + mLoadingMeshes[lod].erase(obj_iter); + } +} + +S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ + return mThread->getActualMeshLOD(mesh_params, lod); +} + +const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (mesh_id.notNull()) + { + skin_map::iterator iter = mSkinMap.find(mesh_id); + if (iter != mSkinMap.end()) + { + return iter->second; + } + + //no skin info known about given mesh, try to fetch it + if (requesting_obj != nullptr) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); + if (iter != mLoadingSkins.end()) + { //request pending for this mesh, append volume id to list + auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj); + if (it == iter->second.end()) { + iter->second.push_back(requesting_obj); + } + } + else + { + //first request for this mesh + mLoadingSkins[mesh_id].push_back(requesting_obj); + mPendingSkinRequests.push(mesh_id); + } + } + } + return nullptr; +} + +void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + if (mesh_id.notNull()) + { + LLModel::Decomposition* decomp = NULL; + decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); + if (iter != mDecompositionMap.end()) + { + decomp = iter->second; + } + + //decomposition block hasn't been fetched yet + if (!decomp || decomp->mPhysicsShapeMesh.empty()) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); + if (iter == mLoadingPhysicsShapes.end()) + { //no request pending for this skin info + // *FIXME: Nothing ever deletes entries, can't be right + mLoadingPhysicsShapes.insert(mesh_id); + mPendingPhysicsShapeRequests.push(mesh_id); + } + } + } +} + +LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + LLModel::Decomposition* ret = NULL; + + if (mesh_id.notNull()) + { + decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); + if (iter != mDecompositionMap.end()) + { + ret = iter->second; + } + + //decomposition block hasn't been fetched yet + if (!ret || ret->mBaseHullMesh.empty()) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + std::set::iterator iter = mLoadingDecompositions.find(mesh_id); + if (iter == mLoadingDecompositions.end()) + { //no request pending for this skin info + mLoadingDecompositions.insert(mesh_id); + mPendingDecompositionRequests.push(mesh_id); + } + } + } + + return ret; +} + +void LLMeshRepository::buildHull(const LLVolumeParams& params, S32 detail) +{ + LLVolume* volume = LLPrimitive::sVolumeManager->refVolume(params, detail); + + if (!volume->mHullPoints) + { + //all default params + //execute first stage + //set simplify mode to retain + //set retain percentage to zero + //run second stage + } + + LLPrimitive::sVolumeManager->unrefVolume(volume); +} + +bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + if (mThread->hasPhysicsShapeInHeader(mesh_id)) + { + return true; + } + + LLModel::Decomposition* decomp = getDecomposition(mesh_id); + if (decomp && !decomp->mHull.empty()) + { + return true; + } + + return false; +} + +bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + if (mThread->hasSkinInfoInHeader(mesh_id)) + { + return true; + } + + const LLMeshSkinInfo* skininfo = getSkinInfo(mesh_id); + if (skininfo) + { + return true; + } + + return false; +} + +bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + return mThread->hasHeader(mesh_id); +} + +bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader &mesh = iter->second.second; + if (mesh.mPhysicsMeshSize > 0) + { + return true; + } + } + + return false; +} + +bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader& mesh = iter->second.second; + if (mesh.mSkinOffset >= 0 + && mesh.mSkinSize > 0) + { + return true; + } + } + + return false; +} + +bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + return iter != mMeshHeader.end(); +} + +void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + std::string upload_url, bool do_upload, + LLHandle fee_observer, LLHandle upload_observer) +{ + LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, + upload_skin, upload_joints, lock_scale_if_joint_position, + upload_url, do_upload, fee_observer, upload_observer); + mUploadWaitList.push_back(thread); +} + +S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + const LLMeshHeader& header = iter->second.second; + + if (header.m404) + { + return -1; + } + + S32 size = header.mLodSize[lod]; + return size; + } + + } + + return -1; +} + +void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, + LLVector3& result_pos, + LLQuaternion& result_rot, + LLVector3& result_scale) +{ + // check for reflection + bool reflected = (transformation.determinant() < 0); + + // compute position + LLVector3 position = LLVector3(0, 0, 0) * transformation; + + // compute scale + LLVector3 x_transformed = LLVector3(1, 0, 0) * transformation - position; + LLVector3 y_transformed = LLVector3(0, 1, 0) * transformation - position; + LLVector3 z_transformed = LLVector3(0, 0, 1) * transformation - position; + F32 x_length = x_transformed.normalize(); + F32 y_length = y_transformed.normalize(); + F32 z_length = z_transformed.normalize(); + LLVector3 scale = LLVector3(x_length, y_length, z_length); + + // adjust for "reflected" geometry + LLVector3 x_transformed_reflected = x_transformed; + if (reflected) + { + x_transformed_reflected *= -1.0; + } + + // compute rotation + LLMatrix3 rotation_matrix; + rotation_matrix.setRows(x_transformed_reflected, y_transformed, z_transformed); + LLQuaternion quat_rotation = rotation_matrix.quaternion(); + quat_rotation.normalize(); // the rotation_matrix might not have been orthoginal. make it so here. + LLVector3 euler_rotation; + quat_rotation.getEulerAngles(&euler_rotation.mV[VX], &euler_rotation.mV[VY], &euler_rotation.mV[VZ]); + + result_pos = position + mOrigin; + result_scale = scale; + result_rot = quat_rotation; +} + +void LLMeshRepository::updateInventory(inventory_data data) +{ + LLMutexLock lock(mMeshMutex); + dump_llsd_to_file(data.mPostData,make_dump_name("update_inventory_post_data_",dump_num)); + dump_llsd_to_file(data.mResponse,make_dump_name("update_inventory_response_",dump_num)); + mInventoryQ.push(data); +} + +void LLMeshRepository::uploadError(LLSD& args) +{ + LLMutexLock lock(mMeshMutex); + mUploadErrorQ.push(args); +} + +F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id) +{ + LLMeshCostData costs; + if (getCostData(mesh_id, costs)) + { + return costs.getEstTrisMax(); + } + else + { + return 0.f; + } +} + +F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id) +{ + LLMeshCostData costs; + if (getCostData(mesh_id, costs)) + { + return costs.getEstTrisForStreamingCost(); + } + else + { + return 0.f; + } +} + +// FIXME replace with calc based on LLMeshCostData +F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) +{ + F32 result = 0.f; + if (mThread && mesh_id.notNull()) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + result = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); + } + } + if (result > 0.f) + { + LLMeshCostData data; + if (getCostData(mesh_id, data)) + { + F32 ref_streaming_cost = data.getRadiusBasedStreamingCost(radius); + F32 ref_weighted_tris = data.getRadiusWeightedTris(radius); + if (!is_approx_equal(ref_streaming_cost,result)) + { + LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL; + } + if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value)) + { + LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL; + } + if (bytes && (*bytes != data.getSizeTotal())) + { + LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.getSizeTotal() << LL_ENDL; + } + if (bytes_visible && (lod >=0) && (lod < LLVolumeLODGroup::NUM_LODS) && (*bytes_visible != data.getSizeByLOD(lod))) + { + LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.getSizeByLOD(lod) << LL_ENDL; + } + } + else + { + LL_WARNS() << "getCostData failed!!!" << LL_ENDL; + } + } + return result; +} + +// FIXME replace with calc based on LLMeshCostData +//static +F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) +{ + if (header.m404 + || header.mLodSize[0] <= 0 + || (header.mVersion > MAX_MESH_VERSION)) + { + return 0.f; + } + + F32 max_distance = 512.f; + + F32 dlowest = llmin(radius/0.03f, max_distance); + F32 dlow = llmin(radius/0.06f, max_distance); + F32 dmid = llmin(radius/0.24f, max_distance); + + static LLCachedControl metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead + static LLCachedControl minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" + static LLCachedControl bytes_per_triangle_ch(gSavedSettings, "MeshBytesPerTriangle", 16); + + F32 metadata_discount = (F32)metadata_discount_ch; + F32 minimum_size = (F32)minimum_size_ch; + F32 bytes_per_triangle = (F32)bytes_per_triangle_ch; + + S32 bytes_lowest = header.mLodSize[0]; + S32 bytes_low = header.mLodSize[1]; + S32 bytes_mid = header.mLodSize[2]; + S32 bytes_high = header.mLodSize[3]; + + if (bytes_high == 0) + { + return 0.f; + } + + if (bytes_mid == 0) + { + bytes_mid = bytes_high; + } + + if (bytes_low == 0) + { + bytes_low = bytes_mid; + } + + if (bytes_lowest == 0) + { + bytes_lowest = bytes_low; + } + + F32 triangles_lowest = llmax((F32) bytes_lowest-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_low = llmax((F32) bytes_low-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_mid = llmax((F32) bytes_mid-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_high = llmax((F32) bytes_high-metadata_discount, minimum_size)/bytes_per_triangle; + + if (bytes) + { + *bytes = 0; + *bytes += header.mLodSize[0]; + *bytes += header.mLodSize[1]; + *bytes += header.mLodSize[2]; + *bytes += header.mLodSize[3]; + } + + if (bytes_visible) + { + lod = LLMeshRepository::getActualMeshLOD(header, lod); + if (lod >= 0 && lod <= 3) + { + *bytes_visible = header.mLodSize[lod]; + } + } + + F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559) + F32 min_area = 1.f; + + F32 high_area = llmin(F_PI*dmid*dmid, max_area); + F32 mid_area = llmin(F_PI*dlow*dlow, max_area); + F32 low_area = llmin(F_PI*dlowest*dlowest, max_area); + F32 lowest_area = max_area; + + lowest_area -= low_area; + low_area -= mid_area; + mid_area -= high_area; + + high_area = llclamp(high_area, min_area, max_area); + mid_area = llclamp(mid_area, min_area, max_area); + low_area = llclamp(low_area, min_area, max_area); + lowest_area = llclamp(lowest_area, min_area, max_area); + + F32 total_area = high_area + mid_area + low_area + lowest_area; + high_area /= total_area; + mid_area /= total_area; + low_area /= total_area; + lowest_area /= total_area; + + F32 weighted_avg = triangles_high*high_area + + triangles_mid*mid_area + + triangles_low*low_area + + triangles_lowest*lowest_area; + + if (unscaled_value) + { + *unscaled_value = weighted_avg; + } + + return weighted_avg/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; +} + +LLMeshCostData::LLMeshCostData() +{ + std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); + std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); +} + +bool LLMeshCostData::init(const LLMeshHeader& header) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); + std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); + + S32 bytes_high = header.mLodSize[3]; + S32 bytes_med = header.mLodSize[2]; + if (bytes_med == 0) + { + bytes_med = bytes_high; + } + S32 bytes_low = header.mLodSize[1]; + if (bytes_low == 0) + { + bytes_low = bytes_med; + } + S32 bytes_lowest = header.mLodSize[0]; + if (bytes_lowest == 0) + { + bytes_lowest = bytes_low; + } + mSizeByLOD[0] = bytes_lowest; + mSizeByLOD[1] = bytes_low; + mSizeByLOD[2] = bytes_med; + mSizeByLOD[3] = bytes_high; + + static LLCachedControl metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead + static LLCachedControl minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" + static LLCachedControl bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16); + + for (S32 i=0; i=0; i--) + { + // How many tris can we have in this LOD without affecting land impact? + // - normally an LOD should be at most half the size of the previous one. + // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller. + allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]); + F32 excess_tris = mEstTrisByLOD[i]-allowed_tris; + if (excess_tris>0.f) + { + LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL; + charged_tris += excess_tris; + } + } + return charged_tris; +} + +F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) +{ + return getRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; +} + +F32 LLMeshCostData::getTriangleBasedStreamingCost() +{ + F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * getEstTrisForStreamingCost(); + return result; +} + +bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + data = LLMeshCostData(); + + if (mThread && mesh_id.notNull()) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader& header = iter->second.second; + + bool header_invalid = (header.m404 + || header.mLodSize[0] <= 0 + || header.mVersion > MAX_MESH_VERSION); + if (!header_invalid) + { + return getCostData(header, data); + } + + return true; + } + } + return false; +} + +bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data) +{ + data = LLMeshCostData(); + + if (!data.init(header)) + { + return false; + } + + return true; +} + +LLPhysicsDecomp::LLPhysicsDecomp() +: LLThread("Physics Decomp") +{ + mInited = false; + mQuitting = false; + mDone = false; + + mSignal = new LLCondition(); + mMutex = new LLMutex(); +} + +LLPhysicsDecomp::~LLPhysicsDecomp() +{ + shutdown(); + + delete mSignal; + mSignal = NULL; + delete mMutex; + mMutex = NULL; +} + +void LLPhysicsDecomp::shutdown() +{ + if (mSignal) + { + mQuitting = true; + // There is only one wait(), but just in case 'broadcast' + mSignal->broadcast(); + + while (!isStopped()) + { + apr_sleep(10); + } + } +} + +void LLPhysicsDecomp::submitRequest(LLPhysicsDecomp::Request* request) +{ + LLMutexLock lock(mMutex); + mRequestQ.push(request); + mSignal->signal(); +} + +//static +S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2) +{ + if (gMeshRepo.mDecompThread && gMeshRepo.mDecompThread->mCurRequest.notNull()) + { + return gMeshRepo.mDecompThread->mCurRequest->statusCallback(status, p1, p2); + } + + return 1; +} + +void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) +{ + mesh.mVertexBase = mCurRequest->mPositions[0].mV; + mesh.mVertexStrideBytes = 12; + mesh.mNumVertices = mCurRequest->mPositions.size(); + + if(!vertex_based) + { + mesh.mIndexType = LLCDMeshData::INT_16; + mesh.mIndexBase = &(mCurRequest->mIndices[0]); + mesh.mIndexStrideBytes = 6; + + mesh.mNumTriangles = mCurRequest->mIndices.size()/3; + } + + if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2) + { + LLCDResult ret = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh, vertex_based); + } + + if (ret) + { + LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; + } + } +} + +void LLPhysicsDecomp::doDecomposition() +{ + LLCDMeshData mesh; + S32 stage = mStageID[mCurRequest->mStage]; + + if (LLConvexDecomposition::getInstance() == NULL) + { + // stub library. do nothing. + return; + } + + //load data intoLLCD + if (stage == 0) + { + setMeshData(mesh, false); + } + + //build parameter map + std::map param_map; + + static const LLCDParam* params = NULL; + static S32 param_count = 0; + if (!params) + { + param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); + } + + for (S32 i = 0; i < param_count; ++i) + { + param_map[params[i].mName] = params+i; + } + + U32 ret = LLCD_OK; + //set parameter values + for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) + { + const std::string& name = iter->first; + const LLSD& value = iter->second; + + const LLCDParam* param = param_map[name]; + + if (param == NULL) + { //couldn't find valid parameter + continue; + } + + + if (param->mType == LLCDParam::LLCD_FLOAT) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); + } + else if (param->mType == LLCDParam::LLCD_INTEGER || + param->mType == LLCDParam::LLCD_ENUM) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); + } + else if (param->mType == LLCDParam::LLCD_BOOLEAN) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); + } + } + + mCurRequest->setStatusMessage("Executing."); + + if (LLConvexDecomposition::getInstance() != NULL) + { + ret = LLConvexDecomposition::getInstance()->executeStage(stage); + } + + if (ret) + { + LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." + << LL_ENDL; + LLMutexLock lock(mMutex); + + mCurRequest->mHull.clear(); + mCurRequest->mHullMesh.clear(); + + mCurRequest->setStatusMessage("FAIL"); + + completeCurrent(); + } + else + { + mCurRequest->setStatusMessage("Reading results"); + + S32 num_hulls =0; + if (LLConvexDecomposition::getInstance() != NULL) + { + num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); + } + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(num_hulls); + + mCurRequest->mHullMesh.clear(); + mCurRequest->mHullMesh.resize(num_hulls); + } + + for (S32 i = 0; i < num_hulls; ++i) + { + std::vector p; + LLCDHull hull; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); + + const F32* v = hull.mVertexBase; + + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } + + LLCDMeshData mesh; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); + + get_vertex_buffer_from_mesh(mesh, mCurRequest->mHullMesh[i]); + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull[i] = p; + } + } + + { + LLMutexLock lock(mMutex); + mCurRequest->setStatusMessage("FAIL"); + completeCurrent(); + } + } +} + +void LLPhysicsDecomp::completeCurrent() +{ + LLMutexLock lock(mMutex); + mCompletedQ.push(mCurRequest); + mCurRequest = NULL; +} + +void LLPhysicsDecomp::notifyCompleted() +{ + if (!mCompletedQ.empty()) + { + LLMutexLock lock(mMutex); + while (!mCompletedQ.empty()) + { + Request* req = mCompletedQ.front(); + req->completed(); + mCompletedQ.pop(); + } + } +} + + +void make_box(LLPhysicsDecomp::Request * request) +{ + LLVector3 min,max; + min = request->mPositions[0]; + max = min; + + for (U32 i = 0; i < request->mPositions.size(); ++i) + { + update_min_max(min, max, request->mPositions[i]); + } + + request->mHull.clear(); + + LLModel::hull box; + box.push_back(LLVector3(min[0],min[1],min[2])); + box.push_back(LLVector3(max[0],min[1],min[2])); + box.push_back(LLVector3(min[0],max[1],min[2])); + box.push_back(LLVector3(max[0],max[1],min[2])); + box.push_back(LLVector3(min[0],min[1],max[2])); + box.push_back(LLVector3(max[0],min[1],max[2])); + box.push_back(LLVector3(min[0],max[1],max[2])); + box.push_back(LLVector3(max[0],max[1],max[2])); + + request->mHull.push_back(box); +} + + +void LLPhysicsDecomp::doDecompositionSingleHull() +{ + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + + if (decomp == NULL) + { + //stub. do nothing. + return; + } + + LLCDMeshData mesh; + + setMeshData(mesh, true); + + LLCDResult ret = decomp->buildSingleHull() ; + if (ret) + { + LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; + make_box(mCurRequest); + } + else + { + { + LLMutexLock lock(mMutex); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(1); + mCurRequest->mHullMesh.clear(); + } + + std::vector p; + LLCDHull hull; + + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + decomp->getSingleHull(&hull); + + const F32* v = hull.mVertexBase; + + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull[0] = p; + } + } + + { + completeCurrent(); + + } +} + + +void LLPhysicsDecomp::run() +{ + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + if (decomp == NULL) + { + // stub library. Set init to true so the main thread + // doesn't wait for this to finish. + mInited = true; + return; + } + + decomp->initThread(); + mInited = true; + + static const LLCDStageData* stages = NULL; + static S32 num_stages = 0; + + if (!stages) + { + num_stages = decomp->getStages(&stages); + } + + for (S32 i = 0; i < num_stages; i++) + { + mStageID[stages[i].mName] = i; + } + + while (!mQuitting) + { + mSignal->wait(); + while (!mQuitting && !mRequestQ.empty()) + { + { + LLMutexLock lock(mMutex); + mCurRequest = mRequestQ.front(); + mRequestQ.pop(); + } + + S32& id = *(mCurRequest->mDecompID); + if (id == -1) + { + decomp->genDecomposition(id); + } + decomp->bindDecomposition(id); + + if (mCurRequest->mStage == "single_hull") + { + doDecompositionSingleHull(); + } + else + { + doDecomposition(); + } + } + } + + decomp->quitThread(); + + if (mSignal->isLocked()) + { //let go of mSignal's associated mutex + mSignal->unlock(); + } + + mDone = true; +} + +void LLPhysicsDecomp::Request::assignData(LLModel* mdl) +{ + if (!mdl) + { + return ; + } + + U16 index_offset = 0; + U16 tri[3] ; + + mPositions.clear(); + mIndices.clear(); + mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ; + mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ; + + //queue up vertex positions and indices + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = mdl->getVolumeFace(i); + if (mPositions.size() + face.mNumVertices > 65535) + { + continue; + } + + for (U32 j = 0; j < face.mNumVertices; ++j) + { + mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); + for(U32 k = 0 ; k < 3 ; k++) + { + mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ; + mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ; + } + } + + updateTriangleAreaThreshold() ; + + for (U32 j = 0; j+2 < face.mNumIndices; j += 3) + { + tri[0] = face.mIndices[j] + index_offset ; + tri[1] = face.mIndices[j + 1] + index_offset ; + tri[2] = face.mIndices[j + 2] + index_offset ; + + if(isValidTriangle(tri[0], tri[1], tri[2])) + { + mIndices.push_back(tri[0]); + mIndices.push_back(tri[1]); + mIndices.push_back(tri[2]); + } + } + + index_offset += face.mNumVertices; + } + + return ; +} + +void LLPhysicsDecomp::Request::updateTriangleAreaThreshold() +{ + F32 range = mBBox[1].mV[0] - mBBox[0].mV[0] ; + range = llmin(range, mBBox[1].mV[1] - mBBox[0].mV[1]) ; + range = llmin(range, mBBox[1].mV[2] - mBBox[0].mV[2]) ; + + mTriangleAreaThreshold = llmin(0.0002f, range * 0.000002f) ; +} + +//check if the triangle area is large enough to qualify for a valid triangle +bool LLPhysicsDecomp::Request::isValidTriangle(U16 idx1, U16 idx2, U16 idx3) +{ + LLVector3 a = mPositions[idx2] - mPositions[idx1] ; + LLVector3 b = mPositions[idx3] - mPositions[idx1] ; + F32 c = a * b ; + + return ((a*a) * (b*b) - c * c) > mTriangleAreaThreshold ; +} + +void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg) +{ + mStatusMessage = msg; +} + +void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) +{ + decomp.mMesh.resize(decomp.mHull.size()); + + for (U32 i = 0; i < decomp.mHull.size(); ++i) + { + LLCDHull hull; + hull.mNumVertices = decomp.mHull[i].size(); + hull.mVertexBase = decomp.mHull[i][0].mV; + hull.mVertexStrideBytes = 12; + + LLCDMeshData mesh; + LLCDResult res = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); + } + if (res == LLCD_OK) + { + get_vertex_buffer_from_mesh(mesh, decomp.mMesh[i]); + } + } + + if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) + { //get mesh for base hull + LLCDHull hull; + hull.mNumVertices = decomp.mBaseHull.size(); + hull.mVertexBase = decomp.mBaseHull[0].mV; + hull.mVertexStrideBytes = 12; + + LLCDMeshData mesh; + LLCDResult res = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); + } + if (res == LLCD_OK) + { + get_vertex_buffer_from_mesh(mesh, decomp.mBaseHullMesh); + } + } +} + + +bool LLMeshRepository::meshUploadEnabled() +{ + LLViewerRegion *region = gAgent.getRegion(); + if(gSavedSettings.getBOOL("MeshEnabled") && + region) + { + return region->meshUploadEnabled(); + } + return false; +} + +bool LLMeshRepository::meshRezEnabled() +{ + LLViewerRegion *region = gAgent.getRegion(); + if(gSavedSettings.getBOOL("MeshEnabled") && + region) + { + return region->meshRezEnabled(); + } + return false; +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStart() +{ + ++metrics_teleport_start_count; + sQuiescentTimer.start(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStop() +{ + sQuiescentTimer.stop(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) +{ + static bool first_start(true); + + if (first_start) + { + metricsStart(); + first_start = false; + } + sQuiescentTimer.ringBell(0, this_count); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsUpdate() +{ + F64 started, stopped; + U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); + + if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) + { + LLSD metrics; + + metrics["reason"] = "Mesh Download Quiescent"; + metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; + metrics["start"] = started; + metrics["stop"] = stopped; + metrics["fetches"] = LLSD::Integer(total_count); + metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); + metrics["user_cpu"] = double(user_cpu) / 1.0e6; + metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; + LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; + } +} + +// Threading: main thread only +// static +void teleport_started() +{ + LLMeshRepository::metricsStart(); +} + + +void on_new_single_inventory_upload_complete( + LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price) +{ + bool success = false; + + if (upload_price > 0) + { + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", upload_price); + LLNotificationsUtil::add("UploadPayment", args); + } + + if (item_folder_id.notNull()) + { + U32 everyone_perms = PERM_NONE; + U32 group_perms = PERM_NONE; + U32 next_owner_perms = PERM_ALL; + if (server_response.has("new_next_owner_mask")) + { + // The server provided creation perms so use them. + // Do not assume we got the perms we asked for in + // since the server may not have granted them all. + everyone_perms = server_response["new_everyone_mask"].asInteger(); + group_perms = server_response["new_group_mask"].asInteger(); + next_owner_perms = server_response["new_next_owner_mask"].asInteger(); + } + else + { + // The server doesn't provide creation perms + // so use old assumption-based perms. + if (inventory_type_string != "snapshot") + { + next_owner_perms = PERM_MOVE | PERM_TRANSFER; + } + } + + LLPermissions new_perms; + new_perms.init( + gAgent.getID(), + gAgent.getID(), + LLUUID::null, + LLUUID::null); + + new_perms.initMasks( + PERM_ALL, + PERM_ALL, + everyone_perms, + group_perms, + next_owner_perms); + + U32 inventory_item_flags = 0; + if (server_response.has("inventory_flags")) + { + inventory_item_flags = (U32)server_response["inventory_flags"].asInteger(); + if (inventory_item_flags != 0) + { + LL_INFOS() << "inventory_item_flags " << inventory_item_flags << LL_ENDL; + } + } + S32 creation_date_now = time_corrected(); + LLPointer item = new LLViewerInventoryItem( + server_response["new_inventory_item"].asUUID(), + item_folder_id, + new_perms, + server_response["new_asset"].asUUID(), + asset_type, + inventory_type, + item_name, + item_description, + LLSaleInfo::DEFAULT, + inventory_item_flags, + creation_date_now); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + success = true; + + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + + // Show the preview panel for textures and sounds to let + // user know that the image (or snapshot) arrived intact. + LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); + if (panel) + { + + panel->setSelection( + server_response["new_inventory_item"].asUUID(), + TAKE_FOCUS_NO); + } + else + { + LLInventoryPanel::openInventoryPanelAndSetSelection(true, server_response["new_inventory_item"].asUUID(), true, false, true); + } + + // restore keyboard focus + gFocusMgr.setKeyboardFocus(focus); + } + else + { + LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; + } + + // Todo: This is mesh repository code, is following code really needed? + // remove the "Uploading..." message + LLUploadDialog::modalUploadFinished(); + + // Let the Snapshot floater know we have finished uploading a snapshot to inventory. + LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); + if (asset_type == LLAssetType::AT_TEXTURE && floater_snapshot) + { + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); + } +} -- cgit v1.2.3 From b42f9d836b4c0f7fbd4bdae1734021e2a09fdbe8 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 1 Jun 2024 15:49:26 +0200 Subject: Re-enable a lot of compiler warnings for MSVC and address the C4267 "possible loss of precision" warnings --- indra/newview/llmeshrepository.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index f0fd731fb5..134bbc7b1d 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -916,7 +916,7 @@ void LLMeshRepoThread::run() // Dispatch all HttpHandler notifications mHttpRequest->update(0L); } - sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update + sRequestWaterLevel = static_cast(mHttpRequestSet.size()); // Stats data update // NOTE: order of queue processing intentionally favors LOD requests over header requests // Todo: we are processing mLODReqQ, mHeaderReqQ, mSkinRequests, mDecompositionRequests and mPhysicsShapeRequests @@ -3082,7 +3082,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo LLCore::BufferArray * body(response->getBody()); S32 body_offset(0); U8 * data(NULL); - S32 data_size(body ? body->size() : 0); + auto data_size(body ? body->size() : 0); if (data_size > 0) { @@ -3143,7 +3143,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo if (data) { body->read(body_offset, (char *) data, data_size - body_offset); - LLMeshRepository::sBytesReceived += data_size; + LLMeshRepository::sBytesReceived += static_cast(data_size); } else { @@ -3152,7 +3152,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo } } - processData(body, body_offset, data, data_size - body_offset); + processData(body, body_offset, data, static_cast(data_size) - body_offset); delete [] data; } @@ -3624,8 +3624,8 @@ S32 LLMeshRepository::update() return 0 ; } - S32 size = mUploadWaitList.size() ; - for (S32 i = 0; i < size; ++i) + auto size = mUploadWaitList.size() ; + for (size_t i = 0; i < size; ++i) { mUploads.push_back(mUploadWaitList[i]); mUploadWaitList[i]->preStart() ; @@ -3633,7 +3633,7 @@ S32 LLMeshRepository::update() } mUploadWaitList.clear() ; - return size ; + return static_cast(size); } void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) @@ -4864,7 +4864,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) { mesh.mVertexBase = mCurRequest->mPositions[0].mV; mesh.mVertexStrideBytes = 12; - mesh.mNumVertices = mCurRequest->mPositions.size(); + mesh.mNumVertices = static_cast(mCurRequest->mPositions.size()); if(!vertex_based) { @@ -4872,7 +4872,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) mesh.mIndexBase = &(mCurRequest->mIndices[0]); mesh.mIndexStrideBytes = 6; - mesh.mNumTriangles = mCurRequest->mIndices.size()/3; + mesh.mNumTriangles = static_cast(mCurRequest->mIndices.size())/3; } if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2) @@ -5283,10 +5283,10 @@ void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) { decomp.mMesh.resize(decomp.mHull.size()); - for (U32 i = 0; i < decomp.mHull.size(); ++i) + for (size_t i = 0; i < decomp.mHull.size(); ++i) { LLCDHull hull; - hull.mNumVertices = decomp.mHull[i].size(); + hull.mNumVertices = static_cast(decomp.mHull[i].size()); hull.mVertexBase = decomp.mHull[i][0].mV; hull.mVertexStrideBytes = 12; @@ -5305,7 +5305,7 @@ void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) { //get mesh for base hull LLCDHull hull; - hull.mNumVertices = decomp.mBaseHull.size(); + hull.mNumVertices = static_cast(decomp.mBaseHull.size()); hull.mVertexBase = decomp.mBaseHull[0].mV; hull.mVertexStrideBytes = 12; -- cgit v1.2.3 From c0fad3028fd55c2067ce6a0ae4382cffe1014284 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 10 Jun 2024 16:42:43 +0200 Subject: Re-enable compiler warnings C4018, C4100, C4231 and C4506 --- indra/newview/llmeshrepository.cpp | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'indra/newview/llmeshrepository.cpp') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 134bbc7b1d..01c922df16 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -448,7 +448,7 @@ U32 get_volume_memory_size(const LLVolume* volume) U32 indices = 0; U32 vertices = 0; - for (U32 i = 0; i < volume->getNumVolumeFaces(); ++i) + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) { const LLVolumeFace& face = volume->getVolumeFace(i); indices += face.mNumIndices; @@ -5203,16 +5203,16 @@ void LLPhysicsDecomp::Request::assignData(LLModel* mdl) { if (!mdl) { - return ; + return; } U16 index_offset = 0; - U16 tri[3] ; + U16 tri[3]{}; mPositions.clear(); mIndices.clear(); - mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ; - mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ; + mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN); + mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX); //queue up vertex positions and indices for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) @@ -5223,36 +5223,34 @@ void LLPhysicsDecomp::Request::assignData(LLModel* mdl) continue; } - for (U32 j = 0; j < face.mNumVertices; ++j) + for (S32 j = 0; j < face.mNumVertices; ++j) { mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); - for(U32 k = 0 ; k < 3 ; k++) + for (U32 k = 0 ; k < 3 ; k++) { - mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ; - mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ; + mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]); + mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]); } } - updateTriangleAreaThreshold() ; + updateTriangleAreaThreshold(); - for (U32 j = 0; j+2 < face.mNumIndices; j += 3) + for (S32 j = 0; j+2 < face.mNumIndices; j += 3) { tri[0] = face.mIndices[j] + index_offset ; - tri[1] = face.mIndices[j + 1] + index_offset ; - tri[2] = face.mIndices[j + 2] + index_offset ; + tri[1] = face.mIndices[j + 1] + index_offset; + tri[2] = face.mIndices[j + 2] + index_offset; - if(isValidTriangle(tri[0], tri[1], tri[2])) + if (isValidTriangle(tri[0], tri[1], tri[2])) { - mIndices.push_back(tri[0]); - mIndices.push_back(tri[1]); - mIndices.push_back(tri[2]); + mIndices.emplace_back(tri[0]); + mIndices.emplace_back(tri[1]); + mIndices.emplace_back(tri[2]); } } index_offset += face.mNumVertices; } - - return ; } void LLPhysicsDecomp::Request::updateTriangleAreaThreshold() -- cgit v1.2.3