Merge release/2025.05 into develop

master
Andrey Kleshchev 2025-08-06 14:17:18 +03:00 committed by GitHub
commit acc8928330
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 3489 additions and 1000 deletions

View File

@ -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: |

View File

@ -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.

View File

@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES
llavatarjoint.cpp
llavatarjointmesh.cpp
lldriverparam.cpp
lljointdata.h
lllocaltextureobject.cpp
llpolyskeletaldistortion.cpp
llpolymesh.cpp

View File

@ -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;
@ -105,11 +108,17 @@ public:
S32 getNumBones() const { return mNumBones; }
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

View File

@ -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);

View File

@ -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

View File

@ -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)
{
*alpha_cursor++ = pixel[3];
pixel += 4;
LL_INFOS("Morph") << "GL Error while reading back morph texture. Error code: " << error << LL_ENDL;
}
else
{
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();

View File

@ -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),

View File

@ -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;

View File

@ -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;
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); });
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
}

View File

@ -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

View File

@ -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))
{

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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

View File

@ -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];
}
}
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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 ;
}

View File

@ -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

View File

@ -1 +1 @@
7.1.14
7.2.0

View File

@ -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"

View File

@ -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>

View File

@ -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;

View File

@ -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;
}
}
// 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();
}
for (S32 double_sided = 0; double_sided < 2; ++double_sided)
if (mLoadIntoVRAM)
{
RenderData& rd = mRenderData[double_sided];
for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
{
rd.mBatches[i].resize(mat_count);
// 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();
}
// 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)
{
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();
// 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;
}
}
continue;
}
// allocate vertex buffer and pack it
if (vertex_count[variant] > 0)
for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
{
U32 mat_idx = mat_id + 1;
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();
U32 attribute_mask = 0;
// for each mesh
for (auto& mesh : mMeshes)
{
// for each primitive
for (auto& primitive : mesh.mPrimitives)
{
if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
{
primitive.upload(vb);
// 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();
// 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;
}
}
}
vb->unmapBuffer();
// 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);
vb->unbind();
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)
{
if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
{
primitive.upload(vb);
}
}
}
vb->unmapBuffer();
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);

View File

@ -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;

View File

@ -158,6 +158,12 @@ namespace LL
dst.load3(src);
}
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)
{
@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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();

View File

@ -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)

View File

@ -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,

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
LLMediaCtrl* mAvatarPicker;
};
#endif

View File

@ -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()

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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
{

View File

@ -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;
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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);
}
if (gDirUtilp->fileExists(lod_filename))
{
LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
if (fmp)
std::string::size_type i = lod_filename_lower.rfind(ext);
if (i != std::string::npos)
{
fmp->setCtrlLoadFromFile(next_lod);
ext_pos = i;
found_ext = ext;
break;
}
}
if (ext_pos != std::string::npos)
{
// 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
{
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->mModelLoader->mNumOfFetchingTextures--;
preview->mTexturesNeedScaling = true;
}
if (preview->mNumOfFetchingTextures > 0)
{
preview->mNumOfFetchingTextures--;
preview->warnTextureScaling();
}
}
}

View File

@ -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,

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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>);

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
};

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -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,11 +2808,17 @@ 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">

View File

@ -7135,6 +7135,20 @@ You don&apos;t have permission to view this notecard.
<tag>fail</tag>
</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"
@ -9453,8 +9467,11 @@ Unable to upload texture: &apos;[NAME]&apos;
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

View File

@ -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"

View File

@ -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>