diff options
41 files changed, 982 insertions, 489 deletions
diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 2748da9a1d..84cb42056a 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -140,7 +140,7 @@ public: LLVector3 mHeadOffset{}; // current head position LLAvatarJoint* mRoot{ nullptr }; - typedef std::map<std::string, LLJoint*> joint_map_t; + typedef std::map<std::string, LLJoint*, std::less<>> joint_map_t; joint_map_t mJointMap; typedef std::map<std::string, LLVector3> joint_state_map_t; @@ -153,7 +153,7 @@ public: public: typedef std::vector<LLAvatarJoint*> avatar_joint_list_t; const avatar_joint_list_t& getSkeleton() { return mSkeleton; } - typedef std::map<std::string, std::string> joint_alias_map_t; + typedef std::map<std::string, std::string, std::less<>> joint_alias_map_t; const joint_alias_map_t& getJointAliases(); typedef std::map<std::string, std::string> joint_parent_map_t; // matrix plus parent typedef std::map<std::string, glm::mat4> joint_rest_map_t; diff --git a/indra/llappearance/lltexlayer.cpp b/indra/llappearance/lltexlayer.cpp index aa48a2d621..b3800e6981 100644 --- a/indra/llappearance/lltexlayer.cpp +++ b/indra/llappearance/lltexlayer.cpp @@ -1293,7 +1293,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC { if (!force_render && !hasMorph()) { - LL_DEBUGS() << "skipping renderMorphMasks for " << getUUID() << LL_ENDL; + LL_DEBUGS("Morph") << "skipping renderMorphMasks for " << getUUID() << LL_ENDL; return; } LL_PROFILE_ZONE_SCOPED; @@ -1325,7 +1325,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC success &= param->render( x, y, width, height ); if (!success && !force_render) { - LL_DEBUGS() << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL; + LL_DEBUGS("Morph") << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL; return; } } @@ -1365,7 +1365,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC } else { - LL_WARNS() << "Skipping rendering of " << getInfo()->mStaticImageFileName + LL_WARNS("Morph") << "Skipping rendering of " << getInfo()->mStaticImageFileName << "; expected 1 or 4 components." << LL_ENDL; } } @@ -1404,8 +1404,8 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC // We can get bad morph masks during login, on minimize, and occasional gl errors. // We should only be doing this when we believe something has changed with respect to the user's appearance. { - LL_DEBUGS("Avatar") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL; - // clear out a slot if we have filled our cache + LL_DEBUGS("Morph") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL; + // clear out a slot if we have filled our cache S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1; while ((S32)mAlphaCache.size() >= max_cache_entries) { @@ -1444,13 +1444,20 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC } glGetTexImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGBA, GL_UNSIGNED_BYTE, temp); - - U8* alpha_cursor = alpha_data; - U8* pixel = temp; - for (int i = 0; i < pixels; i++) + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + { + LL_INFOS("Morph") << "GL Error while reading back morph texture. Error code: " << error << LL_ENDL; + } + else { - *alpha_cursor++ = pixel[3]; - pixel += 4; + U8* alpha_cursor = alpha_data; + U8* pixel = temp; + for (int i = 0; i < pixels; i++) + { + *alpha_cursor++ = pixel[3]; + pixel += 4; + } } gGL.getTexUnit(0)->disable(); diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp index 9dace08e6f..581e9f62d5 100644 --- a/indra/llcharacter/llbvhloader.cpp +++ b/indra/llcharacter/llbvhloader.cpp @@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str ) // LLBVHLoader() //----------------------------------------------------------------------------- -LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map ) +LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map ) { reset(); errorLine = 0; @@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error } // Recognize all names we've been told are legal. - for (std::map<std::string, std::string>::value_type& alias_pair : joint_alias_map) + for (const auto& [alias, joint] : joint_alias_map) { - makeTranslation( alias_pair.first , alias_pair.second ); + makeTranslation(alias, joint); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index de31f76dd3..ae2e347ba1 100644 --- a/indra/llcharacter/llbvhloader.h +++ b/indra/llcharacter/llbvhloader.h @@ -227,7 +227,7 @@ class LLBVHLoader friend class LLKeyframeMotion; public: // Constructor - LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map ); + LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map ); ~LLBVHLoader(); /* diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index ecbcdb3bf5..8efcd9dd29 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,12 +77,11 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint *LLCharacter::getJoint( const std::string &name ) +LLJoint* LLCharacter::getJoint(std::string_view name) { - LLJoint* joint = NULL; + LLJoint* joint = nullptr; - LLJoint *root = getRootJoint(); - if (root) + if (LLJoint* root = getRootJoint()) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 6143ec8cd1..0046c5da7e 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -76,7 +76,7 @@ public: // get the specified joint // default implementation does recursive search, // subclasses may optimize/cache results. - virtual LLJoint *getJoint( const std::string &name ); + virtual LLJoint* getJoint(std::string_view name); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index e5d25b52f0..692941a892 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -28,6 +28,7 @@ #include "apr_portable.h" +#include "llapp.h" #include "llthread.h" #include "llmutex.h" @@ -35,6 +36,7 @@ #include "lltrace.h" #include "lltracethreadrecorder.h" #include "llexception.h" +#include "workqueue.h" #if LL_LINUX #include <sched.h> @@ -106,6 +108,27 @@ namespace return s_thread_id; } +#if LL_WINDOWS + + static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific + + U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS* exception_infop) + { + if (LLApp::instance()->reportCrashToBugsplat((void*)exception_infop)) + { + // Handled + return EXCEPTION_CONTINUE_SEARCH; + } + else if (code == STATUS_MSC_EXCEPTION) + { + // C++ exception, go on + return EXCEPTION_CONTINUE_SEARCH; + } + + // handle it, convert to std::exception + return EXCEPTION_EXECUTE_HANDLER; + } +#endif // LL_WINDOWS } // anonymous namespace LL_COMMON_API bool on_main_thread() @@ -157,20 +180,11 @@ void LLThread::threadRun() // Run the user supplied function do { - try - { - run(); - } - catch (const LLContinueError &e) - { - LL_WARNS("THREAD") << "ContinueException on thread '" << mName << - "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; - //output possible call stacks to log file. - LLError::LLCallStacks::print(); - - LOG_UNHANDLED_EXCEPTION("LLThread"); - continue; - } +#ifdef LL_WINDOWS + sehHandle(); // Structured Exception Handling +#else + tryRun(); +#endif break; } while (true); @@ -188,6 +202,69 @@ void LLThread::threadRun() mStatus = STOPPED; } +void LLThread::tryRun() +{ + try + { + run(); + } + catch (const LLContinueError& e) + { + LL_WARNS("THREAD") << "ContinueException on thread '" << mName << + "'. Error what is: '" << e.what() << "'" << LL_ENDL; + LLError::LLCallStacks::print(); + + LOG_UNHANDLED_EXCEPTION("LLThread"); + } + catch (std::bad_alloc&) + { + // Todo: improve this, this is going to have a different callstack + // instead of showing where it crashed + LL_WARNS("THREAD") << "Out of memory in a thread: " << mName << LL_ENDL; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->post( + // Bind the current exception, rethrow it in main loop. + []() { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("THREAD") << "Out of memory in a thread" << LL_ENDL; + }); + } +#ifndef LL_WINDOWS + catch (...) + { + // Stash any other kind of uncaught exception to be rethrown by main thread. + LL_WARNS("THREAD") << "Capturing and rethrowing uncaught exception in LLThread " + << mName << LL_ENDL; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->post( + // Bind the current exception, rethrow it in main loop. + [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + } +#endif // else LL_WINDOWS +} + +#ifdef LL_WINDOWS +void LLThread::sehHandle() +{ + __try + { + // handle stop and continue exceptions first + tryRun(); + } + __except (exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + // convert to C++ styled exception + // Note: it might be better to use _se_set_translator + // if you want exception to inherit full callstack + char integer_string[512]; + sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); + throw std::exception(integer_string); + } +} +#endif + LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : mPaused(false), mName(name), diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 21264351e5..8794ac93aa 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -97,6 +97,11 @@ private: // static function passed to APR thread creation routine void threadRun(); + void tryRun(); + +#ifdef LL_WINDOWS + void sehHandle(); +#endif protected: std::string mName; diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 0759447902..a11f9b5ca2 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -880,9 +880,10 @@ LLDAELoader::LLDAELoader( void* opaque_userdata, JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, - std::map<std::string, std::string>& jointAliasMap, + std::map<std::string, std::string, std::less<>>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess) : LLModelLoader( filename, @@ -895,8 +896,9 @@ LLDAELoader::LLDAELoader( jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh), - mGeneratedModelLimit(modelLimit), + maxJointsPerMesh, + modelLimit, + debugMode), mPreprocessDAE(preprocess) { } @@ -1680,6 +1682,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do { materials[model->mMaterialList[i]] = LLImportMaterial(); } + // todo: likely a bug here, shouldn't be using suffixed label, see how it gets used in other places. mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); stretch_extents(model, transformation); } @@ -2412,7 +2415,7 @@ std::string LLDAELoader::getElementLabel(daeElement *element) } // static -size_t LLDAELoader::getSuffixPosition(std::string label) +size_t LLDAELoader::getSuffixPosition(const std::string &label) { if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) { diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 4531e03474..0335011a56 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -47,19 +47,20 @@ public: dae_model_map mModelsMap; LLDAELoader( - std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void* opaque_userdata, - JointTransformMap& jointTransformMap, - JointNameSet& jointsFromNodes, - std::map<std::string, std::string>& jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - bool preprocess); + std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void* opaque_userdata, + JointTransformMap& jointTransformMap, + JointNameSet& jointsFromNodes, + std::map<std::string, std::string, std::less<>>& jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode, + bool preprocess); virtual ~LLDAELoader() ; virtual bool OpenFile(const std::string& filename); @@ -97,13 +98,12 @@ protected: bool loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& models_out, U32 submodel_limit); static std::string getElementLabel(daeElement *element); - static size_t getSuffixPosition(std::string label); + static size_t getSuffixPosition(const std::string& label); static std::string getLodlessLabel(daeElement *element); static std::string preprocessDAE(std::string filename); private: - U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mPreprocessDAE; }; diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 3d31cfbb7f..a33f25307e 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -818,7 +818,7 @@ LLSD LLModel::writeModel( bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite, + EWriteModelMode write_mode, bool as_slm, int submodel_id) { @@ -1097,10 +1097,10 @@ LLSD LLModel::writeModel( } } - return writeModelToStream(ostr, mdl, nowrite, as_slm); + return writeModelToStream(ostr, mdl, write_mode, as_slm); } -LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bool as_slm) +LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, EWriteModelMode write_mode, bool as_slm) { std::string::size_type cur_offset = 0; @@ -1162,7 +1162,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bo } } - if (!nowrite) + if (write_mode == WRITE_HUMAN) + { + ostr << mdl; + } + else if (write_mode == WRITE_BINARY) { LLSDSerialize::toBinary(header, ostr); diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..6501b3dc50 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -160,6 +160,12 @@ public: bool loadSkinInfo(LLSD& header, std::istream& is); bool loadDecomposition(LLSD& header, std::istream& is); + enum EWriteModelMode + { + WRITE_NO = 0, + WRITE_BINARY, + WRITE_HUMAN, + }; static LLSD writeModel( std::ostream& ostr, LLModel* physics, @@ -171,14 +177,14 @@ public: bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite = false, + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false, int submodel_id = 0); static LLSD writeModelToStream( std::ostream& ostr, LLSD& mdl, - bool nowrite = false, bool as_slm = false); + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false); void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); } diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index f97ac16a83..db4c4259f3 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -113,7 +113,9 @@ LLModelLoader::LLModelLoader( JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh) + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode) : mJointList( jointTransformMap ) , mJointsFromNode( jointsFromNodes ) , LLThread("Model Loader") @@ -133,6 +135,8 @@ LLModelLoader::LLModelLoader( , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) , mMaxJointsPerMesh(maxJointsPerMesh) +, mGeneratedModelLimit(modelLimit) +, mDebugMode(debugMode) , mJointMap(legalJointNamesMap) { assert_main_thread(); @@ -149,7 +153,42 @@ LLModelLoader::~LLModelLoader() void LLModelLoader::run() { mWarningsArray.clear(); - doLoadModel(); + try + { + doLoadModel(); + } + // Model loader isn't mission critical, so we just log all exceptions + catch (const LLException& e) + { + LL_WARNS("THREAD") << "LLException in model loader: " << e.what() << "" << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLModelLoader"); + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + // todo: we are inside of a thread, push this into main thread worker, // not into doOnIdleOneTime that laks tread safety doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); @@ -203,7 +242,9 @@ bool LLModelLoader::doLoadModel() } } - return OpenFile(mFilename); + bool res = OpenFile(mFilename); + dumpDebugData(); // conditional on mDebugMode + return res; } void LLModelLoader::setLoadState(U32 state) @@ -470,6 +511,11 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std:: void LLModelLoader::dumpDebugData() { + if (mDebugMode == 0) + { + return; + } + std::string log_file = mFilename + "_importer.txt"; LLStringUtil::toLower(log_file); llofstream file; @@ -508,17 +554,102 @@ void LLModelLoader::dumpDebugData() } } - file << "Inv Bind matrices.\n"; + file << "\nInv Bind matrices.\n"; for (auto& bind : inv_bind) { file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; } - file << "Alt Bind matrices.\n"; + file << "\nAlt Bind matrices.\n"; for (auto& bind : alt_bind) { file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; } + + if (mDebugMode == 2) + { + S32 model_count = 0; + for (LLPointer<LLModel>& mdl : mModelList) + { + const LLVolume::face_list_t &face_list = mdl->getVolumeFaces(); + for (S32 face = 0; face < face_list.size(); face++) + { + const LLVolumeFace& vf = face_list[face]; + file << "\nModel: " << mdl->mLabel + << " face " << face + << " has " << vf.mNumVertices + << " vertices and " << vf.mNumIndices + << " indices " << "\n"; + + file << "\nPositions for model: " << mdl->mLabel << " face " << face << "\n"; + + for (S32 pos = 0; pos < vf.mNumVertices; ++pos) + { + file << vf.mPositions[pos] << " "; + } + + file << "\n\nIndices for model: " << mdl->mLabel << " face " << face << "\n"; + + for (S32 ind = 0; ind < vf.mNumIndices; ++ind) + { + file << vf.mIndices[ind] << " "; + } + } + + file << "\n\nWeights for model: " << mdl->mLabel; + for (auto& weights : mdl->mSkinWeights) + { + file << "\nVertex: " << weights.first << " Weights: "; + for (auto& weight : weights.second) + { + file << weight.mJointIdx << ":" << weight.mWeight << " "; + } + } + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } + } + else if (mDebugMode > 2) + { + file << "\nModel LLSDs\n"; + S32 model_count = 0; + // some files contain too many models, so stop at 5. + for (LLPointer<LLModel>& mdl : mModelList) + { + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + size_t joint_count = skin_info.mJointNames.size(); + size_t alt_count = skin_info.mAlternateBindMatrix.size(); + + LLModel::writeModel( + file, + nullptr, + mdl, + nullptr, + nullptr, + nullptr, + mdl->mPhysics, + joint_count > 0, + alt_count > 0, + false, + LLModel::WRITE_HUMAN, + false, + mdl->mSubmodelID); + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } + } } //called in the main thread diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 7c808dcae0..06a17f006e 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -36,7 +36,7 @@ class LLJoint; typedef std::map<std::string, LLMatrix4> JointTransformMap; typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt; -typedef std::map<std::string, std::string> JointMap; +typedef std::map<std::string, std::string, std::less<>> JointMap; typedef std::deque<std::string> JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; @@ -129,6 +129,7 @@ public: U32 mMaxJointsPerMesh; + U32 mDebugMode; // see dumDebugData() for details LLModelLoader( std::string filename, @@ -141,7 +142,9 @@ public: JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh); + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode); virtual ~LLModelLoader(); virtual void setNoNormalize() { mNoNormalize = true; } @@ -210,6 +213,7 @@ protected: bool mRigValidJointUpload; U32 mLegacyRigFlags; + U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mNoNormalize; bool mNoOptimize; diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 3f8903ca09..1db36d91f9 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1870,8 +1870,17 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes); if(!imageraw->allocateDataSize(width, height, ncomponents, glbytes)) { - LL_WARNS() << "Memory allocation failed for reading back texture. Size is: " << glbytes << LL_ENDL ; - LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ; + constexpr S64 MAX_GL_BYTES = 2048 * 2048; + if (glbytes > 0 && glbytes <= MAX_GL_BYTES) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << glbytes << LL_ENDL; + } + else + { + LL_WARNS() << "Memory allocation failed for reading back texture. Data size is: " << glbytes << LL_ENDL; + LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL; + } return false ; } @@ -1882,8 +1891,18 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre { if(!imageraw->allocateDataSize(width, height, ncomponents)) { - LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL ; - LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ; + constexpr F32 MAX_IMAGE_SIZE = 2048 * 2048; + F32 size = (F32)width * (F32)height * (F32)ncomponents; + if (size > 0 && size <= MAX_IMAGE_SIZE) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << size << LL_ENDL; + } + else + { + LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL; + LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL; + } return false ; } diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 099f298456..0ee843cc60 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.14 +7.2.0 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5a185a665d..1cac6e9709 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2,7 +2,7 @@ <llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> <map> - <key>ImporterDebug</key> + <key>ImporterDebugVerboseLogging</key> <map> <key>Comment</key> <string>Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines.</string> @@ -27,7 +27,7 @@ <key>ImporterModelLimit</key> <map> <key>Comment</key> - <string>Limits amount of importer generated models for dae files</string> + <string>Limits amount of importer generated (when over 8 faces) models for dae and gltf files</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -35,6 +35,17 @@ <key>Value</key> <integer>768</integer> </map> + <key>ImporterDebugMode</key> + <map> + <key>Comment</key> + <string>At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and positions/weights of first 5 models, at 3 dumps skinning data and models as llsd</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>0</integer> + </map> <key>ImporterPreprocessDAE</key> <map> <key>Comment</key> diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7ae255e054..3019a12446 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,20 +86,23 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -LLGLTFLoader::LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map<std::string, std::string> &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - std::vector<LLJointData> viewer_skeleton) //, - //bool preprocess) +static const S32 VERTICIES_LIMIT = USHRT_MAX - 2; + +LLGLTFLoader::LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map<std::string, std::string, std::less<>> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode, + std::vector<LLJointData> viewer_skeleton) //, + //bool preprocess) : LLModelLoader( filename, lod, load_cb, @@ -110,9 +113,12 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ) - , mGeneratedModelLimit(modelLimit) + maxJointsPerMesh, + modelLimit, + debugMode) , mViewerJointData(viewer_skeleton) + , mGltfLoaded(false) + , mApplyXYRotation(false) { } @@ -120,11 +126,39 @@ LLGLTFLoader::~LLGLTFLoader() {} bool LLGLTFLoader::OpenFile(const std::string &filename) { + // Clear the material cache for new file + mMaterialCache.clear(); + tinygltf::TinyGLTF loader; std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename, false); + try + { + mGltfLoaded = mGLTFAsset.load(filename, false); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLGLTFLoader"); + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } if (!mGltfLoaded) { @@ -157,6 +191,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) void LLGLTFLoader::addModelToScene( LLModel* pModel, + const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, @@ -175,12 +210,23 @@ void LLGLTFLoader::addModelToScene( U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; if (face_limit < volume_faces) { + LL_WARNS("GLTF_IMPORT") << "Model contains " << volume_faces + << " faces, exceeding the limit of " << face_limit << LL_ENDL; + + LLSD args; + args["Message"] = "ModelTooManySubmodels"; + args["MODEL_NAME"] = pModel->mLabel; + args["SUBMODEL_COUNT"] = static_cast<S32>(llfloor((F32)volume_faces / LL_SCULPT_MESH_MAX_FACES)); + args["SUBMODEL_LIMIT"] = static_cast<S32>(submodel_limit); + mWarningsArray.append(args); + pModel->setNumVolumeFaces(face_limit); } LLVolume::face_list_t remainder; std::vector<LLModel*> ready_models; LLModel* current_model = pModel; + do { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); @@ -198,7 +244,26 @@ void LLGLTFLoader::addModelToScene( LLModel* next = new LLModel(volume_params, 0.f); next->ClearFacesAndMaterials(); next->mSubmodelID = ++submodelID; - next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + + std::string instance_name = model_name; + if (next->mSubmodelID > 0) + { + instance_name += (char)((int)'a' + next->mSubmodelID); + } + // Check for duplicates and add copy suffix if needed + int duplicate_count = 0; + for (const auto& inst : mScene[transformation]) + { + if (inst.mLabel == instance_name) + { + ++duplicate_count; + } + } + if (duplicate_count > 0) { + instance_name += "_copy_" + std::to_string(duplicate_count); + } + next->mLabel = instance_name; + next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -248,7 +313,13 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + // Keep base name for scene instance. + std::string instance_name = model->mLabel; + // Add suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so models + // need unique names not just in scope of one lod, but across lods. + model->mLabel += lod_suffix[mLod]; + mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials)); stretch_extents(model, transformation); } } @@ -276,7 +347,7 @@ bool LLGLTFLoader::parseMeshes() } // Populate the joints from skins first. - // There's not many skins - and you can pretty easily iterate through the nodes from that. + // Multiple meshes can share the same skin, so preparing skins beforehand. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { populateJointsFromSkin(i); @@ -284,7 +355,9 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map<std::string, S32> mesh_name_counts; - U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; + + // For now use mesh count, but might be better to do 'mNodes.size() - joints count'. + U32 submodel_limit = mGLTFAsset.mMeshes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mMeshes.size() : 0; // Check if we have scenes defined if (!mGLTFAsset.mScenes.empty()) @@ -318,20 +391,7 @@ bool LLGLTFLoader::parseMeshes() return false; } - // Check total model count against limit - U32 total_models = static_cast<U32>(mModelList.size()); - if (total_models > mGeneratedModelLimit) - { - LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models - << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; - - LLSD args; - args["Message"] = "TooManyMeshParts"; - args["PART_COUNT"] = static_cast<S32>(total_models); - args["LIMIT"] = static_cast<S32>(mGeneratedModelLimit); - mWarningsArray.append(args); - return false; - } + checkGlobalJointUsage(); return true; } @@ -341,9 +401,9 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32> if (node_idx < 0 || node_idx >= static_cast<S32>(mGLTFAsset.mNodes.size())) return; - auto& node = mGLTFAsset.mNodes[node_idx]; + const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; - LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + LL_DEBUGS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") << " - children: " << node.mChildren.size() << LL_ENDL; @@ -354,10 +414,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32> material_map mats; LLModel* pModel = new LLModel(volume_params, 0.f); - auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage - std::string base_name = mesh.mName; + std::string base_name = getLodlessLabel(mesh); if (base_name.empty()) { base_name = "mesh_" + std::to_string(node.mMesh); @@ -365,7 +425,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32> S32 instance_count = mesh_name_counts[base_name]++; - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + // make name unique + if (instance_count > 0) + { + base_name = base_name + "_copy_" + std::to_string(instance_count); + } + + if (populateModelFromMesh(pModel, base_name, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -413,7 +479,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32> mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats); mats.clear(); } else @@ -478,7 +544,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 } } -bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) { const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; if (legal_name.empty()) @@ -493,32 +559,155 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + // Track joint usage for this skin, for the sake of unused joints detection + mJointUsage[gltf_skin_idx][gltf_joint_idx]++; + return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_index, S32 fallback_index) { - // Set the requested label for the floater display and uploading - pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Check cache first + auto cached = mMaterialCache.find(material_index); + if (cached != mMaterialCache.end()) + { + return cached->second; + } + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color + + // Generate material name + std::string materialName = generateMaterialName(material_index, fallback_index); + + // Process material if available + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + { + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + std::string filename = processTexture(texIndex, "base_color", material->mName); + + if (!filename.empty()) + { + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + + // Check if the texture is already loaded + S32 sourceIndex; + if (validateTextureIndex(texIndex, sourceIndex)) + { + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + if (image.mTexture.notNull()) + { + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; + } + } + } + } + } + + // Create cached material with both material and name + LLGLTFImportMaterial cachedMat(impMat, materialName); + + // Cache the processed material + mMaterialCache[material_index] = cachedMat; + return cachedMat; +} + +std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name) +{ + S32 sourceIndex; + if (!validateTextureIndex(texture_index, sourceIndex)) + return ""; + + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - // Create unique model name - std::string base_name = mesh.mName; - if (base_name.empty()) + // Process URI-based textures + if (!image.mUri.empty()) { - S32 mesh_index = static_cast<S32>(&mesh - &mGLTFAsset.mMeshes[0]); - base_name = "mesh_" + std::to_string(mesh_index); + std::string filename = image.mUri; + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = filename; + args["MATERIAL_NAME"] = material_name; + mWarningsArray.append(args); + + return filename; + } + + // Process embedded textures + if (image.mBufferView >= 0) + { + return extractTextureToTempFile(texture_index, texture_type); } - LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + return ""; +} + +bool LLGLTFLoader::validateTextureIndex(S32 texture_index, S32& source_index) +{ + if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) + return false; + + source_index = mGLTFAsset.mTextures[texture_index].mSource; + if (source_index < 0 || source_index >= mGLTFAsset.mImages.size()) + return false; + + return true; +} - if (instance_count > 0) +std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) +{ + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) { - pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + std::string materialName = material->mName; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(material_index); + } + return materialName; } else { - pModel->mLabel = base_name; + return fallback_index >= 0 ? "mat_default" + std::to_string(fallback_index) : "mat_default"; } +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) +{ + // Set the requested label for the floater display and uploading + pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Set only name, suffix will be added later + pModel->mLabel = base_name; + + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; pModel->ClearFacesAndMaterials(); @@ -550,11 +739,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_use.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt, 0); for (size_t i = 0; i < jointCnt; ++i) { - if (mJointNames[i].empty()) + if (mJointNames[skinIdx][i].empty()) { // This might need to hold a substitute index gltf_joint_index_use[i] = -1; // mark as unsupported @@ -572,91 +761,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LLVolumeFace face; std::vector<GLTFVertex> vertices; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color - - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) - { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - - // Set diffuse color from base color factor - impMat.mDiffuseColor = LLColor4( - material->mPbrMetallicRoughness.mBaseColorFactor[0], - material->mPbrMetallicRoughness.mBaseColorFactor[1], - material->mPbrMetallicRoughness.mBaseColorFactor[2], - material->mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Process base color texture if it exists - if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) - { - S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) - { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) - { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - - // Use URI as texture file name - if (!image.mUri.empty()) - { - // URI might be a remote URL or a local path - std::string filename = image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) - { - filename = filename.substr(pos + 1); - } - - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename - << " for material: " << material->mName << LL_ENDL; - - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); - - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // Texture will be loaded later through the callback system - LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; - } - } - else if (image.mTexture.notNull()) - { - // No URI but we have a texture, use it directly - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; - } - else if (image.mBufferView >= 0) - { - // For embedded textures (no URI but has buffer data) - std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); - if (!temp_filename.empty()) - { - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - } - } - } - } - } - } + // Use cached material processing + LLGLTFImportMaterial cachedMat = processMaterial(prim.mMaterial, pModel->getNumVolumeFaces() - 1); + LLImportMaterial impMat = cachedMat; + std::string materialName = cachedMat.name; + mats[materialName] = impMat; if (prim.getIndexCount() % 3 != 0) { @@ -839,26 +948,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) - { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; - - if (materialName.empty()) - { - materialName = "mat" + std::to_string(prim.mMaterial); - } - } - else - { - materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); - } - mats[materialName] = impMat; - // Indices handling - if (faceVertices.size() >= USHRT_MAX) + if (faceVertices.size() >= VERTICIES_LIMIT) { // Will have to remap 32 bit indices into 16 bit indices // For the sake of simplicity build vector of 32 bit indices first @@ -881,22 +972,40 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // remap 32 bit into multiple 16 bit ones + // Generates a vertex remap table with no gaps in the resulting sequence + std::vector<U32> remap(faceVertices.size()); + size_t vertex_count = meshopt_generateVertexRemap(&remap[0], &indices_32[0], indices_32.size(), &faceVertices[0], faceVertices.size(), sizeof(LLVolumeFace::VertexData)); + + // Manually remap vertices + std::vector<LLVolumeFace::VertexData> optimized_vertices(vertex_count); + for (size_t i = 0; i < vertex_count; ++i) + { + optimized_vertices[i] = faceVertices[remap[i]]; + } + + std::vector<U32> optimized_indices(indices_32.size()); + meshopt_remapIndexBuffer(&optimized_indices[0], &indices_32[0], indices_32.size(), &remap[0]); + + // Sort indices to improve mesh splits (reducing amount of duplicated indices) + meshopt_optimizeVertexCache(&optimized_indices[0], &optimized_indices[0], indices_32.size(), vertex_count); + std::vector<U16> indices_16; - std::vector<S64> vertices_remap; // should it be a point map? - vertices_remap.resize(faceVertices.size(), -1); + std::vector<S64> vertices_remap; + vertices_remap.resize(vertex_count, -1); + S32 created_faces = 0; std::vector<LLVolumeFace::VertexData> face_verts; min = glm::vec3(FLT_MAX); max = glm::vec3(-FLT_MAX); - for (size_t idx = 0; idx < indices_32.size(); idx++) + + for (size_t idx = 0; idx < optimized_indices.size(); idx++) { - size_t vert_index = indices_32[idx]; + size_t vert_index = optimized_indices[idx]; if (vertices_remap[vert_index] == -1) { // First encounter, add it size_t new_vert_idx = face_verts.size(); vertices_remap[vert_index] = (S64)new_vert_idx; - face_verts.push_back(faceVertices[vert_index]); + face_verts.push_back(optimized_vertices[vert_index]); vert_index = new_vert_idx; // Update min/max bounds @@ -925,7 +1034,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } indices_16.push_back((U16)vert_index); - if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) + if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1) { LLVolumeFace face; face.fillFromLegacyData(face_verts, indices_16); @@ -933,6 +1042,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); pModel->getMaterialList().push_back(materialName); + created_faces++; std::fill(vertices_remap.begin(), vertices_remap.end(), -1); indices_16.clear(); @@ -950,7 +1060,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); pModel->getMaterialList().push_back(materialName); + created_faces++; } + + LL_INFOS("GLTF_IMPORT") << "Primitive " << (S32)prim_idx << " from model " << pModel->mLabel + << " is over vertices limit, it was split into " << created_faces + << " faces" << LL_ENDL; + LLSD args; + args["Message"] = "ModelSplitPrimitive"; + args["MODEL_NAME"] = pModel->mLabel; + args["FACE_COUNT"] = created_faces; + mWarningsArray.append(args); } else { @@ -996,11 +1116,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 replacement_index = 0; std::vector<S32> gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); + gltfindex_to_joitindex_map.resize(jointCnt, -1); - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map<std::string, S32> goup_use_count; + + for (const auto& elem : mJointGroups) + { + goup_use_count[elem.second.mGroup] = 0; + goup_use_count[elem.second.mParentGroup] = 0; + } + // Assume that 'Torso' group is always in use since that's what everything else is attached to goup_use_count["Torso"] = 1; // Note that Collisions and Extra groups are all over the place, might want to include them from the start @@ -1053,7 +1180,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // this step needs only joints that have zero uses continue; } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { break; } @@ -1088,16 +1215,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { + // mMaxJointsPerMesh ususlly is equal to LL_MAX_JOINTS_PER_MESH_OBJECT + // and is 110. LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel << " Count: " << (S32)skin_info.mInvBindMatrix.size() - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + << " Limit:" << (S32)mMaxJointsPerMesh << LL_ENDL; LLSD args; - args["Message"] = "ModelTooManyJoint"; + args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + args["MAX"] = (S32)mMaxJointsPerMesh; mWarningsArray.append(args); } @@ -1135,6 +1264,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); mJointNames.resize(skin_idx + 1); + mJointUsage.resize(skin_idx + 1); mValidJointsCount.resize(skin_idx + 1, 0); } @@ -1144,7 +1274,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; JointNodeData& data = joints_data[joint]; data.mNodeIdx = joint; data.mJointListIdx = i; @@ -1173,21 +1303,8 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } } - if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) - { - LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" - << " Count: " << mValidJointsCount[skin_idx] - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; - - LLSD args; - args["Message"] = "SkinJointsOverLimit"; - args["SKIN_INDEX"] = (S32)skin_idx; - args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; - mWarningsArray.append(args); - } - // Go over viewer joints and build overrides + // This is needed because gltf skeleton doesn't necessarily match viewer's skeleton. glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { @@ -1197,8 +1314,10 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); + + // Viewer supports a limited set of joints, mark them as legal bool legal_joint = false; if (mJointMap.find(legal_name) != mJointMap.end()) { @@ -1210,6 +1329,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { mJointNames[skin_idx].emplace_back(); } + mJointUsage[skin_idx].push_back(0); // Compute bind matrices @@ -1223,8 +1343,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } else if (inverse_count > i) { - // Transalte existing bind matrix to viewer's skeleton - // todo: probably should be 'to viewer's overriden skeleton' + // Transalte existing bind matrix to viewer's overriden skeleton glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); @@ -1232,20 +1351,20 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { // If bind matrices aren't present (they are optional in gltf), // assume an identy matrix - // todo: find a model with this, might need to use rotated matrix + // todo: find a model with this, might need to use YZ rotated matrix glm::mat4 inv_bind(1.0f); glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } @@ -1257,7 +1376,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); newInverse.setTranslation(original_joint_transform.getTranslation()); - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) @@ -1268,6 +1387,21 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mJointsFromNode.push_front(legal_name); } } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints < joint_count) + { + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx + << " defines " << joint_count + << " joints, but only " << valid_joints + << " were recognized and are compatible." << LL_ENDL; + LLSD args; + args["Message"] = "SkinUsupportedJoints"; + args["SKIN_INDEX"] = skin_idx; + args["JOINT_COUNT"] = joint_count; + args["LEGAL_COUNT"] = valid_joints; + mWarningsArray.append(args); + } } void LLGLTFLoader::populateJointGroups() @@ -1279,109 +1413,6 @@ void LLGLTFLoader::populateJointGroups() } } - -S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const -{ - S32 source_joint_node = gltf_skin.mJoints[source_joint]; - S32 root_node = source_joint_node; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - if (mJointMap.find(jointNode.mName) != mJointMap.end()) - { - return i; - } - break; - } - } - } while (root_node != found_node); - - return -1; -} - -S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - - if (mJointMap.find(jointNode.mName) != mJointMap.end()) - { - std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } - } while (root_node != found_node); - - return root_node; -} - -S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = 0; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } while (root_node != found_node); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; - LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; - return root_node; -} - -S32 LLGLTFLoader::findParentNode(S32 node) const -{ - S32 size = (S32)mGLTFAsset.mNodes.size(); - for (S32 i = 0; i < size; i++) - { - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; - std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); - if (it != jointNode.mChildren.end()) - { - return i; - } - } - return -1; -} - void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) { JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; @@ -1396,7 +1427,6 @@ void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string & void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { - glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); if (found_node != names_to_nodes.end()) @@ -1434,13 +1464,30 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::quat rotation; glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + // Viewer allows overrides, which are base joint with applied translation override. + // fortunately normal bones use only translation, without rotation or scale node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); - glm::mat4 override_joint = node.mOverrideMatrix; - override_joint = glm::scale(override_joint, viewer_data.mScale); + glm::mat4 overriden_joint = node.mOverrideMatrix; - rest = parent_rest * override_joint; - node.mOverrideRestMatrix = rest; + // todo: if gltf bone had rotation or scale, they probably should be saved here + // then applied to bind matrix + rest = parent_rest * overriden_joint; + if (viewer_data.mIsJoint) + { + node.mOverrideRestMatrix = rest; + } + else + { + // This is likely incomplete or even wrong. + // Viewer Collision bones specify rotation and scale. + // Importer should apply rotation and scale to this matrix and save as needed + // then subsctruct them from bind matrix + // Todo: get models that use collision bones, made by different programs + + overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); + node.mOverrideRestMatrix = parent_support_rest * overriden_joint; + } } else { @@ -1574,7 +1621,7 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) for (S32 i= 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; - auto joint_node = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &joint_node = mGLTFAsset.mNodes[joint]; // todo: we are doing this search thing everywhere, // just pre-translate every joint @@ -1604,6 +1651,47 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } +void LLGLTFLoader::checkGlobalJointUsage() +{ + // Check if some joints remained unused + for (S32 skin_idx = 0; skin_idx < (S32)mGLTFAsset.mSkins.size(); ++skin_idx) + { + const LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_idx]; + S32 joint_count = (S32)gltf_skin.mJoints.size(); + S32 used_joints = 0; + for (S32 i = 0; i < joint_count; ++i) + { + S32 joint = gltf_skin.mJoints[i]; + if (mJointUsage[skin_idx][i] == 0) + { + // Joint is unused, log it + LL_INFOS("GLTF_DEBUG") << "Joint " << mJointNames[skin_idx][i] + << " in skin " << skin_idx << " is unused." << LL_ENDL; + } + else + { + used_joints++; + } + } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints > used_joints) + { + S32 unsed_joints = valid_joints - used_joints; + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx + << " declares " << valid_joints + << " valid joints, of them " << unsed_joints + << " remained unused" << LL_ENDL; + LLSD args; + args["Message"] = "SkinUnusedJoints"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = valid_joints; + args["USED_COUNT"] = used_joints; + mWarningsArray.append(args); + } + } +} + std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) @@ -1705,6 +1793,27 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } args["EXT"] = ext; mWarningsArray.append(args); + + LL_WARNS("GLTF_IMPORT") << "Model uses unsupported extension: " << ext << LL_ENDL; + } +} + +size_t LLGLTFLoader::getSuffixPosition(const std::string &label) +{ + if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) + { + return label.rfind('_'); + } + return -1; +} + +std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Mesh& mesh) +{ + size_t ext_pos = getSuffixPosition(mesh.mName); + if (ext_pos != -1) + { + return mesh.mName.substr(0, ext_pos); } + return mesh.mName; } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 048f7df7f5..e8b91996c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -71,20 +71,29 @@ class LLGLTFLoader : public LLModelLoader typedef std::map <S32, JointNodeData> joints_data_map_t; typedef std::map <std::string, S32> joints_name_to_node_map_t; + class LLGLTFImportMaterial : public LLImportMaterial + { + public: + std::string name; + LLGLTFImportMaterial() = default; + LLGLTFImportMaterial(const LLImportMaterial& mat, const std::string& n) : LLImportMaterial(mat), name(n) {} + }; + LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map<std::string, std::string> &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - std::vector<LLJointData> viewer_skeleton); //, - //bool preprocess ); + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map<std::string, std::string, std::less<>> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode, + std::vector<LLJointData> viewer_skeleton); //, + //bool preprocess ); virtual ~LLGLTFLoader(); virtual bool OpenFile(const std::string &filename); @@ -101,9 +110,8 @@ class LLGLTFLoader : public LLModelLoader protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; - bool mGltfLoaded; + bool mGltfLoaded = false; bool mApplyXYRotation = false; - U32 mGeneratedModelLimit; // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to @@ -116,6 +124,7 @@ protected: bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; joint_names_t mJointNames; // empty string when no legal name for a given idx + std::vector<std::vector<S32>> mJointUsage; // detect and warn about unsed joints // what group a joint belongs to. // For purpose of stripping unused groups when joints are over limit. @@ -124,25 +133,29 @@ protected: std::string mGroup; std::string mParentGroup; }; - typedef std::map<std::string, JointGroups> joint_to_group_map_t; + typedef std::map<std::string, JointGroups, std::less<> > joint_to_group_map_t; joint_to_group_map_t mJointGroups; // per skin joint count, needs to be tracked for the sake of limits check. std::vector<S32> mValidJointsCount; + // Cached material information + typedef std::map<S32, LLGLTFImportMaterial> MaterialCache; + MaterialCache mMaterialCache; + private: bool parseMeshes(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); - bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const; - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); + bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); + LLGLTFImportMaterial processMaterial(S32 material_index, S32 fallback_index); + std::string processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name); + bool validateTextureIndex(S32 texture_index, S32& source_index); + std::string generateMaterialName(S32 material_index, S32 fallback_index = -1); + bool populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); void populateJointsFromSkin(S32 skin_idx); void populateJointGroups(); - void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); - S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; - S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; - S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint - S32 findParentNode(S32 node) const; + void addModelToScene(LLModel* pModel, const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); void buildJointGroup(LLJointData& viewer_data, const std::string& parent_group); void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; @@ -150,11 +163,15 @@ private: glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); + void checkGlobalJointUsage(); std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); void notifyUnsupportedExtension(bool unsupported); + static size_t getSuffixPosition(const std::string& label); + static std::string getLodlessLabel(const LL::GLTF::Mesh& mesh); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here @@ -192,10 +209,6 @@ private: // bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit); - static std::string getElementLabel(gltfElement *element); - static size_t getSuffixPosition(std::string label); - static std::string getLodlessLabel(gltfElement *element); - static std::string preprocessGLTF(std::string filename); */ diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index aea4492223..f4b2fdfdf7 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2245,10 +2245,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string) // Callback for LLError::LLUserWarningMsg void errorHandler(const std::string& title_string, const std::string& message_string, S32 code) { - if (!message_string.empty()) - { - OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); - } + // message is going to hang viewer, create marker first switch (code) { case LLError::LLUserWarningMsg::ERROR_OTHER: @@ -2256,6 +2253,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st break; case LLError::LLUserWarningMsg::ERROR_BAD_ALLOC: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_BAD_ALLOC); + // When system run out of memory and errorHandler gets called from a thread, + // main thread might keep going while OSMessageBox freezes the caller. + // Todo: handle it better, but for now disconnect to avoid making things worse + gDisconnected = true; break; case LLError::LLUserWarningMsg::ERROR_MISSING_FILES: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES); @@ -2263,6 +2264,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st default: break; } + if (!message_string.empty()) + { + OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + } } void LLAppViewer::initLoggingAndGetLastDuration() @@ -5679,6 +5684,27 @@ void LLAppViewer::forceErrorThreadCrash() thread->start(); } +void LLAppViewer::forceExceptionThreadCrash() +{ + class LLCrashTestThread : public LLThread + { + public: + + LLCrashTestThread() : LLThread("Crash logging test thread") + { + } + + void run() + { + throw std::exception(); + } + }; + + LL_WARNS() << "This is a deliberate exception in a thread" << LL_ENDL; + LLCrashTestThread* thread = new LLCrashTestThread(); + thread->start(); +} + void LLAppViewer::initMainloopTimeout(std::string_view state, F32 secs) { if (!mMainloopTimeout) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b4b8e5bac3..bafe952659 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -175,6 +175,7 @@ public: virtual void forceErrorCoroprocedureCrash(); virtual void forceErrorWorkQueueCrash(); virtual void forceErrorThreadCrash(); + virtual void forceExceptionThreadCrash(); // The list is found in app_settings/settings_files.xml // but since they are used explicitly in code, diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index b94c31ec04..5ee93be061 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks() getChild<LLUICtrl>("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); } -std::map <std::string, std::string> LLFloaterBvhPreview::getJointAliases() +std::map<std::string, std::string, std::less<>> LLFloaterBvhPreview::getJointAliases() { LLPointer<LLVOAvatar> av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); return av->getJointAliases(); @@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild() ELoadStatus load_status = E_ST_OK; S32 line_number = 0; - std::map<std::string, std::string> joint_alias_map = getJointAliases(); + auto joint_alias_map = getJointAliases(); loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); std::string status = getString(STATUS[load_status]); diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index ae64521492..bb69ab65ef 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -108,7 +108,7 @@ public: S32 status, LLExtStat ext_status); private: void setAnimCallbacks() ; - std::map <std::string, std::string> getJointAliases(); + std::map<std::string, std::string, std::less<>> getJointAliases(); protected: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 84b9cb18f8..bd33ab2dbe 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1487,7 +1487,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) { // Populate table - std::map<std::string, std::string> joint_alias_map; + std::map<std::string, std::string, std::less<>> joint_alias_map; mModelPreview->getJointAliases(joint_alias_map); S32 conflicts = 0; diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 378f5fdf91..5b3ac53d51 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -1333,15 +1333,6 @@ const std::string LLMaterialEditor::buildMaterialDescription() desc << mNormalName; } - // trim last char if it's a ',' in case there is no normal texture - // present and the code above inserts one - // (no need to check for string length - always has initial string) - std::string::iterator iter = desc.str().end() - 1; - if (*iter == ',') - { - desc.str().erase(iter); - } - // sanitize the material description so that it's compatible with the inventory // note: split this up because clang doesn't like operating directly on the // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a @@ -1349,6 +1340,15 @@ const std::string LLMaterialEditor::buildMaterialDescription() std::string inv_desc = desc.str(); LLInventoryObject::correctInventoryName(inv_desc); + // trim last char if it's a ',' in case there is no normal texture + // present and the code above inserts one + // (no need to check for string length - always has initial string) + std::string::iterator iter = inv_desc.end() - 1; + if (*iter == ',') + { + inv_desc.erase(iter); + } + return inv_desc; } @@ -2685,10 +2685,8 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c // so we can include everything if (stripped_uri.length() > 0) { - // example "DamagedHelmet: base layer" + // example "base layer" return STRINGIZE( - mMaterialNameShort << - ": " << stripped_uri << " (" << texture_type << @@ -2697,28 +2695,17 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c } else // uri doesn't include the type (because the uri is empty) - // so we must reorganize the string a bit to include the name - // and an explicit name type + // include an explicit name type { - // example "DamagedHelmet: (Emissive)" - return STRINGIZE( - mMaterialNameShort << - " (" << - texture_type << - ")" - ); + // example "Emissive" + return texture_type; } } else - // uri includes the type so just use it directly with the - // name of the material + // uri includes the type so just use it directly { - return STRINGIZE( - // example: AlienBust: normal_layer - mMaterialNameShort << - ": " << - stripped_uri - ); + // example: "normal_layer" + return stripped_uri; } } diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e7e95034b2..142a3dac39 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2395,6 +2395,11 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p // might be good idea to turn mesh into pointer to avoid making a copy mesh.mVolume = NULL; } + { + // make sure skin info is not removed from list while we are decreasing reference count + LLMutexLock lock(mSkinMapMutex); + skin_info = nullptr; + } return MESH_OK; } } @@ -2694,10 +2699,14 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) S32 instance_num = 0; - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + // Handle models, ignore submodels for now. + // Probably should pre-sort by mSubmodelID instead of running twice. + // Note: mInstance should be sorted by model name for the sake of + // deterministic order. + for (auto& iter : mInstance) { LLMeshUploadData data; - data.mBaseModel = iter->first; + data.mBaseModel = iter.first; if (data.mBaseModel->mSubmodelID) { @@ -2706,7 +2715,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) continue; } - LLModelInstance& first_instance = *(iter->second.begin()); + LLModelInstance& first_instance = *(iter.second.begin()); for (S32 i = 0; i < 5; i++) { data.mModel[i] = first_instance.mLOD[i]; @@ -2740,7 +2749,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); @@ -2753,8 +2762,8 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) } // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); + for (instance_list::iterator instance_iter = iter.second.begin(); + instance_iter != iter.second.end(); ++instance_iter) { @@ -2852,10 +2861,11 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) } } - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + // Now handle the submodels. + for (auto& iter : mInstance) { LLMeshUploadData data; - data.mBaseModel = iter->first; + data.mBaseModel = iter.first; if (!data.mBaseModel->mSubmodelID) { @@ -2864,7 +2874,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) continue; } - LLModelInstance& first_instance = *(iter->second.begin()); + LLModelInstance& first_instance = *(iter.second.begin()); for (S32 i = 0; i < 5; i++) { data.mModel[i] = first_instance.mLOD[i]; @@ -2898,7 +2908,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); @@ -2911,8 +2921,8 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) } // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); + for (instance_list::iterator instance_iter = iter.second.begin(); + instance_iter != iter.second.end(); ++instance_iter) { diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 0847c29d0d..4c3901408f 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -674,7 +674,22 @@ public: typedef std::vector<LLModelInstance> instance_list; instance_list mInstanceList; - typedef std::map<LLPointer<LLModel>, instance_list> instance_map; + // Upload should happen in deterministic order, so sort instances by model name. + struct LLUploadModelInstanceLess + { + inline bool operator()(const LLPointer<LLModel>& a, const LLPointer<LLModel>& b) const + { + if (a.isNull() || b.isNull()) + { + llassert(false); // We are uploading these models, they shouldn't be null. + return true; + } + // Note: probably can sort by mBaseModel->mSubmodelID here as well to avoid + // running over the list twice in wholeModelToLLSD. + return a->mLabel < b->mLabel; + } + }; + typedef std::map<LLPointer<LLModel>, instance_list, LLUploadModelInstanceLess> instance_map; instance_map mInstance; LLMutex* mMutex; diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index fc0a3ec58f..ac697383e2 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -164,10 +164,12 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mPhysicsSearchLOD(LLModel::LOD_PHYSICS) , mResetJoints(false) , mModelNoErrors(true) + , mLoading(false) + , mModelLoader(nullptr) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) - , mImporterDebug(LLCachedControl<bool>(gSavedSettings, "ImporterDebug", false)) + , mImporterDebug(LLCachedControl<bool>(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; mCameraDistance = 0.f; @@ -176,11 +178,9 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mCameraZoom = 1.f; mTextureName = 0; mPreviewLOD = 0; - mModelLoader = NULL; mMaxTriangleLimit = 0; mDirty = false; mGenLOD = false; - mLoading = false; mLookUpLodFiles = false; mLoadState = LLModelLoader::STARTING; mGroup = 0; @@ -212,6 +212,7 @@ LLModelPreview::~LLModelPreview() { mModelLoader->shutdown(); mModelLoader = NULL; + mLoading = false; } if (mPreviewAvatar) @@ -692,7 +693,7 @@ void LLModelPreview::saveUploadData(const std::string& filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position, - false, true, instance.mModel->mSubmodelID); + LLModel::WRITE_BINARY, true, instance.mModel->mSubmodelID); data["mesh"][instance.mModel->mLocalID] = str.str(); } @@ -754,6 +755,10 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable LL_WARNS() << out.str() << LL_ENDL; LLFloaterModelPreview::addStringToLog(out, true); assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS); + if (mModelLoader == nullptr) + { + mLoading = false; + } return; } @@ -781,7 +786,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - std::map<std::string, std::string> joint_alias_map; + std::map<std::string, std::string, std::less<>> joint_alias_map; getJointAliases(joint_alias_map); LLHandle<LLModelPreview> preview_handle = getHandle(); @@ -807,6 +812,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), gSavedSettings.getBOOL("ImporterPreprocessDAE")); } else @@ -827,6 +833,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), viewer_skeleton); } @@ -1812,7 +1819,7 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) { - LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; + LL_DEBUGS("Upload") << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; // Allow LoD from -1 to LLModel::LOD_PHYSICS if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) { @@ -1889,6 +1896,12 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d mMaxTriangleLimit = base_triangle_count; + // For logging purposes + S32 meshes_processed = 0; + S32 meshes_simplified = 0; + S32 meshes_sloppy_simplified = 0; + S32 meshes_fail_count = 0; + // Build models S32 start = LLModel::LOD_HIGH; @@ -1898,7 +1911,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d { start = which_lod; end = which_lod; - } + }; for (S32 lod = start; lod >= end; --lod) { @@ -1961,6 +1974,11 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d const LLVolumeFace &face = base->getVolumeFace(face_idx); LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); new_face = face; + meshes_fail_count++; + } + else + { + meshes_simplified++; } } } @@ -1973,7 +1991,18 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY) < 0) { // Sloppy failed and returned an invalid model - genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL) < 0) + { + meshes_fail_count++; + } + else + { + meshes_simplified++; + } + } + else + { + meshes_sloppy_simplified++; } } } @@ -2073,25 +2102,28 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); } - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << precise_ratio << " simplified using per model method." << LL_ENDL; + meshes_simplified++; } else { - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << sloppy_ratio << " sloppily simplified using per model method." << LL_ENDL; + meshes_sloppy_simplified++; } } else { - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << precise_ratio << " simplified using per model method." << LL_ENDL; + meshes_simplified++; } } @@ -2105,6 +2137,8 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d //copy material list target_model->mMaterialList = base->mMaterialList; + meshes_processed++; + if (!validate_model(target_model)) { LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL; @@ -2134,6 +2168,11 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d } } } + + LL_INFOS("Upload") << "LOD " << which_lod << ", Mesh optimizer processed meshes : " << meshes_processed + <<" simplified: " << meshes_simplified + << ", slopily simplified: " << meshes_sloppy_simplified + << ", failures: " << meshes_fail_count << LL_ENDL; } void LLModelPreview::updateStatusMessages() diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 0086a9a86d..25dd37590d 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -276,7 +276,7 @@ LLRender::eTexIndex LLPanelFace::getMatTextureChannel() return LLRender::NORMAL_MAP; break; case MATTYPE_SPECULAR: // "Shininess (specular)" - if (getCurrentNormalMap().notNull()) + if (getCurrentSpecularMap().notNull()) return LLRender::SPECULAR_MAP; break; } diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp index 910509928d..7f5076bd56 100644 --- a/indra/newview/llreflectionmap.cpp +++ b/indra/newview/llreflectionmap.cpp @@ -177,7 +177,7 @@ void LLReflectionMap::autoAdjustOrigin() mPriority = 1; mOrigin.load3(mViewerObject->getPositionAgent().mV); - if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject)->getReflectionProbeIsBox()) + if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject.get())->getReflectionProbeIsBox()) { LLVector3 s = mViewerObject->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f)); mRadius = s.magVec(); diff --git a/indra/newview/llreflectionmap.h b/indra/newview/llreflectionmap.h index d20bba7059..a818793550 100644 --- a/indra/newview/llreflectionmap.h +++ b/indra/newview/llreflectionmap.h @@ -124,7 +124,7 @@ public: LLSpatialGroup* mGroup = nullptr; // viewer object this probe is tracking (if any) - LLViewerObject* mViewerObject = nullptr; + LLPointer<LLViewerObject> mViewerObject = nullptr; // what priority should this probe have (higher is higher priority) // currently only 0 or 1 diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp index f2abc7b8b7..3391b7adf7 100644 --- a/indra/newview/llreflectionmapmanager.cpp +++ b/indra/newview/llreflectionmapmanager.cpp @@ -1146,7 +1146,7 @@ void LLReflectionMapManager::updateUniforms() { if (refmap->mViewerObject && refmap->mViewerObject->getVolume()) { // have active manual probes live-track the object they're associated with - LLVOVolume* vobj = (LLVOVolume*)refmap->mViewerObject; + LLVOVolume* vobj = (LLVOVolume*)refmap->mViewerObject.get(); refmap->mOrigin.load3(vobj->getPositionAgent().mV); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 1c9a892a4f..44157d2d2d 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -289,6 +289,7 @@ void force_error_coroutine_crash(); void force_error_coroprocedure_crash(); void force_error_work_queue_crash(); void force_error_thread_crash(); +void force_exception_thread_crash(); void handle_force_delete(); void print_object_info(); @@ -2663,6 +2664,15 @@ class LLAdvancedForceErrorThreadCrash : public view_listener_t } }; +class LLAdvancedForceExceptionThreadCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_exception_thread_crash(); + return true; + } +}; + class LLAdvancedForceErrorDisconnectViewer : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -8696,6 +8706,11 @@ void force_error_thread_crash() LLAppViewer::instance()->forceErrorThreadCrash(); } +void force_exception_thread_crash() +{ + LLAppViewer::instance()->forceExceptionThreadCrash(); +} + class LLToolsUseSelectionForGrid : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9898,6 +9913,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedForceErrorCoroprocedureCrash(), "Advanced.ForceErrorCoroprocedureCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorWorkQueueCrash(), "Advanced.ForceErrorWorkQueueCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); + view_listener_t::addMenu(new LLAdvancedForceExceptionThreadCrash(), "Advanced.ForceExceptionThreadCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); // Advanced (toplevel) diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 9743ec0c59..84195997c3 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -94,7 +94,7 @@ class LLFileEnableUploadModel : public view_listener_t bool handleEvent(const LLSD& userdata) { LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) LLFloaterReg::findInstance("upload_model"); - if (fmp && fmp->isModelLoading()) + if (fmp && !fmp->isDead() && fmp->isModelLoading()) { return false; } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index d9a3ec3004..dcba891f9f 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6306,13 +6306,13 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint *LLVOAvatar::getJoint( const std::string &name ) +LLJoint* LLVOAvatar::getJoint(std::string_view name) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = NULL; + LLJoint* jointp = nullptr; - if (iter == mJointMap.end() || iter->second == NULL) + if (iter == mJointMap.end() || iter->second == nullptr) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint *LLVOAvatar::getJoint( const std::string &name ) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index ab27c5752d..9eb8d3f880 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(const std::string &name); + virtual LLJoint* getJoint(std::string_view name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 90ff4067f2..ebba9ba291 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -697,17 +697,17 @@ void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) } // virtual -LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) +LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) { std::lock_guard lock(mJointMapMutex); - LLJoint *jointp = NULL; + LLJoint* jointp = nullptr; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f9bea41b1d..f7cd974ab0 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -90,7 +90,7 @@ public: /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(const std::string &name); + /*virtual*/ LLJoint* getJoint(std::string_view name); /*virtual*/ void renderJoints(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index f8543ba1aa..a86b9c7da2 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,6 +61,7 @@ <string name="ParsingErrorNoRoot">Document has no root</string> <string name="ParsingErrorNoScene">Document has no visual_scene</string> <string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string> + <string name="UnknownException">Importer crashed while processing [FILENAME], if you encounter this and file is valid, please report the issue to Second Life Support. Exception: [EXCEPTION].</string> <!-- GLTF specific messages --> <string name="NoScenesFound">No scenes defined in GLTF file</string> @@ -71,12 +72,16 @@ <string name="TextureFound">Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME]</string> <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored</string> <string name="UnsupportedExtension">Unable to load model, unsupported extension: [EXT]</string> - <string name="TooManyMeshParts">Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT]</string> <string name="FailedToCreateTempFile">Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE]</string> <string name="SkinJointsOverLimit">Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis.</string> - <string name="ModelTooManyJoint">Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail</string> + <string name="SkinUsupportedJoints">Skin [SKIN_INDEX] defines [JOINT_COUNT] joints, but only [LEGAL_COUNT] were recognized and are compatible</string> + <string name="SkinUnusedJoints">Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, of them only [USED_COUNT] were used</string> + <string name="ModelTooManyJoints">Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail</string> + <string name="ModelSplitPrimitive">Too many vertices in primitive [MODEL_NAME], it was split into [FACE_COUNT] faces</string> + <string name="ModelTooManySubmodels">Model [MODEL_NAME] contains [SUBMODEL_COUNT] generated mesh parts, parts were trimmed to [SUBMODEL_LIMIT]</string> <string name="ParsingErrorMissingBuffer">Buffer is either missing or empty [BUFFER_NAME].</string> <string name="ParsingErrorMissingBufferBin">Buffer is either missing or empty. Check presence of [BUFFER_URI] file.</string> + <string name="ParsingErrorException">Parser failed to process [FILENAME], file might be corrupt, incomplete or protected from reading. Exception: [EXCEPTION].</string> <panel follows="top|left" @@ -1421,7 +1426,7 @@ word_wrap="true"> </text_editor> <check_box - control_name="ImporterDebug" + control_name="ImporterDebugVerboseLogging" follows="top|left" top_pad="9" left="6" diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index 84252fe32c..ce5ab54d26 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -67,7 +67,7 @@ decimal_digits="1" stat="frametimejitter95"/> <stat_bar name="framet_jitter_stddev" - label="frametime jitter standard deviation" + label="frametime jitter std dev" decimal_digits="1" stat="frametimejitterstddev"/> <stat_bar name="framet_99th" @@ -79,7 +79,7 @@ decimal_digits="1" stat="frametime95"/> <stat_bar name="framet_stddev" - label="frametime standard deviation" + label="frametime std dev" decimal_digits="1" stat="frametimestddev"/> <stat_bar name="framet_events" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 343f0c0059..1be1864b0a 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2808,12 +2808,18 @@ function="World.EnvPreset" function="Advanced.ForceErrorWorkQueueCrash" /> </menu_item_call> <menu_item_call - label="Force a Crash in a Thread" - name="Force a Crash in a Thread"> + label="Force an LLError Crash in a Thread" + name="Force an LLError Crash in a Thread"> <menu_item_call.on_click function="Advanced.ForceErrorThreadCrash" /> </menu_item_call> <menu_item_call + label="Force an Exception Crash in a Thread" + name="Force an Exception Crash in a Thread"> + <menu_item_call.on_click + function="Advanced.ForceExceptionThreadCrash" /> + </menu_item_call> + <menu_item_call label="Force Disconnect Viewer" name="Force Disconnect Viewer"> <menu_item_call.on_click |