diff options
67 files changed, 3486 insertions, 997 deletions
| diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 198785d39b..4bf2af644a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -218,8 +218,10 @@ jobs:                prefix=${ba[0]}                if [ "$prefix" == "project" ]; then                    IFS='_' read -ra prj <<< "${ba[1]}" +                  prj_str="${prj[*]}"                    # uppercase first letter of each word -                  export viewer_channel="Second Life Project ${prj[*]^}" +                  capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') +                  export viewer_channel="Second Life Project $capitalized"                elif  [[ "$prefix" == "release" || "$prefix" == "main" ]];                then                    export viewer_channel="Second Life Release" @@ -455,7 +457,6 @@ jobs:            prerelease: true            generate_release_notes: true            target_commitish: ${{ github.sha }} -          previous_tag: release            append_body: true            fail_on_unmatched_files: true            files: | diff --git a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md index 690146265c..afb3d5b337 100644 --- a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md +++ b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md @@ -103,13 +103,11 @@ Perform the testing procedure on both sets of cubes.  Ensure that debug setting `MediaFirstClickInteract` is set to `4` -This test case requires two pairs of cubes, and the second pair must be deeded or set to a group that your testing account is a member of, but does not have set as active at the beginning of the test. As long as the second set of cubes is set to a group that your primary test account is a member of, the avatar that owns them does not matter. +This test case requires two cubes, and the second cube must be deeded or set to a group that your testing account is a member of. As long as the second set of cubes is set to a group that your test account is a member of, the avatar that owns them does not matter. -1. Perform the testing procedure on both sets of cubes. -2. Activate the group that the second set of cubes is set / deeded to -3. Perform the testing procedure on both sets of cubes once more. +Perform the testing procedure on both sets of cubes. -**Expected observations:** Both cubes owned by your primary testing account will not react to mouse cursor hover events and clicks without needing a focus click. Cube A set to group will react to mouse cursor hover events and clicks without needing a focus click, but Cube B will not. +**Expected observations:** The cube owned by your primary account will not react to mouse cursor hover events and clicks without needing a focus click. The cube set to group will react to mouse cursor hover events and clicks without needing a focus click.  ### Case 5 (MEDIA_FIRST_CLICK_FRIEND) @@ -144,16 +142,16 @@ Note: This requires the avatar that is performing the tests to physically be in  ### Case 7 (MEDIA_FIRST_CLICK_ANY) (optional) -Ensure that debug setting `MediaFirstClickInteract` is set to `31` +Ensure that debug setting `MediaFirstClickInteract` is set to `32767`  Repeat test cases 1-6.  1. Test case 1 should fail  2. Test cases 2-6 should pass -### Case 8 (MEDIA_FIRST_CLICK_ALL) (optional) +### Case 8 (MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG) (optional) -Ensure that debug setting `MediaFirstClickInteract` is set to `1073741824` +Ensure that debug setting `MediaFirstClickInteract` is set to `65535`  Repeat test cases 1-6, there is no pass/fail for this run. diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index c3be8bc78e..6744c8d8a4 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES      llavatarjoint.cpp      llavatarjointmesh.cpp      lldriverparam.cpp +    lljointdata.h      lllocaltextureobject.cpp      llpolyskeletaldistortion.cpp      llpolymesh.cpp diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 3d66809ed6..dab18c240d 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,16 +29,17 @@  #include "llavatarappearance.h"  #include "llavatarappearancedefines.h"  #include "llavatarjointmesh.h" +#include "lljointdata.h"  #include "llstl.h"  #include "lldir.h"  #include "llpolymorph.h"  #include "llpolymesh.h"  #include "llpolyskeletaldistortion.h" -#include "llstl.h"  #include "lltexglobalcolor.h"  #include "llwearabledata.h"  #include "boost/bind.hpp"  #include "boost/tokenizer.hpp" +#include "v4math.h"  using namespace LLAvatarAppearanceDefines; @@ -71,11 +72,13 @@ public:          mChildren.clear();      }      bool parseXml(LLXmlTreeNode* node); +    glm::mat4 getJointMatrix();  private:      std::string mName;      std::string mSupport;      std::string mAliases; +    std::string mGroup;      bool mIsJoint;      LLVector3 mPos;      LLVector3 mEnd; @@ -106,10 +109,16 @@ public:      S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; }  private: +    typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t; +    static void getJointMatricesAndHierarhy( +        LLAvatarBoneInfo* bone_info, +        LLJointData& data, +        const glm::mat4& parent_mat); + +private:      S32 mNumBones;      S32 mNumCollisionVolumes;      LLAvatarAppearance::joint_alias_map_t mJointAliasMap; -    typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;      bone_info_list_t mBoneInfoList;  }; @@ -1598,6 +1607,15 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)          mSupport = "base";      } +    // Skeleton has 133 bones, but shader only allows 110 (LL_MAX_JOINTS_PER_MESH_OBJECT) +    // Groups can be used by importer to cut out unused groups of joints +    static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group"); +    if (!node->getFastAttributeString(group_string, mGroup)) +    { +        LL_WARNS() << "Bone without group " << mName << LL_ENDL; +        mGroup = "global"; +    } +      if (mIsJoint)      {          static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot"); @@ -1623,6 +1641,21 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)      return true;  } + +glm::mat4 LLAvatarBoneInfo::getJointMatrix() +{ +    glm::mat4 mat(1.0f); +    // 1. Scaling +    mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2])); +    // 2. Rotation (Euler angles rad) +    mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0)); +    mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0)); +    mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1)); +    // 3. Position +    mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2])); +    return mat; +} +  //-----------------------------------------------------------------------------  // LLAvatarSkeletonInfo::parseXml()  //----------------------------------------------------------------------------- @@ -1653,6 +1686,25 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node)      return true;  } +void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( +    LLAvatarBoneInfo* bone_info, +    LLJointData& data, +    const glm::mat4& parent_mat) +{ +    data.mName = bone_info->mName; +    data.mJointMatrix = bone_info->getJointMatrix(); +    data.mScale = glm::vec3(bone_info->mScale[0], bone_info->mScale[1], bone_info->mScale[2]); +    data.mRotation = bone_info->mRot; +    data.mRestMatrix = parent_mat * data.mJointMatrix; +    data.mIsJoint = bone_info->mIsJoint; +    data.mGroup = bone_info->mGroup; +    for (LLAvatarBoneInfo* child_info : bone_info->mChildren) +    { +        LLJointData& child_data = data.mChildren.emplace_back(); +        getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix); +    } +} +  //Make aliases for joint and push to map.  void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info)  { @@ -1714,6 +1766,16 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases      return mJointAliasMap;  } +void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const +{ +    glm::mat4 identity(1.f); +    for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList) +    { +        LLJointData& child_data = data.emplace_back(); +        LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity); +    } +} +  //-----------------------------------------------------------------------------  // parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 99bc5eeef5..84cb42056a 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -34,6 +34,7 @@  #include "lltexlayer.h"  #include "llviewervisualparam.h"  #include "llxmltree.h" +#include "v4math.h"  class LLTexLayerSet;  class LLTexGlobalColor; @@ -41,6 +42,7 @@ class LLTexGlobalColorInfo;  class LLWearableData;  class LLAvatarBoneInfo;  class LLAvatarSkeletonInfo; +class LLJointData;  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  // LLAvatarAppearance @@ -153,7 +155,9 @@ public:      const avatar_joint_list_t& getSkeleton() { return mSkeleton; }      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; +    void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const;  protected:      static bool         parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h new file mode 100644 index 0000000000..2fc26198ee --- /dev/null +++ b/indra/llappearance/lljointdata.h @@ -0,0 +1,66 @@ +/** + * @file lljointdata.h + * @brief LLJointData class for holding individual joint data and skeleton + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLJOINTDATA_H +#define LL_LLJOINTDATA_H + +#include "v4math.h" + +// may be just move LLAvatarBoneInfo +class LLJointData +{ +public: +    std::string mName; +    std::string mGroup; +    glm::mat4 mJointMatrix; +    glm::mat4 mRestMatrix; +    glm::vec3 mScale; +    LLVector3 mRotation; + +    typedef std::vector<LLJointData> bones_t; +    bones_t mChildren; + +    bool mIsJoint; // if not, collision_volume +    enum SupportCategory +    { +        SUPPORT_BASE, +        SUPPORT_EXTENDED +    }; +    SupportCategory mSupport; +    void setSupport(const std::string& support) +    { +        if (support == "extended") +        { +            mSupport = SUPPORT_EXTENDED; +        } +        else +        { +            mSupport = SUPPORT_BASE; +        } +    } +}; + +#endif //LL_LLJOINTDATA_H 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/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/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index c8ece616b2..ea4feec58c 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -182,14 +182,22 @@ void LL::WorkQueueBase::callWork(const Work& work)      }      catch (...)      { -        // Stash any other kind of uncaught exception to be rethrown by main thread. -        LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase " -            << getKey() << 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); }); +        if (getKey() != "mainloop") +        { +            // Stash any other kind of uncaught exception to be rethrown by main thread. +            LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase " +                                << getKey() << 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); }); +        } +        else +        { +            // let main loop crash +            throw; +        }      }  #endif // else LL_WINDOWS  } diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 3d8e02cb16..e13f0bbd96 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,7 +12,6 @@ include(TinyGLTF)  set(llprimitive_SOURCE_FILES      lldaeloader.cpp -    llgltfloader.cpp      llgltfmaterial.cpp      llmaterialid.cpp      llmaterial.cpp @@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES  set(llprimitive_HEADER_FILES      CMakeLists.txt      lldaeloader.h -    llgltfloader.h      llgltfmaterial.h      llgltfmaterial_templates.h      legacy_object_types.h diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..bfcd84a43d 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -204,12 +204,15 @@ LLModel::EModelStatus load_face_from_dom_triangles(      if (idx_stride <= 0          || (pos_source && pos_offset >= idx_stride) +        || (pos_source && pos_offset < 0)          || (tc_source && tc_offset >= idx_stride) -        || (norm_source && norm_offset >= idx_stride)) +        || (tc_source && tc_offset < 0) +        || (norm_source && norm_offset >= idx_stride) +        || (norm_source && norm_offset < 0))      {          // Looks like these offsets should fit inside idx_stride          // Might be good idea to also check idx.getCount()%idx_stride != 0 -        LL_WARNS() << "Invalid pos_offset " << pos_offset <<  ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; +        LL_WARNS() << "Invalid idx_stride " << idx_stride << ", pos_offset " << pos_offset <<  ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL;          return LLModel::BAD_ELEMENT;      } @@ -883,6 +886,7 @@ LLDAELoader::LLDAELoader(      std::map<std::string, std::string, std::less<>>&     jointAliasMap,      U32                 maxJointsPerMesh,      U32                 modelLimit, +    U32                 debugMode,      bool                preprocess)  : LLModelLoader(          filename, @@ -895,8 +899,9 @@ LLDAELoader::LLDAELoader(          jointTransformMap,          jointsFromNodes,          jointAliasMap, -        maxJointsPerMesh), -  mGeneratedModelLimit(modelLimit), +        maxJointsPerMesh, +        modelLimit, +        debugMode),    mPreprocessDAE(preprocess)  {  } @@ -1680,6 +1685,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 +2418,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 dc20feca52..0335011a56 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -59,6 +59,7 @@ public:          std::map<std::string, std::string, std::less<>>& jointAliasMap,          U32                                              maxJointsPerMesh,          U32                                              modelLimit, +        U32                                              debugMode,          bool                                             preprocess);      virtual ~LLDAELoader() ; @@ -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/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp deleted file mode 100644 index 724b1a88b2..0000000000 --- a/indra/llprimitive/llgltfloader.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/** - * @file LLGLTFLoader.cpp - * @brief LLGLTFLoader class implementation - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA - * $/LicenseInfo$ - */ - -#include "llgltfloader.h" - -// Import & define single-header gltf import/export lib -#define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_USE_CPP14  // default is C++ 11 - -// tinygltf by default loads image files using STB -#define STB_IMAGE_IMPLEMENTATION -// to use our own image loading: -// 1. replace this definition with TINYGLTF_NO_STB_IMAGE -// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) - -// tinygltf saves image files using STB -#define STB_IMAGE_WRITE_IMPLEMENTATION -// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) - -// Additionally, disable inclusion of STB header files entirely with -// TINYGLTF_NO_INCLUDE_STB_IMAGE -// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE -#include "tinygltf/tiny_gltf.h" - - -// TODO: includes inherited from dae loader.  Validate / prune - -#include "llsdserialize.h" -#include "lljoint.h" - -#include "llmatrix4a.h" - -#include <boost/regex.hpp> -#include <boost/algorithm/string/replace.hpp> - -static const std::string lod_suffix[LLModel::NUM_LODS] = -{ -    "_LOD0", -    "_LOD1", -    "_LOD2", -    "", -    "_PHYS", -}; - - -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) //, -    //bool                                            preprocess) -    : LLModelLoader( filename, -                     lod, -                     load_cb, -                     joint_lookup_func, -                     texture_load_func, -                     state_cb, -                     opaque_userdata, -                     jointTransformMap, -                     jointsFromNodes, -                     jointAliasMap, -                     maxJointsPerMesh ), -    //mPreprocessGLTF(preprocess), -    mMeshesLoaded(false), -    mMaterialsLoaded(false) -{ -} - -LLGLTFLoader::~LLGLTFLoader() {} - -bool LLGLTFLoader::OpenFile(const std::string &filename) -{ -    tinygltf::TinyGLTF loader; -    std::string        error_msg; -    std::string        warn_msg; -    std::string filename_lc(filename); -    LLStringUtil::toLower(filename_lc); - -    // Load a tinygltf model fom a file. Assumes that the input filename has already been -    // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. -    if (std::string::npos == filename_lc.rfind(".gltf")) -    {  // file is binary -        mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename); -    } -    else -    {  // file is ascii -        mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename); -    } - -    if (!mGltfLoaded) -    { -        if (!warn_msg.empty()) -            LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; -        if (!error_msg.empty()) -            LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; -        return false; -    } - -    mMeshesLoaded = parseMeshes(); -    if (mMeshesLoaded) uploadMeshes(); - -    mMaterialsLoaded = parseMaterials(); -    if (mMaterialsLoaded) uploadMaterials(); - -    return (mMeshesLoaded || mMaterialsLoaded); -} - -bool LLGLTFLoader::parseMeshes() -{ -    if (!mGltfLoaded) return false; - -    // 2022-04 DJH Volume params from dae example. TODO understand PCODE -    LLVolumeParams volume_params; -    volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - -    for (tinygltf::Mesh mesh : mGltfModel.meshes) -    { -        LLModel *pModel = new LLModel(volume_params, 0.f); - -        if (populateModelFromMesh(pModel, mesh)         && -            (LLModel::NO_ERRORS == pModel->getStatus()) && -            validate_model(pModel)) -        { -            mModelList.push_back(pModel); -        } -        else -        { -            setLoadState(ERROR_MODEL + pModel->getStatus()); -            delete(pModel); -            return false; -        } -    } -    return true; -} - -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh) -{ -    pModel->mLabel = mesh.name; -    int pos_idx; -    tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a; - -    auto prims = mesh.primitives; -    for (auto prim : prims) -    { -        if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices]; - -        pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1; -        if (pos_idx >= 0) -        { -            positions_a = mGltfModel.accessors[pos_idx]; -            if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType) -                continue; -            auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView]; -            auto positions_buf = mGltfModel.buffers[positions_bv.buffer]; -            //auto type = positions_vb. -            //if (positions_buf.name -        } - -#if 0 -        int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx; -        norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1; -        tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1; -        uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1; -        uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1; -        color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1; -        color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1; -#endif - -        if (prim.mode == TINYGLTF_MODE_TRIANGLES) -        { -            //auto pos = mesh.    TODO resume here DJH 2022-04 -        } -    } - -    //pModel->addFace() -    return false; -} - -bool LLGLTFLoader::parseMaterials() -{ -    if (!mGltfLoaded) return false; - -    // fill local texture data structures -    mSamplers.clear(); -    for (auto in_sampler : mGltfModel.samplers) -    { -        gltf_sampler sampler; -        sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; -        sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; -        sampler.wrapS     = in_sampler.wrapS; -        sampler.wrapT     = in_sampler.wrapT; -        sampler.name      = in_sampler.name; // unused -        mSamplers.push_back(sampler); -    } - -    mImages.clear(); -    for (auto in_image : mGltfModel.images) -    { -        gltf_image image; -        image.numChannels     = in_image.component; -        image.bytesPerChannel = in_image.bits >> 3;     // Convert bits to bytes -        image.pixelType       = in_image.pixel_type;    // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc -        image.size            = static_cast<U32>(in_image.image.size()); -        image.height          = in_image.height; -        image.width           = in_image.width; -        image.data            = in_image.image.data(); - -        if (in_image.as_is) -        { -            LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; -            return false; -        } - -        if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) -        { -            LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; -            return false; -        } - -        mImages.push_back(image); -    } - -    mTextures.clear(); -    for (auto in_tex : mGltfModel.textures) -    { -        gltf_texture tex; -        tex.imageIdx   = in_tex.source; -        tex.samplerIdx = in_tex.sampler; -        tex.imageUuid.setNull(); - -        if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) -        { -            LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; -            return false; -        } - -        mTextures.push_back(tex); -    } - -    // parse each material -    for (tinygltf::Material gltf_material : mGltfModel.materials) -    { -        gltf_render_material mat; -        mat.name = gltf_material.name; - -        tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; -        mat.hasPBR = true;  // Always true, for now - -        mat.baseColor.set(pbr.baseColorFactor.data()); -        mat.hasBaseTex = pbr.baseColorTexture.index >= 0; -        mat.baseColorTexIdx = pbr.baseColorTexture.index; -        mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - -        mat.metalness = pbr.metallicFactor; -        mat.roughness = pbr.roughnessFactor; -        mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; -        mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; -        mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - -        mat.normalScale = gltf_material.normalTexture.scale; -        mat.hasNormalTex = gltf_material.normalTexture.index >= 0; -        mat.normalTexIdx = gltf_material.normalTexture.index; -        mat.normalTexCoords = gltf_material.normalTexture.texCoord; - -        mat.occlusionScale = gltf_material.occlusionTexture.strength; -        mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; -        mat.occlusionTexIdx = gltf_material.occlusionTexture.index; -        mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - -        mat.emissiveColor.set(gltf_material.emissiveFactor.data()); -        mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; -        mat.emissiveTexIdx = gltf_material.emissiveTexture.index; -        mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - -        mat.alphaMode = gltf_material.alphaMode; -        mat.alphaMask = gltf_material.alphaCutoff; - -        if ((mat.hasNormalTex    && (mat.normalTexIdx     >= mTextures.size())) || -            (mat.hasOcclusionTex && (mat.occlusionTexIdx  >= mTextures.size())) || -            (mat.hasEmissiveTex  && (mat.emissiveTexIdx   >= mTextures.size())) || -            (mat.hasBaseTex      && (mat.baseColorTexIdx  >= mTextures.size())) || -            (mat.hasMRTex        && (mat.metalRoughTexIdx >= mTextures.size()))) -        { -            LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; -            return false; -        } - -        if ((mat.hasNormalTex    && (mat.normalTexCoords      > 2)) ||    // mesh can have up to 3 sets of UV -            (mat.hasOcclusionTex && (mat.occlusionTexCoords   > 2)) || -            (mat.hasEmissiveTex  && (mat.emissiveTexCoords    > 2)) || -            (mat.hasBaseTex      && (mat.baseColorTexCoords   > 2)) || -            (mat.hasMRTex        && (mat.metalRoughTexCoords  > 2))) -        { -            LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; -            return false; -        } - -        mMaterials.push_back(mat); -    } - -    return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ -    llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ -    for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple -    { -        if (mat.hasBaseTex) -        { -            gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; -            if (gtex.imageUuid.isNull()) -            { -                gtex.imageUuid = imageBufferToTextureUUID(gtex); -            } -        } - -        if (mat.hasMRTex) -        { -            gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; -            if (gtex.imageUuid.isNull()) -            { -                gtex.imageUuid = imageBufferToTextureUUID(gtex); -            } -        } - -        if (mat.hasNormalTex) -        { -            gltf_texture& gtex = mTextures[mat.normalTexIdx]; -            if (gtex.imageUuid.isNull()) -            { -                gtex.imageUuid = imageBufferToTextureUUID(gtex); -            } -        } - -        if (mat.hasOcclusionTex) -        { -            gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; -            if (gtex.imageUuid.isNull()) -            { -                gtex.imageUuid = imageBufferToTextureUUID(gtex); -            } -        } - -        if (mat.hasEmissiveTex) -        { -            gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; -            if (gtex.imageUuid.isNull()) -            { -                gtex.imageUuid = imageBufferToTextureUUID(gtex); -            } -        } -    } -} - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) -{ -    //gltf_image& image = mImages[tex.imageIdx]; -    //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - -    // fill an LLSD container with image+sampler data - -    // upload texture - -    // retrieve UUID - -    return LLUUID::null; -} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h deleted file mode 100644 index 848a07c1e4..0000000000 --- a/indra/llprimitive/llgltfloader.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @file LLGLTFLoader.h - * @brief LLGLTFLoader class definition - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGLTFLoader_H -#define LL_LLGLTFLoader_H - -#include "tinygltf/tiny_gltf.h" - -#include "llglheaders.h" -#include "llmodelloader.h" - -// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD - -class gltf_sampler -{ -public: -    // Uses GL enums -    S32 minFilter;      // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR -    S32 magFilter;      // GL_NEAREST or GL_LINEAR -    S32 wrapS;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT -    S32 wrapT;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT -    //S32 wrapR;        // Found in some sample files, but not part of glTF 2.0 spec. Ignored. -    std::string name;   // optional, currently unused -    // extensions and extras are sampler optional fields that we don't support - at least initially -}; - -class gltf_image -{ -public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) -    U8* data;               // ptr to decoded image data -    U32 size;               // in bytes, regardless of channel width -    U32 width; -    U32 height; -    U32 numChannels;        // range 1..4 -    U32 bytesPerChannel;    // converted from gltf "bits", expects only 8, 16 or 32 as input -    U32 pixelType;          // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT -}; - -class gltf_texture -{ -public: -    U32 imageIdx; -    U32 samplerIdx; -    LLUUID imageUuid = LLUUID::null; -}; - -class gltf_render_material -{ -public: -    std::string name; - -    // scalar values -    LLColor4    baseColor;      // linear encoding. Multiplied with vertex color, if present. -    double      metalness; -    double      roughness; -    double      normalScale;    // scale applies only to X,Y components of normal -    double      occlusionScale; // strength multiplier for occlusion -    LLColor4    emissiveColor;  // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) -    std::string alphaMode;      // "OPAQUE", "MASK" or "BLEND" -    double      alphaMask;      // alpha cut-off - -    // textures -    U32 baseColorTexIdx;    // always sRGB encoded -    U32 metalRoughTexIdx;   // always linear, roughness in G channel, metalness in B channel -    U32 normalTexIdx;       // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) -    U32 occlusionTexIdx;    // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded -    U32 emissiveTexIdx;     // always stored as sRGB, in nits (candela / meter^2) - -    // texture coordinates -    U32 baseColorTexCoords; -    U32 metalRoughTexCoords; -    U32 normalTexCoords; -    U32 occlusionTexCoords; -    U32 emissiveTexCoords; - -    // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? - -    bool        hasPBR; -    bool        hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; - -    // This field is populated after upload -    LLUUID      material_uuid = LLUUID::null; - -}; - -class gltf_mesh -{ -public: -    std::string name; - -    // TODO add mesh import DJH 2022-04 - -}; - -class LLGLTFLoader : public LLModelLoader -{ -  public: -    typedef std::map<std::string, LLImportMaterial> material_map; - -    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); //, -                    //bool                                          preprocess ); -    virtual ~LLGLTFLoader(); - -    virtual bool OpenFile(const std::string &filename); - -protected: -    tinygltf::Model mGltfModel; -    bool            mGltfLoaded; -    bool            mMeshesLoaded; -    bool            mMaterialsLoaded; - -    std::vector<gltf_mesh>              mMeshes; -    std::vector<gltf_render_material>   mMaterials; - -    std::vector<gltf_texture>           mTextures; -    std::vector<gltf_image>             mImages; -    std::vector<gltf_sampler>           mSamplers; - -private: -    bool parseMeshes(); -    void uploadMeshes(); -    bool parseMaterials(); -    void uploadMaterials(); -    bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh); -    LLUUID imageBufferToTextureUUID(const gltf_texture& tex); - -    //    bool mPreprocessGLTF; - -    /*  Below inherited from dae loader - unknown if/how useful here - -    void processElement(gltfElement *element, bool &badElement, GLTF *gltf); -    void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); - -    material_map     getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); -    LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); -    LLColor4         getGltfColor(gltfElement *element); - -    gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); - -    bool isNodeAJoint(gltfNode *pNode); -    void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms); -    void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); -    void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); -    void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); -    void buildJointToNodeMappingFromScene(gltfElement *pRoot); -    void processJointToNodeMapping(gltfNode *pNode); -    void processChildJoints(gltfNode *pParentNode); - -    bool verifyCount(int expected, int result); - -    // Verify that a controller matches vertex counts -    bool verifyController(gltfController *pController); - -    static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); -    static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); - -    static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); - -    // Loads a mesh breaking it into one or more models as necessary -    // to get around volume face limitations while retaining >8 materials -    // -    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); -    */ - -}; -#endif  // LL_LLGLTFLLOADER_H diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 4e3e49ec9f..a33f25307e 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces()      }  } +void LLModel::normalizeVolumeFacesAndWeights() +{ +    if (!mVolumeFaces.empty()) +    { +        LLVector4a min, max; + +        // For all of the volume faces +        // in the model, loop over +        // them and see what the extents +        // of the volume along each axis. +        min = mVolumeFaces[0].mExtents[0]; +        max = mVolumeFaces[0].mExtents[1]; + +        for (U32 i = 1; i < mVolumeFaces.size(); ++i) +        { +            LLVolumeFace& face = mVolumeFaces[i]; + +            update_min_max(min, max, face.mExtents[0]); +            update_min_max(min, max, face.mExtents[1]); + +            if (face.mTexCoords) +            { +                LLVector2& min_tc = face.mTexCoordExtents[0]; +                LLVector2& max_tc = face.mTexCoordExtents[1]; + +                min_tc = face.mTexCoords[0]; +                max_tc = face.mTexCoords[0]; + +                for (S32 j = 1; j < face.mNumVertices; ++j) +                { +                    update_min_max(min_tc, max_tc, face.mTexCoords[j]); +                } +            } +            else +            { +                face.mTexCoordExtents[0].set(0, 0); +                face.mTexCoordExtents[1].set(1, 1); +            } +        } + +        // Now that we have the extents of the model +        // we can compute the offset needed to center +        // the model at the origin. + +        // Compute center of the model +        // and make it negative to get translation +        // needed to center at origin. +        LLVector4a trans; +        trans.setAdd(min, max); +        trans.mul(-0.5f); + +        // Compute the total size along all +        // axes of the model. +        LLVector4a size; +        size.setSub(max, min); + +        // Prevent division by zero. +        F32 x = size[0]; +        F32 y = size[1]; +        F32 z = size[2]; +        F32 w = size[3]; +        if (fabs(x) < F_APPROXIMATELY_ZERO) +        { +            x = 1.0; +        } +        if (fabs(y) < F_APPROXIMATELY_ZERO) +        { +            y = 1.0; +        } +        if (fabs(z) < F_APPROXIMATELY_ZERO) +        { +            z = 1.0; +        } +        size.set(x, y, z, w); + +        // Compute scale as reciprocal of size +        LLVector4a scale; +        scale.splat(1.f); +        scale.div(size); + +        LLVector4a inv_scale(1.f); +        inv_scale.div(scale); + +        for (U32 i = 0; i < mVolumeFaces.size(); ++i) +        { +            LLVolumeFace& face = mVolumeFaces[i]; + +            // We shrink the extents so +            // that they fall within +            // the unit cube. +            // VFExtents change +            face.mExtents[0].add(trans); +            face.mExtents[0].mul(scale); + +            face.mExtents[1].add(trans); +            face.mExtents[1].mul(scale); + +            // For all the positions, we scale +            // the positions to fit within the unit cube. +            LLVector4a* pos = (LLVector4a*)face.mPositions; +            LLVector4a* norm = (LLVector4a*)face.mNormals; +            LLVector4a* t = (LLVector4a*)face.mTangents; + +            for (S32 j = 0; j < face.mNumVertices; ++j) +            { +                pos[j].add(trans); +                pos[j].mul(scale); +                if (norm && !norm[j].equals3(LLVector4a::getZero())) +                { +                    norm[j].mul(inv_scale); +                    norm[j].normalize3(); +                } + +                if (t) +                { +                    F32 w = t[j].getF32ptr()[3]; +                    t[j].mul(inv_scale); +                    t[j].normalize3(); +                    t[j].getF32ptr()[3] = w; +                } +            } +        } + +        weight_map old_weights = mSkinWeights; +        mSkinWeights.clear(); +        mPosition.clear(); + +        for (auto& weights : old_weights) +        { +            LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); +            pos.add(trans); +            pos.mul(scale); +            LLVector3 scaled_pos(pos.getF32ptr()); +            mPosition.push_back(scaled_pos); +            mSkinWeights[scaled_pos] = weights.second; +        } + +        // mNormalizedScale is the scale at which +        // we would need to multiply the model +        // by to get the original size of the +        // model instead of the normalized size. +        LLVector4a normalized_scale; +        normalized_scale.splat(1.f); +        normalized_scale.div(scale); +        mNormalizedScale.set(normalized_scale.getF32ptr()); +        mNormalizedTranslation.set(trans.getF32ptr()); +        mNormalizedTranslation *= -1.f; + +        // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) +        for (auto& face : mVolumeFaces) +        { +            face.mNormalizedScale = mNormalizedScale; +        } +    } +} +  void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const  {      scale_out = mNormalizedScale; @@ -662,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)  { @@ -941,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; @@ -1006,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); @@ -1561,11 +1721,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi      {          ret["joint_names"][i] = mJointNames[i]; +        // For model to work at all there must be a matching bind matrix, +        // so supply an indentity one if it isn't true +        // Note: can build an actual bind matrix from joints +        const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity(); +        if (i >= mInvBindMatrix.size()) +        { +            LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " +                                     << mInvBindMatrix.size() << LL_ENDL; +        } +          for (U32 j = 0; j < 4; j++)          {              for (U32 k = 0; k < 4; k++)              { -                ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; +                ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k];              }          }      } @@ -1578,15 +1748,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi          }      } -    if ( include_joints && mAlternateBindMatrix.size() > 0 ) +    // optional 'joint overrides' +    if (include_joints && mAlternateBindMatrix.size() > 0)      {          for (U32 i = 0; i < mJointNames.size(); ++i)          { +            // If there is not enough to match mJointNames, +            // either supply no alternate matrixes at all or supply +            // replacements +            const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); +            if (i >= mAlternateBindMatrix.size()) +            { +                LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " +                                         << mAlternateBindMatrix.size() << LL_ENDL; +            }              for (U32 j = 0; j < 4; j++)              {                  for (U32 k = 0; k < 4; k++)                  { -                    ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; +                    ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k];                  }              }          } diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index fe28926720..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(); } @@ -202,6 +208,7 @@ public:      void sortVolumeFacesByMaterialName();      void normalizeVolumeFaces(); +    void normalizeVolumeFacesAndWeights();      void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);      void remapVolumeFaces();      void optimizeVolumeFaces(); diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 7facd53a72..0383659f62 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -33,6 +33,7 @@  #include "llmatrix4a.h"  #include <boost/bind.hpp> +#include <boost/exception/diagnostic_information.hpp>  std::list<LLModelLoader*> LLModelLoader::sActiveLoaderList; @@ -113,7 +114,9 @@ LLModelLoader::LLModelLoader(      JointTransformMap&  jointTransformMap,      JointNameSet&       jointsFromNodes,      JointMap&           legalJointNamesMap, -    U32                 maxJointsPerMesh) +    U32                 maxJointsPerMesh, +    U32                 modelLimit, +    U32                 debugMode)  : mJointList( jointTransformMap )  , mJointsFromNode( jointsFromNodes )  , LLThread("Model Loader") @@ -121,7 +124,6 @@ LLModelLoader::LLModelLoader(  , mLod(lod)  , mTrySLM(false)  , mFirstTransform(true) -, mNumOfFetchingTextures(0)  , mLoadCallback(load_cb)  , mJointLookupFunc(joint_lookup_func)  , mTextureLoadFunc(texture_load_func) @@ -132,7 +134,10 @@ LLModelLoader::LLModelLoader(  , mNoNormalize(false)  , mNoOptimize(false)  , mCacheOnlyHitIfRigged(false) +, mTexturesNeedScaling(false)  , mMaxJointsPerMesh(maxJointsPerMesh) +, mGeneratedModelLimit(modelLimit) +, mDebugMode(debugMode)  , mJointMap(legalJointNamesMap)  {      assert_main_thread(); @@ -149,7 +154,44 @@ 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"] = boost::current_exception_diagnostic_information(); +        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));  } @@ -201,7 +243,9 @@ bool LLModelLoader::doLoadModel()          }      } -    return OpenFile(mFilename); +    bool res = OpenFile(mFilename); +    dumpDebugData(); // conditional on mDebugMode +    return res;  }  void LLModelLoader::setLoadState(U32 state) @@ -466,6 +510,148 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std::      return true;  } +void LLModelLoader::dumpDebugData() +{ +    if (mDebugMode == 0) +    { +        return; +    } + +    std::string log_file = mFilename + "_importer.txt"; +    LLStringUtil::toLower(log_file); +    llofstream file; +    file.open(log_file.c_str()); +    if (!file) +    { +        LL_WARNS() << "dumpDebugData failed to open file " << log_file << LL_ENDL; +        return; +    } +    file << "Importing: " << mFilename << "\n"; + +    std::map<std::string, LLMatrix4a> inv_bind; +    std::map<std::string, LLMatrix4a> alt_bind; +    for (LLPointer<LLModel>& mdl : mModelList) +    { + +        file << "Model name: " << mdl->mLabel << "\n"; +        const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; +        file << "Shape Bind matrix: " << skin_info.mBindShapeMatrix << "\n"; +        file << "Skin Weights count: " << (S32)mdl->mSkinWeights.size() << "\n"; + +        // some objects might have individual bind matrices, +        // but for now it isn't accounted for +        size_t joint_count = skin_info.mJointNames.size(); +        for (size_t i = 0; i< joint_count;i++) +        { +            const std::string& joint = skin_info.mJointNames[i]; +            if (skin_info.mInvBindMatrix.size() > i) +            { +                inv_bind[joint] = skin_info.mInvBindMatrix[i]; +            } +            if (skin_info.mAlternateBindMatrix.size() > i) +            { +                alt_bind[joint] = skin_info.mAlternateBindMatrix[i]; +            } +        } +    } + +    file << "\nInv Bind matrices.\n"; +    for (auto& bind : inv_bind) +    { +        file << "Joint: " << bind.first << " Matrix: " << bind.second << "\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  void LLModelLoader::loadTextures() @@ -484,7 +670,7 @@ void LLModelLoader::loadTextures()                  if(!material.mDiffuseMapFilename.empty())                  { -                    mNumOfFetchingTextures += mTextureLoadFunc(material, mOpaqueData); +                    mTextureLoadFunc(material, mOpaqueData);                  }              }          } diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index aece922111..335d809386 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -109,8 +109,10 @@ public:      bool mTrySLM;      bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) +    bool mTexturesNeedScaling;      model_list      mModelList; +    // The scene is pretty much what ends up getting loaded for upload.  Basically assign things to this guy if you want something uploaded.      scene               mScene;      typedef std::queue<LLPointer<LLModel> > model_queue; @@ -119,10 +121,16 @@ public:      model_queue mPhysicsQ;      //map of avatar joints as named in COLLADA assets to internal joint names +    // Do not use this for anything other than looking up the name of a joint.  This is populated elsewhere.      JointMap            mJointMap; + +    // The joint list is what you want to use to actually setup the specific joint transformations.      JointTransformMap&  mJointList;      JointNameSet&       mJointsFromNode; + +      U32                 mMaxJointsPerMesh; +    U32                 mDebugMode; // see dumDebugData() for details      LLModelLoader(          std::string                         filename, @@ -135,7 +143,9 @@ public:          JointTransformMap&                  jointTransformMap,          JointNameSet&                       jointsFromNodes,          JointMap&                           legalJointNamesMap, -        U32                                 maxJointsPerMesh); +        U32                                 maxJointsPerMesh, +        U32                                 modelLimit, +        U32                                 debugMode);      virtual ~LLModelLoader();      virtual void setNoNormalize() { mNoNormalize = true; } @@ -161,9 +171,6 @@ public:      void stretch_extents(const LLModel* model, const LLMatrix4& mat); -    S32 mNumOfFetchingTextures ; // updated in the main thread -    bool areTexturesReady() { return !mNumOfFetchingTextures; } // called in the main thread. -      bool verifyCount( int expected, int result );      //Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps) @@ -192,6 +199,7 @@ public:      const LLSD logOut() const { return mWarningsArray; }      void clearLog() { mWarningsArray.clear(); } +    void dumpDebugData();  protected: @@ -203,6 +211,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/CMakeLists.txt b/indra/newview/CMakeLists.txt index 035feee898..759a40fc08 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -79,6 +79,7 @@ set(viewer_SOURCE_FILES      gltf/accessor.cpp      gltf/primitive.cpp      gltf/animation.cpp +    gltf/llgltfloader.cpp      groupchatlistener.cpp      llaccountingcostmanager.cpp      llaisapi.cpp @@ -181,11 +182,11 @@ set(viewer_SOURCE_FILES      llflexibleobject.cpp      llfloater360capture.cpp      llfloaterabout.cpp +    llfloateravatarwelcomepack.cpp      llfloaterbvhpreview.cpp      llfloateraddpaymentmethod.cpp      llfloaterauction.cpp      llfloaterautoreplacesettings.cpp -    llfloateravatar.cpp      llfloateravatarpicker.cpp      llfloateravatarrendersettings.cpp      llfloateravatartextures.cpp @@ -749,6 +750,7 @@ set(viewer_HEADER_FILES      gltf/buffer_util.h      gltf/primitive.h      gltf/animation.h +    gltf/llgltfloader.h      llaccountingcost.h      llaccountingcostmanager.h      llaisapi.h @@ -852,11 +854,11 @@ set(viewer_HEADER_FILES      llflexibleobject.h      llfloater360capture.h      llfloaterabout.h +    llfloateravatarwelcomepack.h      llfloaterbvhpreview.h      llfloateraddpaymentmethod.h      llfloaterauction.h      llfloaterautoreplacesettings.h -    llfloateravatar.h      llfloateravatarpicker.h      llfloateravatarrendersettings.h      llfloateravatartextures.h 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/commands.xml b/indra/newview/app_settings/commands.xml index 4a3dfffde1..7bcfecf9fa 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -26,9 +26,9 @@             label_ref="Command_Avatar_Label"             tooltip_ref="Command_Avatar_Tooltip"             execute_function="Floater.ToggleOrBringToFront" -           execute_parameters="avatar" +           execute_parameters="avatar_welcome_pack"             is_running_function="Floater.IsOpen" -           is_running_parameters="avatar" +           is_running_parameters="avatar_welcome_pack"             />    <command name="build"             available_in_toybox="true" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b9e3ebf502..0614be8bf6 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> @@ -621,16 +632,16 @@        <key>Value</key>        <real>16.0</real>      </map> -    <key>AvatarPickerURL</key> +    <key>AvatarWelcomePack</key>      <map> -      <key>Comment</key> -      <string>Avatar picker contents</string> -      <key>Persist</key> -      <integer>1</integer> -      <key>Type</key> -      <string>String</string> -      <key>Value</key> -      <string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/avatars.html</string> +        <key>Comment</key> +        <string>Avatar Welcome Pack contents</string> +        <key>Persist</key> +        <integer>1</integer> +        <key>Type</key> +        <string>String</string> +        <key>Value</key> +        <string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>      </map>      <!--AvatarBakedTextureUploadTimeout is in use by QA-->      <key>AvatarBakedTextureUploadTimeout</key> @@ -16270,13 +16281,13 @@      <key>MediaFirstClickInteract</key>      <map>        <key>Comment</key> -      <string>This setting controls which media (once loaded) does not require a first click to focus before interaction can begin. This allows clicks to be passed directly to media bypassing the focus click requirement. This setting is a bitfield, precomputed values are as follows: Disabled=0; Worn HUDs only=1; Owned objects=3; Friend objects=7; Group objects=15; Landowner objects=31; Any object=31; All MOAP=1073741824. For complete details see lltoolpie.h enum MediaFirstClickTypes.</string> +      <string>This setting controls which media (once loaded) does not require a first click to focus before interaction can begin. This allows clicks to be passed directly to media bypassing the focus click requirement. This setting is a bitfield, precomputed values are as follows: Disabled=0; Worn HUDs only=1; Owned objects=2; Friend objects=4; Group objects=8; Landowner objects=16; Any object=32767; All MOAP=32768. For complete details see lltoolpie.h enum MediaFirstClickTypes.</string>        <key>Persist</key>        <integer>1</integer>        <key>Type</key>        <string>S32</string>        <key>Value</key> -      <integer>1</integer> +      <integer>31</integer>      </map>      <key>EnableSelectionHints</key>      <map> diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index d1845605d4..03f7331893 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -159,7 +159,7 @@ bool Buffer::prep(Asset& asset)          std::string dir = gDirUtilp->getDirName(asset.mFilename);          std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri; -        std::ifstream file(bin_file, std::ios::binary); +        llifstream file(bin_file.c_str(), std::ios::binary);          if (!file.is_open())          {              LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL; diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..e24aea4a28 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,6 +50,10 @@ namespace LL              "KHR_texture_transform"          }; +        static std::unordered_set<std::string> ExtensionsIgnored = { +            "KHR_materials_pbrSpecularGlossiness" +        }; +          Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)          {              if (alpha_mode == "OPAQUE") @@ -472,11 +476,14 @@ void Asset::update()              for (auto& image : mImages)              { -                if (image.mTexture.notNull()) -                { // HACK - force texture to be loaded full rez -                    // TODO: calculate actual vsize -                    image.mTexture->addTextureStats(2048.f * 2048.f); -                    image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                if (image.mLoadIntoTexturePipe) +                { +                    if (image.mTexture.notNull()) +                    { // HACK - force texture to be loaded full rez +                        // TODO: calculate actual vsize +                        image.mTexture->addTextureStats(2048.f * 2048.f); +                        image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                    }                  }              }          } @@ -486,18 +493,23 @@ void Asset::update()  bool Asset::prep()  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; -    // check required extensions and fail if not supported -    bool unsupported = false; +    // check required extensions      for (auto& extension : mExtensionsRequired)      {          if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())          { -            LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; -            unsupported = true; +            if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) +            { +                LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; +                mUnsupportedExtensions.push_back(extension); +            } +            else +            { +                mIgnoredExtensions.push_back(extension); +            }          }      } - -    if (unsupported) +    if (mUnsupportedExtensions.size() > 0)      {          return false;      } @@ -513,7 +525,7 @@ bool Asset::prep()      for (auto& image : mImages)      { -        if (!image.prep(*this)) +        if (!image.prep(*this, mLoadIntoVRAM))          {              return false;          } @@ -542,102 +554,106 @@ bool Asset::prep()              return false;          }      } +    if (mLoadIntoVRAM) +    { +        // prepare vertex buffers -    // prepare vertex buffers - -    // material count is number of materials + 1 for default material -    U32 mat_count = (U32) mMaterials.size() + 1; - -    if (LLGLSLShader::sCurBoundShaderPtr == nullptr) -    { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer -        gDebugProgram.bind(); -    } +        // material count is number of materials + 1 for default material +        U32 mat_count = (U32) mMaterials.size() + 1; -    for (S32 double_sided = 0; double_sided < 2; ++double_sided) -    { -        RenderData& rd = mRenderData[double_sided]; -        for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) -        { -            rd.mBatches[i].resize(mat_count); +        if (LLGLSLShader::sCurBoundShaderPtr == nullptr) +        { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer +            gDebugProgram.bind();          } -        // for each material -        for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) +        for (S32 double_sided = 0; double_sided < 2; ++double_sided)          { -            // for each shader variant -            U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; -            U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - -            S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; -            if (ds_mat != double_sided) +            RenderData& rd = mRenderData[double_sided]; +            for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)              { -                continue; +                rd.mBatches[i].resize(mat_count);              } -            for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) +            // for each material +            for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)              { -                U32 attribute_mask = 0; -                // for each mesh -                for (auto& mesh : mMeshes) +                // for each shader variant +                U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; +                U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + +                S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; +                if (ds_mat != double_sided)                  { -                    // for each primitive -                    for (auto& primitive : mesh.mPrimitives) +                    continue; +                } + +                for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) +                { +                    U32 attribute_mask = 0; +                    // for each mesh +                    for (auto& mesh : mMeshes)                      { -                        if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                        // for each primitive +                        for (auto& primitive : mesh.mPrimitives)                          { -                            // accumulate vertex and index counts -                            primitive.mVertexOffset = vertex_count[variant]; -                            primitive.mIndexOffset = index_count[variant]; +                            if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                            { +                                // accumulate vertex and index counts +                                primitive.mVertexOffset = vertex_count[variant]; +                                primitive.mIndexOffset = index_count[variant]; -                            vertex_count[variant] += primitive.getVertexCount(); -                            index_count[variant] += primitive.getIndexCount(); +                                vertex_count[variant] += primitive.getVertexCount(); +                                index_count[variant] += primitive.getIndexCount(); -                            // all primitives of a given variant and material should all have the same attribute mask -                            llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); -                            attribute_mask |= primitive.mAttributeMask; +                                // all primitives of a given variant and material should all have the same attribute mask +                                llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); +                                attribute_mask |= primitive.mAttributeMask; +                            }                          }                      } -                } -                // allocate vertex buffer and pack it -                if (vertex_count[variant] > 0) -                { -                    U32 mat_idx = mat_id + 1; -                    LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); +                    // allocate vertex buffer and pack it +                    if (vertex_count[variant] > 0) +                    { +                        U32 mat_idx = mat_id + 1; +                        #if 0 +                        LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); -                    rd.mBatches[variant][mat_idx].mVertexBuffer = vb; -                    vb->allocateBuffer(vertex_count[variant], -                        index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used -                    vb->setBuffer(); +                        rd.mBatches[variant][mat_idx].mVertexBuffer = vb; +                        vb->allocateBuffer(vertex_count[variant], +                            index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used +                        vb->setBuffer(); -                    for (auto& mesh : mMeshes) -                    { -                        for (auto& primitive : mesh.mPrimitives) +                        for (auto& mesh : mMeshes)                          { -                            if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                            for (auto& primitive : mesh.mPrimitives)                              { -                                primitive.upload(vb); +                                if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                                { +                                    primitive.upload(vb); +                                }                              }                          } -                    } -                    vb->unmapBuffer(); +                        vb->unmapBuffer(); -                    vb->unbind(); +                        vb->unbind(); +                        #endif +                    }                  }              }          } -    } -    // sanity check that all primitives have a vertex buffer -    for (auto& mesh : mMeshes) -    { -        for (auto& primitive : mesh.mPrimitives) +        // sanity check that all primitives have a vertex buffer +        for (auto& mesh : mMeshes)          { -            llassert(primitive.mVertexBuffer.notNull()); +            for (auto& primitive : mesh.mPrimitives) +            { +                //llassert(primitive.mVertexBuffer.notNull()); +            }          }      } - +    #if 0      // build render batches      for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)      { @@ -664,6 +680,7 @@ bool Asset::prep()              }          }      } +    #endif      return true;  } @@ -672,13 +689,14 @@ Asset::Asset(const Value& src)      *this = src;  } -bool Asset::load(std::string_view filename) +bool Asset::load(std::string_view filename, bool loadIntoVRAM)  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; +    mLoadIntoVRAM = loadIntoVRAM;      mFilename = filename;      std::string ext = gDirUtilp->getExtension(mFilename); -    std::ifstream file(filename.data(), std::ios::binary); +    llifstream file(filename.data(), std::ios::binary);      if (file.is_open())      {          std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); @@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename)          }          else if (ext == "glb")          { -            return loadBinary(str); +            return loadBinary(str, mLoadIntoVRAM);          }          else          { @@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename)      return false;  } -bool Asset::loadBinary(const std::string& data) +bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM)  { +    mLoadIntoVRAM = loadIntoVRAM;      // load from binary gltf      const U8* ptr = (const U8*)data.data();      const U8* end = ptr + data.size(); @@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView)  LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset) +bool Image::prep(Asset& asset, bool loadIntoVRAM)  { +    mLoadIntoTexturePipe = loadIntoVRAM;      LLUUID id;      if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())      { // loaded from an asset, fetch the texture from the asset system @@ -951,12 +971,12 @@ bool Image::prep(Asset& asset)      { // embedded in a buffer, load the texture from the buffer          BufferView& bufferView = asset.mBufferViews[mBufferView];          Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - -        U8* data = buffer.mData.data() + bufferView.mByteOffset; - -        mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - -        if (mTexture.isNull()) +        if (mLoadIntoTexturePipe) +        { +            U8* data = buffer.mData.data() + bufferView.mByteOffset; +            mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); +        } +        else if (mTexture.isNull() && mLoadIntoTexturePipe)          {              LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;              LL_WARNS("GLTF") << "  image: " << mName << LL_ENDL; @@ -971,12 +991,12 @@ bool Image::prep(Asset& asset)          std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;          LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); -        if (tracking_id.notNull()) +        if (tracking_id.notNull() && mLoadIntoTexturePipe)          {              LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);              mTexture = LLViewerTextureManager::getFetchedTexture(world_id);          } -        else +        else if (mLoadIntoTexturePipe)          {              LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;              LL_WARNS("GLTF") << "  image: " << mName << LL_ENDL; @@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset)          return false;      } -    if (!asset.mFilename.empty()) +    if (!asset.mFilename.empty() && mLoadIntoTexturePipe)      { // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload          mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);          mTexture->forceToSaveRawImage(0, F32_MAX); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..b9554d753c 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL              void serialize(boost::json::object& dst) const;          }; +        // Image is for images that we want to load for the given asset.  This acts as an interface into the viewer's texture pipe.          class Image          {          public: @@ -301,6 +302,8 @@ namespace LL              S32 mBits = -1;              S32 mPixelType = -1; +            bool mLoadIntoTexturePipe = false; +              LLPointer<LLViewerFetchedTexture> mTexture;              const Image& operator=(const Value& src); @@ -316,7 +319,7 @@ namespace LL              // preserve only uri and name              void clearData(Asset& asset); -            bool prep(Asset& asset); +            bool prep(Asset& asset, bool loadIntoVRAM);          };          // Render Batch -- vertex buffer and list of primitives to render using @@ -391,6 +394,10 @@ namespace LL              // UBO for storing material data              U32 mMaterialsUBO = 0; +            bool mLoadIntoVRAM = false; + +            std::vector<std::string> mUnsupportedExtensions; +            std::vector<std::string> mIgnoredExtensions;              // prepare for first time use              bool prep(); @@ -428,12 +435,12 @@ namespace LL              // accepts .gltf and .glb files              // Any existing data will be lost              // returns result of prep() on success -            bool load(std::string_view filename); +            bool load(std::string_view filename, bool loadIntoVRAM);              // load .glb contents from memory              // data - binary contents of .glb file              // returns result of prep() on success -            bool loadBinary(const std::string& data); +            bool loadBinary(const std::string& data, bool loadIntoVRAM);              const Asset& operator=(const Value& src);              void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index ef9bba8128..be36c5e90b 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -159,6 +159,12 @@ namespace LL          }          template<> +        inline void copyVec3<F32, LLColor4U>(F32* src, LLColor4U& dst) +        { +            dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255); +        } + +        template<>          inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)          {              dst.set((U8)(src[0]), (U8)(src[1]), (U8)(src[2]), 255); @@ -369,6 +375,11 @@ namespace LL          template<class T>          inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)          { +            if (accessor.mBufferView == INVALID_INDEX) +            { +                LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL; +                return; +            }              const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];              const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];              const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp new file mode 100644 index 0000000000..dd1d327683 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.cpp @@ -0,0 +1,1822 @@ +/** + * @file LLGLTFLoader.cpp + * @brief LLGLTFLoader class implementation + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llgltfloader.h" +#include "meshoptimizer.h" +#include <glm/gtc/packing.hpp> + +// Import & define single-header gltf import/export lib +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_USE_CPP14  // default is C++ 11 + +// tinygltf by default loads image files using STB +#define STB_IMAGE_IMPLEMENTATION +// to use our own image loading: +// 1. replace this definition with TINYGLTF_NO_STB_IMAGE +// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) + +// tinygltf saves image files using STB +#define STB_IMAGE_WRITE_IMPLEMENTATION +// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) + +// Additionally, disable inclusion of STB header files entirely with +// TINYGLTF_NO_INCLUDE_STB_IMAGE +// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "tinygltf/tiny_gltf.h" + + +// TODO: includes inherited from dae loader.  Validate / prune + +#include "llsdserialize.h" +#include "lljoint.h" +#include "llbase64.h" +#include "lldir.h" + +#include "llmatrix4a.h" + +#include <boost/regex.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/exception/diagnostic_information.hpp> +#include <fstream> + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ +    "_LOD0", +    "_LOD1", +    "_LOD2", +    "", +    "_PHYS", +}; + +// Premade rotation matrix, GLTF is Y-up while SL is Z-up +static const glm::mat4 coord_system_rotation( +    1.f, 0.f, 0.f, 0.f, +    0.f, 0.f, 1.f, 0.f, +    0.f, -1.f, 0.f, 0.f, +    0.f, 0.f, 0.f, 1.f +); + + +static const glm::mat4 coord_system_rotationxy( +    0.f, 1.f, 0.f, 0.f, +    -1.f, 0.f, 0.f, 0.f, +    0.f, 0.f, 1.f, 0.f, +    0.f, 0.f, 0.f, 1.f +); + +static const S32 VERTEX_SPLIT_SAFETY_MARGIN = 3 * 3 + 1; // 10 vertices: 3 complete triangles plus remapping overhead +static const S32 VERTEX_LIMIT = USHRT_MAX - VERTEX_SPLIT_SAFETY_MARGIN; + +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, +                     joint_lookup_func, +                     texture_load_func, +                     state_cb, +                     opaque_userdata, +                     jointTransformMap, +                     jointsFromNodes, +                     jointAliasMap, +                     maxJointsPerMesh, +                     modelLimit, +                     debugMode) +    , mViewerJointData(viewer_skeleton) +    , mGltfLoaded(false) +    , mApplyXYRotation(false) +{ +} + +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); + +    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"] = boost::current_exception_diagnostic_information(); +        mWarningsArray.append(args); +        setLoadState(ERROR_PARSING); +        return false; +    } + +    if (!mGltfLoaded) +    { +        notifyUnsupportedExtension(true); + +        for (const auto& buffer : mGLTFAsset.mBuffers) +        { +            if (buffer.mByteLength > 0 && buffer.mData.empty()) +            { +                bool bin_file = buffer.mUri.ends_with(".bin"); +                LLSD args; +                args["Message"] = bin_file ? "ParsingErrorMissingBufferBin" : "ParsingErrorMissingBuffer"; +                args["BUFFER_NAME"] = buffer.mName; +                args["BUFFER_URI"] = buffer.mUri; +                mWarningsArray.append(args); +            } +        } +        setLoadState(ERROR_PARSING); +        return false; +    } + +    notifyUnsupportedExtension(false); + +    bool meshesLoaded = parseMeshes(); + +    setLoadState(DONE); + +    return meshesLoaded; +} + +void LLGLTFLoader::addModelToScene( +    LLModel* pModel, +    const std::string& model_name, +    U32 submodel_limit, +    const LLMatrix4& transformation, +    const LLVolumeParams& volume_params, +    const material_map& mats) +{ +    U32 volume_faces = pModel->getNumVolumeFaces(); + +    // Side-steps all manner of issues when splitting models +    // and matching lower LOD materials to base models +    // +    pModel->sortVolumeFacesByMaterialName(); + +    int submodelID = 0; + +    // remove all faces that definitely won't fit into one model and submodel limit +    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); + +        volume_faces = static_cast<U32>(remainder.size()); + +        // Don't add to scene yet because weights and materials aren't ready. +        // Just save it +        ready_models.push_back(current_model); + +        // If we have left-over volume faces, create another model +        // to absorb them. +        if (volume_faces) +        { +            LLModel* next = new LLModel(volume_params, 0.f); +            next->ClearFacesAndMaterials(); +            next->mSubmodelID = ++submodelID; + +            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; +            next->mSkinWeights = current_model->mSkinWeights; +            next->mPosition = current_model->mPosition; + +            const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo; +            LLMeshSkinInfo& next_skin_info = next->mSkinInfo; +            next_skin_info.mJointNames = current_skin_info.mJointNames; +            next_skin_info.mJointNums = current_skin_info.mJointNums; +            next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix; +            next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix; +            next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix; +            next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset; + + +            if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES) +            { +                next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end()); +                current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES); +            } + +            current_model = next; +        } + +        remainder.clear(); + +    } while (volume_faces); + +    for (auto model : ready_models) +    { +        // remove unused/redundant vertices +        model->remapVolumeFaces(); + +        mModelList.push_back(model); + +        std::map<std::string, LLImportMaterial> materials; +        for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) +        { +            material_map::const_iterator found = mats.find(model->mMaterialList[i]); +            if (found != mats.end()) +            { +                materials[model->mMaterialList[i]] = found->second; +            } +            else +            { +                materials[model->mMaterialList[i]] = LLImportMaterial(); +            } +        } +        // 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); +    } +} + +bool LLGLTFLoader::parseMeshes() +{ +    if (!mGltfLoaded) return false; + +    // 2022-04 DJH Volume params from dae example. TODO understand PCODE +    LLVolumeParams volume_params; +    volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + +    mTransform.setIdentity(); + +    for (auto& node : mGLTFAsset.mNodes) +    { +        // Make node matrix valid for correct transformation +        node.makeMatrixValid(); +    } + +    if (mGLTFAsset.mSkins.size() > 0) +    { +        checkForXYrotation(mGLTFAsset.mSkins[0]); +        populateJointGroups(); +    } + +    // Populate the joints from skins first. +    // Multiple meshes can share the same skin, so preparing skins beforehand. +    for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) +    { +        populateJointsFromSkin(i); +    } + +    // Track how many times each mesh name has been used +    std::map<std::string, S32> mesh_name_counts; + +    // 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()) +    { +        // Process the default scene (or first scene if no default) +        S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + +        if (scene_idx < mGLTFAsset.mScenes.size()) +        { +            const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + +            LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + +            // Process all root nodes defined in the scene +            for (S32 root_idx : scene.mNodes) +            { +                if (root_idx >= 0 && root_idx < static_cast<S32>(mGLTFAsset.mNodes.size())) +                { +                    processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); +                } +            } +        } +    } +    else +    { +        LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + +        LLSD args; +        args["Message"] = "NoScenesFound"; +        mWarningsArray.append(args); +        return false; +    } + +    checkGlobalJointUsage(); + +    return true; +} + +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) +{ +    if (node_idx < 0 || node_idx >= static_cast<S32>(mGLTFAsset.mNodes.size())) +        return; + +    const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; + +    LL_DEBUGS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" +                            << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") +                            << " - children: " << node.mChildren.size() << LL_ENDL; + +    // Process this node's mesh if it has one +    if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) +    { +        LLMatrix4    transformation; +        material_map mats; + +        LLModel* pModel = new LLModel(volume_params, 0.f); +        const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; + +        // Get base mesh name and track usage +        std::string base_name = getLodlessLabel(mesh); +        if (base_name.empty()) +        { +            base_name = "mesh_" + std::to_string(node.mMesh); +        } + +        S32 instance_count = mesh_name_counts[base_name]++; + +        // 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)) +        { +            mTransform.setIdentity(); +            transformation = mTransform; + +            // adjust the transformation to compensate for mesh normalization +            LLVector3 mesh_scale_vector; +            LLVector3 mesh_translation_vector; +            pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + +            LLMatrix4 mesh_translation; +            mesh_translation.setTranslation(mesh_translation_vector); +            mesh_translation *= transformation; +            transformation = mesh_translation; + +            LLMatrix4 mesh_scale; +            mesh_scale.initScale(mesh_scale_vector); +            mesh_scale *= transformation; +            transformation = mesh_scale; + +            if (node.mSkin >= 0) +            { +                // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh +                // into the coordinate space of the joints. +                // In GLTF, this matrix is omitted, and it is assumed that this transform is either +                // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. +                // +                // TODO: There appears to be missing rotation when joints rotate the model +                // or inverted bind matrices are missing inherited rotation +                // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly +                // prior to skinning) + +                pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); +                LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; +            } + +            if (transformation.determinant() < 0) +            { // negative scales are not supported +                LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform.  domInstance_geometry: " +                           << pModel->mLabel << LL_ENDL; +                LLSD args; +                args["Message"] = "NegativeScaleNormTrans"; +                args["LABEL"]   = pModel->mLabel; +                mWarningsArray.append(args); +            } + +            addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats); +            mats.clear(); +        } +        else +        { +            setLoadState(ERROR_MODEL + pModel->getStatus()); +            delete pModel; +            return; +        } +    } +    else if (node.mMesh >= 0) +    { +        // Log invalid mesh reference +        LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName +                                << ") references invalid mesh " << node.mMesh +                                << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + +        LLSD args; +        args["Message"] = "InvalidMeshReference"; +        args["NODE_NAME"] = node.mName; +        args["MESH_INDEX"] = node.mMesh; +        args["TOTAL_MESHES"] = static_cast<S32>(mGLTFAsset.mMeshes.size()); +        mWarningsArray.append(args); +    } + +    // Process all children recursively +    for (S32 child_idx : node.mChildren) +    { +        processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); +    } +} + +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const +{ +    if (node_index < 0 || node_index >= static_cast<S32>(asset.mNodes.size())) +    { +        combined_transform = glm::mat4(1.0f); +        return; +    } + +    const auto& node = asset.mNodes[node_index]; + +    // Ensure the node's matrix is valid +    const_cast<LL::GLTF::Node&>(node).makeMatrixValid(); + +    // Start with this node's transform +    combined_transform = node.mMatrix; + +    // Find and apply parent transform if it exists +    for (size_t i = 0; i < asset.mNodes.size(); ++i) +    { +        const auto& potential_parent = asset.mNodes[i]; +        auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index); + +        if (it != potential_parent.mChildren.end()) +        { +            // Found parent - recursively get its combined transform and apply it +            glm::mat4 parent_transform; +            computeCombinedNodeTransform(asset, static_cast<S32>(i), parent_transform); +            combined_transform = parent_transform * combined_transform; +            return; // Early exit - a node can only have one parent +        } +    } +} + +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()) +    { +        llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 +        return false; +    } +    skin_info.mJointNames.push_back(legal_name); +    skin_info.mJointNums.push_back(-1); + +    // In scope of same skin multiple meshes reuse same bind matrices +    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; +} + +LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_index, S32 fallback_index) +{ +    // 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()) +                    { +                        mTexturesNeedScaling |= image.mHeight > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT || image.mWidth > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; +                        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]; + +    // Process URI-based textures +    if (!image.mUri.empty()) +    { +        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); +    } + +    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; +} + +std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) +{ +    if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) +    { +        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 +    { +        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(); + +    S32 skinIdx = nodeno.mSkin; + +    // Compute final combined transform matrix (hierarchy + coordinate rotation) +    S32 node_index = static_cast<S32>(&nodeno - &mGLTFAsset.mNodes[0]); +    glm::mat4 hierarchy_transform; +    computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); + +    // Combine transforms: coordinate rotation applied to hierarchy transform +    glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; +    if (mApplyXYRotation) +    { +        final_transform = coord_system_rotationxy * final_transform; +    } + +    // Check if we have a negative scale (flipped coordinate system) +    bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; + +    // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) +    const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); + +    // Mark unsuported joints with '-1' so that they won't get added into weights +    // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. +    std::vector<S32> gltf_joint_index_use; +    if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) +    { +        LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + +        size_t jointCnt = gltf_skin.mJoints.size(); +        gltf_joint_index_use.resize(jointCnt, 0); + +        for (size_t i = 0; i < jointCnt; ++i) +        { +            if (mJointNames[skinIdx][i].empty()) +            { +                // This might need to hold a substitute index +                gltf_joint_index_use[i] = -1; // mark as unsupported +            } +        } +    } + +    for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) +    { +        const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; + +        // So primitives already have all of the data we need for a given face in SL land. +        // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call +        // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 +        LLVolumeFace face; +        std::vector<GLTFVertex> vertices; + +        // 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) +        { +            LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx +                << ": Invalid index count " << prim.getIndexCount() +                << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; + +            LLSD args; +            args["Message"] = "InvalidGeometryNonTriangulated"; +            args["MESH_NAME"] = mesh.mName; +            args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx); +            args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount()); +            mWarningsArray.append(args); +            return false; // Skip this primitive +        } + +        // Apply the global scale and center offset to all vertices +        for (U32 i = 0; i < prim.getVertexCount(); i++) +        { +            // Use pre-computed final_transform +            glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); +            glm::vec4 transformed_pos = final_transform * pos; + +            GLTFVertex vert; +            vert.position = glm::vec3(transformed_pos); + +            if (!prim.mNormals.empty()) +            { +                // Use pre-computed normal_transform +                glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); +                vert.normal = glm::normalize(normal_transform * normal_vec); +            } +            else +            { +                // Use default normal (pointing up in model space) +                vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); +                LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; +            } + +            vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + +            if (skinIdx >= 0) +            { +                vert.weights = glm::vec4(prim.mWeights[i]); + +                auto accessorIdx = prim.mAttributes.at("JOINTS_0"); +                LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; +                if (accessorIdx >= 0) +                { +                    auto accessor = mGLTFAsset.mAccessors[accessorIdx]; +                    componentType = accessor.mComponentType; +                } + +                // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. +                // Detect and unpack accordingly. +                if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) +                { +                    auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); +                    vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); +                } +                else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) +                { +                    vert.joints = glm::unpackUint4x16(prim.mJoints[i]); +                } +                else +                { +                    vert.joints = glm::zero<glm::u16vec4>(); +                    vert.weights = glm::zero<glm::vec4>(); +                } +            } +            vertices.push_back(vert); +        } + +        // Check for empty vertex array before processing +        if (vertices.empty()) +        { +            LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL; +            LLSD args; +            args["Message"] = "EmptyVertexArray"; +            args["MESH_NAME"] = mesh.mName; +            args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx); +            args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount()); +            mWarningsArray.append(args); +            return false; // Skip this primitive +        } + +        std::vector<LLVolumeFace::VertexData> faceVertices; +        glm::vec3 min = glm::vec3(FLT_MAX); +        glm::vec3 max = glm::vec3(-FLT_MAX); + +        for (U32 i = 0; i < vertices.size(); i++) +        { +            LLVolumeFace::VertexData vert; + +            // Update min/max bounds +            if (i == 0) +            { +                min = max = vertices[i].position; +            } +            else +            { +                min.x = std::min(min.x, vertices[i].position.x); +                min.y = std::min(min.y, vertices[i].position.y); +                min.z = std::min(min.z, vertices[i].position.z); +                max.x = std::max(max.x, vertices[i].position.x); +                max.y = std::max(max.y, vertices[i].position.y); +                max.z = std::max(max.z, vertices[i].position.z); +            } + +            LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); +            LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); +            vert.setPosition(position); +            vert.setNormal(normal); +            vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); +            faceVertices.push_back(vert); + +            if (skinIdx >= 0) +            { +                // create list of weights that influence this vertex +                LLModel::weight_list weight_list; + +                // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) +                // don't reindex them yet, more indexes will be removed +                // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be +                // 'empty' ones +                if (gltf_joint_index_use[vertices[i].joints.x] >= 0 +                    && vertices[i].weights.x > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); +                    gltf_joint_index_use[vertices[i].joints.x]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.y] >= 0 +                    && vertices[i].weights.y > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); +                    gltf_joint_index_use[vertices[i].joints.y]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.z] >= 0 +                    && vertices[i].weights.z > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); +                    gltf_joint_index_use[vertices[i].joints.z]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.w] >= 0 +                    && vertices[i].weights.w > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); +                    gltf_joint_index_use[vertices[i].joints.w]++; +                } + +                std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + +                std::vector<LLModel::JointWeight> wght; +                F32                               total = 0.f; + +                for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) +                { +                    // take up to 4 most significant weights +                    // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. +                    wght.push_back(weight_list[j]); +                    total += weight_list[j].mWeight; +                } + +                if (total != 0.f) +                { +                    F32 scale = 1.f / total; +                    if (scale != 1.f) +                    { // normalize weights +                        for (U32 j = 0; j < wght.size(); ++j) +                        { +                            wght[j].mWeight *= scale; +                        } +                    } +                } + +                if (wght.size() > 0) +                { +                    pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; +                } +            } +        } + +        // Indices handling +        if (faceVertices.size() >= VERTEX_LIMIT) +        { +            // Will have to remap 32 bit indices into 16 bit indices +            // For the sake of simplicity build vector of 32 bit indices first +            std::vector<U32> indices_32; +            for (U32 i = 0; i < prim.getIndexCount(); i += 3) +            { +                // When processing indices, flip winding order if needed +                if (hasNegativeScale) +                { +                    // Flip winding order for negative scale +                    indices_32.push_back(prim.mIndexArray[i]); +                    indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two +                    indices_32.push_back(prim.mIndexArray[i + 1]); +                } +                else +                { +                    indices_32.push_back(prim.mIndexArray[i]); +                    indices_32.push_back(prim.mIndexArray[i + 1]); +                    indices_32.push_back(prim.mIndexArray[i + 2]); +                } +            } + +            // 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; +            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 < optimized_indices.size(); 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(optimized_vertices[vert_index]); +                    vert_index = new_vert_idx; + +                    // Update min/max bounds +                    const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); +                    if (new_vert_idx == 0) +                    { +                        min.x = vec[0]; +                        min.y = vec[1]; +                        min.z = vec[2]; +                        max = min; +                    } +                    else +                    { +                        min.x = std::min(min.x, vec[0]); +                        min.y = std::min(min.y, vec[1]); +                        min.z = std::min(min.z, vec[2]); +                        max.x = std::max(max.x, vec[0]); +                        max.y = std::max(max.y, vec[1]); +                        max.z = std::max(max.z, vec[2]); +                    } +                } +                else +                { +                    // already in vector, get position +                    vert_index = (size_t)vertices_remap[vert_index]; +                } +                indices_16.push_back((U16)vert_index); + +                if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTEX_LIMIT) +                { +                    LLVolumeFace face; +                    face.fillFromLegacyData(face_verts, indices_16); +                    face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +                    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(); +                    face_verts.clear(); + +                    min = glm::vec3(FLT_MAX); +                    max = glm::vec3(-FLT_MAX); +                } +            } +            if (indices_16.size() > 0 && face_verts.size() > 0) +            { +                LLVolumeFace face; +                face.fillFromLegacyData(face_verts, indices_16); +                face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +                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 +        { +            // can use indices directly +            std::vector<U16> indices; +            for (U32 i = 0; i < prim.getIndexCount(); i += 3) +            { +                // When processing indices, flip winding order if needed +                if (hasNegativeScale) +                { +                    // Flip winding order for negative scale +                    indices.push_back(prim.mIndexArray[i]); +                    indices.push_back(prim.mIndexArray[i + 2]); // Swap these two +                    indices.push_back(prim.mIndexArray[i + 1]); +                } +                else +                { +                    indices.push_back(prim.mIndexArray[i]); +                    indices.push_back(prim.mIndexArray[i + 1]); +                    indices.push_back(prim.mIndexArray[i + 2]); +                } +            } + +            face.fillFromLegacyData(faceVertices, indices); +            face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +            face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + +            pModel->getVolumeFaces().push_back(face); +            pModel->getMaterialList().push_back(materialName); +        } +    } + +    // Call normalizeVolumeFacesAndWeights to compute proper extents +    pModel->normalizeVolumeFacesAndWeights(); + +    // Fill joint names, bind matrices and remap weight indices +    if (skinIdx >= 0) +    { +        LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; +        LLMeshSkinInfo& skin_info = pModel->mSkinInfo; +        S32 valid_joints_count = mValidJointsCount[skinIdx]; + +        S32 replacement_index = 0; +        std::vector<S32> gltfindex_to_joitindex_map; +        size_t jointCnt = gltf_skin.mJoints.size(); +        gltfindex_to_joitindex_map.resize(jointCnt, -1); + +        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 +            // or add individual when parents are added + +            // Check which groups are in use +            for (size_t i = 0; i < jointCnt; ++i) +            { +                std::string& joint_name = mJointNames[skinIdx][i]; +                if (!joint_name.empty()) +                { +                    if (gltf_joint_index_use[i] > 0) +                    { +                        const JointGroups &group = mJointGroups[joint_name]; +                        // Joint in use, increment it's groups +                        goup_use_count[group.mGroup]++; +                        goup_use_count[group.mParentGroup]++; +                    } +                } +            } + +            // 1. add joints that are in use directly +            for (size_t i = 0; i < jointCnt; ++i) +            { +                // Process joint name and idnex +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] <= 0) +                { +                    // unsupported (-1) joint, drop it +                    // unused (0) joint, drop it +                    continue; +                } + +                if (addJointToModelSkin(skin_info, skinIdx, i)) +                { +                    gltfindex_to_joitindex_map[i] = replacement_index++; +                } +            } + +            // 2. add joints from groups that this model's joints belong to +            // It's perfectly valid to have more joints than is in use +            // Ex: sandals that make your legs digitigrade despite not skining to +            // knees or the like. +            // Todo: sort and add by usecount +            for (size_t i = 0; i < jointCnt; ++i) +            { +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] != 0) +                { +                    // this step needs only joints that have zero uses +                    continue; +                } +                if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) +                { +                    break; +                } +                const std::string& legal_name = mJointNames[skinIdx][i]; +                std::string group_name = mJointGroups[legal_name].mGroup; +                if (goup_use_count[group_name] > 0) +                { +                    if (addJointToModelSkin(skin_info, skinIdx, i)) +                    { +                        gltfindex_to_joitindex_map[i] = replacement_index++; +                    } +                } +            } +        } +        else +        { +            // Less than 110, just add every valid joint +            for (size_t i = 0; i < jointCnt; ++i) +            { +                // Process joint name and idnex +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] < 0) +                { +                    // unsupported (-1) joint, drop it +                    continue; +                } + +                if (addJointToModelSkin(skin_info, skinIdx, i)) +                { +                    gltfindex_to_joitindex_map[i] = replacement_index++; +                } +            } +        } + +        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)mMaxJointsPerMesh << LL_ENDL; +            LLSD args; +            args["Message"] = "ModelTooManyJoints"; +            args["MODEL_NAME"] = pModel->mLabel; +            args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); +            args["MAX"] = (S32)mMaxJointsPerMesh; +            mWarningsArray.append(args); +        } + +        // Remap indices for pModel->mSkinWeights +        for (auto& weights : pModel->mSkinWeights) +        { +            for (auto& weight : weights.second) +            { +                weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; +            } +        } +    } + +    return true; +} + +void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) +{ +    const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; + +    LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL; + +    if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size()) +    { +        LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; +        LLSD args; +        args["Message"] = "InvBindCountMismatch"; +        mWarningsArray.append(args); +    } + +    S32 joint_count = (S32)skin.mJoints.size(); +    S32 inverse_count = (S32)skin.mInverseBindMatricesData.size(); +    if (mInverseBindMatrices.size() <= 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); +    } + +    // fill up joints related data +    joints_data_map_t joints_data; +    joints_name_to_node_map_t names_to_nodes; +    for (S32 i = 0; i < joint_count; i++) +    { +        S32 joint = skin.mJoints[i]; +        const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; +        JointNodeData& data = joints_data[joint]; +        data.mNodeIdx = joint; +        data.mJointListIdx = i; +        data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin); +        data.mGltfMatrix = jointNode.mMatrix; +        data.mOverrideMatrix = glm::mat4(1.f); + +        if (mJointMap.find(jointNode.mName) != mJointMap.end()) +        { +            data.mName = mJointMap[jointNode.mName]; +            data.mIsValidViewerJoint = true; +            mValidJointsCount[skin_idx]++; +        } +        else +        { +            data.mName = jointNode.mName; +            data.mIsValidViewerJoint = false; +        } +        names_to_nodes[data.mName] = joint; + +        for (S32 child : jointNode.mChildren) +        { +            JointNodeData& child_data = joints_data[child]; +            child_data.mParentNodeIdx = joint; +            child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint; +        } +    } + +    // 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) +    { +        buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); +    } + +    for (S32 i = 0; i < joint_count; i++) +    { +        S32 joint = skin.mJoints[i]; +        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()) +        { +            legal_name = mJointMap[legal_name]; +            legal_joint = true; +            mJointNames[skin_idx].push_back(legal_name); +        } +        else +        { +            mJointNames[skin_idx].emplace_back(); +        } +        mJointUsage[skin_idx].push_back(0); + +        // Compute bind matrices + +        if (!legal_joint) +        { +            // Add placeholder to not break index. +            // Not going to be used by viewer, will be stripped from skin_info. +            LLMatrix4 gltf_transform; +            gltf_transform.setIdentity(); +            mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); +        } +        else if (inverse_count > i) +        { +            // 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); +            glm::mat4 tranlated_original = skeleton_transform * rotated_original; +            glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + +            LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); +            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 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_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; +            mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); +        } + +        // Compute Alternative matrices also known as overrides +        LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix)); + +        // Viewer seems to care only about translation part, +        // but for parity with collada taking original value +        LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); +        newInverse.setTranslation(original_joint_transform.getTranslation()); + +        LL_DEBUGS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; +        mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); + +        if (legal_joint) +        { +            // Might be needed for uploader UI to correctly identify overriden joints +            // but going to be incorrect if multiple skins are present +            mJointList[legal_name] = newInverse; +            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() +{ +    std::string parent; +    for (auto& viewer_data : mViewerJointData) +    { +        buildJointGroup(viewer_data, parent); +    } +} + +void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) +{ +    JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; +    jount_group_data.mGroup = viewer_data.mGroup; +    jount_group_data.mParentGroup = parent_group; + +    for (LLJointData& child_data : viewer_data.mChildren) +    { +        buildJointGroup(child_data, viewer_data.mGroup); +    } +} + +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 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()) +    { +        S32 gltf_node_idx = found_node->second; +        JointNodeData& node = gltf_nodes[gltf_node_idx]; +        node.mIsOverrideValid = true; +        node.mViewerRestMatrix = viewer_data.mRestMatrix; + +        glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; +        if (mApplyXYRotation) +        { +            gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; +        } + +        glm::mat4 translated_joint; +        // Example: +        // Viewer has pelvis->spine1->spine2->torso. +        // gltf example model has pelvis->torso +        // By doing glm::inverse(transalted_rest_spine2) * gltf_rest_torso +        // We get what torso would have looked like if gltf had a spine2 +        if (viewer_data.mIsJoint) +        { +            translated_joint = glm::inverse(parent_rest) * gltf_joint_rest_pose; +        } +        else +        { +            translated_joint = glm::inverse(parent_support_rest) * gltf_joint_rest_pose; +        } + +        glm::vec3 translation_override; +        glm::vec3 skew; +        glm::vec3 scale; +        glm::vec4 perspective; +        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 overriden_joint = node.mOverrideMatrix; + +        // 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 +    { +        // No override for this joint +        rest = parent_rest * viewer_data.mJointMatrix; +    } + +    glm::mat4 support_rest(1.f); +    if (viewer_data.mSupport == LLJointData::SUPPORT_BASE) +    { +        support_rest = rest; +    } +    else +    { +        support_rest = parent_support_rest; +    } + +    for (LLJointData& child_data : viewer_data.mChildren) +    { +        buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, support_rest); +    } +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ +    // This is inefficient since we are recalculating some joints multiple times over +    // Todo: cache it? + +    if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) +    { +        return glm::mat4(1.0f); +    } + +    const auto& node = mGLTFAsset.mNodes[joint_node_index]; + +    // Find and apply parent transform if it exists +    for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) +    { +        const auto& potential_parent = mGLTFAsset.mNodes[i]; +        auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + +        if (it != potential_parent.mChildren.end()) +        { +            // Found parent +            if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) +            { +                // parent is a joint - recursively combine transform +                // assumes that matrix is already valid +                return buildGltfRestMatrix(static_cast<S32>(i), gltf_skin) * node.mMatrix; +            } +        } +    } +    // Should we return armature or stop earlier? +    return node.mMatrix; +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ +    // This is inefficient since we are recalculating some joints multiple times over +    // Todo: cache it? + +    if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) +    { +        return glm::mat4(1.0f); +    } + +    auto& data = joint_data.at(joint_node_index); + +    if (data.mParentNodeIdx >=0) +    { +        return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; +    } +    // Should we return armature or stop earlier? +    return data.mGltfMatrix; +} + +// This function computes the transformation matrix needed to convert from GLTF skeleton space +// to viewer skeleton space for a specific joint + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const +{ +    const JointNodeData& node_data = joints_data_map.at(gltf_node_index); +    if (!node_data.mIsOverrideValid) +    { +        // For now assume they are identical and return an identity (for ease of debuging) +        return glm::mat4(1.0f); +    } + +    // Get the GLTF joint's rest pose (in GLTF coordinate system) +    const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; +    glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; + +    LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + +    LLMatrix4 transform(glm::value_ptr(rest_pose)); + +    LL_CONT << transform << LL_ENDL; + +    // Compute transformation from GLTF space to viewer space +    // This assumes both skeletons are in rest pose initially +    return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); +} + +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ +    glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); +    glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; +    // Normally for shoulders it should be something close to +    // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} +    // rotated one will look like +    // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} +    // Todo: This is a cheap hack, +    // figure out how rotation is supposed to work +    return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ +    // HACK: figure out model's rotation from shoulders' matrix. +    // This is wrong on many levels: +    // Too limited (only models that have shoulders), +    // Will not work well with things that emulate 3 hands in some manner +    // Only supports xy 90 degree rotation +    // Todo: figure out how to find skeleton's orientation Correctly +    // when model is rotated at a triangle level +    constexpr char right_shoulder_str[] = "mShoulderRight"; +    constexpr char left_shoulder_str[] = "mShoulderLeft"; + +    S32 size = (S32)gltf_skin.mJoints.size(); +    S32 joints_found = 0; +    for (S32 i= 0; i < size; i++) +    { +        S32 joint = gltf_skin.mJoints[i]; +        const LL::GLTF::Node &joint_node = mGLTFAsset.mNodes[joint]; + +        // todo: we are doing this search thing everywhere, +        // just pre-translate every joint +        JointMap::iterator found = mJointMap.find(joint_node.mName); +        if (found == mJointMap.end()) +        { +            // unsupported joint +            continue; +        } +        if (found->second == right_shoulder_str || found->second == left_shoulder_str) +        { +            if (checkForXYrotation(gltf_skin, joint, i)) +            { +                joints_found++; +            } +            else +            { +                return; +            } +        } +    } + +    if (joints_found == 2) +    { +        // Both joints in a weird position/rotation, assume rotated model +        mApplyXYRotation = true; +    } +} + +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()) +        return ""; + +    S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; +    if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) +        return ""; + +    LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + +    // Handle URI-based textures +    if (!image.mUri.empty()) +    { +        return image.mUri; // Return URI directly +    } + +    // Handle embedded textures +    if (image.mBufferView >= 0) +    { +        if (image.mBufferView < mGLTFAsset.mBufferViews.size()) +        { +            const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; +            if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) +            { +                const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + +                if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) +                { +                    // Extract image data +                    const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; +                    U32 data_size = buffer_view.mByteLength; + +                    // Determine the file extension +                    std::string extension = ".png"; // Default +                    if (!image.mMimeType.empty()) +                    { +                        if (image.mMimeType == "image/jpeg") +                            extension = ".jpg"; +                        else if (image.mMimeType == "image/png") +                            extension = ".png"; +                    } +                    else if (data_size >= 4) +                    { +                        if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) +                            extension = ".jpg"; // JPEG magic bytes +                        else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) +                            extension = ".png"; // PNG magic bytes +                    } + +                    // Create a temporary file +                    std::string temp_dir = gDirUtilp->getTempDir(); +                    std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + +                                               "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; + +                    // Write the image data to the temporary file +                    std::ofstream temp_file(temp_filename, std::ios::binary); +                    if (temp_file.is_open()) +                    { +                        temp_file.write(reinterpret_cast<const char*>(data_ptr), data_size); +                        temp_file.close(); + +                        LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; +                        return temp_filename; +                    } +                    else +                    { +                        LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + +                        LLSD args; +                        args["Message"] = "FailedToCreateTempFile"; +                        args["TEXTURE_INDEX"] = sourceIndex; +                        args["TEXTURE_TYPE"]  = texture_type; +                        args["TEMP_FILE"] = temp_filename; +                        mWarningsArray.append(args); +                    } +                } +            } +        } +    } + +    return ""; +} + +void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) +{ +    std::vector<std::string> extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions; +    if (extensions.size() > 0) +    { +        LLSD args; +        args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension"; +        std::string del; +        std::string ext; +        for (auto& extension : extensions) +        { +            ext += del; +            ext += extension; +            del = ","; +        } +        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 new file mode 100644 index 0000000000..e8b91996c7 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.h @@ -0,0 +1,216 @@ +/** + * @file LLGLTFLoader.h + * @brief LLGLTFLoader class definition + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGLTFLoader_H +#define LL_LLGLTFLoader_H + +#include "tinygltf/tiny_gltf.h" + +#include "asset.h" + +#include "llglheaders.h" +#include "lljointdata.h" +#include "llmodelloader.h" + +class LLGLTFLoader : public LLModelLoader +{ +  public: +    typedef std::map<std::string, LLImportMaterial> material_map; +    typedef std::map<std::string, std::string> joint_viewer_parent_map_t; +    typedef std::map<std::string, glm::mat4> joint_viewer_rest_map_t; +    typedef std::map<S32, glm::mat4> joint_node_mat4_map_t; + +    struct JointNodeData +    { +        JointNodeData() +            : mJointListIdx(-1) +            , mNodeIdx(-1) +            , mParentNodeIdx(-1) +            , mIsValidViewerJoint(false) +            , mIsParentValidViewerJoint(false) +            , mIsOverrideValid(false) +        { + +        } +        S32 mJointListIdx; +        S32 mNodeIdx; +        S32 mParentNodeIdx; +        glm::mat4 mGltfRestMatrix; +        glm::mat4 mViewerRestMatrix; +        glm::mat4 mOverrideRestMatrix; +        glm::mat4 mGltfMatrix; +        glm::mat4 mOverrideMatrix; +        std::string mName; +        bool mIsValidViewerJoint; +        bool mIsParentValidViewerJoint; +        bool mIsOverrideValid; +    }; +    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, 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); + +    struct GLTFVertex +    { +        glm::vec3 position; +        glm::vec3 normal; +        glm::vec2 uv0; +        glm::u16vec4 joints; +        glm::vec4 weights; +    }; + +protected: +    LL::GLTF::Asset mGLTFAsset; +    tinygltf::Model mGltfModel; +    bool            mGltfLoaded = false; +    bool            mApplyXYRotation = false; + +    // GLTF isn't aware of viewer's skeleton and uses it's own, +    // so need to take viewer's joints and use them to +    // recalculate iverse bind matrices +    std::vector<LLJointData>             mViewerJointData; + +    // vector of vectors because of a posibility of having more than one skin +    typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t; +    typedef std::vector<std::vector<std::string> > joint_names_t; +    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. +    struct JointGroups +    { +        std::string mGroup; +        std::string mParentGroup; +    }; +    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); +    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, 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; +    glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; +    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 + +    void processElement(gltfElement *element, bool &badElement, GLTF *gltf); +    void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); + +    material_map     getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); +    LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); +    LLColor4         getGltfColor(gltfElement *element); + +    gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); + +    bool isNodeAJoint(gltfNode *pNode); +    void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms); +    void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); +    void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); +    void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); +    void buildJointToNodeMappingFromScene(gltfElement *pRoot); +    void processJointToNodeMapping(gltfNode *pNode); +    void processChildJoints(gltfNode *pParentNode); + +    bool verifyCount(int expected, int result); + +    // Verify that a controller matches vertex counts +    bool verifyController(gltfController *pController); + +    static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); +    static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); + +    static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); + +    // Loads a mesh breaking it into one or more models as necessary +    // to get around volume face limitations while retaining >8 materials +    // +    bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit); + +    static std::string preprocessGLTF(std::string filename); +    */ + +}; +#endif  // LL_LLGLTFLLOADER_H diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 9faead9533..3cb5e9a0d7 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename)  {      std::shared_ptr<Asset> asset = std::make_shared<Asset>(); -    if (asset->load(filename)) +    if (asset->load(filename, true))      {          gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions          asset->updateTransforms(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index a5535d4bcc..28b40543e9 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2262,10 +2262,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: @@ -2273,6 +2270,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); @@ -2280,6 +2281,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() @@ -5704,6 +5709,28 @@ void LLAppViewer::forceErrorThreadCrash()      thread->start();  } +void LLAppViewer::forceExceptionThreadCrash() +{ +    class LLCrashTestThread : public LLThread +    { +    public: + +        LLCrashTestThread() : LLThread("Crash logging test thread") +        { +        } + +        void run() +        { +            const std::string exception_text = "This is a deliberate exception in a thread"; +            throw std::runtime_error(exception_text); +        } +    }; + +    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 0424bdd34f..14e96afe94 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/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..41e954b7fa 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance;  #define XML_FILTER L"XML files (*.xml)\0*.xml\0"  #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"  #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0"  #define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"  #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"  #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" @@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter)          break;      case FFLOAD_MODEL:          mOFN.lpstrFilter = MODEL_FILTER \ +            COLLADA_FILTER \ +            MATERIAL_FILTER \              L"\0";          break;      case FFLOAD_MATERIAL: @@ -671,6 +673,8 @@ std::unique_ptr<std::vector<std::string>> LLFilePicker::navOpenFilterProc(ELoadF          case FFLOAD_HDRI:              allowedv->push_back("exr");          case FFLOAD_MODEL: +            allowedv->push_back("gltf"); +            allowedv->push_back("glb");          case FFLOAD_COLLADA:              allowedv->push_back("dae");              break; diff --git a/indra/newview/llfloateravatar.cpp b/indra/newview/llfloateravatarwelcomepack.cpp index 404316275d..82e44d1398 100644 --- a/indra/newview/llfloateravatar.cpp +++ b/indra/newview/llfloateravatarwelcomepack.cpp @@ -1,7 +1,7 @@  /** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer + * @file llfloateravatarwelcomepack.cpp + * @author Callum Prentice (callum@lindenlab.com) + * @brief Floater container for the Avatar Welcome Pack we app   *   * $LicenseInfo:firstyear=2011&license=viewerlgpl$   * Second Life Viewer Source Code @@ -27,17 +27,16 @@  #include "llviewerprecompiledheaders.h" -#include "llfloateravatar.h" +#include "llfloateravatarwelcomepack.h"  #include "lluictrlfactory.h"  #include "llmediactrl.h" - -LLFloaterAvatar::LLFloaterAvatar(const LLSD& key) +LLFloaterAvatarWelcomePack::LLFloaterAvatarWelcomePack(const LLSD& key)      :   LLFloater(key)  {  } -LLFloaterAvatar::~LLFloaterAvatar() +LLFloaterAvatarWelcomePack::~LLFloaterAvatarWelcomePack()  {      if (mAvatarPicker)      { @@ -47,15 +46,13 @@ LLFloaterAvatar::~LLFloaterAvatar()      }  } -bool LLFloaterAvatar::postBuild() +bool LLFloaterAvatarWelcomePack::postBuild()  {      mAvatarPicker = findChild<LLMediaCtrl>("avatar_picker_contents");      if (mAvatarPicker)      {          mAvatarPicker->clearCache();      } -    enableResizeCtrls(true, true, false); +      return true;  } - - diff --git a/indra/newview/llfloateravatar.h b/indra/newview/llfloateravatarwelcomepack.h index fb591c8306..a332d46708 100644 --- a/indra/newview/llfloateravatar.h +++ b/indra/newview/llfloateravatarwelcomepack.h @@ -1,7 +1,7 @@  /** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer + * @file llfloateravatarwelcomepack.h + * @author Callum Prentice (callum@lindenlab.com) + * @brief Floater container for the Avatar Welcome Pack we app   *   * $LicenseInfo:firstyear=2011&license=viewerlgpl$   * Second Life Viewer Source Code @@ -25,22 +25,21 @@   * $/LicenseInfo$   */ -#ifndef LL_FLOATER_AVATAR_H -#define LL_FLOATER_AVATAR_H +#pragma once  #include "llfloater.h" +  class LLMediaCtrl; -class LLFloaterAvatar: +class LLFloaterAvatarWelcomePack:      public LLFloater  {      friend class LLFloaterReg; -private: -    LLFloaterAvatar(const LLSD& key); -    ~LLFloaterAvatar(); -    bool postBuild() override; -    LLMediaCtrl* mAvatarPicker; -}; +    private: +        LLFloaterAvatarWelcomePack(const LLSD& key); +        ~LLFloaterAvatarWelcomePack(); +        bool postBuild() override; -#endif +        LLMediaCtrl* mAvatarPicker; +}; diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 47471edb92..332a98031f 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@  #include "llcallbacklist.h"  #include "llviewertexteditor.h"  #include "llviewernetwork.h" +#include "llmaterialeditor.h"  //static @@ -164,7 +165,7 @@ bool LLFloaterModelPreview::postBuild()      for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod)      {          LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]); -        lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod)); +        lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod, true));          lod_source_combo->setCurrentByIndex(mLODMode[lod]);          getChild<LLButton>("lod_browse_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onBrowseLOD, this, lod)); @@ -619,11 +620,9 @@ void LLFloaterModelPreview::onJointListSelection()      LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");      LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");      LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list"); -    LLScrollListCtrl *joints_scale = panel->getChild<LLScrollListCtrl>("scale_overrides_list");      LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");      joints_pos->deleteAllItems(); -    joints_scale->deleteAllItems();      LLScrollListItem *selected = joints_list->getFirstSelected();      if (selected) @@ -757,7 +756,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)          mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode);          break;      default: -        LL_ERRS() << "Only supposed to be called to generate models, val: " << mode << LL_ENDL; +        LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL;          break;      } @@ -767,7 +766,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)          LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[i]);          if (lod_source_combo->getCurrentIndex() == LLModelPreview::USE_LOD_ABOVE)          { -            onLoDSourceCommit(i); +            onLoDSourceCommit(i, false);          }          else          { @@ -1341,26 +1340,26 @@ void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLS      {          std::string str;          switch (lod) -{ +        {          case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break;          case LLModel::LOD_LOW:      str = "LOD1 "; break;          case LLModel::LOD_MEDIUM:   str = "LOD2 "; break;          case LLModel::LOD_PHYSICS:  str = "PHYS "; break;          case LLModel::LOD_HIGH:     str = "LOD3 ";   break;          default: break; -} +        }          LLStringUtil::format_map_t args_msg;          LLSD::map_const_iterator iter = args.beginMap();          LLSD::map_const_iterator end = args.endMap();          for (; iter != end; ++iter) -{ +        {              args_msg[iter->first] = iter->second.asString();          }          str += sInstance->getString(message, args_msg);          sInstance->addStringToLogTab(str, flash);      } -    } +}  // static  void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash) @@ -1761,7 +1760,7 @@ void LLFloaterModelPreview::toggleCalculateButton(bool visible)      }  } -void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) +void LLFloaterModelPreview::onLoDSourceCommit(S32 lod, bool refresh_ui)  {      mModelPreview->updateLodControls(lod); @@ -1770,9 +1769,17 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod)      if (index == LLModelPreview::MESH_OPTIMIZER_AUTO          || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY          || index == LLModelPreview::MESH_OPTIMIZER_PRECISE) -    { //rebuild LoD to update triangle counts +    { +        // rebuild LoD to update triangle counts          onLODParamCommit(lod, true);      } +    else if (refresh_ui && index == LLModelPreview::USE_LOD_ABOVE) +    { +        // Update mUploadData for updateStatusMessages +        mModelPreview->rebuildUploadData(); +        // Update UI with new triangle values +        mModelPreview->updateStatusMessages(); +    }  }  void LLFloaterModelPreview::resetDisplayOptions() diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 6adc084fe8..5d23dc8d8e 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -208,7 +208,7 @@ private:      void onClickCalculateBtn();      void onJointListSelection(); -    void onLoDSourceCommit(S32 lod); +    void onLoDSourceCommit(S32 lod, bool refresh_ui);      void modelUpdated(bool calculate_visible); diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index da808bd8f2..03979edbc1 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -371,6 +371,7 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key)      mWaitingForTracker(false),      mIsClosing(false),      mSetToUserPosition(true), +    mProcessingSearchUpdate(false),      mTrackedLocation(0.0,0.0,0.0),      mTrackedStatus(LLTracker::TRACKING_NOTHING),      mParcelInfoObserver(nullptr), @@ -384,7 +385,7 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key)      mCommitCallbackRegistrar.add("WMap.Location",       boost::bind(&LLFloaterWorldMap::onLocationCommit, this));      mCommitCallbackRegistrar.add("WMap.AvatarCombo",    boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this));      mCommitCallbackRegistrar.add("WMap.Landmark",       boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this)); -    mCommitCallbackRegistrar.add("WMap.SearchResult",   boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this)); +    mCommitCallbackRegistrar.add("WMap.SearchResult", [this](LLUICtrl* ctrl, const LLSD& data) { LLFloaterWorldMap::onCommitSearchResult(false); });      mCommitCallbackRegistrar.add("WMap.GoHome",         boost::bind(&LLFloaterWorldMap::onGoHome, this));      mCommitCallbackRegistrar.add("WMap.Teleport",       boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this));      mCommitCallbackRegistrar.add("WMap.ShowTarget",     boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this)); @@ -829,6 +830,7 @@ void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item)  void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)  { +    mProcessingSearchUpdate = false;      LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global);      if (!sim_info)      { @@ -968,7 +970,10 @@ void LLFloaterWorldMap::updateLocation()              }          } -        mLocationEditor->setValue(sim_name); +        if (!mProcessingSearchUpdate) +        { +            mLocationEditor->setValue(sim_name); +        }          // refresh coordinate display to reflect where user clicked.          LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); @@ -1242,6 +1247,7 @@ void LLFloaterWorldMap::onGoHome()  {      gAgent.teleportHome();      closeFloater(); +    mProcessingSearchUpdate = false;  } @@ -1411,6 +1417,7 @@ void LLFloaterWorldMap::onLocationCommit()      {          return;      } +    mProcessingSearchUpdate = true;      LLStringUtil::toLower(str);      mCompletingRegionName = str; @@ -1432,6 +1439,7 @@ void LLFloaterWorldMap::onCoordinatesCommit()      {          return;      } +    mProcessingSearchUpdate = false;      S32 x_coord = (S32)mTeleportCoordSpinX->getValue().asReal();      S32 y_coord = (S32)mTeleportCoordSpinY->getValue().asReal(); @@ -1445,6 +1453,7 @@ void LLFloaterWorldMap::onCoordinatesCommit()  void LLFloaterWorldMap::onClearBtn()  {      mTrackedStatus = LLTracker::TRACKING_NOTHING; +    mProcessingSearchUpdate = false;      LLTracker::stopTracking(true);      LLWorldMap::getInstance()->cancelTracking();      mSLURL = LLSLURL();                 // Clear the SLURL since it's invalid @@ -1461,6 +1470,7 @@ void LLFloaterWorldMap::onShowAgentBtn()      mMapView->setPanWithInterpTime(0, 0, false, 0.1f);  // false == animate      // Set flag so user's location will be displayed if not tracking anything else      mSetToUserPosition = true; +    mProcessingSearchUpdate = false;  }  void LLFloaterWorldMap::onClickTeleportBtn() @@ -1616,6 +1626,12 @@ void LLFloaterWorldMap::teleport()              gAgent.teleportViaLocation( pos_global );          }      } + +    if (mProcessingSearchUpdate) +    { +        mProcessingSearchUpdate = false; +        mTrackedSimName.clear(); +    }  }  void LLFloaterWorldMap::flyToLandmark() @@ -1741,18 +1757,20 @@ void LLFloaterWorldMap::updateSims(bool found_null_sim)          {              mSearchResults->selectByValue(match);              mSearchResults->setFocus(true); -            onCommitSearchResult(); +            onCommitSearchResult(false /*fully commit the only option*/);          }          // else let user decide          else          { -            mSearchResults->operateOnAll(LLCtrlListInterface::OP_DESELECT); +            mSearchResults->selectFirstItem();              mSearchResults->setFocus(true); +            onCommitSearchResult(true /*don't update text field*/);          }      }      else      {          // if we found nothing, say "none" +        mProcessingSearchUpdate = false;          mSearchResults->setCommentText(LLTrans::getString("worldmap_results_none_found"));          mSearchResults->operateOnAll(LLCtrlListInterface::OP_DESELECT);      } @@ -1766,7 +1784,7 @@ void LLFloaterWorldMap::onTeleportFinished()      }  } -void LLFloaterWorldMap::onCommitSearchResult() +void LLFloaterWorldMap::onCommitSearchResult(bool from_search)  {      std::string sim_name = mSearchResults->getSelectedValue().asString();      if (sim_name.empty()) @@ -1797,8 +1815,14 @@ void LLFloaterWorldMap::onCommitSearchResult()              pos_global.mdV[VY] += (F64)pos_local.mV[VY];              pos_global.mdV[VZ] = (F64)pos_local.mV[VZ]; -            mLocationEditor->setValue(sim_name); +            // Commiting search string automatically selects first item in the search list, +            // in such case onCommitSearchResult shouldn't modify search string +            if (!from_search) +            { +                mLocationEditor->setValue(sim_name); +            }              trackLocation(pos_global); +            mProcessingSearchUpdate = from_search;              mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);              break;          } diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index bf1650eb7c..9558ca2615 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -176,7 +176,7 @@ protected:      void            onLocationFocusChanged( LLFocusableElement* ctrl );      void            onLocationCommit();      void            onCoordinatesCommit(); -    void            onCommitSearchResult(); +    void            onCommitSearchResult(bool from_search);      void            onTeleportFinished(); @@ -213,6 +213,7 @@ private:      bool                    mIsClosing;      bool                    mSetToUserPosition; +    bool                    mProcessingSearchUpdate; // Don't update search string from what user set it to      LLVector3d              mTrackedLocation;      LLTracker::ETrackingStatus mTrackedStatus; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index cf1efd283a..f1ca69416b 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2682,6 +2682,7 @@ bool LLInventoryModel::loadSkeleton(      LL_PROFILE_ZONE_SCOPED;      LL_DEBUGS(LOG_INV) << "importing inventory skeleton for " << owner_id << LL_ENDL; +    LLTimer timer;      typedef std::set<LLPointer<LLViewerInventoryCategory>, InventoryIDPtrLess> cat_set_t;      cat_set_t temp_cats;      bool rv = true; @@ -2966,7 +2967,8 @@ bool LLInventoryModel::loadSkeleton(      }      LL_INFOS(LOG_INV) << "Successfully loaded " << cached_category_count -                      << " categories and " << cached_item_count << " items from cache." +                      << " categories and " << cached_item_count << " items from cache" +                      << " after " << timer.getElapsedTimeF32() << " seconds."                        << LL_ENDL;      return rv; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 5a067ba454..443a0022fb 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -364,9 +364,28 @@ void LLInventoryPanel::initializeViewBuilding()          if (mInventory->isInventoryUsable()              && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT)          { +            LLTimer timer;              // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect              const F64 max_time = 20.f;              initializeViews(max_time); + +            if (mViewsInitialized == VIEWS_INITIALIZED) +            { +                LL_INFOS("Inventory") +                    << "Fully initialized inventory panel " << getName() +                    << " with " << (S32)mItemMap.size() +                    << " views in " << timer.getElapsedTimeF32() << " seconds." +                    << LL_ENDL; +            } +            else +            { +                LL_INFOS("Inventory") +                    << "Partially initialized inventory panel " << getName() +                    << " with " << (S32)mItemMap.size() +                    << " views in " << timer.getElapsedTimeF32() +                    << " seconds. Pending known views: " << (S32)mBuildViewsQueue.size() +                    << LL_ENDL; +            }          }          else          { diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 28160177f6..3cab060357 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI(          {              combo_picker->mComboOptions->addSimpleElement(*iter);          } -        combo_picker->mComboOptions->selectFirstItem(); +        // select 'Bulk Upload All' option +        combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1);          combo_picker->openFloater(LLSD(title));          combo_picker->setFocus(true); @@ -1332,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 @@ -1348,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;  } @@ -2478,6 +2479,42 @@ void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::      pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img,          mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C); +    if (open_floater) +    { +        bool textures_scaled = false; +        if (mBaseColorFetched && mBaseColorJ2C +            && (mBaseColorFetched->getWidth() != mBaseColorJ2C->getWidth() +                || mBaseColorFetched->getHeight() != mBaseColorJ2C->getHeight())) +        { +            textures_scaled = true; +        } +        else if (mNormalFetched && mNormalJ2C +            && (mNormalFetched->getWidth() != mNormalJ2C->getWidth() +                || mNormalFetched->getHeight() != mNormalJ2C->getHeight())) +        { +            textures_scaled = true; +        } +        else if (mMetallicRoughnessFetched && mMetallicRoughnessJ2C +            && (mMetallicRoughnessFetched->getWidth() != mMetallicRoughnessJ2C->getWidth() +                || mMetallicRoughnessFetched->getHeight() != mMetallicRoughnessJ2C->getHeight())) +        { +            textures_scaled = true; +        } +        else if (mEmissiveFetched && mEmissiveJ2C +            && (mEmissiveFetched->getWidth() != mEmissiveJ2C->getWidth() +                || mEmissiveFetched->getHeight() != mEmissiveJ2C->getHeight())) +        { +            textures_scaled = true; +        } + +        if (textures_scaled) +        { +            LLSD args; +            args["MAX_SIZE"] = LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; +            LLNotificationsUtil::add("MaterialImagesWereScaled", args); +        } +    } +      LLUUID base_color_id;      if (mBaseColorFetched.notNull())      { @@ -2684,10 +2721,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 << @@ -2696,28 +2731,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 f25cb25517..eb9e054600 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2401,6 +2401,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;          }      } @@ -2700,10 +2705,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)          { @@ -2712,7 +2721,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]; @@ -2746,7 +2755,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)                  mUploadSkin,                  mUploadJoints,                  mLockScaleIfJointPosition, -                false, +                LLModel::WRITE_BINARY,                  false,                  data.mBaseModel->mSubmodelID); @@ -2759,8 +2768,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)          { @@ -2858,10 +2867,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)          { @@ -2870,7 +2880,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]; @@ -2904,7 +2914,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)                  mUploadSkin,                  mUploadJoints,                  mLockScaleIfJointPosition, -                false, +                LLModel::WRITE_BINARY,                  false,                  data.mBaseModel->mSubmodelID); @@ -2917,8 +2927,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 49c0006f66..d68fc9c02d 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@  #include "llmodelloader.h"  #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h"  #include "llfloatermodelpreview.h"  #include "llagent.h" @@ -40,6 +40,7 @@  #include "lldrawable.h"  #include "llface.h"  #include "lliconctrl.h" +#include "lljointdata.h"  #include "llmatrix4a.h"  #include "llmeshrepository.h"  #include "llmeshoptimizer.h" @@ -163,10 +164,14 @@ 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)) +    , mNumOfFetchingTextures(0) +    , mTexturesNeedScaling(false) +    , mImporterDebug(LLCachedControl<bool>(gSavedSettings, "ImporterDebugVerboseLogging", false))  {      mNeedsUpdate = true;      mCameraDistance = 0.f; @@ -175,11 +180,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; @@ -211,6 +214,7 @@ LLModelPreview::~LLModelPreview()      {          mModelLoader->shutdown();          mModelLoader = NULL; +        mLoading = false;      }      if (mPreviewAvatar) @@ -557,10 +561,7 @@ void LLModelPreview::rebuildUploadData()                              texture->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle<LLModelPreview>(getHandle()), &mCallbackTextureList, false);                              texture->forceToSaveRawImage(0, F32_MAX);                              texture->updateFetch(); -                            if (mModelLoader) -                            { -                                mModelLoader->mNumOfFetchingTextures++; -                            } +                            mNumOfFetchingTextures++;                          }                      }                  } @@ -691,7 +692,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();          } @@ -753,6 +754,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;      } @@ -806,10 +811,14 @@ 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      { +        LLVOAvatar* av = getPreviewAvatar(); +        std::vector<LLJointData> viewer_skeleton; +        av->getJointMatricesAndHierarhy(viewer_skeleton);          mModelLoader = new LLGLTFLoader(              filename,              lod, @@ -822,7 +831,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable              mJointsFromNode,              joint_alias_map,              LLSkinningUtil::getMaxJointCount(), -            gSavedSettings.getU32("ImporterModelLimit")); +            gSavedSettings.getU32("ImporterModelLimit"), +            gSavedSettings.getU32("ImporterDebugMode"), +            viewer_skeleton);      }      if (force_disable_slm) @@ -985,7 +996,9 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod)      setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload());      setLegacyRigFlags(mModelLoader->getLegacyRigFlags()); +    mTexturesNeedScaling |= mModelLoader->mTexturesNeedScaling;      mModelLoader->loadTextures(); +    warnTextureScaling();      if (loaded_lod == -1)      { //populate all LoDs from model loader scene @@ -1807,7 +1820,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)      { @@ -1884,6 +1897,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; @@ -1893,7 +1912,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)      { @@ -1956,6 +1975,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++;                      }                  }              } @@ -1968,7 +1992,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++;                      }                  }              } @@ -2068,25 +2103,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++;                  }              } @@ -2100,6 +2138,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; @@ -2129,6 +2169,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() @@ -2466,7 +2511,7 @@ void LLModelPreview::updateStatusMessages()          LLMutexLock lock(this);          if (mModelLoader)          { -            if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) +            if (!areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean())              {                  // Some textures are still loading, prevent upload until they are done                  mModelNoErrors = false; @@ -3039,9 +3084,12 @@ void LLModelPreview::loadedCallback(      S32 lod,      void* opaque)  { +    if(LLModelPreview::sIgnoreLoadedCallback) +        return; +      LLModelPreview* pPreview = static_cast<LLModelPreview*>(opaque);      LLMutexLock lock(pPreview); -    if (pPreview && pPreview->mModelLoader && !LLModelPreview::sIgnoreLoadedCallback) +    if (pPreview && pPreview->mModelLoader)      {          // Load loader's warnings into floater's log tab          const LLSD out = pPreview->mModelLoader->logOut(); @@ -3090,25 +3138,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod)      S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS;      std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; -    std::string ext = ".dae";      std::string lod_filename_lower(lod_filename);      LLStringUtil::toLower(lod_filename_lower); -    std::string::size_type i = lod_filename_lower.rfind(ext); -    if (i != std::string::npos) + +    // Check for each supported file extension +    std::vector<std::string> supported_exts = { ".dae", ".gltf", ".glb" }; +    std::string found_ext; +    std::string::size_type ext_pos = std::string::npos; + +    for (const auto& ext : supported_exts)      { -        lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); +        std::string::size_type i = lod_filename_lower.rfind(ext); +        if (i != std::string::npos) +        { +            ext_pos = i; +            found_ext = ext; +            break; +        }      } -    if (gDirUtilp->fileExists(lod_filename)) + +    if (ext_pos != std::string::npos)      { -        LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; -        if (fmp) +        // Replace extension with LOD suffix + original extension +        std::string lod_file_to_check = lod_filename; +        lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext); + +        if (gDirUtilp->fileExists(lod_file_to_check)) +        { +            LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; +            if (fmp) +            { +                fmp->setCtrlLoadFromFile(next_lod); +            } +            loadModel(lod_file_to_check, next_lod); +        } +        else          { -            fmp->setCtrlLoadFromFile(next_lod); +            lookupLODModelFiles(next_lod);          } -        loadModel(lod_filename, next_lod);      }      else      { +        // No recognized extension found, continue with next LOD          lookupLODModelFiles(next_lod);      }  } @@ -3149,6 +3220,7 @@ U32 LLModelPreview::loadTextures(LLImportMaterial& material, LLHandle<LLModelPre          tex->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle<LLModelPreview>(handle), &preview->mCallbackTextureList, false);          tex->forceToSaveRawImage(0, F32_MAX);          material.setDiffuseMap(tex->getID()); // record tex ID +        preview->mNumOfFetchingTextures++;          return 1;      } @@ -3993,6 +4065,18 @@ void LLModelPreview::setPreviewLOD(S32 lod)      updateStatusMessages();  } +void LLModelPreview::warnTextureScaling() +{ +    if (areTexturesReady() && mTexturesNeedScaling) +    { +        std::ostringstream out; +        out << "One or more textures in this model were scaled to be within the allowed limits."; +        LL_INFOS() << out.str() << LL_ENDL; +        LLSD args; +        LLFloaterModelPreview::addStringToLog("ModelTextureScaling", args, true, -1); +    } +} +  //static  void LLModelPreview::textureLoadedCallback(      bool success, @@ -4013,11 +4097,19 @@ void LLModelPreview::textureLoadedCallback(          LLModelPreview* preview = static_cast<LLModelPreview*>(handle->get());          preview->refresh(); -        if (final && preview->mModelLoader) +        if (final)          { -            if (preview->mModelLoader->mNumOfFetchingTextures > 0) +            if (src_vi +                && (src_vi->getOriginalWidth() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT +                    || src_vi->getOriginalHeight() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT)) +            { +                preview->mTexturesNeedScaling = true; +            } + +            if (preview->mNumOfFetchingTextures > 0)              { -                preview->mModelLoader->mNumOfFetchingTextures--; +                preview->mNumOfFetchingTextures--; +                preview->warnTextureScaling();              }          }      } diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 0873263587..7b3b699b33 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -204,6 +204,7 @@ public:      std::vector<S32> mLodsQuery;      std::vector<S32> mLodsWithParsingError;      bool mHasDegenerate; +    bool areTexturesReady() { return !mNumOfFetchingTextures; }  protected: @@ -213,6 +214,7 @@ protected:      static LLJoint* lookupJointByName(const std::string&, void* opaque);      static U32          loadTextures(LLImportMaterial& material, LLHandle<LLModelPreview> handle); +    void warnTextureScaling();      void lookupLODModelFiles(S32 lod);  private: @@ -242,6 +244,9 @@ private:      /// Not read unless mWarnOfUnmatchedPhyicsMeshes is true.      LLPointer<LLModel> mDefaultPhysicsShapeP; +    S32 mNumOfFetchingTextures; +    bool mTexturesNeedScaling; +      typedef enum      {          MESH_OPTIMIZER_FULL, 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/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index cee43f3cff..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette(      initJointNums(const_cast<LLMeshSkinInfo*>(skin), avatar); +    if (skin->mInvBindMatrix.size() < count ) +    { +        // faulty model? mInvBindMatrix.size() should have matched mJointNames.size() +        return; +    } +      LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS];      for (S32 j = 0; j < count; ++j) @@ -354,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a                              {                                  rig_info_tab[joint_num].setIsRiggedTo(true); -                                const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; +                                size_t bind_poses_size = skin->mBindPoseMatrix.size(); +                                const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity();                                  LLVector4a pos_joint_space;                                  mat.affineTransform(pos, pos_joint_space); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 0a3870b6f9..2409b71f00 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2107,9 +2107,6 @@ bool idle_startup()          do_startup_frame(); -        // We're successfully logged in. -        gSavedSettings.setBOOL("FirstLoginThisInstall", false); -          LLFloaterReg::showInitialVisibleInstances();          LLFloaterGridStatus::getInstance()->startGridStatusTimer(); @@ -2455,6 +2452,27 @@ bool idle_startup()          LLPerfStats::StatsRecorder::setAutotuneInit(); +        // Display Avatar Welcome Pack the first time a user logs in +        // (or clears their settings....) +        if (gSavedSettings.getBOOL("FirstLoginThisInstall")) +        { +            LLFloater* avatar_welcome_pack_floater = LLFloaterReg::findInstance("avatar_welcome_pack"); +            if (avatar_welcome_pack_floater != nullptr) +            { +                // There is a (very - 1 in ~50 times) hard to repro bug where the login +                // page is not hidden when the AWP floater is presented. This (agressive) +                // approach to always close it seems like the best fix for now. +                LLPanelLogin::closePanel(); + +                avatar_welcome_pack_floater->setVisible(true); +            } +        } + +        //// We're successfully logged in. +        // 2025-06 Moved lower down in the state machine so the Avatar Welcome Pack +        // floater display can be triggered correctly. +        gSavedSettings.setBOOL("FirstLoginThisInstall", false); +          return true;      } diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index 618955c83b..0fd9faab35 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1541,12 +1541,6 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo          return false;      } -    // Own objects -    if((FirstClickPref & MEDIA_FIRST_CLICK_OWN) && object->permYouOwner()) -    { -        LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_OWN" << LL_ENDL; -        return true; -    }      // HUD attachments      if((FirstClickPref & MEDIA_FIRST_CLICK_HUD) && object->isHUDAttachment())      { @@ -1569,21 +1563,29 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo          return false;      } +    // Own objects +    if((FirstClickPref & MEDIA_FIRST_CLICK_OWN) && owner_id == gAgent.getID()) +    { +        LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_OWN" << LL_ENDL; +        return true; +    } +      // Check if the object is owned by a friend of the agent      if(FirstClickPref & MEDIA_FIRST_CLICK_FRIEND)      { -        LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_FRIEND. id: " << owner_id << LL_ENDL; -        return LLAvatarTracker::instance().isBuddy(owner_id); +        if(LLAvatarTracker::instance().isBuddy(owner_id)) +        { +            LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_FRIEND. id: " << owner_id << LL_ENDL; +            return true; +        }      }      // Check for objects set to or owned by the active group      if(FirstClickPref & MEDIA_FIRST_CLICK_GROUP)      { -        // Get our active group -        LLUUID active_group = gAgent.getGroupID(); -        if(active_group.notNull() && (active_group == group_id || active_group == owner_id)) +        if(gAgent.isInGroup(group_id) || gAgent.isInGroup(owner_id))          { -            LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_GROUP.Active group: " << active_group << ", group_id:" << group_id << ", owner_id: " << owner_id << LL_ENDL; +            LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_GROUP. group_id:" << group_id << ", owner_id: " << owner_id << LL_ENDL;              return true;          }      } diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index d9daad9515..ec54e0207d 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -99,10 +99,10 @@ private:          MEDIA_FIRST_CLICK_LAND       = 1 << 4,    // 0b00010000 (16)          // Covers any object with PRIM_MEDIA_FIRST_CLICK_INTERACT (combines all previous flags) -        MEDIA_FIRST_CLICK_ANY        = ~(3<<30),  // 0b00111111111111111111111111111111 +        MEDIA_FIRST_CLICK_ANY        = (1 << 15) - 1, // 0b0111111111111111 (32767)          // Covers all media regardless of other rules or PRIM_MEDIA_FIRST_CLICK_INTERACT -        MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG = 1 << 30    // 0b01000000000000000000000000000000 (1073741824) +        MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG = 1 << 15  // 0b10000000000000000 (32768)      };      bool shouldAllowFirstMediaInteraction(const LLPickInfo& info, bool moap_flag);      bool handleMediaClick(const LLPickInfo& info); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 4d9c2f3281..4b3af6d7e8 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -38,8 +38,8 @@  #include "llfloateraddpaymentmethod.h"  #include "llfloaterauction.h"  #include "llfloaterautoreplacesettings.h" -#include "llfloateravatar.h"  #include "llfloateravatarpicker.h" +#include "llfloateravatarwelcomepack.h"  #include "llfloateravatarrendersettings.h"  #include "llfloateravatartextures.h"  #include "llfloaterbanduration.h" @@ -331,8 +331,8 @@ void LLViewerFloaterReg::registerFloaters()      LLFloaterReg::add("appearance", "floater_my_appearance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>);      LLFloaterReg::add("associate_listing", "floater_associate_listing.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAssociateListing>);      LLFloaterReg::add("auction", "floater_auction.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAuction>); -    LLFloaterReg::add("avatar", "floater_avatar.xml",  (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatar>);      LLFloaterReg::add("avatar_picker", "floater_avatar_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatarPicker>); +    LLFloaterReg::add("avatar_welcome_pack", "floater_avatar_welcome_pack.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatarWelcomePack>);      LLFloaterReg::add("avatar_render_settings", "floater_avatar_render_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatarRenderSettings>);      LLFloaterReg::add("avatar_textures", "floater_avatar_textures.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatarTextures>); 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 316f841717..990296c11e 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -95,7 +95,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/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 2f2aab5b76..a4f308bbf9 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -229,7 +229,11 @@ LLTrace::SampleStatHandle<F64Milliseconds > FRAMETIME_JITTER("frametimejitter",                                              FRAMETIME_JITTER_STDDEV("frametimejitterstddev", "Standard deviation of frametime jitter in a 5 second period."),                                              FRAMETIME_STDDEV("frametimestddev", "Standard deviation of frametime in a 5 second period."); -LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS("frametimeevents", "Number of frametime events in the session.  Applies when jitter exceeds 10% of the previous frame."); +LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS("frametimeevents", "Number of frametime events in the session.  Applies when jitter exceeds 10% of the previous frame."), +                                FRAMETIME_JITTER_EVENTS_PER_MINUTE("frametimeeventspm", "Average number of frametime events per minute."), +                                FRAMETIME_JITTER_EVENTS_LAST_MINUTE("frametimeeventslastmin", "Number of frametime events in the last minute."); + +LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session.");  LLTrace::EventStatHandle<LLUnit<F64, LLUnits::Meters> > AGENT_POSITION_SNAP("agentpositionsnap", "agent position corrections"); @@ -309,24 +313,28 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff)  {      if (gFrameCount && mLastTimeDiff > (F64Seconds)0.0)      { +        mTotalTime += time_diff;          sample(LLStatViewer::FRAMETIME, time_diff);          // old stats that were never really used          F64Seconds jit = (F64Seconds)std::fabs((mLastTimeDiff - time_diff));          sample(LLStatViewer::FRAMETIME_JITTER, jit);          mTotalFrametimeJitter += jit;          sample(LLStatViewer::FRAMETIME_JITTER_CUMULATIVE, mTotalFrametimeJitter); +        sample(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION, mTotalFrametimeJitter / mTotalTime);          static LLCachedControl<F32> frameTimeEventThreshold(gSavedSettings, "StatsFrametimeEventThreshold", 0.1f);          if (time_diff - mLastTimeDiff > mLastTimeDiff * frameTimeEventThreshold())          {              sample(LLStatViewer::FRAMETIME_JITTER_EVENTS, mFrameJitterEvents++); +            mFrameJitterEventsLastMinute++;          }          mFrameTimes.push_back(time_diff);          mFrameTimesJitter.push_back(jit);          mLastFrameTimeSample += time_diff; +        mTimeSinceLastEventSample += time_diff;          static LLCachedControl<S32> frameTimeSampleSeconds(gSavedSettings, "StatsFrametimeSampleSeconds", 5); @@ -356,6 +364,17 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff)              mFrameTimesJitter.clear();              mLastFrameTimeSample = F64Seconds(0);          } + +        if (mTimeSinceLastEventSample >= 60) +        { +            mEventMinutes++; +            // Calculate average events per minute +            U64 frame_time_events_per_minute = (U64)mFrameJitterEvents / mEventMinutes; +            sample(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE, frame_time_events_per_minute); +            sample(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE, mFrameJitterEventsLastMinute); +            mFrameJitterEventsLastMinute   = 0; +            mTimeSinceLastEventSample    = F64Seconds(0); +        }      }      mLastTimeDiff = time_diff;  } diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 1d84ab9fbc..63fb7d4a17 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -277,9 +277,13 @@ private:      F64Seconds mLastTimeDiff;  // used for time stat updates      F64Seconds mTotalFrametimeJitter; -    U32 mFrameJitterEvents; +    U32 mFrameJitterEvents = 0; +    U32 mFrameJitterEventsLastMinute = 0; +    U32 mEventMinutes = 0; +    F64Seconds mTotalTime;      F64Seconds              mLastFrameTimeSample; // used for frame time stats +    F64Seconds              mTimeSinceLastEventSample;      std::vector<F64Seconds>      mFrameTimes;          // used for frame time stats      std::vector<F64Seconds> mFrameTimesJitter;    // used for frame time jitter stats  }; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index f14ca62c83..6aade15d23 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2291,13 +2291,13 @@ void LLViewerWindow::initWorldUI()              url = LLWeb::expandURLSubstitutions(url, LLSD());              destinations->navigateTo(url, HTTP_CONTENT_TEXT_HTML);          } -        LLMediaCtrl* avatar_picker = LLFloaterReg::getInstance("avatar")->findChild<LLMediaCtrl>("avatar_picker_contents"); -        if (avatar_picker) +        LLMediaCtrl* avatar_welcome_pack = LLFloaterReg::getInstance("avatar_welcome_pack")->findChild<LLMediaCtrl>("avatar_picker_contents"); +        if (avatar_welcome_pack)          { -            avatar_picker->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); -            std::string url = gSavedSettings.getString("AvatarPickerURL"); +            avatar_welcome_pack->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); +            std::string url = gSavedSettings.getString("AvatarWelcomePack");              url = LLWeb::expandURLSubstitutions(url, LLSD()); -            avatar_picker->navigateTo(url, HTTP_CONTENT_TEXT_HTML); +            avatar_welcome_pack->navigateTo(url, HTTP_CONTENT_TEXT_HTML);          }      }  } diff --git a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml new file mode 100644 index 0000000000..795d642755 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater + positioning="cascading" + legacy_header_height="225" + can_minimize="true" + can_close="true" + can_resize="false" + min_height="438" + min_width="530" + height="438" + layout="topleft" + name="Avatar Welcome Pack" + single_instance="true" + save_rect="true" + save_visibility="true" + title="AVATAR WELCOME PACK" + width="530"> +    <web_browser +      top="25" +      height="438" +      width="530" +      follows="all" +      name="avatar_picker_contents" +      trusted_content="true"/> +</floater> 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 90223fcda8..f11d687840 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -14,7 +14,7 @@   legacy_header_height="25">    <string name="status_idle"></string> -  <string name="status_parse_error">Error: Dae parsing issue - see log for details.</string> +  <string name="status_parse_error">Error: Model parsing issue - see log for details.</string>    <string name="status_bind_shape_orientation">Warning: bind shape matrix is not in standard X-forward orientation.</string>    <string name="status_material_mismatch">Error: Material of model is not a subset of reference model.</string>    <string name="status_reading_file">Loading...</string> @@ -39,12 +39,14 @@    <string name="decomposing">Analyzing...</string>    <string name="simplifying">Simplifying...</string>    <string name="tbd">TBD</string> +  <string name="ModelTextureScaling">One or more textures in this model were scaled to be within the allowed limits.</string>    <!-- Warnings and info from model loader-->    <string name="TooManyJoint">Skinning disabled due to too many joints: [JOINTS], maximum: [MAX]</string>    <string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>    <string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>    <string name="ModelLoaded">Model [MODEL_NAME] loaded</string> +  <string name="InvBindCountMismatch">Bind matrices count mismatch joints count</string>    <string name="IncompleteTC">Texture coordinates data is not complete.</string>    <string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string> @@ -60,6 +62,27 @@    <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> +  <string name="InvalidMeshReference">Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES])</string> +  <string name="InvalidGeometryNonTriangulated">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated)</string> +  <string name="EmptyVertexArray">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Empty vertex array</string> +  <string name="ErrorIndexLimit">Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT]</string> +  <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="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="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" @@ -1404,7 +1427,7 @@           word_wrap="true">          </text_editor>          <check_box -         control_name="ImporterDebug" +         control_name="ImporterDebugVerboseLogging"           follows="top|left"           top_pad="9"           left="6" @@ -1706,7 +1729,6 @@ Analysed:     height="408"/>    <panel      follows="right|bottom" -    can_resize="false"      height="140"      layout="topleft"      name="right_panel" diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index 84252fe32c..f2309eb817 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -54,38 +54,19 @@                    label="jitter"                    decimal_digits="1"                    stat="frametimejitter"/> -          <stat_bar name="framet_cumulative" -                    label="jitter cumulative" -                    decimal_digits="1" -                    stat="frametimejitcumulative"/> -          <stat_bar name="framet_jitter_99th" -                    label="jitter 99th percentile" -                    decimal_digits="1" -                    stat="frametimejitter99"/> -          <stat_bar name="framet_jitter_95th" -                    label="jitter 95th percentile" -                    decimal_digits="1" -                    stat="frametimejitter95"/> -          <stat_bar name="framet_jitter_stddev" -                    label="frametime jitter standard deviation" -                    decimal_digits="1" -                    stat="frametimejitterstddev"/> -          <stat_bar name="framet_99th" -                    label="frametime 99th percentile" -                    decimal_digits="1" -                    stat="frametime99"/> -          <stat_bar name="framet_95th" -                    label="frametime 95th percentile" -                    decimal_digits="1" -                    stat="frametime95"/> -          <stat_bar name="framet_stddev" -                    label="frametime standard deviation" -                    decimal_digits="1" -                    stat="frametimestddev"/> -          <stat_bar name="framet_events" -                    label="frametime events" -                    decimal_digits="1" -                    stat="frametimeevents"/> +          <stat_bar name="normalized_cumulative_frametime" +                    label="normalized sess. jitter" +                    decimal_digits="4" +                    stat="normalizedframetimejitter"/> +          <stat_bar name="frame_events_per_minute" +                    label="frame events/minute" +                    decimal_digits="2" +                    stat="frametimeeventspm"/> +          <stat_bar name="frame_events_last_minute" +                    label="frame events last min." +                    decimal_digits="0" +                    stat="frametimeeventslastmin"/> +                    <stat_bar name="bandwidth"                    label="UDP Data Received"                    stat="activemessagedatareceived" @@ -106,6 +87,38 @@          <stat_view name="render"                     label="Render"                     setting="OpenDebugStatRender"> +            <stat_bar name="framet_cumulative" +                    label="jitter cumulative" +                    decimal_digits="1" +                    stat="frametimejitcumulative"/> +            <stat_bar name="framet_jitter_99th" +                      label="jitter 99th percentile" +                      decimal_digits="1" +                      stat="frametimejitter99"/> +            <stat_bar name="framet_jitter_95th" +                      label="jitter 95th percentile" +                      decimal_digits="1" +                      stat="frametimejitter95"/> +            <stat_bar name="framet_jitter_stddev" +                      label="frametime jitter std dev" +                      decimal_digits="1" +                      stat="frametimejitterstddev"/> +            <stat_bar name="framet_99th" +                      label="frametime 99th percentile" +                      decimal_digits="1" +                      stat="frametime99"/> +            <stat_bar name="framet_95th" +                      label="frametime 95th percentile" +                      decimal_digits="1" +                      stat="frametime95"/> +            <stat_bar name="framet_stddev" +                      label="frametime std dev" +                      decimal_digits="1" +                      stat="frametimestddev"/> +            <stat_bar name="framet_events" +                      label="frametime events" +                      decimal_digits="0" +                      stat="frametimeevents"/>            <stat_bar name="ktrisframe"                      label="KTris per Frame"                      unit_label="ktris/fr" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 343f0c0059..dc057f2482 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -411,11 +411,11 @@        </menu_item_call>        <menu_item_separator/>        <menu_item_call -        label="Complete avatars..." -        name="Avatar Picker"> +        label="Avatar Welcome Pack..." +        name="Avatar Welcome Pack">          <menu_item_call.on_click           function="Floater.ToggleOrBringToFront" -         parameter="avatar" /> +         parameter="avatar_welcome_pack" />        </menu_item_call>        <menu_item_separator/> @@ -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 diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 93797c0b58..f76f9cc96c 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -7136,6 +7136,20 @@ You don't have permission to view this notecard.    </notification>    <notification +   icon="alertmodal.tga" +   name="MaterialImagesWereScaled" +   type="alertmodal"> +One or more textures in this material were scaled to be within the allowed limits. +Textures must have power of two dimensions and must not exceed [MAX_SIZE]x[MAX_SIZE] pixels. +    <unique/> +    <tag>confirm</tag> +    <usetemplate +     ignoretext="Warn if textures will be scaled during upload." +     name="okignore" +     yestext="OK"/> +  </notification> + +  <notification     icon="notifytip.tga"     name="RezItemNoPermissions"     type="notifytip"> @@ -9453,8 +9467,11 @@ Unable to upload texture: '[NAME]'     icon="alertmodal.tga"     name="CannotUploadMaterial"     type="alertmodal"> -There was a problem uploading the file +Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions.      <tag>fail</tag> +    <usetemplate +     name="okbutton" +     yestext="OK"/>    </notification>    <notification diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml index de6132aec6..52413abe74 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml @@ -422,11 +422,11 @@          <item              label="Anyone's objects"              name="media_first_interact_any" -            value="1073741823"/> +            value="32767"/>          <item              label="All MOAP"              name="media_first_click_all" -            value="2147483647"/> +            value="65535"/>      </combo_box>     <check_box      name="media_show_on_others_btn" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index faa751bbf1..532d536589 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4186,7 +4186,7 @@ Try enclosing path to the editor with double quotes.  name="Command_360_Capture_Label">360 snapshot</string>    <string name="Command_AboutLand_Label">About land</string>    <string name="Command_Appearance_Label">Outfits</string> -  <string name="Command_Avatar_Label">Complete avatars</string> +  <string name="Command_Avatar_Label">Avatar Welcome Pack</string>    <string name="Command_Build_Label">Build</string>    <string name="Command_Chat_Label">Chat</string>    <string name="Command_Conversations_Label">Conversations</string> | 
