5528 lines
158 KiB
C++
5528 lines
158 KiB
C++
/**
|
|
* @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 + boost::lexical_cast<std::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 boost::enable_shared_from_this<LLMeshHandlerBase>
|
|
{
|
|
public:
|
|
typedef boost::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_ONCE(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 ? false : true;
|
|
}
|
|
|
|
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_ONCE(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 ? false : true;
|
|
}
|
|
|
|
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_ONCE(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 ? false : true;
|
|
}
|
|
|
|
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_ONCE(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 ? false : true;
|
|
}
|
|
|
|
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())
|
|
{
|
|
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())
|
|
{
|
|
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, TAKE_FOCUS_NO, 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")));
|
|
}
|
|
}
|