summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llappearance/llavatarappearance.h4
-rw-r--r--indra/llappearance/lltexlayer.cpp29
-rw-r--r--indra/llcharacter/llbvhloader.cpp6
-rw-r--r--indra/llcharacter/llbvhloader.h2
-rw-r--r--indra/llcharacter/llcharacter.cpp7
-rw-r--r--indra/llcharacter/llcharacter.h2
-rw-r--r--indra/llcommon/llthread.cpp105
-rw-r--r--indra/llcommon/llthread.h5
-rw-r--r--indra/llprimitive/lldaeloader.cpp11
-rw-r--r--indra/llprimitive/lldaeloader.h30
-rw-r--r--indra/llprimitive/llmodel.cpp12
-rw-r--r--indra/llprimitive/llmodel.h10
-rw-r--r--indra/llprimitive/llmodelloader.cpp141
-rw-r--r--indra/llprimitive/llmodelloader.h8
-rw-r--r--indra/llrender/llimagegl.cpp27
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/app_settings/settings.xml15
-rw-r--r--indra/newview/gltf/llgltfloader.cpp719
-rw-r--r--indra/newview/gltf/llgltfloader.h67
-rw-r--r--indra/newview/llappviewer.cpp34
-rw-r--r--indra/newview/llappviewer.h1
-rw-r--r--indra/newview/llfloaterbvhpreview.cpp4
-rw-r--r--indra/newview/llfloaterbvhpreview.h2
-rw-r--r--indra/newview/llfloatermodelpreview.cpp2
-rw-r--r--indra/newview/llmaterialeditor.cpp45
-rw-r--r--indra/newview/llmeshrepository.cpp34
-rw-r--r--indra/newview/llmeshrepository.h17
-rw-r--r--indra/newview/llmodelpreview.cpp61
-rw-r--r--indra/newview/llpanelface.cpp2
-rw-r--r--indra/newview/llreflectionmap.cpp2
-rw-r--r--indra/newview/llreflectionmap.h2
-rw-r--r--indra/newview/llreflectionmapmanager.cpp2
-rw-r--r--indra/newview/llviewermenu.cpp16
-rw-r--r--indra/newview/llviewermenufile.cpp2
-rw-r--r--indra/newview/llvoavatar.cpp8
-rw-r--r--indra/newview/llvoavatar.h2
-rw-r--r--indra/newview/llvoavatarself.cpp6
-rw-r--r--indra/newview/llvoavatarself.h2
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml11
-rw-r--r--indra/newview/skins/default/xui/en/floater_stats.xml4
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml10
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