/** * @file llmeshrepository.h * @brief Client-side repository of mesh assets. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010-2013, 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$ */ #ifndef LL_MESH_REPOSITORY_H #define LL_MESH_REPOSITORY_H #include #include #include "llassettype.h" #include "llmodel.h" #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" #include "lldeadmantimer.h" #include "httpcommon.h" #include "httprequest.h" #include "httpoptions.h" #include "httpheaders.h" #include "httphandler.h" #include "llthread.h" #define LLCONVEXDECOMPINTER_STATIC 1 #include "llconvexdecomposition.h" #include "lluploadfloaterobservers.h" class LLVOVolume; class LLMutex; class LLCondition; class LLMeshRepository; typedef enum e_mesh_processing_result_enum { MESH_OK = 0, MESH_NO_DATA = 1, MESH_OUT_OF_MEMORY, MESH_HTTP_REQUEST_FAILED, MESH_PARSE_FAILURE, MESH_INVALID, MESH_UNKNOWN } EMeshProcessingResult; class LLMeshUploadData { public: LLPointer mBaseModel; LLPointer mModel[5]; LLUUID mUUID; U32 mRetries; std::string mRSVP; std::string mAssetData; LLSD mPostData; LLMeshUploadData() { mRetries = 0; } }; class LLTextureUploadData { public: LLViewerFetchedTexture* mTexture; LLUUID mUUID; std::string mRSVP; std::string mLabel; U32 mRetries; std::string mAssetData; LLSD mPostData; LLTextureUploadData() { mRetries = 0; } LLTextureUploadData(LLViewerFetchedTexture* texture, std::string& label) : mTexture(texture), mLabel(label) { mRetries = 0; } }; class LLPhysicsDecomp : public LLThread { public: typedef std::map decomp_params; class Request : public LLRefCount { public: //input params S32* mDecompID; std::string mStage; std::vector mPositions; std::vector mIndices; decomp_params mParams; //output state std::string mStatusMessage; std::vector mHullMesh; LLModel::convex_hull_decomposition mHull; //status message callback, called from decomposition thread virtual S32 statusCallback(const char* status, S32 p1, S32 p2) = 0; //completed callback, called from the main thread virtual void completed() = 0; virtual void setStatusMessage(const std::string& msg); bool isValid() const {return mPositions.size() > 2 && mIndices.size() > 2 ;} protected: //internal use LLVector3 mBBox[2] ; F32 mTriangleAreaThreshold ; void assignData(LLModel* mdl) ; void updateTriangleAreaThreshold() ; bool isValidTriangle(U16 idx1, U16 idx2, U16 idx3) ; }; LLCondition* mSignal; LLMutex* mMutex; bool mInited; bool mQuitting; bool mDone; LLPhysicsDecomp(); ~LLPhysicsDecomp(); void shutdown(); void submitRequest(Request* request); static S32 llcdCallback(const char*, S32, S32); void cancel(); void setMeshData(LLCDMeshData& mesh, bool vertex_based); void doDecomposition(); void doDecompositionSingleHull(); virtual void run(); void completeCurrent(); void notifyCompleted(); std::map mStageID; typedef std::queue > request_queue; request_queue mRequestQ; LLPointer mCurRequest; std::queue > mCompletedQ; }; class RequestStats { public: RequestStats() : mRetries(0) {}; void updateTime(); bool canRetry() const; bool isDelayed() const; U32 getRetries() { return mRetries; } private: U32 mRetries; LLFrameTimer mTimer; }; class LLMeshHeader { public: LLMeshHeader() {} explicit LLMeshHeader(const LLSD& header) { fromLLSD(header); } void fromLLSD(const LLSD& header) { const char* lod[] = { "lowest_lod", "low_lod", "medium_lod", "high_lod" }; mVersion = header["version"].asInteger(); for (U32 i = 0; i < 4; ++i) { mLodOffset[i] = header[lod[i]]["offset"].asInteger(); mLodSize[i] = header[lod[i]]["size"].asInteger(); } mSkinOffset = header["skin"]["offset"].asInteger(); mSkinSize = header["skin"]["size"].asInteger(); mPhysicsConvexOffset = header["physics_convex"]["offset"].asInteger(); mPhysicsConvexSize = header["physics_convex"]["size"].asInteger(); mPhysicsMeshOffset = header["physics_mesh"]["offset"].asInteger(); mPhysicsMeshSize = header["physics_mesh"]["size"].asInteger(); m404 = header.has("404"); } S32 mVersion = -1; S32 mSkinOffset = -1; S32 mSkinSize = -1; S32 mPhysicsConvexOffset = -1; S32 mPhysicsConvexSize = -1; S32 mPhysicsMeshOffset = -1; S32 mPhysicsMeshSize = -1; S32 mLodOffset[4] = { -1 }; S32 mLodSize[4] = { -1 }; bool m404 = false; }; class LLMeshRepoThread : public LLThread { public: volatile static S32 sActiveHeaderRequests; volatile static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; static S32 sRequestLowWater; static S32 sRequestHighWater; static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread LLMutex* mMutex; LLMutex* mHeaderMutex; LLCondition* mSignal; //map of known mesh headers typedef boost::unordered_map> mesh_header_map; // pair is header_size and data mesh_header_map mMeshHeader; class HeaderRequest : public RequestStats { public: const LLVolumeParams mMeshParams; HeaderRequest(const LLVolumeParams& mesh_params) : RequestStats(), mMeshParams(mesh_params) { } bool operator<(const HeaderRequest& rhs) const { return mMeshParams < rhs.mMeshParams; } }; class LODRequest : public RequestStats { public: LLVolumeParams mMeshParams; S32 mLOD; F32 mScore; LODRequest(const LLVolumeParams& mesh_params, S32 lod) : RequestStats(), mMeshParams(mesh_params), mLOD(lod), mScore(0.f) { } }; struct CompareScoreGreater { bool operator()(const LODRequest& lhs, const LODRequest& rhs) { return lhs.mScore > rhs.mScore; // greatest = first } }; class UUIDBasedRequest : public RequestStats { public: LLUUID mId; UUIDBasedRequest(const LLUUID& id) : RequestStats(), mId(id) { } bool operator<(const UUIDBasedRequest& rhs) const { return mId < rhs.mId; } }; class LoadedMesh { public: LLPointer mVolume; LLVolumeParams mMeshParams; S32 mLOD; LoadedMesh(LLVolume* volume, const LLVolumeParams& mesh_params, S32 lod) : mVolume(volume), mMeshParams(mesh_params), mLOD(lod) { } }; //set of requested skin info std::deque mSkinRequests; // list of completed skin info requests std::deque> mSkinInfoQ; // list of skin info requests that have failed or are unavailaibe std::deque mSkinUnavailableQ; //set of requested decompositions std::set mDecompositionRequests; //set of requested physics shapes std::set mPhysicsShapeRequests; // list of completed Decomposition info requests std::list mDecompositionQ; //queue of requested headers std::queue mHeaderReqQ; //queue of requested LODs std::queue mLODReqQ; //queue of unavailable LODs (either asset doesn't exist or asset doesn't have desired LOD) std::deque mUnavailableQ; //queue of successfully loaded meshes std::deque mLoadedQ; //map of pending header requests and currently desired LODs typedef std::unordered_map > pending_lod_map; pending_lod_map mPendingLOD; // map of mesh ID to skin info (mirrors LLMeshRepository::mSkinMap) /// NOTE: LLMeshRepository::mSkinMap is accessed very frequently, so maintain a copy here to avoid mutex overhead typedef std::unordered_map> skin_map; skin_map mSkinMap; // workqueue for processing generic requests LL::WorkQueue mWorkQueue; // llcorehttp library interface objects. LLCore::HttpStatus mHttpStatus; LLCore::HttpRequest * mHttpRequest; LLCore::HttpOptions::ptr_t mHttpOptions; LLCore::HttpOptions::ptr_t mHttpLargeOptions; LLCore::HttpHeaders::ptr_t mHttpHeaders; LLCore::HttpRequest::policy_t mHttpPolicyClass; LLCore::HttpRequest::policy_t mHttpLargePolicyClass; typedef std::unordered_set http_request_set; http_request_set mHttpRequestSet; // Outstanding HTTP requests std::string mGetMeshCapability; LLMeshRepoThread(); ~LLMeshRepoThread(); virtual void run(); void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); bool fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry = true); bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry = true); EMeshProcessingResult headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); EMeshProcessingResult lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); bool decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size); EMeshProcessingResult physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size); bool hasPhysicsShapeInHeader(const LLUUID& mesh_id); bool hasSkinInfoInHeader(const LLUUID& mesh_id); bool hasHeader(const LLUUID& mesh_id); void notifyLoadedMeshes(); S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); void loadMeshSkinInfo(const LLUUID& mesh_id); void loadMeshDecomposition(const LLUUID& mesh_id); void loadMeshPhysicsShape(const LLUUID& mesh_id); //send request for skin info, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) bool fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry = true); //send request for decomposition, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) bool fetchMeshDecomposition(const LLUUID& mesh_id); //send request for PhysicsShape, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) bool fetchMeshPhysicsShape(const LLUUID& mesh_id); static void incActiveLODRequests(); static void decActiveLODRequests(); static void incActiveHeaderRequests(); static void decActiveHeaderRequests(); // Set the caps strings and preferred version for constructing // mesh fetch URLs. // // Mutex: must be holding mMutex when called void setGetMeshCap(const std::string & get_mesh); // Mutex: acquires mMutex void constructUrl(LLUUID mesh_id, std::string * url); private: // Issue a GET request to a URL with 'Range' header using // the correct policy class and other attributes. If an invalid // handle is returned, the request failed and caller must retry // or dispose of handler. // // Threads: Repo thread only LLCore::HttpHandle getByteRange(const std::string & url, size_t offset, size_t len, const LLCore::HttpHandler::ptr_t &handler); }; // Class whose instances represent a single upload-type request for // meshes: one fee query or one actual upload attempt. Yes, it creates // a unique thread for that single request. As it is 1:1, it can also // trivially serve as the HttpHandler object for request completion // notifications. class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler { private: S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. public: class DecompRequest : public LLPhysicsDecomp::Request { public: LLPointer mModel; LLPointer mBaseModel; LLMeshUploadThread* mThread; DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread); S32 statusCallback(const char* status, S32 p1, S32 p2) { return 1; } void completed(); }; LLPointer mFinalDecomp; volatile bool mPhysicsComplete; typedef std::map, std::vector > hull_map; hull_map mHullMap; typedef std::vector instance_list; instance_list mInstanceList; typedef std::map, instance_list> instance_map; instance_map mInstance; LLMutex* mMutex; S32 mPendingUploads; LLVector3 mOrigin; bool mFinished; bool mUploadTextures; bool mUploadSkin; bool mUploadJoints; bool mLockScaleIfJointPosition; volatile bool mDiscarded; LLHost mHost; std::string mWholeModelFeeCapability; std::string mWholeModelUploadURL; 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 = true, LLHandle fee_observer = (LLHandle()), LLHandle upload_observer = (LLHandle())); ~LLMeshUploadThread(); bool finished() const { return mFinished; } virtual void run(); void preStart(); void discard() ; bool isDiscarded() const; void generateHulls(); void doWholeModelUpload(); void requestWholeModelFee(); void wholeModelToLLSD(LLSD& dest, bool include_textures); void decomposeMeshMatrix(LLMatrix4& transformation, LLVector3& result_pos, LLQuaternion& result_rot, LLVector3& result_scale); void setFeeObserverHandle(LLHandle observer_handle) { mFeeObserverHandle = observer_handle; } void setUploadObserverHandle(LLHandle observer_handle) { mUploadObserverHandle = observer_handle; } // Inherited from LLCore::HttpHandler virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); static LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material); private: LLHandle mFeeObserverHandle; LLHandle mUploadObserverHandle; bool mDoUpload; // if false only model data will be requested, otherwise the model will be uploaded LLSD mModelData; // llcorehttp library interface objects. LLCore::HttpStatus mHttpStatus; LLCore::HttpRequest * mHttpRequest; LLCore::HttpOptions::ptr_t mHttpOptions; LLCore::HttpHeaders::ptr_t mHttpHeaders; LLCore::HttpRequest::policy_t mHttpPolicyClass; }; // Params related to streaming cost, render cost, and scene complexity tracking. class LLMeshCostData { public: LLMeshCostData(); bool init(const LLMeshHeader& header); // Size for given LOD S32 getSizeByLOD(S32 lod); // Sum of all LOD sizes. S32 getSizeTotal(); // Estimated triangle counts for the given LOD. F32 getEstTrisByLOD(S32 lod); // Estimated triangle counts for the largest LOD. Typically this // is also the "high" LOD, but not necessarily. F32 getEstTrisMax(); // Triangle count as computed by original streaming cost // formula. Triangles in each LOD are weighted based on how // frequently they will be seen. // This was called "unscaled_value" in the original getStreamingCost() functions. F32 getRadiusWeightedTris(F32 radius); // Triangle count used by triangle-based cost formula. Based on // triangles in highest LOD plus potentially partial charges for // lower LODs depending on complexity. F32 getEstTrisForStreamingCost(); // Streaming cost. This should match the server-side calculation // for the corresponding volume. F32 getRadiusBasedStreamingCost(F32 radius); // New streaming cost formula, currently only used for animated objects. F32 getTriangleBasedStreamingCost(); private: // From the "size" field of the mesh header. LOD 0=lowest, 3=highest. std::array mSizeByLOD; // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest. std::array mEstTrisByLOD; }; class LLMeshRepository { public: //metrics static U32 sBytesReceived; static U32 sMeshRequestCount; // Total request count, http or cached, all component types static U32 sHTTPRequestCount; // Http GETs issued (not large) static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests static U32 sHTTPRetryCount; // Total request retries whether successful or failed static U32 sHTTPErrorCount; // Requests ending in error static U32 sLODPending; static U32 sLODProcessing; static U32 sCacheBytesRead; static U32 sCacheBytesWritten; static U32 sCacheBytesHeaders; static U32 sCacheBytesSkins; static U32 sCacheBytesDecomps; static U32 sCacheReads; static U32 sCacheWrites; static U32 sMaxLockHoldoffs; // Maximum sequential locking failures static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events // Estimated triangle count of the largest LOD F32 getEstTrianglesMax(LLUUID mesh_id); F32 getEstTrianglesStreamingCost(LLUUID mesh_id); F32 getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); static F32 getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); bool getCostData(LLUUID mesh_id, LLMeshCostData& data); bool getCostData(LLMeshHeader& header, LLMeshCostData& data); LLMeshRepository(); void init(); void unregisterAllMeshes(); void shutdown(); S32 update(); void unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail); void unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vobj); //mesh management functions S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); void notifyLoadedMeshes(); void notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume); void notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod); void notifySkinInfoReceived(LLMeshSkinInfo* info); void notifySkinInfoUnavailable(const LLUUID& info); void notifyDecompositionReceived(LLModel::Decomposition* info); S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); static S32 getActualMeshLOD(LLMeshHeader& header, S32 lod); const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj = nullptr); LLModel::Decomposition* getDecomposition(const LLUUID& mesh_id); void fetchPhysicsShape(const LLUUID& mesh_id); bool hasPhysicsShape(const LLUUID& mesh_id); bool hasSkinInfo(const LLUUID& mesh_id); bool hasHeader(const LLUUID& mesh_id); void buildHull(const LLVolumeParams& params, S32 detail); void buildPhysicsMesh(LLModel::Decomposition& decomp); bool meshUploadEnabled(); bool meshRezEnabled(); void uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, std::string upload_url, bool do_upload = true, LLHandle fee_observer= (LLHandle()), LLHandle upload_observer = (LLHandle())); S32 getMeshSize(const LLUUID& mesh_id, S32 lod); // Quiescent timer management, main thread only. static void metricsStart(); static void metricsStop(); static void metricsProgress(unsigned int count); static void metricsUpdate(); typedef boost::unordered_map > mesh_load_map; mesh_load_map mLoadingMeshes[4]; typedef std::unordered_map> skin_map; skin_map mSkinMap; typedef std::map decomposition_map; decomposition_map mDecompositionMap; LLMutex* mMeshMutex; std::vector mPendingRequests; //list of mesh ids awaiting skin info typedef boost::unordered_map > skin_load_map; skin_load_map mLoadingSkins; //list of mesh ids that need to send skin info fetch requests std::queue mPendingSkinRequests; //list of mesh ids awaiting decompositions std::unordered_set mLoadingDecompositions; //list of mesh ids that need to send decomposition fetch requests std::queue mPendingDecompositionRequests; //list of mesh ids awaiting physics shapes std::unordered_set mLoadingPhysicsShapes; //list of mesh ids that need to send physics shape fetch requests std::queue mPendingPhysicsShapeRequests; U32 mMeshThreadCount; LLMeshRepoThread* mThread; std::vector mUploads; std::vector mUploadWaitList; LLPhysicsDecomp* mDecompThread; LLFrameTimer mSkinInfoCullTimer; class inventory_data { public: LLSD mPostData; LLSD mResponse; inventory_data(const LLSD& data, const LLSD& content) : mPostData(data), mResponse(content) { } }; std::queue mInventoryQ; std::queue mUploadErrorQ; void uploadError(LLSD& args); void updateInventory(inventory_data data); }; extern LLMeshRepository gMeshRepo; const F32 ANIMATED_OBJECT_BASE_COST = 15.0f; const F32 ANIMATED_OBJECT_COST_PER_KTRI = 1.5f; #endif