diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
---|---|---|
committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llmeshrepository.cpp | |
parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/newview/llmeshrepository.cpp')
-rw-r--r-- | indra/newview/llmeshrepository.cpp | 11062 |
1 files changed, 5531 insertions, 5531 deletions
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 9a2f47cc8d..869d1a6ac8 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<LLMeshHandlerBase> -{ -public: - typedef std::shared_ptr<LLMeshHandlerBase> 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<std::string> 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<LODRequest> 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<LODRequest>::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) - { - mLODReqQ.push(*iter); - ++LLMeshRepository::sLODProcessing; - } - } - } - - if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - std::list<HeaderRequest> 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<HeaderRequest>::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<UUIDBasedRequest> incomplete; - while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - - mMutex->lock(); - auto req = mSkinRequests.front(); - mSkinRequests.pop_front(); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.emplace_back(req); - } - else if (!fetchMeshSkinInfo(req.mId, req.canRetry())) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.emplace_back(req); - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.push_back(req); - LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (const auto& req : incomplete) - { - mSkinRequests.push_back(req); - } - } - } - - // holding lock, try next list - // *TODO: For UI/debug-oriented lists, we might drop the fine- - // grained locking as there's a lowered expectation of smoothness - // in these cases. - if (!mDecompositionRequests.empty()) - { - std::set<UUIDBasedRequest> incomplete; - while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set<UUIDBasedRequest>::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<UUIDBasedRequest> incomplete; - while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set<UUIDBasedRequest>::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<bool> 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<boost::iostreams::array_source> 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<LLVolume> 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<LLVolume> volume = new LLVolume(volume_params,0); - - if (volume->unpackVolumeFaces(data, data_size)) - { - d->mPhysicsShapeMesh.clear(); - - std::vector<LLVector3>& pos = d->mPhysicsShapeMesh.mPositions; - std::vector<LLVector3>& 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<LLWholeModelFeeObserver> fee_observer, - LLHandle<LLWholeModelUploadObserver> 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<LLViewerTexture* > textures; - std::map<LLViewerTexture*,S32> texture_index; - - std::map<LLModel*,S32> 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<LLImageJ2C> 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<LLImageJ2C> 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<LoadedMesh> 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<LODRequest> 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<LLMeshSkinInfo*> skin_info_q; - std::deque<UUIDBasedRequest> skin_info_unavail_q; - std::list<LLModel::Decomposition*> 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); - - // <FS:Ansariel> 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); - - // <FS:Ansariel> 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; - } - } - // </FS:Ansariel> - } - } - 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 - // <FS:Ansariel> 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 - // <FS:Ansariel> 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 - // <FS:Ansariel> 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 - // <FS:Ansariel> 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<LLMeshUploadThread*>::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<LLViewerInventoryCategory> 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<LLUUID, F32> 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<LLMeshRepoThread::LODRequest>::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<LLUUID>::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<LLUUID>::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<LLModelInstance>& 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<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> 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<U32> metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl<U32> minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl<U32> 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<U32> metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl<U32> minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl<U32> bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16); - - for (S32 i=0; i<LLVolumeLODGroup::NUM_LODS; i++) - { - mEstTrisByLOD[i] = llmax((F32)mSizeByLOD[i] - (F32)metadata_discount, (F32)minimum_size) / (F32)bytes_per_triangle; - } - - return true; -} - - -S32 LLMeshCostData::getSizeByLOD(S32 lod) -{ - if (llclamp(lod,0,3) != lod) - { - return 0; - } - return mSizeByLOD[lod]; -} - -S32 LLMeshCostData::getSizeTotal() -{ - return mSizeByLOD[0] + mSizeByLOD[1] + mSizeByLOD[2] + mSizeByLOD[3]; -} - -F32 LLMeshCostData::getEstTrisByLOD(S32 lod) -{ - if (llclamp(lod,0,3) != lod) - { - return 0.f; - } - return mEstTrisByLOD[lod]; -} - -F32 LLMeshCostData::getEstTrisMax() -{ - return llmax(mEstTrisByLOD[0], mEstTrisByLOD[1], mEstTrisByLOD[2], mEstTrisByLOD[3]); -} - -F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) -{ - F32 max_distance = 512.f; - - F32 dlowest = llmin(radius/0.03f, max_distance); - F32 dlow = llmin(radius/0.06f, max_distance); - F32 dmid = llmin(radius/0.24f, max_distance); - - F32 triangles_lowest = mEstTrisByLOD[0]; - F32 triangles_low = mEstTrisByLOD[1]; - F32 triangles_mid = mEstTrisByLOD[2]; - F32 triangles_high = mEstTrisByLOD[3]; - - F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559) - F32 min_area = 1.f; - - F32 high_area = llmin(F_PI*dmid*dmid, max_area); - F32 mid_area = llmin(F_PI*dlow*dlow, max_area); - F32 low_area = llmin(F_PI*dlowest*dlowest, max_area); - F32 lowest_area = max_area; - - lowest_area -= low_area; - low_area -= mid_area; - mid_area -= high_area; - - high_area = llclamp(high_area, min_area, max_area); - mid_area = llclamp(mid_area, min_area, max_area); - low_area = llclamp(low_area, min_area, max_area); - lowest_area = llclamp(lowest_area, min_area, max_area); - - F32 total_area = high_area + mid_area + low_area + lowest_area; - high_area /= total_area; - mid_area /= total_area; - low_area /= total_area; - lowest_area /= total_area; - - F32 weighted_avg = triangles_high*high_area + - triangles_mid*mid_area + - triangles_low*low_area + - triangles_lowest*lowest_area; - - return weighted_avg; -} - -F32 LLMeshCostData::getEstTrisForStreamingCost() -{ - LL_DEBUGS("StreamingCost") << "tris_by_lod: " - << mEstTrisByLOD[0] << ", " - << mEstTrisByLOD[1] << ", " - << mEstTrisByLOD[2] << ", " - << mEstTrisByLOD[3] << LL_ENDL; - - F32 charged_tris = mEstTrisByLOD[3]; - F32 allowed_tris = mEstTrisByLOD[3]; - const F32 ENFORCE_FLOOR = 64.0f; - for (S32 i=2; i>=0; i--) - { - // How many tris can we have in this LOD without affecting land impact? - // - normally an LOD should be at most half the size of the previous one. - // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller. - allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]); - F32 excess_tris = mEstTrisByLOD[i]-allowed_tris; - if (excess_tris>0.f) - { - LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL; - charged_tris += excess_tris; - } - } - return charged_tris; -} - -F32 LLMeshCostData::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<std::string, const LLCDParam*> 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<LLVector3> 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<LLVector3> 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<LLViewerInventoryItem> 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<LLMeshHandlerBase>
+{
+public:
+ typedef std::shared_ptr<LLMeshHandlerBase> 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<std::string> 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<LODRequest> 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<LODRequest>::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++)
+ {
+ mLODReqQ.push(*iter);
+ ++LLMeshRepository::sLODProcessing;
+ }
+ }
+ }
+
+ if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ std::list<HeaderRequest> 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<HeaderRequest>::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<UUIDBasedRequest> incomplete;
+ while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+
+ mMutex->lock();
+ auto req = mSkinRequests.front();
+ mSkinRequests.pop_front();
+ mMutex->unlock();
+ if (req.isDelayed())
+ {
+ incomplete.emplace_back(req);
+ }
+ else if (!fetchMeshSkinInfo(req.mId, req.canRetry()))
+ {
+ if (req.canRetry())
+ {
+ req.updateTime();
+ incomplete.emplace_back(req);
+ }
+ else
+ {
+ LLMutexLock locker(mMutex);
+ mSkinUnavailableQ.push_back(req);
+ LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL;
+ }
+ }
+ }
+
+ if (!incomplete.empty())
+ {
+ LLMutexLock locker(mMutex);
+ for (const auto& req : incomplete)
+ {
+ mSkinRequests.push_back(req);
+ }
+ }
+ }
+
+ // holding lock, try next list
+ // *TODO: For UI/debug-oriented lists, we might drop the fine-
+ // grained locking as there's a lowered expectation of smoothness
+ // in these cases.
+ if (!mDecompositionRequests.empty())
+ {
+ std::set<UUIDBasedRequest> incomplete;
+ while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ mMutex->lock();
+ std::set<UUIDBasedRequest>::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<UUIDBasedRequest> incomplete;
+ while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater)
+ {
+ mMutex->lock();
+ std::set<UUIDBasedRequest>::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<bool> 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<boost::iostreams::array_source> 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<LLVolume> 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<LLVolume> volume = new LLVolume(volume_params,0);
+
+ if (volume->unpackVolumeFaces(data, data_size))
+ {
+ d->mPhysicsShapeMesh.clear();
+
+ std::vector<LLVector3>& pos = d->mPhysicsShapeMesh.mPositions;
+ std::vector<LLVector3>& 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<LLWholeModelFeeObserver> fee_observer,
+ LLHandle<LLWholeModelUploadObserver> 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<LLViewerTexture* > textures;
+ std::map<LLViewerTexture*,S32> texture_index;
+
+ std::map<LLModel*,S32> 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<LLImageJ2C> 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<LLImageJ2C> 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<LoadedMesh> 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<LODRequest> 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<LLMeshSkinInfo*> skin_info_q;
+ std::deque<UUIDBasedRequest> skin_info_unavail_q;
+ std::list<LLModel::Decomposition*> 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);
+
+ // <FS:Ansariel> 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);
+
+ // <FS:Ansariel> 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;
+ }
+ }
+ // </FS:Ansariel>
+ }
+ }
+ 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
+ // <FS:Ansariel> 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
+ // <FS:Ansariel> 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
+ // <FS:Ansariel> 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
+ // <FS:Ansariel> 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<LLMeshUploadThread*>::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<LLViewerInventoryCategory> 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<LLUUID, F32> 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<LLMeshRepoThread::LODRequest>::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<LLUUID>::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<LLUUID>::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<LLModelInstance>& 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<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> 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<U32> metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
+ static LLCachedControl<U32> minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free"
+ static LLCachedControl<U32> 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<U32> metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
+ static LLCachedControl<U32> minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free"
+ static LLCachedControl<U32> bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16);
+
+ for (S32 i=0; i<LLVolumeLODGroup::NUM_LODS; i++)
+ {
+ mEstTrisByLOD[i] = llmax((F32)mSizeByLOD[i] - (F32)metadata_discount, (F32)minimum_size) / (F32)bytes_per_triangle;
+ }
+
+ return true;
+}
+
+
+S32 LLMeshCostData::getSizeByLOD(S32 lod)
+{
+ if (llclamp(lod,0,3) != lod)
+ {
+ return 0;
+ }
+ return mSizeByLOD[lod];
+}
+
+S32 LLMeshCostData::getSizeTotal()
+{
+ return mSizeByLOD[0] + mSizeByLOD[1] + mSizeByLOD[2] + mSizeByLOD[3];
+}
+
+F32 LLMeshCostData::getEstTrisByLOD(S32 lod)
+{
+ if (llclamp(lod,0,3) != lod)
+ {
+ return 0.f;
+ }
+ return mEstTrisByLOD[lod];
+}
+
+F32 LLMeshCostData::getEstTrisMax()
+{
+ return llmax(mEstTrisByLOD[0], mEstTrisByLOD[1], mEstTrisByLOD[2], mEstTrisByLOD[3]);
+}
+
+F32 LLMeshCostData::getRadiusWeightedTris(F32 radius)
+{
+ F32 max_distance = 512.f;
+
+ F32 dlowest = llmin(radius/0.03f, max_distance);
+ F32 dlow = llmin(radius/0.06f, max_distance);
+ F32 dmid = llmin(radius/0.24f, max_distance);
+
+ F32 triangles_lowest = mEstTrisByLOD[0];
+ F32 triangles_low = mEstTrisByLOD[1];
+ F32 triangles_mid = mEstTrisByLOD[2];
+ F32 triangles_high = mEstTrisByLOD[3];
+
+ F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559)
+ F32 min_area = 1.f;
+
+ F32 high_area = llmin(F_PI*dmid*dmid, max_area);
+ F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
+ F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
+ F32 lowest_area = max_area;
+
+ lowest_area -= low_area;
+ low_area -= mid_area;
+ mid_area -= high_area;
+
+ high_area = llclamp(high_area, min_area, max_area);
+ mid_area = llclamp(mid_area, min_area, max_area);
+ low_area = llclamp(low_area, min_area, max_area);
+ lowest_area = llclamp(lowest_area, min_area, max_area);
+
+ F32 total_area = high_area + mid_area + low_area + lowest_area;
+ high_area /= total_area;
+ mid_area /= total_area;
+ low_area /= total_area;
+ lowest_area /= total_area;
+
+ F32 weighted_avg = triangles_high*high_area +
+ triangles_mid*mid_area +
+ triangles_low*low_area +
+ triangles_lowest*lowest_area;
+
+ return weighted_avg;
+}
+
+F32 LLMeshCostData::getEstTrisForStreamingCost()
+{
+ LL_DEBUGS("StreamingCost") << "tris_by_lod: "
+ << mEstTrisByLOD[0] << ", "
+ << mEstTrisByLOD[1] << ", "
+ << mEstTrisByLOD[2] << ", "
+ << mEstTrisByLOD[3] << LL_ENDL;
+
+ F32 charged_tris = mEstTrisByLOD[3];
+ F32 allowed_tris = mEstTrisByLOD[3];
+ const F32 ENFORCE_FLOOR = 64.0f;
+ for (S32 i=2; i>=0; i--)
+ {
+ // How many tris can we have in this LOD without affecting land impact?
+ // - normally an LOD should be at most half the size of the previous one.
+ // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller.
+ allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]);
+ F32 excess_tris = mEstTrisByLOD[i]-allowed_tris;
+ if (excess_tris>0.f)
+ {
+ LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL;
+ charged_tris += excess_tris;
+ }
+ }
+ return charged_tris;
+}
+
+F32 LLMeshCostData::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<std::string, const LLCDParam*> 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<LLVector3> 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<LLVector3> 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<LLViewerInventoryItem> 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")));
+ }
+}
|