From d8db0ff30cd1775a66694bd0f1e02795c85722be Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 3 Jun 2025 17:50:15 -0700 Subject: First commit - bare bones of it working - pointing to test page on S3 --- indra/newview/CMakeLists.txt | 2 + indra/newview/app_settings/commands.xml | 4 +- indra/newview/app_settings/settings.xml | 11 ++++ indra/newview/llfloateravatarwelcomepack.cpp | 58 ++++++++++++++++++++++ indra/newview/llfloateravatarwelcomepack.h | 45 +++++++++++++++++ indra/newview/llviewerfloaterreg.cpp | 2 + indra/newview/llviewerwindow.cpp | 8 +++ .../default/xui/en/floater_avatar_welcome_pack.xml | 25 ++++++++++ indra/newview/skins/default/xui/en/menu_viewer.xml | 6 +-- indra/newview/skins/default/xui/en/strings.xml | 2 +- 10 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 indra/newview/llfloateravatarwelcomepack.cpp create mode 100644 indra/newview/llfloateravatarwelcomepack.h create mode 100644 indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 98151e2f4d..172c7f4b1f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -178,6 +178,7 @@ set(viewer_SOURCE_FILES llflexibleobject.cpp llfloater360capture.cpp llfloaterabout.cpp + llfloateravatarwelcomepack.cpp llfloaterbvhpreview.cpp llfloateraddpaymentmethod.cpp llfloaterauction.cpp @@ -849,6 +850,7 @@ set(viewer_HEADER_FILES llflexibleobject.h llfloater360capture.h llfloaterabout.h + llfloateravatarwelcomepack.h llfloaterbvhpreview.h llfloateraddpaymentmethod.h llfloaterauction.h diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 4a3dfffde1..7bcfecf9fa 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -26,9 +26,9 @@ label_ref="Command_Avatar_Label" tooltip_ref="Command_Avatar_Tooltip" execute_function="Floater.ToggleOrBringToFront" - execute_parameters="avatar" + execute_parameters="avatar_welcome_pack" is_running_function="Floater.IsOpen" - is_running_parameters="avatar" + is_running_parameters="avatar_welcome_pack" /> Value http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/avatars.html + AvatarWelcomePack + + Comment + Avatar Welcome Pack contents + Persist + 1 + Type + String + Value + https://sl-viewer-media-system.s3.amazonaws.com/vawp/index.html + AvatarBakedTextureUploadTimeout diff --git a/indra/newview/llfloateravatarwelcomepack.cpp b/indra/newview/llfloateravatarwelcomepack.cpp new file mode 100644 index 0000000000..82e44d1398 --- /dev/null +++ b/indra/newview/llfloateravatarwelcomepack.cpp @@ -0,0 +1,58 @@ +/** + * @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 + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateravatarwelcomepack.h" +#include "lluictrlfactory.h" +#include "llmediactrl.h" + +LLFloaterAvatarWelcomePack::LLFloaterAvatarWelcomePack(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterAvatarWelcomePack::~LLFloaterAvatarWelcomePack() +{ + if (mAvatarPicker) + { + mAvatarPicker->navigateStop(); + mAvatarPicker->clearCache(); //images are reloading each time already + mAvatarPicker->unloadMediaSource(); + } +} + +bool LLFloaterAvatarWelcomePack::postBuild() +{ + mAvatarPicker = findChild("avatar_picker_contents"); + if (mAvatarPicker) + { + mAvatarPicker->clearCache(); + } + + return true; +} diff --git a/indra/newview/llfloateravatarwelcomepack.h b/indra/newview/llfloateravatarwelcomepack.h new file mode 100644 index 0000000000..a332d46708 --- /dev/null +++ b/indra/newview/llfloateravatarwelcomepack.h @@ -0,0 +1,45 @@ +/** + * @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 + * Copyright (C) 2011, 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$ + */ + +#pragma once + +#include "llfloater.h" + +class LLMediaCtrl; + +class LLFloaterAvatarWelcomePack: + public LLFloater +{ + friend class LLFloaterReg; + + private: + LLFloaterAvatarWelcomePack(const LLSD& key); + ~LLFloaterAvatarWelcomePack(); + bool postBuild() override; + + LLMediaCtrl* mAvatarPicker; +}; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 4d9c2f3281..c4daa6dd8b 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -40,6 +40,7 @@ #include "llfloaterautoreplacesettings.h" #include "llfloateravatar.h" #include "llfloateravatarpicker.h" +#include "llfloateravatarwelcomepack.h" #include "llfloateravatarrendersettings.h" #include "llfloateravatartextures.h" #include "llfloaterbanduration.h" @@ -333,6 +334,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("auction", "floater_auction.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar", "floater_avatar.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_picker", "floater_avatar_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("avatar_welcome_pack", "floater_avatar_welcome_pack.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_render_settings", "floater_avatar_render_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_textures", "floater_avatar_textures.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index d2685bcc48..a279664522 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2299,6 +2299,14 @@ void LLViewerWindow::initWorldUI() url = LLWeb::expandURLSubstitutions(url, LLSD()); avatar_picker->navigateTo(url, HTTP_CONTENT_TEXT_HTML); } + LLMediaCtrl* avatar_welcome_pack = LLFloaterReg::getInstance("avatar_welcome_pack")->findChild("avatar_picker_contents"); + if (avatar_welcome_pack) + { + avatar_welcome_pack->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); + std::string url = gSavedSettings.getString("AvatarWelcomePack"); + url = LLWeb::expandURLSubstitutions(url, LLSD()); + avatar_welcome_pack->navigateTo(url, HTTP_CONTENT_TEXT_HTML); + } } } diff --git a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml new file mode 100644 index 0000000000..0ea79e68c2 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml @@ -0,0 +1,25 @@ + + + + diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 343f0c0059..040fae3256 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -411,11 +411,11 @@ + label="Avatar Welcome Pack..." + name="Avatar Welcome Pack"> + parameter="avatar_welcome_pack" /> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index faa751bbf1..532d536589 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4186,7 +4186,7 @@ Try enclosing path to the editor with double quotes. name="Command_360_Capture_Label">360 snapshot About land Outfits - Complete avatars + Avatar Welcome Pack Build Chat Conversations -- cgit v1.2.3 From 8d3d6ee56ad5615f8ffd4790e15b81909590a5f4 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 3 Jun 2025 19:24:04 -0700 Subject: Remove the old avatar selector (Complete Avatars) - replaced by Avatar Welcome Pack --- indra/newview/CMakeLists.txt | 2 -- indra/newview/app_settings/settings.xml | 11 ------ indra/newview/llfloateravatar.cpp | 61 --------------------------------- indra/newview/llfloateravatar.h | 46 ------------------------- indra/newview/llviewerfloaterreg.cpp | 2 -- indra/newview/llviewerwindow.cpp | 8 ----- 6 files changed, 130 deletions(-) delete mode 100644 indra/newview/llfloateravatar.cpp delete mode 100644 indra/newview/llfloateravatar.h diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 172c7f4b1f..dc331b7330 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -183,7 +183,6 @@ set(viewer_SOURCE_FILES llfloateraddpaymentmethod.cpp llfloaterauction.cpp llfloaterautoreplacesettings.cpp - llfloateravatar.cpp llfloateravatarpicker.cpp llfloateravatarrendersettings.cpp llfloateravatartextures.cpp @@ -855,7 +854,6 @@ set(viewer_HEADER_FILES llfloateraddpaymentmethod.h llfloaterauction.h llfloaterautoreplacesettings.h - llfloateravatar.h llfloateravatarpicker.h llfloateravatarrendersettings.h llfloateravatartextures.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 90da70763f..0f0593c625 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -621,17 +621,6 @@ Value 16.0 - AvatarPickerURL - - Comment - Avatar picker contents - Persist - 1 - Type - String - Value - http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/avatars.html - AvatarWelcomePack Comment diff --git a/indra/newview/llfloateravatar.cpp b/indra/newview/llfloateravatar.cpp deleted file mode 100644 index 404316275d..0000000000 --- a/indra/newview/llfloateravatar.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloateravatar.h" -#include "lluictrlfactory.h" -#include "llmediactrl.h" - - -LLFloaterAvatar::LLFloaterAvatar(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterAvatar::~LLFloaterAvatar() -{ - if (mAvatarPicker) - { - mAvatarPicker->navigateStop(); - mAvatarPicker->clearCache(); //images are reloading each time already - mAvatarPicker->unloadMediaSource(); - } -} - -bool LLFloaterAvatar::postBuild() -{ - mAvatarPicker = findChild("avatar_picker_contents"); - if (mAvatarPicker) - { - mAvatarPicker->clearCache(); - } - enableResizeCtrls(true, true, false); - return true; -} - - diff --git a/indra/newview/llfloateravatar.h b/indra/newview/llfloateravatar.h deleted file mode 100644 index fb591c8306..0000000000 --- a/indra/newview/llfloateravatar.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, 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_FLOATER_AVATAR_H -#define LL_FLOATER_AVATAR_H - -#include "llfloater.h" -class LLMediaCtrl; - -class LLFloaterAvatar: - public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterAvatar(const LLSD& key); - ~LLFloaterAvatar(); - bool postBuild() override; - - LLMediaCtrl* mAvatarPicker; -}; - -#endif diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index c4daa6dd8b..4b3af6d7e8 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -38,7 +38,6 @@ #include "llfloateraddpaymentmethod.h" #include "llfloaterauction.h" #include "llfloaterautoreplacesettings.h" -#include "llfloateravatar.h" #include "llfloateravatarpicker.h" #include "llfloateravatarwelcomepack.h" #include "llfloateravatarrendersettings.h" @@ -332,7 +331,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("appearance", "floater_my_appearance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("associate_listing", "floater_associate_listing.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("auction", "floater_auction.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("avatar", "floater_avatar.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_picker", "floater_avatar_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_welcome_pack", "floater_avatar_welcome_pack.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("avatar_render_settings", "floater_avatar_render_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index a279664522..e8a285f494 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2291,14 +2291,6 @@ void LLViewerWindow::initWorldUI() url = LLWeb::expandURLSubstitutions(url, LLSD()); destinations->navigateTo(url, HTTP_CONTENT_TEXT_HTML); } - LLMediaCtrl* avatar_picker = LLFloaterReg::getInstance("avatar")->findChild("avatar_picker_contents"); - if (avatar_picker) - { - avatar_picker->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); - std::string url = gSavedSettings.getString("AvatarPickerURL"); - url = LLWeb::expandURLSubstitutions(url, LLSD()); - avatar_picker->navigateTo(url, HTTP_CONTENT_TEXT_HTML); - } LLMediaCtrl* avatar_welcome_pack = LLFloaterReg::getInstance("avatar_welcome_pack")->findChild("avatar_picker_contents"); if (avatar_welcome_pack) { -- cgit v1.2.3 From f65bb5ecfb8930d7299f20dfeac2ccea95c798c6 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 4 Jun 2025 15:51:39 -0700 Subject: Miniscule change to make the aspect ratio of the non-resizeable floater look better --- indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml index 0ea79e68c2..86aaf2a5c4 100644 --- a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml +++ b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml @@ -5,9 +5,9 @@ can_minimize="true" can_close="true" can_resize="false" - min_height="600" + min_height="660" min_width="800" - height="600" + height="660" layout="topleft" name="Avatar Welcome Pack" single_instance="true" @@ -17,7 +17,7 @@ width="800"> Date: Thu, 5 Jun 2025 22:31:43 -0700 Subject: Open Avatar Welcome Pack floater on first login (or cleared settings) and move it to the center of the screen. Adjust the size of the floater (height) slightly. --- indra/newview/llfloateravatarwelcomepack.cpp | 1 + indra/newview/llstartup.cpp | 20 +++++++++++++++++--- .../default/xui/en/floater_avatar_welcome_pack.xml | 12 ++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/indra/newview/llfloateravatarwelcomepack.cpp b/indra/newview/llfloateravatarwelcomepack.cpp index 82e44d1398..06b6de0cbf 100644 --- a/indra/newview/llfloateravatarwelcomepack.cpp +++ b/indra/newview/llfloateravatarwelcomepack.cpp @@ -48,6 +48,7 @@ LLFloaterAvatarWelcomePack::~LLFloaterAvatarWelcomePack() bool LLFloaterAvatarWelcomePack::postBuild() { + center(); mAvatarPicker = findChild("avatar_picker_contents"); if (mAvatarPicker) { diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index cc4f49c0b4..f065d286c8 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2103,9 +2103,6 @@ bool idle_startup() do_startup_frame(); - // We're successfully logged in. - gSavedSettings.setBOOL("FirstLoginThisInstall", false); - LLFloaterReg::showInitialVisibleInstances(); LLFloaterGridStatus::getInstance()->startGridStatusTimer(); @@ -2451,6 +2448,23 @@ 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) + { + avatar_welcome_pack_floater->center(); + avatar_welcome_pack_floater->setVisible(true); + } + } + + //// We're successfully logged in. + // 2025-06 Moved lower down in the state machine so the Avatar Welcome Pack + // floater display can be triggered correctly. + gSavedSettings.setBOOL("FirstLoginThisInstall", false); + return true; } diff --git a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml index 86aaf2a5c4..1b0f91c7ea 100644 --- a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml +++ b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml @@ -5,20 +5,20 @@ can_minimize="true" can_close="true" can_resize="false" - min_height="660" - min_width="800" - height="660" + min_height="420" + min_width="500" + height="420" layout="topleft" name="Avatar Welcome Pack" single_instance="true" save_rect="true" save_visibility="true" title="AVATAR WELCOME PACK" - width="800"> + width="500"> -- cgit v1.2.3 From a366a49daff63af865aa5e3bda9186d8b0db4814 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 6 Jun 2025 17:21:19 -0700 Subject: tweak the size of the AWP floater to take account of new image sizes. Do not center floater anymore because it obscures the avatar. --- indra/newview/llfloateravatarwelcomepack.cpp | 1 - indra/newview/llstartup.cpp | 6 +++++- .../skins/default/xui/en/floater_avatar_welcome_pack.xml | 12 ++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/indra/newview/llfloateravatarwelcomepack.cpp b/indra/newview/llfloateravatarwelcomepack.cpp index 06b6de0cbf..82e44d1398 100644 --- a/indra/newview/llfloateravatarwelcomepack.cpp +++ b/indra/newview/llfloateravatarwelcomepack.cpp @@ -48,7 +48,6 @@ LLFloaterAvatarWelcomePack::~LLFloaterAvatarWelcomePack() bool LLFloaterAvatarWelcomePack::postBuild() { - center(); mAvatarPicker = findChild("avatar_picker_contents"); if (mAvatarPicker) { diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index f065d286c8..6bf0a50d1c 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2455,7 +2455,11 @@ bool idle_startup() LLFloater* avatar_welcome_pack_floater = LLFloaterReg::findInstance("avatar_welcome_pack"); if (avatar_welcome_pack_floater != nullptr) { - avatar_welcome_pack_floater->center(); + // 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); } } diff --git a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml index 1b0f91c7ea..795d642755 100644 --- a/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml +++ b/indra/newview/skins/default/xui/en/floater_avatar_welcome_pack.xml @@ -5,20 +5,20 @@ can_minimize="true" can_close="true" can_resize="false" - min_height="420" - min_width="500" - height="420" + 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="500"> + width="530"> -- cgit v1.2.3 From 63ef595c301dfb32122f15cac1b1e3b9f110a9bb Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 11 Jun 2025 09:05:29 -0700 Subject: Point the URL for the Avatar Welconme Pack at the production location (Eventually - marshalled by [GIRD LOWER]) --- indra/newview/app_settings/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0f0593c625..ac121999a3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -630,7 +630,7 @@ Type String Value - https://sl-viewer-media-system.s3.amazonaws.com/vawp/index.html + http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html AvatarBakedTextureUploadTimeout -- cgit v1.2.3 From 6619b030ef33101eaff4b0360858867f2e379e79 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 11 Jun 2025 17:38:00 -0400 Subject: Update viewer version. --- indra/newview/VIEWER_VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 099f298456..6329380f96 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.14 +7.1.15 -- cgit v1.2.3 From 75db5e8b6b911e312ebdb9bc10a55d048a70e0be Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 28 Jun 2025 19:31:02 +0200 Subject: Revert "Fix LLCharacter base class constness." and restore improvements from 10a324a1034c177b95545ac7ffaa6aa6abed65ff instead --- indra/llappearance/llavatarappearance.h | 4 ++-- indra/llcharacter/llbvhloader.cpp | 6 +++--- indra/llcharacter/llbvhloader.h | 2 +- indra/llcharacter/llcharacter.cpp | 7 +++---- indra/llcharacter/llcharacter.h | 2 +- indra/llprimitive/lldaeloader.cpp | 2 +- indra/llprimitive/lldaeloader.h | 26 +++++++++++++------------- indra/llprimitive/llmodelloader.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 28 ++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 26 +++++++++++++------------- indra/newview/llfloaterbvhpreview.cpp | 4 ++-- indra/newview/llfloaterbvhpreview.h | 2 +- indra/newview/llfloatermodelpreview.cpp | 2 +- indra/newview/llmodelpreview.cpp | 2 +- indra/newview/llvoavatar.cpp | 8 ++++---- indra/newview/llvoavatar.h | 2 +- indra/newview/llvoavatarself.cpp | 6 +++--- indra/newview/llvoavatarself.h | 2 +- 18 files changed, 66 insertions(+), 67 deletions(-) diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 2748da9a1d..84cb42056a 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -140,7 +140,7 @@ public: LLVector3 mHeadOffset{}; // current head position LLAvatarJoint* mRoot{ nullptr }; - typedef std::map joint_map_t; + typedef std::map> joint_map_t; joint_map_t mJointMap; typedef std::map joint_state_map_t; @@ -153,7 +153,7 @@ public: public: typedef std::vector avatar_joint_list_t; const avatar_joint_list_t& getSkeleton() { return mSkeleton; } - typedef std::map joint_alias_map_t; + typedef std::map> joint_alias_map_t; const joint_alias_map_t& getJointAliases(); typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp index 9dace08e6f..581e9f62d5 100644 --- a/indra/llcharacter/llbvhloader.cpp +++ b/indra/llcharacter/llbvhloader.cpp @@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str ) // LLBVHLoader() //----------------------------------------------------------------------------- -LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ) +LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ) { reset(); errorLine = 0; @@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error } // Recognize all names we've been told are legal. - for (std::map::value_type& alias_pair : joint_alias_map) + for (const auto& [alias, joint] : joint_alias_map) { - makeTranslation( alias_pair.first , alias_pair.second ); + makeTranslation(alias, joint); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index de31f76dd3..ae2e347ba1 100644 --- a/indra/llcharacter/llbvhloader.h +++ b/indra/llcharacter/llbvhloader.h @@ -227,7 +227,7 @@ class LLBVHLoader friend class LLKeyframeMotion; public: // Constructor - LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ); + LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ); ~LLBVHLoader(); /* diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index ecbcdb3bf5..8efcd9dd29 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,12 +77,11 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint *LLCharacter::getJoint( const std::string &name ) +LLJoint* LLCharacter::getJoint(std::string_view name) { - LLJoint* joint = NULL; + LLJoint* joint = nullptr; - LLJoint *root = getRootJoint(); - if (root) + if (LLJoint* root = getRootJoint()) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 6143ec8cd1..0046c5da7e 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -76,7 +76,7 @@ public: // get the specified joint // default implementation does recursive search, // subclasses may optimize/cache results. - virtual LLJoint *getJoint( const std::string &name ); + virtual LLJoint* getJoint(std::string_view name); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 0759447902..eadb052b38 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -880,7 +880,7 @@ LLDAELoader::LLDAELoader( void* opaque_userdata, JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, - std::map& jointAliasMap, + std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, bool preprocess) diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 4531e03474..dc20feca52 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -47,19 +47,19 @@ public: dae_model_map mModelsMap; LLDAELoader( - std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void* opaque_userdata, - JointTransformMap& jointTransformMap, - JointNameSet& jointsFromNodes, - std::map& jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - bool preprocess); + std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void* opaque_userdata, + JointTransformMap& jointTransformMap, + JointNameSet& jointsFromNodes, + std::map>& jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + bool preprocess); virtual ~LLDAELoader() ; virtual bool OpenFile(const std::string& filename); diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 7c808dcae0..caffa34676 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -36,7 +36,7 @@ class LLJoint; typedef std::map JointTransformMap; typedef std::map::iterator JointTransformMapIt; -typedef std::map JointMap; +typedef std::map> JointMap; typedef std::deque JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7ae255e054..bd3c1a3833 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,20 +86,20 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -LLGLTFLoader::LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - std::vector viewer_skeleton) //, - //bool preprocess) +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> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + std::vector viewer_skeleton) //, + //bool preprocess) : LLModelLoader( filename, lod, load_cb, diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 048f7df7f5..b53258ab8b 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -72,19 +72,19 @@ class LLGLTFLoader : public LLModelLoader typedef std::map joints_name_to_node_map_t; 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 &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - std::vector viewer_skeleton); //, - //bool preprocess ); + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + std::vector viewer_skeleton); //, + //bool preprocess ); virtual ~LLGLTFLoader(); virtual bool OpenFile(const std::string &filename); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index b94c31ec04..5ee93be061 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks() getChild("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); } -std::map LLFloaterBvhPreview::getJointAliases() +std::map> LLFloaterBvhPreview::getJointAliases() { LLPointer av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); return av->getJointAliases(); @@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild() ELoadStatus load_status = E_ST_OK; S32 line_number = 0; - std::map joint_alias_map = getJointAliases(); + auto joint_alias_map = getJointAliases(); loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); std::string status = getString(STATUS[load_status]); diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index ae64521492..bb69ab65ef 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -108,7 +108,7 @@ public: S32 status, LLExtStat ext_status); private: void setAnimCallbacks() ; - std::map getJointAliases(); + std::map> getJointAliases(); protected: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 84b9cb18f8..bd33ab2dbe 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1487,7 +1487,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) { // Populate table - std::map joint_alias_map; + std::map> joint_alias_map; mModelPreview->getJointAliases(joint_alias_map); S32 conflicts = 0; diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index fc0a3ec58f..6843f7375d 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -781,7 +781,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - std::map joint_alias_map; + std::map> joint_alias_map; getJointAliases(joint_alias_map); LLHandle preview_handle = getHandle(); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index d9a3ec3004..dcba891f9f 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6306,13 +6306,13 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint *LLVOAvatar::getJoint( const std::string &name ) +LLJoint* LLVOAvatar::getJoint(std::string_view name) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = NULL; + LLJoint* jointp = nullptr; - if (iter == mJointMap.end() || iter->second == NULL) + if (iter == mJointMap.end() || iter->second == nullptr) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint *LLVOAvatar::getJoint( const std::string &name ) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index ab27c5752d..9eb8d3f880 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(const std::string &name); + virtual LLJoint* getJoint(std::string_view name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 90ff4067f2..ebba9ba291 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -697,17 +697,17 @@ void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) } // virtual -LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) +LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) { std::lock_guard lock(mJointMapMutex); - LLJoint *jointp = NULL; + LLJoint* jointp = nullptr; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f9bea41b1d..f7cd974ab0 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -90,7 +90,7 @@ public: /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(const std::string &name); + /*virtual*/ LLJoint* getJoint(std::string_view name); /*virtual*/ void renderJoints(); -- cgit v1.2.3 From 64b56d7ca98201f4cb5a31c21c0ff82220b7c730 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 1 Jul 2025 11:48:22 +0300 Subject: #4190 Provide unsupported extension info in log file --- indra/newview/gltf/llgltfloader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bd3c1a3833..fd0276766c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1705,6 +1705,8 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } args["EXT"] = ext; mWarningsArray.append(args); + + LL_WARNS("GLTF_IMPORT") << "Model uses unsupported extension: " << ext << LL_ENDL; } } -- cgit v1.2.3 From fcd9a9e0deb91c194a662550c6fd3ac406320067 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 18:05:49 +0300 Subject: #4242 Better issue logging --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/gltf/llgltfloader.cpp | 246 +++++++++------------ indra/newview/gltf/llgltfloader.h | 8 +- .../skins/default/xui/en/floater_model_preview.xml | 7 +- 4 files changed, 115 insertions(+), 148 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5a185a665d..447042ccd5 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -27,7 +27,7 @@ ImporterModelLimit Comment - Limits amount of importer generated models for dae files + Limits amount of importer generated (when over 8 faces) models for dae and gltf files Persist 1 Type diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index fd0276766c..71bfc021d3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -175,6 +175,16 @@ void LLGLTFLoader::addModelToScene( U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; if (face_limit < volume_faces) { + LL_WARNS("GLTF_IMPORT") << "Model contains " << volume_faces + << " faces, exceeding the limit of " << face_limit << LL_ENDL; + + LLSD args; + args["Message"] = "ModelTooManySubmodels"; + args["MODEL_NAME"] = pModel->mLabel; + args["SUBMODEL_COUNT"] = static_cast(llfloor((F32)volume_faces / LL_SCULPT_MESH_MAX_FACES)); + args["SUBMODEL_LIMIT"] = static_cast(submodel_limit); + mWarningsArray.append(args); + pModel->setNumVolumeFaces(face_limit); } @@ -284,7 +294,9 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map mesh_name_counts; - U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; + + // For now use mesh count, but might be better to do 'mNodes.size() - joints count'. + U32 submodel_limit = mGLTFAsset.mMeshes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mMeshes.size() : 0; // Check if we have scenes defined if (!mGLTFAsset.mScenes.empty()) @@ -318,20 +330,7 @@ bool LLGLTFLoader::parseMeshes() return false; } - // Check total model count against limit - U32 total_models = static_cast(mModelList.size()); - if (total_models > mGeneratedModelLimit) - { - LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models - << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; - - LLSD args; - args["Message"] = "TooManyMeshParts"; - args["PART_COUNT"] = static_cast(total_models); - args["LIMIT"] = static_cast(mGeneratedModelLimit); - mWarningsArray.append(args); - return false; - } + checkGlobalJointUsage(); return true; } @@ -478,7 +477,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 } } -bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) { const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; if (legal_name.empty()) @@ -493,6 +492,9 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + // Track joint usage for this skin, for the sake of unused joints detection + mJointUsage[gltf_skin_idx][gltf_joint_idx]++; + return true; } @@ -550,7 +552,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_use.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt, 0); for (size_t i = 0; i < jointCnt; ++i) { @@ -885,6 +887,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector indices_16; std::vector vertices_remap; // should it be a point map? vertices_remap.resize(faceVertices.size(), -1); + S32 created_faces = 0; std::vector face_verts; min = glm::vec3(FLT_MAX); max = glm::vec3(-FLT_MAX); @@ -933,6 +936,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); pModel->getMaterialList().push_back(materialName); + created_faces++; std::fill(vertices_remap.begin(), vertices_remap.end(), -1); indices_16.clear(); @@ -950,7 +954,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); pModel->getMaterialList().push_back(materialName); + created_faces++; } + + LL_INFOS("GLTF_IMPORT") << "Primitive " << pModel->mLabel + << " is over vertices limit, it was split into " << created_faces + << " faces" << LL_ENDL; + LLSD args; + args["Message"] = "ModelSplitPrimitive"; + args["MODEL_NAME"] = pModel->mLabel; + args["FACE_COUNT"] = created_faces; + mWarningsArray.append(args); } else { @@ -996,7 +1010,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 replacement_index = 0; std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); + gltfindex_to_joitindex_map.resize(jointCnt, -1); if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) { @@ -1094,7 +1108,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& << " Count: " << (S32)skin_info.mInvBindMatrix.size() << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; LLSD args; - args["Message"] = "ModelTooManyJoint"; + args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; @@ -1135,6 +1149,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); mJointNames.resize(skin_idx + 1); + mJointUsage.resize(skin_idx + 1); mValidJointsCount.resize(skin_idx + 1, 0); } @@ -1173,20 +1188,6 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } } - if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) - { - LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" - << " Count: " << mValidJointsCount[skin_idx] - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; - - LLSD args; - args["Message"] = "SkinJointsOverLimit"; - args["SKIN_INDEX"] = (S32)skin_idx; - args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; - mWarningsArray.append(args); - } - // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) @@ -1210,6 +1211,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { mJointNames[skin_idx].emplace_back(); } + mJointUsage[skin_idx].push_back(0); // Compute bind matrices @@ -1268,6 +1270,21 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mJointsFromNode.push_front(legal_name); } } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints < joint_count) + { + LL_WARNS("GLTF_IMPORT") << "Skin " << skin_idx + << " defines " << joint_count + << " joints, but only " << valid_joints + << " were recognized and are compatible." << LL_ENDL; + LLSD args; + args["Message"] = "SkinUsupportedJoints"; + args["SKIN_INDEX"] = skin_idx; + args["JOINT_COUNT"] = joint_count; + args["LEGAL_COUNT"] = valid_joints; + mWarningsArray.append(args); + } } void LLGLTFLoader::populateJointGroups() @@ -1279,109 +1296,6 @@ void LLGLTFLoader::populateJointGroups() } } - -S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const -{ - S32 source_joint_node = gltf_skin.mJoints[source_joint]; - S32 root_node = source_joint_node; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - if (mJointMap.find(jointNode.mName) != mJointMap.end()) - { - return i; - } - break; - } - } - } while (root_node != found_node); - - return -1; -} - -S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - - if (mJointMap.find(jointNode.mName) != mJointMap.end()) - { - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } - } while (root_node != found_node); - - return root_node; -} - -S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = 0; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } while (root_node != found_node); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; - LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; - return root_node; -} - -S32 LLGLTFLoader::findParentNode(S32 node) const -{ - S32 size = (S32)mGLTFAsset.mNodes.size(); - for (S32 i = 0; i < size; i++) - { - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); - if (it != jointNode.mChildren.end()) - { - return i; - } - } - return -1; -} - void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) { JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; @@ -1436,11 +1350,22 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); - glm::mat4 override_joint = node.mOverrideMatrix; - override_joint = glm::scale(override_joint, viewer_data.mScale); + glm::mat4 overriden_joint = node.mOverrideMatrix; + // This is incomplete or even wrong. + // Viewer allows overrides, which are base joint with applied translation override. + // So we should be taking viewer joint matrix and replacing translation part with an override. + // Or should rebuild the matrix from viewer_data.scale, viewer_data.rotation, translation_override parts. + overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); - rest = parent_rest * override_joint; - node.mOverrideRestMatrix = rest; + rest = parent_rest * overriden_joint; + if (viewer_data.mIsJoint) + { + node.mOverrideRestMatrix = rest; + } + else + { + node.mOverrideRestMatrix = parent_support_rest * overriden_joint; + } } else { @@ -1604,6 +1529,47 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } +void LLGLTFLoader::checkGlobalJointUsage() +{ + // Check if some joints remained unused + for (S32 skin_idx = 0; skin_idx < (S32)mGLTFAsset.mSkins.size(); ++skin_idx) + { + const LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_idx]; + S32 joint_count = (S32)gltf_skin.mJoints.size(); + S32 used_joints = 0; + for (S32 i = 0; i < joint_count; ++i) + { + S32 joint = gltf_skin.mJoints[i]; + if (mJointUsage[skin_idx][i] == 0) + { + // Joint is unused, log it + LL_INFOS("GLTF_DEBUG") << "Joint " << mJointNames[skin_idx][i] + << " in skin " << skin_idx << " is unused." << LL_ENDL; + } + else + { + used_joints++; + } + } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints > used_joints) + { + S32 unsed_joints = valid_joints - used_joints; + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx + << " declares " << valid_joints + << " valid joints, of them " << unsed_joints + << " remained unused" << LL_ENDL; + LLSD args; + args["Message"] = "SkinUnusedJoints"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = valid_joints; + args["USED_COUNT"] = used_joints; + mWarningsArray.append(args); + } + } +} + std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index b53258ab8b..10da5c8651 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -116,6 +116,7 @@ protected: bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; joint_names_t mJointNames; // empty string when no legal name for a given idx + std::vector> mJointUsage; // detect and warn about unsed joints // what group a joint belongs to. // For purpose of stripping unused groups when joints are over limit. @@ -134,15 +135,11 @@ 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); - bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const; + bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointsFromSkin(S32 skin_idx); void populateJointGroups(); void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); - S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; - S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; - S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint - S32 findParentNode(S32 node) const; void buildJointGroup(LLJointData& viewer_data, const std::string& parent_group); void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; @@ -150,6 +147,7 @@ private: glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); + void checkGlobalJointUsage(); std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index f8543ba1aa..af6e4e8613 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -71,10 +71,13 @@ Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] - Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE] Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. - Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail + Skin [SKIN_INDEX] defines [JOINT_COUNT] joints, but only [LEGAL_COUNT] were recognized and are compatible + Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, of them only [USED_COUNT] were used + Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail + Too many vertices in primitive [MESH_NAME], it was split into [FACE_COUNT] faces + Model [MODEL_NAME] contains [SUBMODEL_COUNT] generated mesh parts, parts were trimmed to [SUBMODEL_LIMIT] Buffer is either missing or empty [BUFFER_NAME]. Buffer is either missing or empty. Check presence of [BUFFER_URI] file. -- cgit v1.2.3 From 230a8caa5d839f2f987814d8fb8d94594cddf7f8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 22:16:10 +0300 Subject: private#435 Update GLTF Mesh Import's version to 7.2 --- indra/newview/VIEWER_VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 099f298456..0ee843cc60 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.14 +7.2.0 -- cgit v1.2.3 From 76dd9385f9354fd661d1c7cc1e7c3d9c39355675 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 23:14:24 +0300 Subject: #4315 Crash in GLTF uploader Properly handle importer's crashes in general --- indra/llprimitive/llmodelloader.cpp | 37 +++++++++++++++++++++- indra/newview/gltf/llgltfloader.cpp | 27 +++++++++++++++- indra/newview/llappviewer.cpp | 21 ++++++++++++ indra/newview/llappviewer.h | 1 + indra/newview/llviewermenu.cpp | 16 ++++++++++ .../skins/default/xui/en/floater_model_preview.xml | 2 ++ indra/newview/skins/default/xui/en/menu_viewer.xml | 10 ++++-- 7 files changed, 110 insertions(+), 4 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index f97ac16a83..a5b5d595a2 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -149,7 +149,42 @@ LLModelLoader::~LLModelLoader() void LLModelLoader::run() { mWarningsArray.clear(); - doLoadModel(); + try + { + doLoadModel(); + } + // Model loader isn't mission critical, so we just log all exceptions + catch (const LLException& e) + { + LL_WARNS("THREAD") << "LLException in model loader: " << e.what() << "" << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLModelLoader"); + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + // todo: we are inside of a thread, push this into main thread worker, // not into doOnIdleOneTime that laks tread safety doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 71bfc021d3..bb6a0a590c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -124,7 +124,32 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename, false); + try + { + mGltfLoaded = mGLTFAsset.load(filename, false); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLGLTFLoader"); + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } if (!mGltfLoaded) { diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index aea4492223..9872233e98 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5679,6 +5679,27 @@ void LLAppViewer::forceErrorThreadCrash() thread->start(); } +void LLAppViewer::forceExceptionThreadCrash() +{ + class LLCrashTestThread : public LLThread + { + public: + + LLCrashTestThread() : LLThread("Crash logging test thread") + { + } + + void run() + { + throw std::exception(); + } + }; + + LL_WARNS() << "This is a deliberate exception in a thread" << LL_ENDL; + LLCrashTestThread* thread = new LLCrashTestThread(); + thread->start(); +} + void LLAppViewer::initMainloopTimeout(std::string_view state, F32 secs) { if (!mMainloopTimeout) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b4b8e5bac3..bafe952659 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -175,6 +175,7 @@ public: virtual void forceErrorCoroprocedureCrash(); virtual void forceErrorWorkQueueCrash(); virtual void forceErrorThreadCrash(); + virtual void forceExceptionThreadCrash(); // The list is found in app_settings/settings_files.xml // but since they are used explicitly in code, diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 1c9a892a4f..44157d2d2d 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -289,6 +289,7 @@ void force_error_coroutine_crash(); void force_error_coroprocedure_crash(); void force_error_work_queue_crash(); void force_error_thread_crash(); +void force_exception_thread_crash(); void handle_force_delete(); void print_object_info(); @@ -2663,6 +2664,15 @@ class LLAdvancedForceErrorThreadCrash : public view_listener_t } }; +class LLAdvancedForceExceptionThreadCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_exception_thread_crash(); + return true; + } +}; + class LLAdvancedForceErrorDisconnectViewer : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -8696,6 +8706,11 @@ void force_error_thread_crash() LLAppViewer::instance()->forceErrorThreadCrash(); } +void force_exception_thread_crash() +{ + LLAppViewer::instance()->forceExceptionThreadCrash(); +} + class LLToolsUseSelectionForGrid : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9898,6 +9913,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedForceErrorCoroprocedureCrash(), "Advanced.ForceErrorCoroprocedureCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorWorkQueueCrash(), "Advanced.ForceErrorWorkQueueCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); + view_listener_t::addMenu(new LLAdvancedForceExceptionThreadCrash(), "Advanced.ForceExceptionThreadCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); // Advanced (toplevel) diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index af6e4e8613..618d0a7fda 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,6 +61,7 @@ Document has no root Document has no visual_scene Unable to process mesh without position data. Invalid model. + Importer crashed while processing [FILENAME], if you encounter this and file is valid, please report the issue to Second Life Support. Exception: [EXCEPTION]. No scenes defined in GLTF file @@ -80,6 +81,7 @@ Model [MODEL_NAME] contains [SUBMODEL_COUNT] generated mesh parts, parts were trimmed to [SUBMODEL_LIMIT] Buffer is either missing or empty [BUFFER_NAME]. Buffer is either missing or empty. Check presence of [BUFFER_URI] file. + Parser failed to process [FILENAME], file might be corrupt, incomplete or protected from reading. Exception: [EXCEPTION]. + label="Force an LLError Crash in a Thread" + name="Force an LLError Crash in a Thread"> + + + -- cgit v1.2.3 From 900516a449c618c87517d295db7f76662e555d00 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 23:14:24 +0300 Subject: #4142 Fix missed index --- indra/newview/gltf/llgltfloader.cpp | 2 +- indra/newview/skins/default/xui/en/floater_model_preview.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bb6a0a590c..dd9e0d9b3a 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -581,7 +581,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (size_t i = 0; i < jointCnt; ++i) { - if (mJointNames[i].empty()) + if (mJointNames[skinIdx][i].empty()) { // This might need to hold a substitute index gltf_joint_index_use[i] = -1; // mark as unsupported diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 618d0a7fda..4141191038 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -77,7 +77,7 @@ Skin [SKIN_INDEX] defines [JOINT_COUNT] joints, but only [LEGAL_COUNT] were recognized and are compatible Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, of them only [USED_COUNT] were used Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail - Too many vertices in primitive [MESH_NAME], it was split into [FACE_COUNT] faces + Too many vertices in primitive [MODEL_NAME], it was split into [FACE_COUNT] faces Model [MODEL_NAME] contains [SUBMODEL_COUNT] generated mesh parts, parts were trimmed to [SUBMODEL_LIMIT] Buffer is either missing or empty [BUFFER_NAME]. Buffer is either missing or empty. Check presence of [BUFFER_URI] file. -- cgit v1.2.3 From b79feb60f55efe93ce08609e9c2916d1938dad9e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 2 Jul 2025 18:01:00 +0300 Subject: #4242 Clarify some operations and make scale application a bit more valid --- indra/newview/gltf/llgltfloader.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index dd9e0d9b3a..be3b9f0c4d 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1373,15 +1373,14 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::quat rotation; glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + // Viewer allows overrides, which are base joint with applied translation override. + // fortunately normal bones use only translation, without rotation or scale node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); glm::mat4 overriden_joint = node.mOverrideMatrix; - // This is incomplete or even wrong. - // Viewer allows overrides, which are base joint with applied translation override. - // So we should be taking viewer joint matrix and replacing translation part with an override. - // Or should rebuild the matrix from viewer_data.scale, viewer_data.rotation, translation_override parts. - overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); + // todo: if gltf bone had rotation or scale, they probably should be saved here + // then applied to bind matrix rest = parent_rest * overriden_joint; if (viewer_data.mIsJoint) { @@ -1389,6 +1388,12 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map } else { + // This is likely incomplete or even wrong. + // Viewer Collision bones specify rotation and scale. + // Importer should apply rotation and scale to this matrix and save as needed + // then subsctruct them from bind matrix + + overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); node.mOverrideRestMatrix = parent_support_rest * overriden_joint; } } -- cgit v1.2.3 From 55a79ec56ba955955736b9ffa2e7d470712f871b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 2 Jul 2025 19:31:26 +0300 Subject: #4313 Improved naming of textures --- indra/newview/llmaterialeditor.cpp | 45 ++++++++++++++------------------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 378f5fdf91..5b3ac53d51 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -1333,15 +1333,6 @@ const std::string LLMaterialEditor::buildMaterialDescription() desc << mNormalName; } - // trim last char if it's a ',' in case there is no normal texture - // present and the code above inserts one - // (no need to check for string length - always has initial string) - std::string::iterator iter = desc.str().end() - 1; - if (*iter == ',') - { - desc.str().erase(iter); - } - // sanitize the material description so that it's compatible with the inventory // note: split this up because clang doesn't like operating directly on the // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a @@ -1349,6 +1340,15 @@ const std::string LLMaterialEditor::buildMaterialDescription() std::string inv_desc = desc.str(); LLInventoryObject::correctInventoryName(inv_desc); + // trim last char if it's a ',' in case there is no normal texture + // present and the code above inserts one + // (no need to check for string length - always has initial string) + std::string::iterator iter = inv_desc.end() - 1; + if (*iter == ',') + { + inv_desc.erase(iter); + } + return inv_desc; } @@ -2685,10 +2685,8 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c // so we can include everything if (stripped_uri.length() > 0) { - // example "DamagedHelmet: base layer" + // example "base layer" return STRINGIZE( - mMaterialNameShort << - ": " << stripped_uri << " (" << texture_type << @@ -2697,28 +2695,17 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c } else // uri doesn't include the type (because the uri is empty) - // so we must reorganize the string a bit to include the name - // and an explicit name type + // include an explicit name type { - // example "DamagedHelmet: (Emissive)" - return STRINGIZE( - mMaterialNameShort << - " (" << - texture_type << - ")" - ); + // example "Emissive" + return texture_type; } } else - // uri includes the type so just use it directly with the - // name of the material + // uri includes the type so just use it directly { - return STRINGIZE( - // example: AlienBust: normal_layer - mMaterialNameShort << - ": " << - stripped_uri - ); + // example: "normal_layer" + return stripped_uri; } } -- cgit v1.2.3 From 5a0bbdc510d3aef452b30aa932588aa7dc630d22 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 2 Jul 2025 23:18:05 +0300 Subject: #4242 Debug dump improvement for better comparison with collada output --- indra/llprimitive/lldaeloader.cpp | 6 ++- indra/llprimitive/lldaeloader.h | 2 +- indra/llprimitive/llmodel.cpp | 12 +++-- indra/llprimitive/llmodel.h | 10 +++- indra/llprimitive/llmodelloader.cpp | 57 ++++++++++++++++++++-- indra/llprimitive/llmodelloader.h | 6 ++- indra/newview/app_settings/settings.xml | 13 ++++- indra/newview/gltf/llgltfloader.cpp | 18 ++++--- indra/newview/gltf/llgltfloader.h | 2 +- indra/newview/llmeshrepository.cpp | 4 +- indra/newview/llmodelpreview.cpp | 6 ++- .../skins/default/xui/en/floater_model_preview.xml | 2 +- 12 files changed, 110 insertions(+), 28 deletions(-) diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..aeeca9bcfb 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -883,6 +883,7 @@ LLDAELoader::LLDAELoader( std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess) : LLModelLoader( filename, @@ -895,8 +896,9 @@ LLDAELoader::LLDAELoader( jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh), - mGeneratedModelLimit(modelLimit), + maxJointsPerMesh, + modelLimit, + debugMode), mPreprocessDAE(preprocess) { } diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index dc20feca52..400277d902 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -59,6 +59,7 @@ public: std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess); virtual ~LLDAELoader() ; @@ -103,7 +104,6 @@ protected: static std::string preprocessDAE(std::string filename); private: - U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mPreprocessDAE; }; diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 3d31cfbb7f..a33f25307e 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -818,7 +818,7 @@ LLSD LLModel::writeModel( bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite, + EWriteModelMode write_mode, bool as_slm, int submodel_id) { @@ -1097,10 +1097,10 @@ LLSD LLModel::writeModel( } } - return writeModelToStream(ostr, mdl, nowrite, as_slm); + return writeModelToStream(ostr, mdl, write_mode, as_slm); } -LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bool as_slm) +LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, EWriteModelMode write_mode, bool as_slm) { std::string::size_type cur_offset = 0; @@ -1162,7 +1162,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bo } } - if (!nowrite) + if (write_mode == WRITE_HUMAN) + { + ostr << mdl; + } + else if (write_mode == WRITE_BINARY) { LLSDSerialize::toBinary(header, ostr); diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..6501b3dc50 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -160,6 +160,12 @@ public: bool loadSkinInfo(LLSD& header, std::istream& is); bool loadDecomposition(LLSD& header, std::istream& is); + enum EWriteModelMode + { + WRITE_NO = 0, + WRITE_BINARY, + WRITE_HUMAN, + }; static LLSD writeModel( std::ostream& ostr, LLModel* physics, @@ -171,14 +177,14 @@ public: bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite = false, + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false, int submodel_id = 0); static LLSD writeModelToStream( std::ostream& ostr, LLSD& mdl, - bool nowrite = false, bool as_slm = false); + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false); void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); } diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index a5b5d595a2..61e479c9fd 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -113,7 +113,9 @@ LLModelLoader::LLModelLoader( JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh) + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode) : mJointList( jointTransformMap ) , mJointsFromNode( jointsFromNodes ) , LLThread("Model Loader") @@ -133,6 +135,8 @@ LLModelLoader::LLModelLoader( , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) , mMaxJointsPerMesh(maxJointsPerMesh) +, mGeneratedModelLimit(modelLimit) +, mDebugMode(debugMode) , mJointMap(legalJointNamesMap) { assert_main_thread(); @@ -238,7 +242,9 @@ bool LLModelLoader::doLoadModel() } } - return OpenFile(mFilename); + bool res = OpenFile(mFilename); + dumpDebugData(); // conditional on mDebugMode + return res; } void LLModelLoader::setLoadState(U32 state) @@ -505,6 +511,11 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector& mdl : mModelList) + { + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + size_t joint_count = skin_info.mJointNames.size(); + size_t alt_count = skin_info.mAlternateBindMatrix.size(); + + LLModel::writeModel( + file, + nullptr, + mdl, + nullptr, + nullptr, + nullptr, + mdl->mPhysics, + joint_count > 0, + alt_count > 0, + false, + LLModel::WRITE_HUMAN, + false, + mdl->mSubmodelID); + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } } //called in the main thread diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index caffa34676..06a17f006e 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -129,6 +129,7 @@ public: U32 mMaxJointsPerMesh; + U32 mDebugMode; // see dumDebugData() for details LLModelLoader( std::string filename, @@ -141,7 +142,9 @@ public: JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh); + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode); virtual ~LLModelLoader(); virtual void setNoNormalize() { mNoNormalize = true; } @@ -210,6 +213,7 @@ protected: bool mRigValidJointUpload; U32 mLegacyRigFlags; + U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mNoNormalize; bool mNoOptimize; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 447042ccd5..bfb619ad22 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2,7 +2,7 @@ - ImporterDebug + ImporterDebugVerboseLogging Comment Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines. @@ -35,6 +35,17 @@ Value 768 + ImporterDebugMode + + Comment + At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and first 5 models as llsd + Persist + 1 + Type + U32 + Value + 0 + ImporterPreprocessDAE Comment diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index be3b9f0c4d..48343aa169 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -98,6 +98,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, @@ -110,8 +111,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ) - , mGeneratedModelLimit(modelLimit) + maxJointsPerMesh, + modelLimit, + debugMode) , mViewerJointData(viewer_skeleton) { } @@ -1037,7 +1039,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt, -1); - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; // Assume that 'Torso' group is always in use since that's what everything else is attached to @@ -1092,7 +1094,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // this step needs only joints that have zero uses continue; } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { break; } @@ -1127,16 +1129,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { + // mMaxJointsPerMesh ususlly is equal to LL_MAX_JOINTS_PER_MESH_OBJECT + // and is 110. LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel << " Count: " << (S32)skin_info.mInvBindMatrix.size() - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + << " Limit:" << (S32)mMaxJointsPerMesh << LL_ENDL; LLSD args; args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + args["MAX"] = (S32)mMaxJointsPerMesh; mWarningsArray.append(args); } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 10da5c8651..e250a76982 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -83,6 +83,7 @@ class LLGLTFLoader : public LLModelLoader std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -103,7 +104,6 @@ protected: tinygltf::Model mGltfModel; bool mGltfLoaded; bool mApplyXYRotation = false; - U32 mGeneratedModelLimit; // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e7e95034b2..9382ebb00b 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2740,7 +2740,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); @@ -2898,7 +2898,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 6843f7375d..bbded6f189 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -167,7 +167,7 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) - , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebug", false)) + , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; mCameraDistance = 0.f; @@ -692,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(); } @@ -807,6 +807,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), gSavedSettings.getBOOL("ImporterPreprocessDAE")); } else @@ -827,6 +828,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), viewer_skeleton); } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 4141191038..a86b9c7da2 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1426,7 +1426,7 @@ word_wrap="true"> Date: Wed, 2 Jul 2025 23:19:22 +0300 Subject: #4322 Upload Model button is sometimes disabled when it should not --- indra/newview/llmodelpreview.cpp | 9 +++++++-- indra/newview/llviewermenufile.cpp | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index bbded6f189..39a03d52d7 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -164,6 +164,8 @@ 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) @@ -176,11 +178,9 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mCameraZoom = 1.f; mTextureName = 0; mPreviewLOD = 0; - mModelLoader = NULL; mMaxTriangleLimit = 0; mDirty = false; mGenLOD = false; - mLoading = false; mLookUpLodFiles = false; mLoadState = LLModelLoader::STARTING; mGroup = 0; @@ -212,6 +212,7 @@ LLModelPreview::~LLModelPreview() { mModelLoader->shutdown(); mModelLoader = NULL; + mLoading = false; } if (mPreviewAvatar) @@ -754,6 +755,10 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable LL_WARNS() << out.str() << LL_ENDL; LLFloaterModelPreview::addStringToLog(out, true); assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS); + if (mModelLoader == nullptr) + { + mLoading = false; + } return; } diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 9743ec0c59..84195997c3 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -94,7 +94,7 @@ class LLFileEnableUploadModel : public view_listener_t bool handleEvent(const LLSD& userdata) { LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) LLFloaterReg::findInstance("upload_model"); - if (fmp && fmp->isModelLoading()) + if (fmp && !fmp->isDead() && fmp->isModelLoading()) { return false; } -- cgit v1.2.3 From f1701d3373959d023e35d39dabb2aa5ea44a3fb0 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 3 Jul 2025 19:45:49 +0300 Subject: #4242 Make sure group array is initialized MacOS doesn't seem to like missing init. --- indra/newview/gltf/llgltfloader.cpp | 33 ++++++++++++++++++++++----------- indra/newview/gltf/llgltfloader.h | 4 ++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 48343aa169..5abd4cf5ae 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -115,6 +115,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, modelLimit, debugMode) , mViewerJointData(viewer_skeleton) + , mGltfLoaded(false) + , mApplyXYRotation(false) { } @@ -313,7 +315,7 @@ bool LLGLTFLoader::parseMeshes() } // Populate the joints from skins first. - // There's not many skins - and you can pretty easily iterate through the nodes from that. + // Multiple meshes can share the same skin, so preparing skins beforehand. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { populateJointsFromSkin(i); @@ -367,7 +369,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) return; - auto& node = mGLTFAsset.mNodes[node_idx]; + const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") @@ -380,7 +382,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map material_map mats; LLModel* pModel = new LLModel(volume_params, 0.f); - auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage std::string base_name = mesh.mName; @@ -1042,6 +1044,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; + + for (const auto& elem : mJointGroups) + { + goup_use_count[elem.second.mGroup] = 0; + goup_use_count[elem.second.mParentGroup] = 0; + } + // Assume that 'Torso' group is always in use since that's what everything else is attached to goup_use_count["Torso"] = 1; // Note that Collisions and Extra groups are all over the place, might want to include them from the start @@ -1188,7 +1197,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; JointNodeData& data = joints_data[joint]; data.mNodeIdx = joint; data.mJointListIdx = i; @@ -1218,6 +1227,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } // Go over viewer joints and build overrides + // This is needed because gltf skeleton doesn't necessarily match viewer's skeleton. glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { @@ -1227,8 +1237,10 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); + + // Viewer supports a limited set of joints, mark them as legal bool legal_joint = false; if (mJointMap.find(legal_name) != mJointMap.end()) { @@ -1254,8 +1266,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } else if (inverse_count > i) { - // Transalte existing bind matrix to viewer's skeleton - // todo: probably should be 'to viewer's overriden skeleton' + // Transalte existing bind matrix to viewer's overriden skeleton glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); @@ -1270,7 +1281,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { // If bind matrices aren't present (they are optional in gltf), // assume an identy matrix - // todo: find a model with this, might need to use rotated matrix + // todo: find a model with this, might need to use YZ rotated matrix glm::mat4 inv_bind(1.0f); glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); @@ -1303,7 +1314,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) S32 valid_joints = mValidJointsCount[skin_idx]; if (valid_joints < joint_count) { - LL_WARNS("GLTF_IMPORT") << "Skin " << skin_idx + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx << " defines " << joint_count << " joints, but only " << valid_joints << " were recognized and are compatible." << LL_ENDL; @@ -1339,7 +1350,6 @@ void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string & void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { - glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); if (found_node != names_to_nodes.end()) @@ -1396,6 +1406,7 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map // Viewer Collision bones specify rotation and scale. // Importer should apply rotation and scale to this matrix and save as needed // then subsctruct them from bind matrix + // Todo: get models that use collision bones, made by different programs overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); node.mOverrideRestMatrix = parent_support_rest * overriden_joint; @@ -1533,7 +1544,7 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) for (S32 i= 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; - auto joint_node = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &joint_node = mGLTFAsset.mNodes[joint]; // todo: we are doing this search thing everywhere, // just pre-translate every joint diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index e250a76982..7486c72a95 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -102,7 +102,7 @@ class LLGLTFLoader : public LLModelLoader protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; - bool mGltfLoaded; + bool mGltfLoaded = false; bool mApplyXYRotation = false; // GLTF isn't aware of viewer's skeleton and uses it's own, @@ -125,7 +125,7 @@ protected: std::string mGroup; std::string mParentGroup; }; - typedef std::map joint_to_group_map_t; + typedef std::map > 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. -- cgit v1.2.3 From dd4100181dec0f8ff4da23cf28c1f0295050d392 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 3 Jul 2025 22:31:21 +0300 Subject: #4334 Crash at getIsDynamic --- indra/newview/llreflectionmap.cpp | 2 +- indra/newview/llreflectionmap.h | 2 +- indra/newview/llreflectionmapmanager.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp index 910509928d..7f5076bd56 100644 --- a/indra/newview/llreflectionmap.cpp +++ b/indra/newview/llreflectionmap.cpp @@ -177,7 +177,7 @@ void LLReflectionMap::autoAdjustOrigin() mPriority = 1; mOrigin.load3(mViewerObject->getPositionAgent().mV); - if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject)->getReflectionProbeIsBox()) + if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject.get())->getReflectionProbeIsBox()) { LLVector3 s = mViewerObject->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f)); mRadius = s.magVec(); diff --git a/indra/newview/llreflectionmap.h b/indra/newview/llreflectionmap.h index d20bba7059..a818793550 100644 --- a/indra/newview/llreflectionmap.h +++ b/indra/newview/llreflectionmap.h @@ -124,7 +124,7 @@ public: LLSpatialGroup* mGroup = nullptr; // viewer object this probe is tracking (if any) - LLViewerObject* mViewerObject = nullptr; + LLPointer mViewerObject = nullptr; // what priority should this probe have (higher is higher priority) // currently only 0 or 1 diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp index f2abc7b8b7..3391b7adf7 100644 --- a/indra/newview/llreflectionmapmanager.cpp +++ b/indra/newview/llreflectionmapmanager.cpp @@ -1146,7 +1146,7 @@ void LLReflectionMapManager::updateUniforms() { if (refmap->mViewerObject && refmap->mViewerObject->getVolume()) { // have active manual probes live-track the object they're associated with - LLVOVolume* vobj = (LLVOVolume*)refmap->mViewerObject; + LLVOVolume* vobj = (LLVOVolume*)refmap->mViewerObject.get(); refmap->mOrigin.load3(vobj->getPositionAgent().mV); -- cgit v1.2.3 From ecf220f1b8b56769d5489af2c7c7c3be962dff6e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 3 Jul 2025 23:07:08 +0300 Subject: #4242 Debug dump improvement #2 --- indra/llprimitive/llmodelloader.cpp | 109 +++++++++++++++++++++++--------- indra/newview/app_settings/settings.xml | 2 +- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 61e479c9fd..db4c4259f3 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -566,41 +566,88 @@ void LLModelLoader::dumpDebugData() file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; } - if (mDebugMode < 1) + if (mDebugMode == 2) { - return; - } + S32 model_count = 0; + for (LLPointer& 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 << "\nModel LLSDs\n"; - S32 model_count = 0; - // some files contain too many models, so stop at 5. - for (LLPointer& mdl : mModelList) + 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) { - 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 << "\nModel LLSDs\n"; + S32 model_count = 0; + // some files contain too many models, so stop at 5. + for (LLPointer& mdl : mModelList) { - file << "Too many models, stopping at 5.\n"; - break; + 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; + } } } } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index bfb619ad22..1cac6e9709 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -38,7 +38,7 @@ ImporterDebugMode Comment - At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and first 5 models as llsd + 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 Persist 1 Type -- cgit v1.2.3 From b6343d4169a1a3c10c287f32a86db6c1a52bca4d Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 4 Jul 2025 20:37:29 +0300 Subject: #4323 fix for transparent meshes --- indra/newview/gltf/llgltfloader.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5abd4cf5ae..3bee909951 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,6 +86,8 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); +static const S32 VERTICIES_LIMIT = USHRT_MAX - 2; + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -889,7 +891,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mats[materialName] = impMat; // Indices handling - if (faceVertices.size() >= USHRT_MAX) + if (faceVertices.size() >= VERTICIES_LIMIT) { // Will have to remap 32 bit indices into 16 bit indices // For the sake of simplicity build vector of 32 bit indices first @@ -957,7 +959,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } indices_16.push_back((U16)vert_index); - if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) + if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1) { LLVolumeFace face; face.fillFromLegacyData(face_verts, indices_16); -- cgit v1.2.3 From 6e65c3821e852dd923df18efc1d02c70b42692f6 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 7 Jul 2025 21:25:35 +0300 Subject: #4319 Thread crashes not being reported to bugsplat --- indra/llcommon/llthread.cpp | 105 ++++++++++++++++++++++++++++++++++++++------ indra/llcommon/llthread.h | 5 +++ 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index e5d25b52f0..692941a892 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -28,6 +28,7 @@ #include "apr_portable.h" +#include "llapp.h" #include "llthread.h" #include "llmutex.h" @@ -35,6 +36,7 @@ #include "lltrace.h" #include "lltracethreadrecorder.h" #include "llexception.h" +#include "workqueue.h" #if LL_LINUX #include @@ -106,6 +108,27 @@ namespace return s_thread_id; } +#if LL_WINDOWS + + static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific + + U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS* exception_infop) + { + if (LLApp::instance()->reportCrashToBugsplat((void*)exception_infop)) + { + // Handled + return EXCEPTION_CONTINUE_SEARCH; + } + else if (code == STATUS_MSC_EXCEPTION) + { + // C++ exception, go on + return EXCEPTION_CONTINUE_SEARCH; + } + + // handle it, convert to std::exception + return EXCEPTION_EXECUTE_HANDLER; + } +#endif // LL_WINDOWS } // anonymous namespace LL_COMMON_API bool on_main_thread() @@ -157,20 +180,11 @@ void LLThread::threadRun() // Run the user supplied function do { - try - { - run(); - } - catch (const LLContinueError &e) - { - LL_WARNS("THREAD") << "ContinueException on thread '" << mName << - "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; - //output possible call stacks to log file. - LLError::LLCallStacks::print(); - - LOG_UNHANDLED_EXCEPTION("LLThread"); - continue; - } +#ifdef LL_WINDOWS + sehHandle(); // Structured Exception Handling +#else + tryRun(); +#endif break; } while (true); @@ -188,6 +202,69 @@ void LLThread::threadRun() mStatus = STOPPED; } +void LLThread::tryRun() +{ + try + { + run(); + } + catch (const LLContinueError& e) + { + LL_WARNS("THREAD") << "ContinueException on thread '" << mName << + "'. Error what is: '" << e.what() << "'" << LL_ENDL; + LLError::LLCallStacks::print(); + + LOG_UNHANDLED_EXCEPTION("LLThread"); + } + catch (std::bad_alloc&) + { + // Todo: improve this, this is going to have a different callstack + // instead of showing where it crashed + LL_WARNS("THREAD") << "Out of memory in a thread: " << mName << LL_ENDL; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->post( + // Bind the current exception, rethrow it in main loop. + []() { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("THREAD") << "Out of memory in a thread" << LL_ENDL; + }); + } +#ifndef LL_WINDOWS + catch (...) + { + // Stash any other kind of uncaught exception to be rethrown by main thread. + LL_WARNS("THREAD") << "Capturing and rethrowing uncaught exception in LLThread " + << mName << LL_ENDL; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->post( + // Bind the current exception, rethrow it in main loop. + [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + } +#endif // else LL_WINDOWS +} + +#ifdef LL_WINDOWS +void LLThread::sehHandle() +{ + __try + { + // handle stop and continue exceptions first + tryRun(); + } + __except (exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + // convert to C++ styled exception + // Note: it might be better to use _se_set_translator + // if you want exception to inherit full callstack + char integer_string[512]; + sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); + throw std::exception(integer_string); + } +} +#endif + LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : mPaused(false), mName(name), diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 21264351e5..8794ac93aa 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -97,6 +97,11 @@ private: // static function passed to APR thread creation routine void threadRun(); + void tryRun(); + +#ifdef LL_WINDOWS + void sehHandle(); +#endif protected: std::string mName; -- cgit v1.2.3 From b7dbe0e49e49fd047f701c39006750dec4814395 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Jul 2025 00:06:32 +0300 Subject: #4290 Unresponsive viewer when uploading models Fix severe log spam --- indra/newview/gltf/llgltfloader.cpp | 12 +++++----- indra/newview/llmodelpreview.cpp | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3bee909951..c850663579 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -373,7 +373,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; - LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + LL_DEBUGS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") << " - children: " << node.mChildren.size() << LL_ENDL; @@ -542,7 +542,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& base_name = "mesh_" + std::to_string(mesh_index); } - LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; if (instance_count > 0) { @@ -988,7 +988,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& created_faces++; } - LL_INFOS("GLTF_IMPORT") << "Primitive " << pModel->mLabel + LL_INFOS("GLTF_IMPORT") << "Primitive " << (S32)prim_idx << " from model " << pModel->mLabel << " is over vertices limit, it was split into " << created_faces << " faces" << LL_ENDL; LLSD args; @@ -1276,7 +1276,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else @@ -1289,7 +1289,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } @@ -1301,7 +1301,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); newInverse.setTranslation(original_joint_transform.getTranslation()); - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 39a03d52d7..ac697383e2 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1819,7 +1819,7 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) { - LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; + LL_DEBUGS("Upload") << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; // Allow LoD from -1 to LLModel::LOD_PHYSICS if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) { @@ -1896,6 +1896,12 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d mMaxTriangleLimit = base_triangle_count; + // For logging purposes + S32 meshes_processed = 0; + S32 meshes_simplified = 0; + S32 meshes_sloppy_simplified = 0; + S32 meshes_fail_count = 0; + // Build models S32 start = LLModel::LOD_HIGH; @@ -1905,7 +1911,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d { start = which_lod; end = which_lod; - } + }; for (S32 lod = start; lod >= end; --lod) { @@ -1968,6 +1974,11 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d const LLVolumeFace &face = base->getVolumeFace(face_idx); LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); new_face = face; + meshes_fail_count++; + } + else + { + meshes_simplified++; } } } @@ -1980,7 +1991,18 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY) < 0) { // Sloppy failed and returned an invalid model - genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL) < 0) + { + meshes_fail_count++; + } + else + { + meshes_simplified++; + } + } + else + { + meshes_sloppy_simplified++; } } } @@ -2080,25 +2102,28 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); } - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << precise_ratio << " simplified using per model method." << LL_ENDL; + meshes_simplified++; } else { - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << sloppy_ratio << " sloppily simplified using per model method." << LL_ENDL; + meshes_sloppy_simplified++; } } else { - LL_INFOS() << "Model " << target_model->getName() + LL_DEBUGS("Upload") << "Model " << target_model->getName() << " lod " << which_lod << " resulting ratio " << precise_ratio << " simplified using per model method." << LL_ENDL; + meshes_simplified++; } } @@ -2112,6 +2137,8 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d //copy material list target_model->mMaterialList = base->mMaterialList; + meshes_processed++; + if (!validate_model(target_model)) { LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL; @@ -2141,6 +2168,11 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d } } } + + LL_INFOS("Upload") << "LOD " << which_lod << ", Mesh optimizer processed meshes : " << meshes_processed + <<" simplified: " << meshes_simplified + << ", slopily simplified: " << meshes_sloppy_simplified + << ", failures: " << meshes_fail_count << LL_ENDL; } void LLModelPreview::updateStatusMessages() -- cgit v1.2.3 From 60425f69f7500f55013a6d4f1c02d0b6612a731b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Jul 2025 21:32:07 +0300 Subject: #4314 Fix model suffixes Usecase: Unable to use the same GLB model for physics --- indra/llprimitive/lldaeloader.cpp | 3 +- indra/llprimitive/lldaeloader.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 66 +++++++++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 11 +++---- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index aeeca9bcfb..a11f9b5ca2 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -1682,6 +1682,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do { materials[model->mMaterialList[i]] = LLImportMaterial(); } + // todo: likely a bug here, shouldn't be using suffixed label, see how it gets used in other places. mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); stretch_extents(model, transformation); } @@ -2414,7 +2415,7 @@ std::string LLDAELoader::getElementLabel(daeElement *element) } // static -size_t LLDAELoader::getSuffixPosition(std::string label) +size_t LLDAELoader::getSuffixPosition(const std::string &label) { if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) { diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 400277d902..0335011a56 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -98,7 +98,7 @@ protected: bool loadModelsFromDomMesh(domMesh* mesh, std::vector& 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); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c850663579..a211dad5fa 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -188,6 +188,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) void LLGLTFLoader::addModelToScene( LLModel* pModel, + const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, @@ -239,7 +240,7 @@ void LLGLTFLoader::addModelToScene( LLModel* next = new LLModel(volume_params, 0.f); next->ClearFacesAndMaterials(); next->mSubmodelID = ++submodelID; - next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -289,7 +290,12 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + std::string base_name = model_name; + if (model->mSubmodelID > 0) + { + base_name += (char)((int)'a' + model->mSubmodelID); + } + mScene[transformation].push_back(LLModelInstance(model, base_name, transformation, materials)); stretch_extents(model, transformation); } } @@ -387,7 +393,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage - std::string base_name = mesh.mName; + std::string base_name = getLodlessLabel(mesh); if (base_name.empty()) { base_name = "mesh_" + std::to_string(node.mMesh); @@ -395,7 +401,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map S32 instance_count = mesh_name_counts[base_name]++; - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + // make name unique + if (instance_count > 0) + { + base_name = base_name + "_copy_" + std::to_string(instance_count); + } + + if (populateModelFromMesh(pModel, base_name, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -443,7 +455,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats); mats.clear(); } else @@ -529,29 +541,16 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Set name and suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so they + // need unique names not just in scope of one lod, but across lods. + pModel->mLabel = base_name + lod_suffix[mLod]; - // Create unique model name - std::string base_name = mesh.mName; - if (base_name.empty()) - { - S32 mesh_index = static_cast(&mesh - &mGLTFAsset.mMeshes[0]); - base_name = "mesh_" + std::to_string(mesh_index); - } - - LL_DEBUGS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; - - if (instance_count > 0) - { - pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); - } - else - { - pModel->mLabel = base_name; - } + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; pModel->ClearFacesAndMaterials(); @@ -1723,3 +1722,22 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } } +size_t LLGLTFLoader::getSuffixPosition(const std::string &label) +{ + if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) + { + return label.rfind('_'); + } + return -1; +} + +std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Mesh& mesh) +{ + size_t ext_pos = getSuffixPosition(mesh.mName); + if (ext_pos != -1) + { + return mesh.mName.substr(0, ext_pos); + } + return mesh.mName; +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 7486c72a95..3a9531bf7b 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -136,10 +136,10 @@ private: void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); + bool populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); void populateJointsFromSkin(S32 skin_idx); void populateJointGroups(); - void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); + 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; @@ -153,6 +153,9 @@ private: 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 @@ -190,10 +193,6 @@ private: // bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector &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); */ -- cgit v1.2.3 From 5a8b94bea6bd984e046d1f61034ba92f324c1a83 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 9 Jul 2025 13:03:34 +0300 Subject: #4324 sort indices before splits --- indra/newview/gltf/llgltfloader.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a211dad5fa..c2edf4ab1c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -913,23 +913,40 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // remap 32 bit into multiple 16 bit ones + // Generates a vertex remap table with no gaps in the resulting sequence + std::vector remap(faceVertices.size()); + size_t vertex_count = meshopt_generateVertexRemap(&remap[0], &indices_32[0], indices_32.size(), &faceVertices[0], faceVertices.size(), sizeof(LLVolumeFace::VertexData)); + + // Manually remap vertices + std::vector optimized_vertices(vertex_count); + for (size_t i = 0; i < vertex_count; ++i) + { + optimized_vertices[i] = faceVertices[remap[i]]; + } + + std::vector optimized_indices(indices_32.size()); + meshopt_remapIndexBuffer(&optimized_indices[0], &indices_32[0], indices_32.size(), &remap[0]); + + // Sort indices to improve mesh splits (reducing amount of duplicated indices) + meshopt_optimizeVertexCache(&optimized_indices[0], &optimized_indices[0], indices_32.size(), vertex_count); + std::vector indices_16; - std::vector vertices_remap; // should it be a point map? - vertices_remap.resize(faceVertices.size(), -1); + std::vector vertices_remap; + vertices_remap.resize(vertex_count, -1); S32 created_faces = 0; std::vector face_verts; min = glm::vec3(FLT_MAX); max = glm::vec3(-FLT_MAX); - for (size_t idx = 0; idx < indices_32.size(); idx++) + + for (size_t idx = 0; idx < optimized_indices.size(); idx++) { - size_t vert_index = indices_32[idx]; + size_t vert_index = optimized_indices[idx]; if (vertices_remap[vert_index] == -1) { // First encounter, add it size_t new_vert_idx = face_verts.size(); vertices_remap[vert_index] = (S64)new_vert_idx; - face_verts.push_back(faceVertices[vert_index]); + face_verts.push_back(optimized_vertices[vert_index]); vert_index = new_vert_idx; // Update min/max bounds -- cgit v1.2.3 From e77e74ffbb7cc9a8935d5b296a51a9c4ea94ea59 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 9 Jul 2025 00:22:20 +0300 Subject: #4314 Follow-up: duplicate logic for mSubmodelID --- indra/newview/gltf/llgltfloader.cpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c2edf4ab1c..d625ec1261 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -223,6 +223,7 @@ void LLGLTFLoader::addModelToScene( LLVolume::face_list_t remainder; std::vector ready_models; LLModel* current_model = pModel; + do { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); @@ -240,7 +241,26 @@ void LLGLTFLoader::addModelToScene( LLModel* next = new LLModel(volume_params, 0.f); next->ClearFacesAndMaterials(); next->mSubmodelID = ++submodelID; - next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + + std::string instance_name = model_name; + if (next->mSubmodelID > 0) + { + instance_name += (char)((int)'a' + next->mSubmodelID); + } + // Check for duplicates and add copy suffix if needed + int duplicate_count = 0; + for (const auto& inst : mScene[transformation]) + { + if (inst.mLabel == instance_name) + { + ++duplicate_count; + } + } + if (duplicate_count > 0) { + instance_name += "_copy_" + std::to_string(duplicate_count); + } + next->mLabel = instance_name; + next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -290,12 +310,10 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - std::string base_name = model_name; - if (model->mSubmodelID > 0) - { - base_name += (char)((int)'a' + model->mSubmodelID); - } - mScene[transformation].push_back(LLModelInstance(model, base_name, transformation, materials)); + // Keep base name for scene instance, add LOD suffix to model label for matching + std::string instance_name = model->mLabel; + model->mLabel += lod_suffix[mLod]; + mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials)); stretch_extents(model, transformation); } } -- cgit v1.2.3 From edaf157e19ec5886fa58ba6341db8a91d89e14eb Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 9 Jul 2025 15:52:23 +0300 Subject: #4314 Fix model suffixes #2 --- indra/newview/gltf/llgltfloader.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index d625ec1261..a181c97ac0 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -310,8 +310,11 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - // Keep base name for scene instance, add LOD suffix to model label for matching + // Keep base name for scene instance. std::string instance_name = model->mLabel; + // Add suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so models + // need unique names not just in scope of one lod, but across lods. model->mLabel += lod_suffix[mLod]; mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials)); stretch_extents(model, transformation); @@ -563,10 +566,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas { // Set the requested label for the floater display and uploading pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); - // Set name and suffix. Suffix is nessesary for model matching logic - // because sometimes higher lod can be used as a lower one, so they - // need unique names not just in scope of one lod, but across lods. - pModel->mLabel = base_name + lod_suffix[mLod]; + // Set only name, suffix will be added later + pModel->mLabel = base_name; LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; -- cgit v1.2.3 From 19ffeb6e2d6fe749070fe36facbfa3e3e8641364 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 9 Jul 2025 20:22:07 +0300 Subject: #4357 Crash in LLMeshRepoThread::lodReceived --- indra/newview/llmeshrepository.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 9382ebb00b..b5a1bbd534 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2395,6 +2395,11 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p // might be good idea to turn mesh into pointer to avoid making a copy mesh.mVolume = NULL; } + { + // make sure skin info is not removed from list while we are decreasing reference count + LLMutexLock lock(mSkinMapMutex); + skin_info = nullptr; + } return MESH_OK; } } -- cgit v1.2.3 From 3da493295e68481c2437465c1e0846541e9e43d8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 9 Jul 2025 21:18:12 +0300 Subject: #4296 Crash at renderMorphMasks --- indra/llappearance/lltexlayer.cpp | 29 ++++++++++++++++++----------- indra/llrender/llimagegl.cpp | 27 +++++++++++++++++++++++---- indra/newview/llappviewer.cpp | 13 +++++++++---- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/indra/llappearance/lltexlayer.cpp b/indra/llappearance/lltexlayer.cpp index aa48a2d621..b3800e6981 100644 --- a/indra/llappearance/lltexlayer.cpp +++ b/indra/llappearance/lltexlayer.cpp @@ -1293,7 +1293,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC { if (!force_render && !hasMorph()) { - LL_DEBUGS() << "skipping renderMorphMasks for " << getUUID() << LL_ENDL; + LL_DEBUGS("Morph") << "skipping renderMorphMasks for " << getUUID() << LL_ENDL; return; } LL_PROFILE_ZONE_SCOPED; @@ -1325,7 +1325,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC success &= param->render( x, y, width, height ); if (!success && !force_render) { - LL_DEBUGS() << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL; + LL_DEBUGS("Morph") << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL; return; } } @@ -1365,7 +1365,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC } else { - LL_WARNS() << "Skipping rendering of " << getInfo()->mStaticImageFileName + LL_WARNS("Morph") << "Skipping rendering of " << getInfo()->mStaticImageFileName << "; expected 1 or 4 components." << LL_ENDL; } } @@ -1404,8 +1404,8 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC // We can get bad morph masks during login, on minimize, and occasional gl errors. // We should only be doing this when we believe something has changed with respect to the user's appearance. { - LL_DEBUGS("Avatar") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL; - // clear out a slot if we have filled our cache + LL_DEBUGS("Morph") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL; + // clear out a slot if we have filled our cache S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1; while ((S32)mAlphaCache.size() >= max_cache_entries) { @@ -1444,13 +1444,20 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC } glGetTexImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGBA, GL_UNSIGNED_BYTE, temp); - - U8* alpha_cursor = alpha_data; - U8* pixel = temp; - for (int i = 0; i < pixels; i++) + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + { + LL_INFOS("Morph") << "GL Error while reading back morph texture. Error code: " << error << LL_ENDL; + } + else { - *alpha_cursor++ = pixel[3]; - pixel += 4; + U8* alpha_cursor = alpha_data; + U8* pixel = temp; + for (int i = 0; i < pixels; i++) + { + *alpha_cursor++ = pixel[3]; + pixel += 4; + } } gGL.getTexUnit(0)->disable(); diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 3f8903ca09..1db36d91f9 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1870,8 +1870,17 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes); if(!imageraw->allocateDataSize(width, height, ncomponents, glbytes)) { - LL_WARNS() << "Memory allocation failed for reading back texture. Size is: " << glbytes << LL_ENDL ; - LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ; + constexpr S64 MAX_GL_BYTES = 2048 * 2048; + if (glbytes > 0 && glbytes <= MAX_GL_BYTES) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << glbytes << LL_ENDL; + } + else + { + LL_WARNS() << "Memory allocation failed for reading back texture. Data size is: " << glbytes << LL_ENDL; + LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL; + } return false ; } @@ -1882,8 +1891,18 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre { if(!imageraw->allocateDataSize(width, height, ncomponents)) { - LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL ; - LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ; + constexpr F32 MAX_IMAGE_SIZE = 2048 * 2048; + F32 size = (F32)width * (F32)height * (F32)ncomponents; + if (size > 0 && size <= MAX_IMAGE_SIZE) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << size << LL_ENDL; + } + else + { + LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL; + LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL; + } return false ; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 9872233e98..f4b2fdfdf7 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2245,10 +2245,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string) // Callback for LLError::LLUserWarningMsg void errorHandler(const std::string& title_string, const std::string& message_string, S32 code) { - if (!message_string.empty()) - { - OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); - } + // message is going to hang viewer, create marker first switch (code) { case LLError::LLUserWarningMsg::ERROR_OTHER: @@ -2256,6 +2253,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st break; case LLError::LLUserWarningMsg::ERROR_BAD_ALLOC: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_BAD_ALLOC); + // When system run out of memory and errorHandler gets called from a thread, + // main thread might keep going while OSMessageBox freezes the caller. + // Todo: handle it better, but for now disconnect to avoid making things worse + gDisconnected = true; break; case LLError::LLUserWarningMsg::ERROR_MISSING_FILES: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES); @@ -2263,6 +2264,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st default: break; } + if (!message_string.empty()) + { + OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + } } void LLAppViewer::initLoggingAndGetLastDuration() -- cgit v1.2.3 From 07d0fbe94e89d0509b64232858e384a439810bb8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 11 Jul 2025 22:40:43 +0300 Subject: #4294 Make upload order more deterministic to make root prim more deterministic instead of being random each try --- indra/newview/llmeshrepository.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index b5a1bbd534..6236df1a13 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2699,10 +2699,21 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) S32 instance_num = 0; - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + // Upload should happen in deterministic order, so sort instances by model name. + // Note: probably can sort by mBaseModel->mSubmodelID here as well to avoid + // running over the list twice. + std::vector> sorted_instances(mInstance.begin(), mInstance.end()); + std::sort(sorted_instances.begin(), sorted_instances.end(), + [](const std::pair& a, const std::pair& b) + { + return a.first->mLabel < b.first->mLabel; + }); + + // Handle models, ignore submodels for now + for (auto& iter : sorted_instances) { LLMeshUploadData data; - data.mBaseModel = iter->first; + data.mBaseModel = iter.first; if (data.mBaseModel->mSubmodelID) { @@ -2711,7 +2722,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]; @@ -2758,8 +2769,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) { @@ -2857,10 +2868,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 : sorted_instances) { LLMeshUploadData data; - data.mBaseModel = iter->first; + data.mBaseModel = iter.first; if (!data.mBaseModel->mSubmodelID) { @@ -2869,7 +2881,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]; @@ -2916,8 +2928,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) { -- cgit v1.2.3 From 63134f79434481a450a146eae76f90a9f86091a4 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 14 Jul 2025 15:23:52 +0300 Subject: #4204 Cache processed materials --- indra/newview/gltf/llgltfloader.cpp | 190 ++++++++++++++++++++---------------- indra/newview/gltf/llgltfloader.h | 4 + 2 files changed, 109 insertions(+), 85 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a181c97ac0..f2361f58ba 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -126,6 +126,9 @@ LLGLTFLoader::~LLGLTFLoader() {} bool LLGLTFLoader::OpenFile(const std::string &filename) { + // Clear the material cache for new file + mMaterialCache.clear(); + tinygltf::TinyGLTF loader; std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); @@ -562,6 +565,106 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } +LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) +{ + // Check cache first + auto cached = mMaterialCache.find(material_index); + if (cached != mMaterialCache.end()) + { + return cached->second; + } + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color + + // Process material if available + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + { + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.size()) + { + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + { + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Use URI as texture file name + if (!image.mUri.empty()) + { + // URI might be a remote URL or a local path + std::string filename = image.mUri; + + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + // Store the texture filename + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); + + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) + { + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + // Texture will be loaded later through the callback system + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; + } + } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) + { + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + } + } + } + } + } + } + + // Cache the processed material + mMaterialCache[material_index] = impMat; + return impMat; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading @@ -623,91 +726,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas LLVolumeFace face; std::vector vertices; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color - - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) - { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - - // Set diffuse color from base color factor - impMat.mDiffuseColor = LLColor4( - material->mPbrMetallicRoughness.mBaseColorFactor[0], - material->mPbrMetallicRoughness.mBaseColorFactor[1], - material->mPbrMetallicRoughness.mBaseColorFactor[2], - material->mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Process base color texture if it exists - if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) - { - S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) - { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) - { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - - // Use URI as texture file name - if (!image.mUri.empty()) - { - // URI might be a remote URL or a local path - std::string filename = image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) - { - filename = filename.substr(pos + 1); - } - - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename - << " for material: " << material->mName << LL_ENDL; - - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); - - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // Texture will be loaded later through the callback system - LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; - } - } - else if (image.mTexture.notNull()) - { - // No URI but we have a texture, use it directly - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; - } - else if (image.mBufferView >= 0) - { - // For embedded textures (no URI but has buffer data) - std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); - if (!temp_filename.empty()) - { - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - } - } - } - } - } - } + // Use cached material processing + LLImportMaterial impMat = processMaterial(prim.mMaterial); if (prim.getIndexCount() % 3 != 0) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 3a9531bf7b..b4c6e0c66c 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -131,11 +131,15 @@ protected: // per skin joint count, needs to be tracked for the sake of limits check. std::vector mValidJointsCount; + // Material cache to avoid duplicate processing + std::map 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); + LLImportMaterial processMaterial(S32 material_index); 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(); -- cgit v1.2.3 From d033bbacf0d38079c1879acd04e54e341e78146e Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 14 Jul 2025 17:21:17 +0300 Subject: #4204 Refactor material name and texture handling in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 137 +++++++++++++++++++----------------- indra/newview/gltf/llgltfloader.h | 2 + 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f2361f58ba..3d46db9e6a 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -594,40 +594,20 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) { S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) + std::string filename = processTexture(texIndex, "base_color", material->mName); + + if (!filename.empty()) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) - { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - // Use URI as texture file name - if (!image.mUri.empty()) + // Check if the texture is already loaded + if (texIndex < mGLTFAsset.mTextures.size()) + { + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) { - // URI might be a remote URL or a local path - std::string filename = image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) - { - filename = filename.substr(pos + 1); - } - - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename - << " for material: " << material->mName << LL_ENDL; - - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); - - // If the image has a texture loaded already, use it + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; if (image.mTexture.notNull()) { impMat.setDiffuseMap(image.mTexture->getID()); @@ -635,26 +615,9 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) } else { - // Texture will be loaded later through the callback system LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } - else if (image.mTexture.notNull()) - { - // No URI but we have a texture, use it directly - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; - } - else if (image.mBufferView >= 0) - { - // For embedded textures (no URI but has buffer data) - std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); - if (!temp_filename.empty()) - { - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - } - } } } } @@ -665,6 +628,66 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) return impMat; } +std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name) +{ + if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) + return ""; + + S32 sourceIndex = mGLTFAsset.mTextures[texture_index].mSource; + if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + return ""; + + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Process URI-based textures + if (!image.mUri.empty()) + { + std::string filename = image.mUri; + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = filename; + args["MATERIAL_NAME"] = material_name; + mWarningsArray.append(args); + + return filename; + } + + // Process embedded textures + if (image.mBufferView >= 0) + { + return extractTextureToTempFile(texture_index, texture_type); + } + + return ""; +} + +std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) +{ + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + std::string materialName = material->mName; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(material_index); + } + return materialName; + } + else + { + return fallback_index >= 0 ? "mat_default" + std::to_string(fallback_index) : "mat_default"; + } +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading @@ -910,22 +933,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) - { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; - - if (materialName.empty()) - { - materialName = "mat" + std::to_string(prim.mMaterial); - } - } - else - { - materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); - } + // Generate material name using helper method + std::string materialName = generateMaterialName(prim.mMaterial, pModel->getNumVolumeFaces() - 1); mats[materialName] = impMat; // Indices handling diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index b4c6e0c66c..46506cdf50 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -140,6 +140,8 @@ private: void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); LLImportMaterial processMaterial(S32 material_index); + std::string processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name); + 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(); -- cgit v1.2.3 From 5a50be4e1554c6f96ff52fdbda5ba1c890d18a6d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 14 Jul 2025 18:47:03 +0300 Subject: #4294 Make upload order more deterministic #2 --- indra/newview/llmeshrepository.cpp | 19 ++++++------------- indra/newview/llmeshrepository.h | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 6236df1a13..142a3dac39 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2699,18 +2699,11 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) S32 instance_num = 0; - // Upload should happen in deterministic order, so sort instances by model name. - // Note: probably can sort by mBaseModel->mSubmodelID here as well to avoid - // running over the list twice. - std::vector> sorted_instances(mInstance.begin(), mInstance.end()); - std::sort(sorted_instances.begin(), sorted_instances.end(), - [](const std::pair& a, const std::pair& b) - { - return a.first->mLabel < b.first->mLabel; - }); - - // Handle models, ignore submodels for now - for (auto& iter : sorted_instances) + // 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; @@ -2869,7 +2862,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) } // Now handle the submodels. - for (auto& iter : sorted_instances) + for (auto& iter : mInstance) { LLMeshUploadData data; data.mBaseModel = iter.first; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 0847c29d0d..4c3901408f 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -674,7 +674,22 @@ public: typedef std::vector instance_list; instance_list mInstanceList; - typedef std::map, instance_list> instance_map; + // Upload should happen in deterministic order, so sort instances by model name. + struct LLUploadModelInstanceLess + { + inline bool operator()(const LLPointer& a, const LLPointer& 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, instance_list, LLUploadModelInstanceLess> instance_map; instance_map mInstance; LLMutex* mMutex; -- cgit v1.2.3 From b046a8b72ab7959c789b1b5dfb7da42e97044ba4 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 15 Jul 2025 19:06:28 +0300 Subject: #4204 Cache material names, centralize texture index validation --- indra/newview/gltf/llgltfloader.cpp | 63 ++++++++++++++++++++++--------------- indra/newview/gltf/llgltfloader.h | 16 ++++++++-- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3d46db9e6a..3019a12446 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -565,7 +565,7 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } -LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) +LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_index, S32 fallback_index) { // Check cache first auto cached = mMaterialCache.find(material_index); @@ -577,6 +577,9 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) LLImportMaterial impMat; impMat.mDiffuseColor = LLColor4::white; // Default color + // Generate material name + std::string materialName = generateMaterialName(material_index, fallback_index); + // Process material if available if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) { @@ -602,39 +605,36 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; // Check if the texture is already loaded - if (texIndex < mGLTFAsset.mTextures.size()) + S32 sourceIndex; + if (validateTextureIndex(texIndex, sourceIndex)) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + if (image.mTexture.notNull()) { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; - } + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } } } } + // Create cached material with both material and name + LLGLTFImportMaterial cachedMat(impMat, materialName); + // Cache the processed material - mMaterialCache[material_index] = impMat; - return impMat; + mMaterialCache[material_index] = cachedMat; + return cachedMat; } std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name) { - if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) - return ""; - - S32 sourceIndex = mGLTFAsset.mTextures[texture_index].mSource; - if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + S32 sourceIndex; + if (!validateTextureIndex(texture_index, sourceIndex)) return ""; LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; @@ -669,6 +669,18 @@ std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& t return ""; } +bool LLGLTFLoader::validateTextureIndex(S32 texture_index, S32& source_index) +{ + if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) + return false; + + source_index = mGLTFAsset.mTextures[texture_index].mSource; + if (source_index < 0 || source_index >= mGLTFAsset.mImages.size()) + return false; + + return true; +} + std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) { if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) @@ -750,7 +762,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas std::vector vertices; // Use cached material processing - LLImportMaterial impMat = processMaterial(prim.mMaterial); + LLGLTFImportMaterial cachedMat = processMaterial(prim.mMaterial, pModel->getNumVolumeFaces() - 1); + LLImportMaterial impMat = cachedMat; + std::string materialName = cachedMat.name; + mats[materialName] = impMat; if (prim.getIndexCount() % 3 != 0) { @@ -933,10 +948,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // Generate material name using helper method - std::string materialName = generateMaterialName(prim.mMaterial, pModel->getNumVolumeFaces() - 1); - mats[materialName] = impMat; - // Indices handling if (faceVertices.size() >= VERTICIES_LIMIT) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 46506cdf50..e8b91996c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -71,6 +71,14 @@ class LLGLTFLoader : public LLModelLoader typedef std::map joints_data_map_t; typedef std::map 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, @@ -131,16 +139,18 @@ protected: // per skin joint count, needs to be tracked for the sake of limits check. std::vector mValidJointsCount; - // Material cache to avoid duplicate processing - std::map mMaterialCache; + // Cached material information + typedef std::map 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx); - LLImportMaterial processMaterial(S32 material_index); + 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); -- cgit v1.2.3 From b849e0063d0463e598898b9e12c3e72fc033f9c5 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 16 Jul 2025 16:17:19 +0300 Subject: #4349 fix repeats cannot be adjusted for specular when a normal map is not applied --- indra/newview/llpanelface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 0086a9a86d..25dd37590d 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -276,7 +276,7 @@ LLRender::eTexIndex LLPanelFace::getMatTextureChannel() return LLRender::NORMAL_MAP; break; case MATTYPE_SPECULAR: // "Shininess (specular)" - if (getCurrentNormalMap().notNull()) + if (getCurrentSpecularMap().notNull()) return LLRender::SPECULAR_MAP; break; } -- cgit v1.2.3 From 7a33f2279be82e32acd6e25245026dc305b1ed2b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 16 Jul 2025 21:17:37 +0300 Subject: #4353 abbreviate text label for standard deviation --- indra/newview/skins/default/xui/en/floater_stats.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index 84252fe32c..ce5ab54d26 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -67,7 +67,7 @@ decimal_digits="1" stat="frametimejitter95"/> Date: Thu, 17 Jul 2025 11:23:10 +0300 Subject: Reapply "Merge develop into glTF mesh import" To simplify merging into mesh import branch This reverts commit b0c951ffe348f478f27a85720cc7aeffea32fe73. --- .github/workflows/build.yaml | 5 +- indra/llappearance/CMakeLists.txt | 1 + indra/llappearance/llavatarappearance.cpp | 66 +- indra/llappearance/llavatarappearance.h | 10 +- indra/llappearance/lljointdata.h | 66 + indra/llcharacter/llbvhloader.cpp | 6 +- indra/llcharacter/llbvhloader.h | 2 +- indra/llcharacter/llcharacter.cpp | 7 +- indra/llcharacter/llcharacter.h | 2 +- indra/llprimitive/CMakeLists.txt | 2 - indra/llprimitive/lldaeloader.cpp | 2 +- indra/llprimitive/lldaeloader.h | 26 +- indra/llprimitive/llgltfloader.cpp | 404 ----- indra/llprimitive/llgltfloader.h | 206 --- indra/llprimitive/llmodel.cpp | 182 ++- indra/llprimitive/llmodel.h | 1 + indra/llprimitive/llmodelloader.cpp | 54 + indra/llprimitive/llmodelloader.h | 9 +- indra/newview/CMakeLists.txt | 2 + indra/newview/gltf/asset.cpp | 198 ++- indra/newview/gltf/asset.h | 13 +- indra/newview/gltf/buffer_util.h | 11 + indra/newview/gltf/llgltfloader.cpp | 1710 ++++++++++++++++++++ indra/newview/gltf/llgltfloader.h | 203 +++ indra/newview/gltfscenemanager.cpp | 2 +- indra/newview/llfilepicker.cpp | 6 +- indra/newview/llfloaterbvhpreview.cpp | 4 +- indra/newview/llfloaterbvhpreview.h | 2 +- indra/newview/llfloatermodelpreview.cpp | 7 +- indra/newview/llmaterialeditor.cpp | 3 +- indra/newview/llmodelpreview.cpp | 52 +- indra/newview/llskinningutil.cpp | 9 +- indra/newview/llvoavatar.cpp | 8 +- indra/newview/llvoavatar.h | 2 +- indra/newview/llvoavatarself.cpp | 6 +- indra/newview/llvoavatarself.h | 2 +- .../skins/default/xui/en/floater_model_preview.xml | 20 +- .../newview/skins/default/xui/en/notifications.xml | 5 +- 38 files changed, 2544 insertions(+), 772 deletions(-) create mode 100644 indra/llappearance/lljointdata.h delete mode 100644 indra/llprimitive/llgltfloader.cpp delete mode 100644 indra/llprimitive/llgltfloader.h create mode 100644 indra/newview/gltf/llgltfloader.cpp create mode 100644 indra/newview/gltf/llgltfloader.h diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 198785d39b..4bf2af644a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -218,8 +218,10 @@ jobs: prefix=${ba[0]} if [ "$prefix" == "project" ]; then IFS='_' read -ra prj <<< "${ba[1]}" + prj_str="${prj[*]}" # uppercase first letter of each word - export viewer_channel="Second Life Project ${prj[*]^}" + capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') + export viewer_channel="Second Life Project $capitalized" elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; then export viewer_channel="Second Life Release" @@ -455,7 +457,6 @@ jobs: prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} - previous_tag: release append_body: true fail_on_unmatched_files: true files: | diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index c3be8bc78e..6744c8d8a4 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES llavatarjoint.cpp llavatarjointmesh.cpp lldriverparam.cpp + lljointdata.h lllocaltextureobject.cpp llpolyskeletaldistortion.cpp llpolymesh.cpp diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 3d66809ed6..dab18c240d 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,16 +29,17 @@ #include "llavatarappearance.h" #include "llavatarappearancedefines.h" #include "llavatarjointmesh.h" +#include "lljointdata.h" #include "llstl.h" #include "lldir.h" #include "llpolymorph.h" #include "llpolymesh.h" #include "llpolyskeletaldistortion.h" -#include "llstl.h" #include "lltexglobalcolor.h" #include "llwearabledata.h" #include "boost/bind.hpp" #include "boost/tokenizer.hpp" +#include "v4math.h" using namespace LLAvatarAppearanceDefines; @@ -71,11 +72,13 @@ public: mChildren.clear(); } bool parseXml(LLXmlTreeNode* node); + glm::mat4 getJointMatrix(); private: std::string mName; std::string mSupport; std::string mAliases; + std::string mGroup; bool mIsJoint; LLVector3 mPos; LLVector3 mEnd; @@ -105,11 +108,17 @@ public: S32 getNumBones() const { return mNumBones; } S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; } +private: + typedef std::vector 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 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 &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 nodes from XML tree diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 99bc5eeef5..2748da9a1d 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -34,6 +34,7 @@ #include "lltexlayer.h" #include "llviewervisualparam.h" #include "llxmltree.h" +#include "v4math.h" class LLTexLayerSet; class LLTexGlobalColor; @@ -41,6 +42,7 @@ class LLTexGlobalColorInfo; class LLWearableData; class LLAvatarBoneInfo; class LLAvatarSkeletonInfo; +class LLJointData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLAvatarAppearance @@ -138,7 +140,7 @@ public: LLVector3 mHeadOffset{}; // current head position LLAvatarJoint* mRoot{ nullptr }; - typedef std::map> joint_map_t; + typedef std::map joint_map_t; joint_map_t mJointMap; typedef std::map joint_state_map_t; @@ -151,9 +153,11 @@ public: public: typedef std::vector avatar_joint_list_t; const avatar_joint_list_t& getSkeleton() { return mSkeleton; } - typedef std::map> joint_alias_map_t; + typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); - + typedef std::map joint_parent_map_t; // matrix plus parent + typedef std::map joint_rest_map_t; + void getJointMatricesAndHierarhy(std::vector &data) const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h new file mode 100644 index 0000000000..2fc26198ee --- /dev/null +++ b/indra/llappearance/lljointdata.h @@ -0,0 +1,66 @@ +/** + * @file lljointdata.h + * @brief LLJointData class for holding individual joint data and skeleton + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLJOINTDATA_H +#define LL_LLJOINTDATA_H + +#include "v4math.h" + +// may be just move LLAvatarBoneInfo +class LLJointData +{ +public: + std::string mName; + std::string mGroup; + glm::mat4 mJointMatrix; + glm::mat4 mRestMatrix; + glm::vec3 mScale; + LLVector3 mRotation; + + typedef std::vector bones_t; + bones_t mChildren; + + bool mIsJoint; // if not, collision_volume + enum SupportCategory + { + SUPPORT_BASE, + SUPPORT_EXTENDED + }; + SupportCategory mSupport; + void setSupport(const std::string& support) + { + if (support == "extended") + { + mSupport = SUPPORT_EXTENDED; + } + else + { + mSupport = SUPPORT_BASE; + } + } +}; + +#endif //LL_LLJOINTDATA_H diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp index 581e9f62d5..9dace08e6f 100644 --- a/indra/llcharacter/llbvhloader.cpp +++ b/indra/llcharacter/llbvhloader.cpp @@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str ) // LLBVHLoader() //----------------------------------------------------------------------------- -LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ) +LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ) { reset(); errorLine = 0; @@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error } // Recognize all names we've been told are legal. - for (const auto& [alias, joint] : joint_alias_map) + for (std::map::value_type& alias_pair : joint_alias_map) { - makeTranslation(alias, joint); + makeTranslation( alias_pair.first , alias_pair.second ); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index ae2e347ba1..de31f76dd3 100644 --- a/indra/llcharacter/llbvhloader.h +++ b/indra/llcharacter/llbvhloader.h @@ -227,7 +227,7 @@ class LLBVHLoader friend class LLKeyframeMotion; public: // Constructor - LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ); + LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ); ~LLBVHLoader(); /* diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index 8efcd9dd29..ecbcdb3bf5 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,11 +77,12 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint* LLCharacter::getJoint(std::string_view name) +LLJoint *LLCharacter::getJoint( const std::string &name ) { - LLJoint* joint = nullptr; + LLJoint* joint = NULL; - if (LLJoint* root = getRootJoint()) + LLJoint *root = getRootJoint(); + if (root) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 0046c5da7e..6143ec8cd1 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -76,7 +76,7 @@ public: // get the specified joint // default implementation does recursive search, // subclasses may optimize/cache results. - virtual LLJoint* getJoint(std::string_view name); + virtual LLJoint *getJoint( const std::string &name ); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 3d8e02cb16..e13f0bbd96 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,7 +12,6 @@ include(TinyGLTF) set(llprimitive_SOURCE_FILES lldaeloader.cpp - llgltfloader.cpp llgltfmaterial.cpp llmaterialid.cpp llmaterial.cpp @@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES set(llprimitive_HEADER_FILES CMakeLists.txt lldaeloader.h - llgltfloader.h llgltfmaterial.h llgltfmaterial_templates.h legacy_object_types.h diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..0759447902 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -880,7 +880,7 @@ LLDAELoader::LLDAELoader( void* opaque_userdata, JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, - std::map>& jointAliasMap, + std::map& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, bool preprocess) diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index dc20feca52..4531e03474 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -47,19 +47,19 @@ public: dae_model_map mModelsMap; LLDAELoader( - std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void* opaque_userdata, - JointTransformMap& jointTransformMap, - JointNameSet& jointsFromNodes, - std::map>& jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - bool preprocess); + std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void* opaque_userdata, + JointTransformMap& jointTransformMap, + JointNameSet& jointsFromNodes, + std::map& jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + bool preprocess); virtual ~LLDAELoader() ; virtual bool OpenFile(const std::string& filename); diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp deleted file mode 100644 index 724b1a88b2..0000000000 --- a/indra/llprimitive/llgltfloader.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/** - * @file LLGLTFLoader.cpp - * @brief LLGLTFLoader class implementation - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llgltfloader.h" - -// Import & define single-header gltf import/export lib -#define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_USE_CPP14 // default is C++ 11 - -// tinygltf by default loads image files using STB -#define STB_IMAGE_IMPLEMENTATION -// to use our own image loading: -// 1. replace this definition with TINYGLTF_NO_STB_IMAGE -// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) - -// tinygltf saves image files using STB -#define STB_IMAGE_WRITE_IMPLEMENTATION -// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) - -// Additionally, disable inclusion of STB header files entirely with -// TINYGLTF_NO_INCLUDE_STB_IMAGE -// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE -#include "tinygltf/tiny_gltf.h" - - -// TODO: includes inherited from dae loader. Validate / prune - -#include "llsdserialize.h" -#include "lljoint.h" - -#include "llmatrix4a.h" - -#include -#include - -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> & 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(in_image.image.size()); - image.height = in_image.height; - image.width = in_image.width; - image.data = in_image.image.data(); - - if (in_image.as_is) - { - LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; - return false; - } - - if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) - { - LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; - return false; - } - - mImages.push_back(image); - } - - mTextures.clear(); - for (auto in_tex : mGltfModel.textures) - { - gltf_texture tex; - tex.imageIdx = in_tex.source; - tex.samplerIdx = in_tex.sampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - for (tinygltf::Material gltf_material : mGltfModel.materials) - { - gltf_render_material mat; - mat.name = gltf_material.name; - - tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; - mat.hasPBR = true; // Always true, for now - - mat.baseColor.set(pbr.baseColorFactor.data()); - mat.hasBaseTex = pbr.baseColorTexture.index >= 0; - mat.baseColorTexIdx = pbr.baseColorTexture.index; - mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - - mat.metalness = pbr.metallicFactor; - mat.roughness = pbr.roughnessFactor; - mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; - mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; - mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - - mat.normalScale = gltf_material.normalTexture.scale; - mat.hasNormalTex = gltf_material.normalTexture.index >= 0; - mat.normalTexIdx = gltf_material.normalTexture.index; - mat.normalTexCoords = gltf_material.normalTexture.texCoord; - - mat.occlusionScale = gltf_material.occlusionTexture.strength; - mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; - mat.occlusionTexIdx = gltf_material.occlusionTexture.index; - mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - - mat.emissiveColor.set(gltf_material.emissiveFactor.data()); - mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; - mat.emissiveTexIdx = gltf_material.emissiveTexture.index; - mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - - mat.alphaMode = gltf_material.alphaMode; - mat.alphaMask = gltf_material.alphaCutoff; - - if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || - (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || - (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || - (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || - (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) - { - LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; - return false; - } - - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) - { - LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; - return false; - } - - mMaterials.push_back(mat); - } - - return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ - llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ - for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple - { - if (mat.hasBaseTex) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasMRTex) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasNormalTex) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } -} - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) -{ - //gltf_image& image = mImages[tex.imageIdx]; - //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - - // fill an LLSD container with image+sampler data - - // upload texture - - // retrieve UUID - - return LLUUID::null; -} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h deleted file mode 100644 index 848a07c1e4..0000000000 --- a/indra/llprimitive/llgltfloader.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @file LLGLTFLoader.h - * @brief LLGLTFLoader class definition - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGLTFLoader_H -#define LL_LLGLTFLoader_H - -#include "tinygltf/tiny_gltf.h" - -#include "llglheaders.h" -#include "llmodelloader.h" - -// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD - -class gltf_sampler -{ -public: - // Uses GL enums - S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR - S32 magFilter; // GL_NEAREST or GL_LINEAR - S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored. - std::string name; // optional, currently unused - // extensions and extras are sampler optional fields that we don't support - at least initially -}; - -class gltf_image -{ -public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) - U8* data; // ptr to decoded image data - U32 size; // in bytes, regardless of channel width - U32 width; - U32 height; - U32 numChannels; // range 1..4 - U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input - U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT -}; - -class gltf_texture -{ -public: - U32 imageIdx; - U32 samplerIdx; - LLUUID imageUuid = LLUUID::null; -}; - -class gltf_render_material -{ -public: - std::string name; - - // scalar values - LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present. - double metalness; - double roughness; - double normalScale; // scale applies only to X,Y components of normal - double occlusionScale; // strength multiplier for occlusion - LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) - std::string alphaMode; // "OPAQUE", "MASK" or "BLEND" - double alphaMask; // alpha cut-off - - // textures - U32 baseColorTexIdx; // always sRGB encoded - U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel - U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) - U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded - U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2) - - // texture coordinates - U32 baseColorTexCoords; - U32 metalRoughTexCoords; - U32 normalTexCoords; - U32 occlusionTexCoords; - U32 emissiveTexCoords; - - // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? - - bool hasPBR; - bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; - - // This field is populated after upload - LLUUID material_uuid = LLUUID::null; - -}; - -class gltf_mesh -{ -public: - std::string name; - - // TODO add mesh import DJH 2022-04 - -}; - -class LLGLTFLoader : public LLModelLoader -{ - public: - typedef std::map 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> &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 mMeshes; - std::vector mMaterials; - - std::vector mTextures; - std::vector mImages; - std::vector 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 &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 &models_out, U32 submodel_limit); - - static std::string getElementLabel(gltfElement *element); - static size_t getSuffixPosition(std::string label); - static std::string getLodlessLabel(gltfElement *element); - - static std::string preprocessGLTF(std::string filename); - */ - -}; -#endif // LL_LLGLTFLLOADER_H diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 4e3e49ec9f..3d31cfbb7f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces() } } +void LLModel::normalizeVolumeFacesAndWeights() +{ + if (!mVolumeFaces.empty()) + { + LLVector4a min, max; + + // For all of the volume faces + // in the model, loop over + // them and see what the extents + // of the volume along each axis. + min = mVolumeFaces[0].mExtents[0]; + max = mVolumeFaces[0].mExtents[1]; + + for (U32 i = 1; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + update_min_max(min, max, face.mExtents[0]); + update_min_max(min, max, face.mExtents[1]); + + if (face.mTexCoords) + { + LLVector2& min_tc = face.mTexCoordExtents[0]; + LLVector2& max_tc = face.mTexCoordExtents[1]; + + min_tc = face.mTexCoords[0]; + max_tc = face.mTexCoords[0]; + + for (S32 j = 1; j < face.mNumVertices; ++j) + { + update_min_max(min_tc, max_tc, face.mTexCoords[j]); + } + } + else + { + face.mTexCoordExtents[0].set(0, 0); + face.mTexCoordExtents[1].set(1, 1); + } + } + + // Now that we have the extents of the model + // we can compute the offset needed to center + // the model at the origin. + + // Compute center of the model + // and make it negative to get translation + // needed to center at origin. + LLVector4a trans; + trans.setAdd(min, max); + trans.mul(-0.5f); + + // Compute the total size along all + // axes of the model. + LLVector4a size; + size.setSub(max, min); + + // Prevent division by zero. + F32 x = size[0]; + F32 y = size[1]; + F32 z = size[2]; + F32 w = size[3]; + if (fabs(x) < F_APPROXIMATELY_ZERO) + { + x = 1.0; + } + if (fabs(y) < F_APPROXIMATELY_ZERO) + { + y = 1.0; + } + if (fabs(z) < F_APPROXIMATELY_ZERO) + { + z = 1.0; + } + size.set(x, y, z, w); + + // Compute scale as reciprocal of size + LLVector4a scale; + scale.splat(1.f); + scale.div(size); + + LLVector4a inv_scale(1.f); + inv_scale.div(scale); + + for (U32 i = 0; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + // We shrink the extents so + // that they fall within + // the unit cube. + // VFExtents change + face.mExtents[0].add(trans); + face.mExtents[0].mul(scale); + + face.mExtents[1].add(trans); + face.mExtents[1].mul(scale); + + // For all the positions, we scale + // the positions to fit within the unit cube. + LLVector4a* pos = (LLVector4a*)face.mPositions; + LLVector4a* norm = (LLVector4a*)face.mNormals; + LLVector4a* t = (LLVector4a*)face.mTangents; + + for (S32 j = 0; j < face.mNumVertices; ++j) + { + pos[j].add(trans); + pos[j].mul(scale); + if (norm && !norm[j].equals3(LLVector4a::getZero())) + { + norm[j].mul(inv_scale); + norm[j].normalize3(); + } + + if (t) + { + F32 w = t[j].getF32ptr()[3]; + t[j].mul(inv_scale); + t[j].normalize3(); + t[j].getF32ptr()[3] = w; + } + } + } + + weight_map old_weights = mSkinWeights; + mSkinWeights.clear(); + mPosition.clear(); + + for (auto& weights : old_weights) + { + LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); + pos.add(trans); + pos.mul(scale); + LLVector3 scaled_pos(pos.getF32ptr()); + mPosition.push_back(scaled_pos); + mSkinWeights[scaled_pos] = weights.second; + } + + // mNormalizedScale is the scale at which + // we would need to multiply the model + // by to get the original size of the + // model instead of the normalized size. + LLVector4a normalized_scale; + normalized_scale.splat(1.f); + normalized_scale.div(scale); + mNormalizedScale.set(normalized_scale.getF32ptr()); + mNormalizedTranslation.set(trans.getF32ptr()); + mNormalizedTranslation *= -1.f; + + // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) + for (auto& face : mVolumeFaces) + { + face.mNormalizedScale = mNormalizedScale; + } + } +} + void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; @@ -1561,11 +1717,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 +1744,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - if ( include_joints && mAlternateBindMatrix.size() > 0 ) + // optional 'joint overrides' + if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { + // If there is not enough to match mJointNames, + // either supply no alternate matrixes at all or supply + // replacements + const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mAlternateBindMatrix.size()) + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " + << mAlternateBindMatrix.size() << LL_ENDL; + } for (U32 j = 0; j < 4; j++) { for (U32 k = 0; k < 4; k++) { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; } } } diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index fe28926720..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -202,6 +202,7 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); + void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 7facd53a72..f97ac16a83 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -150,6 +150,8 @@ void LLModelLoader::run() { mWarningsArray.clear(); doLoadModel(); + // 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)); } @@ -466,6 +468,58 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector inv_bind; + std::map alt_bind; + for (LLPointer& 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 << "Inv Bind matrices.\n"; + for (auto& bind : inv_bind) + { + file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; + } + + file << "Alt Bind matrices.\n"; + for (auto& bind : alt_bind) + { + file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; + } +} //called in the main thread void LLModelLoader::loadTextures() diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index aece922111..7c808dcae0 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -36,7 +36,7 @@ class LLJoint; typedef std::map JointTransformMap; typedef std::map::iterator JointTransformMapIt; -typedef std::map> JointMap; +typedef std::map JointMap; typedef std::deque JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; @@ -111,6 +111,7 @@ public: bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) 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 > model_queue; @@ -119,9 +120,14 @@ 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; LLModelLoader( @@ -192,6 +198,7 @@ public: const LLSD logOut() const { return mWarningsArray; } void clearLog() { mWarningsArray.clear(); } + void dumpDebugData(); protected: diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 98151e2f4d..dee3d5ed59 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES gltf/accessor.cpp gltf/primitive.cpp gltf/animation.cpp + gltf/llgltfloader.cpp groupchatlistener.cpp llaccountingcostmanager.cpp llaisapi.cpp @@ -746,6 +747,7 @@ set(viewer_HEADER_FILES gltf/buffer_util.h gltf/primitive.h gltf/animation.h + gltf/llgltfloader.h llaccountingcost.h llaccountingcostmanager.h llaisapi.h diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..8c9f77686a 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,6 +50,10 @@ namespace LL "KHR_texture_transform" }; + static std::unordered_set ExtensionsIgnored = { + "KHR_materials_pbrSpecularGlossiness" + }; + Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) { if (alpha_mode == "OPAQUE") @@ -472,11 +476,14 @@ void Asset::update() for (auto& image : mImages) { - if (image.mTexture.notNull()) - { // HACK - force texture to be loaded full rez - // TODO: calculate actual vsize - image.mTexture->addTextureStats(2048.f * 2048.f); - image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + if (image.mLoadIntoTexturePipe) + { + if (image.mTexture.notNull()) + { // HACK - force texture to be loaded full rez + // TODO: calculate actual vsize + image.mTexture->addTextureStats(2048.f * 2048.f); + image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + } } } } @@ -486,18 +493,23 @@ void Asset::update() bool Asset::prep() { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; - // check required extensions and fail if not supported - bool unsupported = false; + // check required extensions for (auto& extension : mExtensionsRequired) { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { - LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - unsupported = true; + if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) + { + LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; + mUnsupportedExtensions.push_back(extension); + } + else + { + mIgnoredExtensions.push_back(extension); + } } } - - if (unsupported) + if (mUnsupportedExtensions.size() > 0) { return false; } @@ -513,7 +525,7 @@ bool Asset::prep() for (auto& image : mImages) { - if (!image.prep(*this)) + if (!image.prep(*this, mLoadIntoVRAM)) { return false; } @@ -542,102 +554,106 @@ bool Asset::prep() return false; } } + if (mLoadIntoVRAM) + { + // prepare vertex buffers - // prepare vertex buffers - - // material count is number of materials + 1 for default material - U32 mat_count = (U32) mMaterials.size() + 1; - - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); - } + // material count is number of materials + 1 for default material + U32 mat_count = (U32) mMaterials.size() + 1; - for (S32 double_sided = 0; double_sided < 2; ++double_sided) - { - RenderData& rd = mRenderData[double_sided]; - for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) - { - rd.mBatches[i].resize(mat_count); + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); } - // for each material - for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) + for (S32 double_sided = 0; double_sided < 2; ++double_sided) { - // for each shader variant - U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - - S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; - if (ds_mat != double_sided) + RenderData& rd = mRenderData[double_sided]; + for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) { - continue; + rd.mBatches[i].resize(mat_count); } - for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + // for each material + for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) { - U32 attribute_mask = 0; - // for each mesh - for (auto& mesh : mMeshes) + // for each shader variant + U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + + S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; + if (ds_mat != double_sided) { - // for each primitive - for (auto& primitive : mesh.mPrimitives) + continue; + } + + for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + { + U32 attribute_mask = 0; + // for each mesh + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + // for each primitive + for (auto& primitive : mesh.mPrimitives) { - // accumulate vertex and index counts - primitive.mVertexOffset = vertex_count[variant]; - primitive.mIndexOffset = index_count[variant]; + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + // accumulate vertex and index counts + primitive.mVertexOffset = vertex_count[variant]; + primitive.mIndexOffset = index_count[variant]; - vertex_count[variant] += primitive.getVertexCount(); - index_count[variant] += primitive.getIndexCount(); + vertex_count[variant] += primitive.getVertexCount(); + index_count[variant] += primitive.getIndexCount(); - // all primitives of a given variant and material should all have the same attribute mask - llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); - attribute_mask |= primitive.mAttributeMask; + // all primitives of a given variant and material should all have the same attribute mask + llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); + attribute_mask |= primitive.mAttributeMask; + } } } - } - // allocate vertex buffer and pack it - if (vertex_count[variant] > 0) - { - U32 mat_idx = mat_id + 1; - LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); + // allocate vertex buffer and pack it + if (vertex_count[variant] > 0) + { + U32 mat_idx = mat_id + 1; + #if 0 + LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); - rd.mBatches[variant][mat_idx].mVertexBuffer = vb; - vb->allocateBuffer(vertex_count[variant], - index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used - vb->setBuffer(); + rd.mBatches[variant][mat_idx].mVertexBuffer = vb; + vb->allocateBuffer(vertex_count[variant], + index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used + vb->setBuffer(); - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + for (auto& primitive : mesh.mPrimitives) { - primitive.upload(vb); + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + primitive.upload(vb); + } } } - } - vb->unmapBuffer(); + vb->unmapBuffer(); - vb->unbind(); + vb->unbind(); + #endif + } } } } - } - // sanity check that all primitives have a vertex buffer - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + // sanity check that all primitives have a vertex buffer + for (auto& mesh : mMeshes) { - llassert(primitive.mVertexBuffer.notNull()); + for (auto& primitive : mesh.mPrimitives) + { + //llassert(primitive.mVertexBuffer.notNull()); + } } } - + #if 0 // build render batches for (S32 node_id = 0; node_id < mNodes.size(); ++node_id) { @@ -664,6 +680,7 @@ bool Asset::prep() } } } + #endif return true; } @@ -672,9 +689,10 @@ 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); @@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename) } else if (ext == "glb") { - return loadBinary(str); + return loadBinary(str, mLoadIntoVRAM); } else { @@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename) return false; } -bool Asset::loadBinary(const std::string& data) +bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) { + mLoadIntoVRAM = loadIntoVRAM; // load from binary gltf const U8* ptr = (const U8*)data.data(); const U8* end = ptr + data.size(); @@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView) LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset) +bool Image::prep(Asset& asset, bool loadIntoVRAM) { + mLoadIntoTexturePipe = loadIntoVRAM; LLUUID id; if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) { // loaded from an asset, fetch the texture from the asset system @@ -951,12 +971,12 @@ bool Image::prep(Asset& asset) { // embedded in a buffer, load the texture from the buffer BufferView& bufferView = asset.mBufferViews[mBufferView]; Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - - U8* data = buffer.mData.data() + bufferView.mByteOffset; - - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - - if (mTexture.isNull()) + if (mLoadIntoTexturePipe) + { + U8* data = buffer.mData.data() + bufferView.mByteOffset; + mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); + } + else if (mTexture.isNull() && mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -971,12 +991,12 @@ bool Image::prep(Asset& asset) std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri; LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); - if (tracking_id.notNull()) + if (tracking_id.notNull() && mLoadIntoTexturePipe) { LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); } - else + else if (mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset) return false; } - if (!asset.mFilename.empty()) + if (!asset.mFilename.empty() && mLoadIntoTexturePipe) { // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW); mTexture->forceToSaveRawImage(0, F32_MAX); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..b9554d753c 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL void serialize(boost::json::object& dst) const; }; + // Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe. class Image { public: @@ -301,6 +302,8 @@ namespace LL S32 mBits = -1; S32 mPixelType = -1; + bool mLoadIntoTexturePipe = false; + LLPointer 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 mUnsupportedExtensions; + std::vector mIgnoredExtensions; // prepare for first time use bool prep(); @@ -428,12 +435,12 @@ namespace LL // accepts .gltf and .glb files // Any existing data will be lost // returns result of prep() on success - bool load(std::string_view filename); + bool load(std::string_view filename, bool loadIntoVRAM); // load .glb contents from memory // data - binary contents of .glb file // returns result of prep() on success - bool loadBinary(const std::string& data); + bool loadBinary(const std::string& data, bool loadIntoVRAM); const Asset& operator=(const Value& src); void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index ef9bba8128..be36c5e90b 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -158,6 +158,12 @@ namespace LL dst.load3(src); } + template<> + inline void copyVec3(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* src, LLColor4U& dst) { @@ -369,6 +375,11 @@ namespace LL template inline void copy(Asset& asset, Accessor& accessor, LLStrider& dst) { + if (accessor.mBufferView == INVALID_INDEX) + { + LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL; + return; + } const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView]; const Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp new file mode 100644 index 0000000000..7ae255e054 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.cpp @@ -0,0 +1,1710 @@ +/** + * @file LLGLTFLoader.cpp + * @brief LLGLTFLoader class implementation + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llgltfloader.h" +#include "meshoptimizer.h" +#include + +// Import & define single-header gltf import/export lib +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_USE_CPP14 // default is C++ 11 + +// tinygltf by default loads image files using STB +#define STB_IMAGE_IMPLEMENTATION +// to use our own image loading: +// 1. replace this definition with TINYGLTF_NO_STB_IMAGE +// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) + +// tinygltf saves image files using STB +#define STB_IMAGE_WRITE_IMPLEMENTATION +// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) + +// Additionally, disable inclusion of STB header files entirely with +// TINYGLTF_NO_INCLUDE_STB_IMAGE +// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "tinygltf/tiny_gltf.h" + + +// TODO: includes inherited from dae loader. Validate / prune + +#include "llsdserialize.h" +#include "lljoint.h" +#include "llbase64.h" +#include "lldir.h" + +#include "llmatrix4a.h" + +#include +#include +#include + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ + "_LOD0", + "_LOD1", + "_LOD2", + "", + "_PHYS", +}; + +// Premade rotation matrix, GLTF is Y-up while SL is Z-up +static const glm::mat4 coord_system_rotation( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + + +static const glm::mat4 coord_system_rotationxy( + 0.f, 1.f, 0.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + +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 &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + std::vector viewer_skeleton) //, + //bool preprocess) + : LLModelLoader( filename, + lod, + load_cb, + joint_lookup_func, + texture_load_func, + state_cb, + opaque_userdata, + jointTransformMap, + jointsFromNodes, + jointAliasMap, + maxJointsPerMesh ) + , mGeneratedModelLimit(modelLimit) + , mViewerJointData(viewer_skeleton) +{ +} + +LLGLTFLoader::~LLGLTFLoader() {} + +bool LLGLTFLoader::OpenFile(const std::string &filename) +{ + tinygltf::TinyGLTF loader; + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + + mGltfLoaded = mGLTFAsset.load(filename, false); + + if (!mGltfLoaded) + { + notifyUnsupportedExtension(true); + + for (const auto& buffer : mGLTFAsset.mBuffers) + { + if (buffer.mByteLength > 0 && buffer.mData.empty()) + { + bool bin_file = buffer.mUri.ends_with(".bin"); + LLSD args; + args["Message"] = bin_file ? "ParsingErrorMissingBufferBin" : "ParsingErrorMissingBuffer"; + args["BUFFER_NAME"] = buffer.mName; + args["BUFFER_URI"] = buffer.mUri; + mWarningsArray.append(args); + } + } + setLoadState(ERROR_PARSING); + return false; + } + + notifyUnsupportedExtension(false); + + bool meshesLoaded = parseMeshes(); + + setLoadState(DONE); + + return meshesLoaded; +} + +void LLGLTFLoader::addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params, + const material_map& mats) +{ + U32 volume_faces = pModel->getNumVolumeFaces(); + + // Side-steps all manner of issues when splitting models + // and matching lower LOD materials to base models + // + pModel->sortVolumeFacesByMaterialName(); + + int submodelID = 0; + + // remove all faces that definitely won't fit into one model and submodel limit + U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; + if (face_limit < volume_faces) + { + pModel->setNumVolumeFaces(face_limit); + } + + LLVolume::face_list_t remainder; + std::vector ready_models; + LLModel* current_model = pModel; + do + { + current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); + + volume_faces = static_cast(remainder.size()); + + // Don't add to scene yet because weights and materials aren't ready. + // Just save it + ready_models.push_back(current_model); + + // If we have left-over volume faces, create another model + // to absorb them. + if (volume_faces) + { + LLModel* next = new LLModel(volume_params, 0.f); + next->ClearFacesAndMaterials(); + next->mSubmodelID = ++submodelID; + next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + next->getVolumeFaces() = remainder; + next->mNormalizedScale = current_model->mNormalizedScale; + next->mNormalizedTranslation = current_model->mNormalizedTranslation; + next->mSkinWeights = current_model->mSkinWeights; + next->mPosition = current_model->mPosition; + + const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo; + LLMeshSkinInfo& next_skin_info = next->mSkinInfo; + next_skin_info.mJointNames = current_skin_info.mJointNames; + next_skin_info.mJointNums = current_skin_info.mJointNums; + next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix; + next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix; + next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix; + next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset; + + + if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES) + { + next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end()); + current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES); + } + + current_model = next; + } + + remainder.clear(); + + } while (volume_faces); + + for (auto model : ready_models) + { + // remove unused/redundant vertices + model->remapVolumeFaces(); + + mModelList.push_back(model); + + std::map materials; + for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) + { + material_map::const_iterator found = mats.find(model->mMaterialList[i]); + if (found != mats.end()) + { + materials[model->mMaterialList[i]] = found->second; + } + else + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } + } + mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + stretch_extents(model, transformation); + } +} + +bool LLGLTFLoader::parseMeshes() +{ + if (!mGltfLoaded) return false; + + // 2022-04 DJH Volume params from dae example. TODO understand PCODE + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + + mTransform.setIdentity(); + + for (auto& node : mGLTFAsset.mNodes) + { + // Make node matrix valid for correct transformation + node.makeMatrixValid(); + } + + if (mGLTFAsset.mSkins.size() > 0) + { + checkForXYrotation(mGLTFAsset.mSkins[0]); + populateJointGroups(); + } + + // Populate the joints from skins first. + // There's not many skins - and you can pretty easily iterate through the nodes from that. + for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) + { + populateJointsFromSkin(i); + } + + // Track how many times each mesh name has been used + std::map mesh_name_counts; + U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; + + // Check if we have scenes defined + if (!mGLTFAsset.mScenes.empty()) + { + // Process the default scene (or first scene if no default) + S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + + if (scene_idx < mGLTFAsset.mScenes.size()) + { + const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + + LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + + // Process all root nodes defined in the scene + for (S32 root_idx : scene.mNodes) + { + if (root_idx >= 0 && root_idx < static_cast(mGLTFAsset.mNodes.size())) + { + processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); + } + } + } + } + else + { + LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + + LLSD args; + args["Message"] = "NoScenesFound"; + mWarningsArray.append(args); + return false; + } + + // Check total model count against limit + U32 total_models = static_cast(mModelList.size()); + if (total_models > mGeneratedModelLimit) + { + LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models + << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; + + LLSD args; + args["Message"] = "TooManyMeshParts"; + args["PART_COUNT"] = static_cast(total_models); + args["LIMIT"] = static_cast(mGeneratedModelLimit); + mWarningsArray.append(args); + return false; + } + + return true; +} + +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) +{ + if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) + return; + + auto& node = mGLTFAsset.mNodes[node_idx]; + + LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") + << " - children: " << node.mChildren.size() << LL_ENDL; + + // Process this node's mesh if it has one + if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) + { + LLMatrix4 transformation; + material_map mats; + + LLModel* pModel = new LLModel(volume_params, 0.f); + auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(node.mMesh); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) + { + mTransform.setIdentity(); + transformation = mTransform; + + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (node.mSkin >= 0) + { + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: There appears to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; + } + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + << pModel->mLabel << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = pModel->mLabel; + mWarningsArray.append(args); + } + + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete pModel; + return; + } + } + else if (node.mMesh >= 0) + { + // Log invalid mesh reference + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName + << ") references invalid mesh " << node.mMesh + << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + + LLSD args; + args["Message"] = "InvalidMeshReference"; + args["NODE_NAME"] = node.mName; + args["MESH_INDEX"] = node.mMesh; + args["TOTAL_MESHES"] = static_cast(mGLTFAsset.mMeshes.size()); + mWarningsArray.append(args); + } + + // Process all children recursively + for (S32 child_idx : node.mChildren) + { + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); + } +} + +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const +{ + if (node_index < 0 || node_index >= static_cast(asset.mNodes.size())) + { + combined_transform = glm::mat4(1.0f); + return; + } + + const auto& node = asset.mNodes[node_index]; + + // Ensure the node's matrix is valid + const_cast(node).makeMatrixValid(); + + // Start with this node's transform + combined_transform = node.mMatrix; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < asset.mNodes.size(); ++i) + { + const auto& potential_parent = asset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent - recursively get its combined transform and apply it + glm::mat4 parent_transform; + computeCombinedNodeTransform(asset, static_cast(i), parent_transform); + combined_transform = parent_transform * combined_transform; + return; // Early exit - a node can only have one parent + } + } +} + +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +{ + const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; + if (legal_name.empty()) + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + return false; + } + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + + return true; +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +{ + // Set the requested label for the floater display and uploading + pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + + // Create unique model name + std::string base_name = mesh.mName; + if (base_name.empty()) + { + S32 mesh_index = static_cast(&mesh - &mGLTFAsset.mMeshes[0]); + base_name = "mesh_" + std::to_string(mesh_index); + } + + LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + + if (instance_count > 0) + { + pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); + } + else + { + pModel->mLabel = base_name; + } + + pModel->ClearFacesAndMaterials(); + + S32 skinIdx = nodeno.mSkin; + + // Compute final combined transform matrix (hierarchy + coordinate rotation) + S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); + glm::mat4 hierarchy_transform; + computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); + + // Combine transforms: coordinate rotation applied to hierarchy transform + glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + if (mApplyXYRotation) + { + final_transform = coord_system_rotationxy * final_transform; + } + + // Check if we have a negative scale (flipped coordinate system) + bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; + + // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) + const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); + + // Mark unsuported joints with '-1' so that they won't get added into weights + // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. + std::vector gltf_joint_index_use; + if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + + size_t jointCnt = gltf_skin.mJoints.size(); + gltf_joint_index_use.resize(jointCnt); + + for (size_t i = 0; i < jointCnt; ++i) + { + if (mJointNames[i].empty()) + { + // This might need to hold a substitute index + gltf_joint_index_use[i] = -1; // mark as unsupported + } + } + } + + for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) + { + const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; + + // So primitives already have all of the data we need for a given face in SL land. + // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call + // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 + LLVolumeFace face; + std::vector vertices; + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color + + // Process material if available + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + { + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.size()) + { + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + { + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Use URI as texture file name + if (!image.mUri.empty()) + { + // URI might be a remote URL or a local path + std::string filename = image.mUri; + + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + // Store the texture filename + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); + + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) + { + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + // Texture will be loaded later through the callback system + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; + } + } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) + { + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + } + } + } + } + } + } + + if (prim.getIndexCount() % 3 != 0) + { + LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx + << ": Invalid index count " << prim.getIndexCount() + << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; + + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } + + // Apply the global scale and center offset to all vertices + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + // Use pre-computed final_transform + glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + glm::vec4 transformed_pos = final_transform * pos; + + GLTFVertex vert; + vert.position = glm::vec3(transformed_pos); + + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); + } + else + { + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; + } + + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) + { + vert.weights = glm::vec4(prim.mWeights[i]); + + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); + LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; + if (accessorIdx >= 0) + { + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + componentType = accessor.mComponentType; + } + + // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. + // Detect and unpack accordingly. + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + vert.joints = glm::unpackUint4x16(prim.mJoints[i]); + } + else + { + vert.joints = glm::zero(); + vert.weights = glm::zero(); + } + } + vertices.push_back(vert); + } + + // Check for empty vertex array before processing + if (vertices.empty()) + { + LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL; + LLSD args; + args["Message"] = "EmptyVertexArray"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } + + std::vector faceVertices; + glm::vec3 min = glm::vec3(FLT_MAX); + glm::vec3 max = glm::vec3(-FLT_MAX); + + for (U32 i = 0; i < vertices.size(); i++) + { + LLVolumeFace::VertexData vert; + + // Update min/max bounds + if (i == 0) + { + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); + } + + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); + LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); + vert.setPosition(position); + vert.setNormal(normal); + vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); + faceVertices.push_back(vert); + + if (skinIdx >= 0) + { + // create list of weights that influence this vertex + LLModel::weight_list weight_list; + + // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) + // don't reindex them yet, more indexes will be removed + // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be + // 'empty' ones + if (gltf_joint_index_use[vertices[i].joints.x] >= 0 + && vertices[i].weights.x > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; + } + if (gltf_joint_index_use[vertices[i].joints.y] >= 0 + && vertices[i].weights.y > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; + } + if (gltf_joint_index_use[vertices[i].joints.z] >= 0 + && vertices[i].weights.z > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use[vertices[i].joints.z]++; + } + if (gltf_joint_index_use[vertices[i].joints.w] >= 0 + && vertices[i].weights.w > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use[vertices[i].joints.w]++; + } + + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + + std::vector wght; + F32 total = 0.f; + + for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) + { + // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. + wght.push_back(weight_list[j]); + total += weight_list[j].mWeight; + } + + if (total != 0.f) + { + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 j = 0; j < wght.size(); ++j) + { + wght[j].mWeight *= scale; + } + } + } + + if (wght.size() > 0) + { + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; + } + } + } + + // Create a unique material name for this primitive + std::string materialName; + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + materialName = material->mName; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + mats[materialName] = impMat; + + // Indices handling + if (faceVertices.size() >= USHRT_MAX) + { + // Will have to remap 32 bit indices into 16 bit indices + // For the sake of simplicity build vector of 32 bit indices first + std::vector indices_32; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices_32.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 1]); + indices_32.push_back(prim.mIndexArray[i + 2]); + } + } + + // remap 32 bit into multiple 16 bit ones + std::vector indices_16; + std::vector vertices_remap; // should it be a point map? + vertices_remap.resize(faceVertices.size(), -1); + std::vector face_verts; + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); + for (size_t idx = 0; idx < indices_32.size(); idx++) + { + size_t vert_index = indices_32[idx]; + if (vertices_remap[vert_index] == -1) + { + // First encounter, add it + size_t new_vert_idx = face_verts.size(); + vertices_remap[vert_index] = (S64)new_vert_idx; + face_verts.push_back(faceVertices[vert_index]); + vert_index = new_vert_idx; + + // Update min/max bounds + const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); + if (new_vert_idx == 0) + { + min.x = vec[0]; + min.y = vec[1]; + min.z = vec[2]; + max = min; + } + else + { + min.x = std::min(min.x, vec[0]); + min.y = std::min(min.y, vec[1]); + min.z = std::min(min.z, vec[2]); + max.x = std::max(max.x, vec[0]); + max.y = std::max(max.y, vec[1]); + max.z = std::max(max.z, vec[2]); + } + } + else + { + // already in vector, get position + vert_index = (size_t)vertices_remap[vert_index]; + } + indices_16.push_back((U16)vert_index); + + if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) + { + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); + + std::fill(vertices_remap.begin(), vertices_remap.end(), -1); + indices_16.clear(); + face_verts.clear(); + + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); + } + } + if (indices_16.size() > 0 && face_verts.size() > 0) + { + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); + } + } + else + { + // can use indices directly + std::vector indices; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 1]); + indices.push_back(prim.mIndexArray[i + 2]); + } + } + + face.fillFromLegacyData(faceVertices, indices); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); + } + } + + // Call normalizeVolumeFacesAndWeights to compute proper extents + pModel->normalizeVolumeFacesAndWeights(); + + // Fill joint names, bind matrices and remap weight indices + if (skinIdx >= 0) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + S32 valid_joints_count = mValidJointsCount[skinIdx]; + + S32 replacement_index = 0; + std::vector gltfindex_to_joitindex_map; + size_t jointCnt = gltf_skin.mJoints.size(); + gltfindex_to_joitindex_map.resize(jointCnt); + + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + std::map goup_use_count; + // Assume that 'Torso' group is always in use since that's what everything else is attached to + goup_use_count["Torso"] = 1; + // Note that Collisions and Extra groups are all over the place, might want to include them from the start + // or add individual when parents are added + + // Check which groups are in use + for (size_t i = 0; i < jointCnt; ++i) + { + std::string& joint_name = mJointNames[skinIdx][i]; + if (!joint_name.empty()) + { + if (gltf_joint_index_use[i] > 0) + { + const JointGroups &group = mJointGroups[joint_name]; + // Joint in use, increment it's groups + goup_use_count[group.mGroup]++; + goup_use_count[group.mParentGroup]++; + } + } + } + + // 1. add joints that are in use directly + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] <= 0) + { + // unsupported (-1) joint, drop it + // unused (0) joint, drop it + continue; + } + + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } + + // 2. add joints from groups that this model's joints belong to + // It's perfectly valid to have more joints than is in use + // Ex: sandals that make your legs digitigrade despite not skining to + // knees or the like. + // Todo: sort and add by usecount + for (size_t i = 0; i < jointCnt; ++i) + { + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] != 0) + { + // this step needs only joints that have zero uses + continue; + } + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + break; + } + const std::string& legal_name = mJointNames[skinIdx][i]; + std::string group_name = mJointGroups[legal_name].mGroup; + if (goup_use_count[group_name] > 0) + { + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } + } + } + else + { + // Less than 110, just add every valid joint + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } + + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } + } + + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel + << " Count: " << (S32)skin_info.mInvBindMatrix.size() + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + LLSD args; + args["Message"] = "ModelTooManyJoint"; + args["MODEL_NAME"] = pModel->mLabel; + args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } + } + + return true; +} + +void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) +{ + const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; + + LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL; + + if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size()) + { + LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + + S32 joint_count = (S32)skin.mJoints.size(); + S32 inverse_count = (S32)skin.mInverseBindMatricesData.size(); + if (mInverseBindMatrices.size() <= skin_idx) + { + mInverseBindMatrices.resize(skin_idx + 1); + mAlternateBindMatrices.resize(skin_idx + 1); + mJointNames.resize(skin_idx + 1); + mValidJointsCount.resize(skin_idx + 1, 0); + } + + // fill up joints related data + joints_data_map_t joints_data; + joints_name_to_node_map_t names_to_nodes; + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + JointNodeData& data = joints_data[joint]; + data.mNodeIdx = joint; + data.mJointListIdx = i; + data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin); + data.mGltfMatrix = jointNode.mMatrix; + data.mOverrideMatrix = glm::mat4(1.f); + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + data.mName = mJointMap[jointNode.mName]; + data.mIsValidViewerJoint = true; + mValidJointsCount[skin_idx]++; + } + else + { + data.mName = jointNode.mName; + data.mIsValidViewerJoint = false; + } + names_to_nodes[data.mName] = joint; + + for (S32 child : jointNode.mChildren) + { + JointNodeData& child_data = joints_data[child]; + child_data.mParentNodeIdx = joint; + child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint; + } + } + + if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" + << " Count: " << mValidJointsCount[skin_idx] + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + + LLSD args; + args["Message"] = "SkinJointsOverLimit"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Go over viewer joints and build overrides + glm::mat4 ident(1.0); + for (auto &viewer_data : mViewerJointData) + { + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); + } + + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + std::string legal_name(jointNode.mName); + bool legal_joint = false; + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + legal_joint = true; + mJointNames[skin_idx].push_back(legal_name); + } + else + { + mJointNames[skin_idx].emplace_back(); + } + + // Compute bind matrices + + if (!legal_joint) + { + // Add placeholder to not break index. + // Not going to be used by viewer, will be stripped from skin_info. + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + else if (inverse_count > i) + { + // Transalte existing bind matrix to viewer's skeleton + // todo: probably should be 'to viewer's overriden skeleton' + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); + glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); + glm::mat4 tranlated_original = skeleton_transform * rotated_original; + glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + else + { + // If bind matrices aren't present (they are optional in gltf), + // assume an identy matrix + // todo: find a model with this, might need to use rotated matrix + glm::mat4 inv_bind(1.0f); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); + inv_bind = glm::inverse(skeleton_transform * inv_bind); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + + // Compute Alternative matrices also known as overrides + LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix)); + + // Viewer seems to care only about translation part, + // but for parity with collada taking original value + LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); + newInverse.setTranslation(original_joint_transform.getTranslation()); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); + + if (legal_joint) + { + // Might be needed for uploader UI to correctly identify overriden joints + // but going to be incorrect if multiple skins are present + mJointList[legal_name] = newInverse; + mJointsFromNode.push_front(legal_name); + } + } +} + +void LLGLTFLoader::populateJointGroups() +{ + std::string parent; + for (auto& viewer_data : mViewerJointData) + { + buildJointGroup(viewer_data, parent); + } +} + + +S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const +{ + S32 source_joint_node = gltf_skin.mJoints[source_joint]; + S32 root_node = source_joint_node; + S32 found_node = source_joint_node; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + return i; + } + break; + } + } + } while (root_node != found_node); + + return -1; +} + +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_node = 0; + S32 found_node = source_joint_node; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + break; + } + } + } + } while (root_node != found_node); + + return root_node; +} + +S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_node = 0; + S32 found_node = 0; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + break; + } + } + } while (root_node != found_node); + + LL_INFOS("GLTF_DEBUG") << "mJointList name: "; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; + LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; + return root_node; +} + +S32 LLGLTFLoader::findParentNode(S32 node) const +{ + S32 size = (S32)mGLTFAsset.mNodes.size(); + for (S32 i = 0; i < size; i++) + { + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); + if (it != jointNode.mChildren.end()) + { + return i; + } + } + return -1; +} + +void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) +{ + JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; + jount_group_data.mGroup = viewer_data.mGroup; + jount_group_data.mParentGroup = parent_group; + + for (LLJointData& child_data : viewer_data.mChildren) + { + buildJointGroup(child_data, viewer_data.mGroup); + } +} + +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const +{ + glm::mat4 new_lefover(1.f); + glm::mat4 rest(1.f); + joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); + if (found_node != names_to_nodes.end()) + { + S32 gltf_node_idx = found_node->second; + JointNodeData& node = gltf_nodes[gltf_node_idx]; + node.mIsOverrideValid = true; + node.mViewerRestMatrix = viewer_data.mRestMatrix; + + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + + glm::mat4 translated_joint; + // Example: + // Viewer has pelvis->spine1->spine2->torso. + // gltf example model has pelvis->torso + // By doing glm::inverse(transalted_rest_spine2) * gltf_rest_torso + // We get what torso would have looked like if gltf had a spine2 + if (viewer_data.mIsJoint) + { + translated_joint = glm::inverse(parent_rest) * gltf_joint_rest_pose; + } + else + { + translated_joint = glm::inverse(parent_support_rest) * gltf_joint_rest_pose; + } + + glm::vec3 translation_override; + glm::vec3 skew; + glm::vec3 scale; + glm::vec4 perspective; + glm::quat rotation; + glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + + node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); + + glm::mat4 override_joint = node.mOverrideMatrix; + override_joint = glm::scale(override_joint, viewer_data.mScale); + + rest = parent_rest * override_joint; + node.mOverrideRestMatrix = rest; + } + else + { + // No override for this joint + rest = parent_rest * viewer_data.mJointMatrix; + } + + glm::mat4 support_rest(1.f); + if (viewer_data.mSupport == LLJointData::SUPPORT_BASE) + { + support_rest = rest; + } + else + { + support_rest = parent_support_rest; + } + + for (LLJointData& child_data : viewer_data.mChildren) + { + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, support_rest); + } +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + const auto& node = mGLTFAsset.mNodes[joint_node_index]; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) + { + const auto& potential_parent = mGLTFAsset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) + { + // parent is a joint - recursively combine transform + // assumes that matrix is already valid + return buildGltfRestMatrix(static_cast(i), gltf_skin) * node.mMatrix; + } + } + } + // Should we return armature or stop earlier? + return node.mMatrix; +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + auto& data = joint_data.at(joint_node_index); + + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; + } + // Should we return armature or stop earlier? + return data.mGltfMatrix; +} + +// This function computes the transformation matrix needed to convert from GLTF skeleton space +// to viewer skeleton space for a specific joint + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const +{ + const JointNodeData& node_data = joints_data_map.at(gltf_node_index); + if (!node_data.mIsOverrideValid) + { + // For now assume they are identical and return an identity (for ease of debuging) + return glm::mat4(1.0f); + } + + // Get the GLTF joint's rest pose (in GLTF coordinate system) + const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; + glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; + + LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + + LLMatrix4 transform(glm::value_ptr(rest_pose)); + + LL_CONT << transform << LL_ENDL; + + // Compute transformation from GLTF space to viewer space + // This assumes both skeletons are in rest pose initially + return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); +} + +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ + glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); + glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; + // Normally for shoulders it should be something close to + // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} + // rotated one will look like + // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} + // Todo: This is a cheap hack, + // figure out how rotation is supposed to work + return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ + // HACK: figure out model's rotation from shoulders' matrix. + // This is wrong on many levels: + // Too limited (only models that have shoulders), + // Will not work well with things that emulate 3 hands in some manner + // Only supports xy 90 degree rotation + // Todo: figure out how to find skeleton's orientation Correctly + // when model is rotated at a triangle level + constexpr char right_shoulder_str[] = "mShoulderRight"; + constexpr char left_shoulder_str[] = "mShoulderLeft"; + + S32 size = (S32)gltf_skin.mJoints.size(); + S32 joints_found = 0; + for (S32 i= 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + auto joint_node = mGLTFAsset.mNodes[joint]; + + // todo: we are doing this search thing everywhere, + // just pre-translate every joint + JointMap::iterator found = mJointMap.find(joint_node.mName); + if (found == mJointMap.end()) + { + // unsupported joint + continue; + } + if (found->second == right_shoulder_str || found->second == left_shoulder_str) + { + if (checkForXYrotation(gltf_skin, joint, i)) + { + joints_found++; + } + else + { + return; + } + } + } + + if (joints_found == 2) + { + // Both joints in a weird position/rotation, assume rotated model + mApplyXYRotation = true; + } +} + +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) +{ + if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) + return ""; + + S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; + if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + return ""; + + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Handle URI-based textures + if (!image.mUri.empty()) + { + return image.mUri; // Return URI directly + } + + // Handle embedded textures + if (image.mBufferView >= 0) + { + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; + if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) + { + const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) + { + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; + + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } + + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; + + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); + + LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; + return temp_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEXTURE_TYPE"] = texture_type; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); + } + } + } + } + } + + return ""; +} + +void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) +{ + std::vector extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions; + if (extensions.size() > 0) + { + LLSD args; + args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension"; + std::string del; + std::string ext; + for (auto& extension : extensions) + { + ext += del; + ext += extension; + del = ","; + } + args["EXT"] = ext; + mWarningsArray.append(args); + } +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h new file mode 100644 index 0000000000..048f7df7f5 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.h @@ -0,0 +1,203 @@ +/** + * @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 material_map; + typedef std::map joint_viewer_parent_map_t; + typedef std::map joint_viewer_rest_map_t; + typedef std::map 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 joints_data_map_t; + typedef std::map joints_name_to_node_map_t; + + 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 &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + std::vector 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; + bool mApplyXYRotation = false; + U32 mGeneratedModelLimit; + + // GLTF isn't aware of viewer's skeleton and uses it's own, + // so need to take viewer's joints and use them to + // recalculate iverse bind matrices + std::vector mViewerJointData; + + // vector of vectors because of a posibility of having more than one skin + typedef std::vector bind_matrices_t; + typedef std::vector > 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 + + // 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 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 mValidJointsCount; + +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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); + bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const; + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); + void populateJointsFromSkin(S32 skin_idx); + void populateJointGroups(); + void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); + S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; + S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; + S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint + S32 findParentNode(S32 node) const; + void 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); + + std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); + + void notifyUnsupportedExtension(bool unsupported); + + // 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 &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 &models_out, U32 submodel_limit); + + static std::string getElementLabel(gltfElement *element); + static size_t getSuffixPosition(std::string label); + static std::string getLodlessLabel(gltfElement *element); + + static std::string preprocessGLTF(std::string filename); + */ + +}; +#endif // LL_LLGLTFLLOADER_H diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 9faead9533..3cb5e9a0d7 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename) { std::shared_ptr asset = std::make_shared(); - if (asset->load(filename)) + if (asset->load(filename, true)) { gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions asset->updateTransforms(); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..41e954b7fa 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance; #define XML_FILTER L"XML files (*.xml)\0*.xml\0" #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0" #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0" #define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0" #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0" #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" @@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter) break; case FFLOAD_MODEL: mOFN.lpstrFilter = MODEL_FILTER \ + COLLADA_FILTER \ + MATERIAL_FILTER \ L"\0"; break; case FFLOAD_MATERIAL: @@ -671,6 +673,8 @@ std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadF case FFLOAD_HDRI: allowedv->push_back("exr"); case FFLOAD_MODEL: + allowedv->push_back("gltf"); + allowedv->push_back("glb"); case FFLOAD_COLLADA: allowedv->push_back("dae"); break; diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 5ee93be061..b94c31ec04 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks() getChild("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); } -std::map> LLFloaterBvhPreview::getJointAliases() +std::map LLFloaterBvhPreview::getJointAliases() { LLPointer av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); return av->getJointAliases(); @@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild() ELoadStatus load_status = E_ST_OK; S32 line_number = 0; - auto joint_alias_map = getJointAliases(); + std::map joint_alias_map = getJointAliases(); loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); std::string status = getString(STATUS[load_status]); diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index bb69ab65ef..ae64521492 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -108,7 +108,7 @@ public: S32 status, LLExtStat ext_status); private: void setAnimCallbacks() ; - std::map> getJointAliases(); + std::map getJointAliases(); protected: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 47471edb92..84b9cb18f8 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@ #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" +#include "llmaterialeditor.h" //static @@ -619,11 +620,9 @@ void LLFloaterModelPreview::onJointListSelection() LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); LLScrollListCtrl *joints_list = panel->getChild("joints_list"); LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); - LLScrollListCtrl *joints_scale = panel->getChild("scale_overrides_list"); LLTextBox *joint_pos_descr = panel->getChild("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; } @@ -1488,7 +1487,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) { // Populate table - std::map> joint_alias_map; + std::map joint_alias_map; mModelPreview->getJointAliases(joint_alias_map); S32 conflicts = 0; diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 28160177f6..378f5fdf91 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI( { combo_picker->mComboOptions->addSimpleElement(*iter); } - combo_picker->mComboOptions->selectFirstItem(); + // select 'Bulk Upload All' option + combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1); combo_picker->openFloater(LLSD(title)); combo_picker->setFocus(true); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 49c0006f66..fc0a3ec58f 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@ #include "llmodelloader.h" #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h" #include "llfloatermodelpreview.h" #include "llagent.h" @@ -40,6 +40,7 @@ #include "lldrawable.h" #include "llface.h" #include "lliconctrl.h" +#include "lljointdata.h" #include "llmatrix4a.h" #include "llmeshrepository.h" #include "llmeshoptimizer.h" @@ -780,7 +781,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - std::map> joint_alias_map; + std::map joint_alias_map; getJointAliases(joint_alias_map); LLHandle preview_handle = getHandle(); @@ -810,6 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable } else { + LLVOAvatar* av = getPreviewAvatar(); + std::vector viewer_skeleton; + av->getJointMatricesAndHierarhy(viewer_skeleton); mModelLoader = new LLGLTFLoader( filename, lod, @@ -822,7 +826,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mJointsFromNode, joint_alias_map, LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit")); + gSavedSettings.getU32("ImporterModelLimit"), + viewer_skeleton); } if (force_disable_slm) @@ -3090,25 +3095,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 supported_exts = { ".dae", ".gltf", ".glb" }; + std::string found_ext; + std::string::size_type ext_pos = std::string::npos; + + for (const auto& ext : supported_exts) { - lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); + std::string::size_type i = lod_filename_lower.rfind(ext); + if (i != std::string::npos) + { + ext_pos = i; + found_ext = ext; + break; + } } - if (gDirUtilp->fileExists(lod_filename)) + + if (ext_pos != std::string::npos) { - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp) + // Replace extension with LOD suffix + original extension + std::string lod_file_to_check = lod_filename; + lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext); + + if (gDirUtilp->fileExists(lod_file_to_check)) + { + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) + { + fmp->setCtrlLoadFromFile(next_lod); + } + loadModel(lod_file_to_check, next_lod); + } + else { - fmp->setCtrlLoadFromFile(next_lod); + lookupLODModelFiles(next_lod); } - loadModel(lod_filename, next_lod); } else { + // No recognized extension found, continue with next LOD lookupLODModelFiles(next_lod); } } diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index cee43f3cff..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette( initJointNums(const_cast(skin), avatar); + if (skin->mInvBindMatrix.size() < count ) + { + // faulty model? mInvBindMatrix.size() should have matched mJointNames.size() + return; + } + LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS]; for (S32 j = 0; j < count; ++j) @@ -354,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a { rig_info_tab[joint_num].setIsRiggedTo(true); - const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; + size_t bind_poses_size = skin->mBindPoseMatrix.size(); + const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity(); LLVector4a pos_joint_space; mat.affineTransform(pos, pos_joint_space); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index dcba891f9f..d9a3ec3004 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6306,13 +6306,13 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint* LLVOAvatar::getJoint(std::string_view name) +LLJoint *LLVOAvatar::getJoint( const std::string &name ) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = nullptr; + LLJoint* jointp = NULL; - if (iter == mJointMap.end() || iter->second == nullptr) + if (iter == mJointMap.end() || iter->second == NULL) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint* LLVOAvatar::getJoint(std::string_view name) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[std::string(name)] = jointp; + mJointMap[name] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 9eb8d3f880..ab27c5752d 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(std::string_view name); + virtual LLJoint* getJoint(const std::string &name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index ebba9ba291..90ff4067f2 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -697,17 +697,17 @@ void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) } // virtual -LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) +LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) { std::lock_guard lock(mJointMapMutex); - LLJoint* jointp = nullptr; + LLJoint *jointp = NULL; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[std::string(name)] = jointp; + mJointMap[name] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f7cd974ab0..f9bea41b1d 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -90,7 +90,7 @@ public: /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(std::string_view name); + /*virtual*/ LLJoint* getJoint(const std::string &name); /*virtual*/ void renderJoints(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 90223fcda8..f8543ba1aa 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -14,7 +14,7 @@ legacy_header_height="25"> - Error: Dae parsing issue - see log for details. + Error: Model parsing issue - see log for details. Warning: bind shape matrix is not in standard X-forward orientation. Error: Material of model is not a subset of reference model. Loading... @@ -45,6 +45,7 @@ Rigged to unrecognized joint name [NAME] Skinning disabled due to [COUNT] unknown joints Model [MODEL_NAME] loaded + Bind matrices count mismatch joints count Texture coordinates data is not complete. Found NaN while loading position data from DAE-Model, invalid model. @@ -61,6 +62,22 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. + + No scenes defined in GLTF file + Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) + Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) + Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Empty vertex array + Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT] + Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] + Model uses unsupported extension: [EXT], related material properties are ignored + Unable to load model, unsupported extension: [EXT] + Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] + Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE] + Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. + Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail + Buffer is either missing or empty [BUFFER_NAME]. + Buffer is either missing or empty. Check presence of [BUFFER_URI] file. + -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. fail + Date: Thu, 17 Jul 2025 15:03:43 -0400 Subject: Add support for frametime events per minute. --- indra/newview/llviewerstats.cpp | 21 +++++- indra/newview/llviewerstats.h | 4 ++ .../newview/skins/default/xui/en/floater_stats.xml | 77 +++++++++++++--------- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 2f2aab5b76..a4f308bbf9 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -229,7 +229,11 @@ LLTrace::SampleStatHandle 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 FRAMETIME_JITTER_EVENTS("frametimeevents", "Number of frametime events in the session. Applies when jitter exceeds 10% of the previous frame."); +LLTrace::SampleStatHandle 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 NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session."); LLTrace::EventStatHandle > 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 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 frameTimeSampleSeconds(gSavedSettings, "StatsFrametimeSampleSeconds", 5); @@ -356,6 +364,17 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff) mFrameTimesJitter.clear(); mLastFrameTimeSample = F64Seconds(0); } + + if (mTimeSinceLastEventSample >= 60) + { + mEventMinutes++; + // Calculate average events per minute + U64 frame_time_events_per_minute = (U64)mFrameJitterEvents / mEventMinutes; + sample(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE, frame_time_events_per_minute); + sample(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE, mFrameJitterEventsLastMinute); + mFrameJitterEventsLastMinute = 0; + mTimeSinceLastEventSample = F64Seconds(0); + } } mLastTimeDiff = time_diff; } diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 1d84ab9fbc..4ec7c6dfac 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -278,8 +278,12 @@ private: F64Seconds mTotalFrametimeJitter; U32 mFrameJitterEvents; + U32 mFrameJitterEventsLastMinute; + U32 mEventMinutes; + F64Seconds mTotalTime; F64Seconds mLastFrameTimeSample; // used for frame time stats + F64Seconds mTimeSinceLastEventSample; std::vector mFrameTimes; // used for frame time stats std::vector mFrameTimesJitter; // used for frame time jitter stats }; diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index ce5ab54d26..f2309eb817 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -54,38 +54,19 @@ label="jitter" decimal_digits="1" stat="frametimejitter"/> - - - - - - - - + + + + + + + + + + + + Date: Sat, 19 Jul 2025 10:12:45 +0300 Subject: #4320 MacOS crash handling --- indra/llcommon/workqueue.cpp | 24 ++++++++++++++++-------- indra/newview/llappviewer.cpp | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index c8ece616b2..ea4feec58c 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -182,14 +182,22 @@ void LL::WorkQueueBase::callWork(const Work& work) } catch (...) { - // Stash any other kind of uncaught exception to be rethrown by main thread. - LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase " - << getKey() << LL_ENDL; - - LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); - main_queue->post( - // Bind the current exception, rethrow it in main loop. - [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + if (getKey() != "mainloop") + { + // Stash any other kind of uncaught exception to be rethrown by main thread. + LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase " + << getKey() << LL_ENDL; + + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->post( + // Bind the current exception, rethrow it in main loop. + [exc = std::current_exception()]() { std::rethrow_exception(exc); }); + } + else + { + // let main loop crash + throw; + } } #endif // else LL_WINDOWS } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f4b2fdfdf7..35fdc18839 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5696,7 +5696,8 @@ void LLAppViewer::forceExceptionThreadCrash() void run() { - throw std::exception(); + const std::string exception_text = "This is a deliberate exception in a thread"; + throw std::runtime_error(exception_text); } }; -- cgit v1.2.3 From 8df303ed8506a0c4fe8965130e1ac9df75d156b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 21 Jul 2025 19:30:05 +0300 Subject: #4399 Crash at load_face_from_dom_triangles Since these offsets are used for idx[i+offset] where i starts from 0, they shouldn't be below 0 to not go out of bounds. --- indra/llprimitive/lldaeloader.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index a11f9b5ca2..bfcd84a43d 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -204,12 +204,15 @@ LLModel::EModelStatus load_face_from_dom_triangles( if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) + || (pos_source && pos_offset < 0) || (tc_source && tc_offset >= idx_stride) - || (norm_source && norm_offset >= idx_stride)) + || (tc_source && tc_offset < 0) + || (norm_source && norm_offset >= idx_stride) + || (norm_source && norm_offset < 0)) { // Looks like these offsets should fit inside idx_stride // Might be good idea to also check idx.getCount()%idx_stride != 0 - LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; + LL_WARNS() << "Invalid idx_stride " << idx_stride << ", pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; return LLModel::BAD_ELEMENT; } -- cgit v1.2.3 From d84897967e836cd2dadf8f609eb98946f500d5b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 22 Jul 2025 01:54:33 +0300 Subject: #4318 Warn or log when texture gets scaled down for material and model upload --- indra/llprimitive/llmodelloader.cpp | 4 +-- indra/llprimitive/llmodelloader.h | 4 +-- indra/newview/gltf/llgltfloader.cpp | 1 + indra/newview/llfloatermodelpreview.cpp | 8 ++--- indra/newview/llmaterialeditor.cpp | 36 ++++++++++++++++++++ indra/newview/llmodelpreview.cpp | 38 +++++++++++++++++----- indra/newview/llmodelpreview.h | 5 +++ .../skins/default/xui/en/floater_model_preview.xml | 1 + .../newview/skins/default/xui/en/notifications.xml | 14 ++++++++ 9 files changed, 94 insertions(+), 17 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index db4c4259f3..630c109d8f 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -123,7 +123,6 @@ LLModelLoader::LLModelLoader( , mLod(lod) , mTrySLM(false) , mFirstTransform(true) -, mNumOfFetchingTextures(0) , mLoadCallback(load_cb) , mJointLookupFunc(joint_lookup_func) , mTextureLoadFunc(texture_load_func) @@ -134,6 +133,7 @@ LLModelLoader::LLModelLoader( , mNoNormalize(false) , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) +, mTexturesNeedScaling(false) , mMaxJointsPerMesh(maxJointsPerMesh) , mGeneratedModelLimit(modelLimit) , mDebugMode(debugMode) @@ -669,7 +669,7 @@ void LLModelLoader::loadTextures() if(!material.mDiffuseMapFilename.empty()) { - mNumOfFetchingTextures += mTextureLoadFunc(material, mOpaqueData); + mTextureLoadFunc(material, mOpaqueData); } } } diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 06a17f006e..335d809386 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -109,6 +109,7 @@ 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. @@ -170,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) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3019a12446..9f6caf4198 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -611,6 +611,7 @@ LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_in LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; if (image.mTexture.notNull()) { + mTexturesNeedScaling |= image.mHeight > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT || image.mWidth > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; impMat.setDiffuseMap(image.mTexture->getID()); LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index bd33ab2dbe..5aef7f20b4 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1340,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) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 5b3ac53d51..3cab060357 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -2479,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()) { diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index ac697383e2..e819f4dec0 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -169,6 +169,8 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) + , mNumOfFetchingTextures(0) + , mTexturesNeedScaling(false) , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; @@ -559,10 +561,7 @@ void LLModelPreview::rebuildUploadData() texture->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle(getHandle()), &mCallbackTextureList, false); texture->forceToSaveRawImage(0, F32_MAX); texture->updateFetch(); - if (mModelLoader) - { - mModelLoader->mNumOfFetchingTextures++; - } + mNumOfFetchingTextures++; } } } @@ -997,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 @@ -2510,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; @@ -3216,6 +3217,7 @@ U32 LLModelPreview::loadTextures(LLImportMaterial& material, LLHandlesetLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle(handle), &preview->mCallbackTextureList, false); tex->forceToSaveRawImage(0, F32_MAX); material.setDiffuseMap(tex->getID()); // record tex ID + preview->mNumOfFetchingTextures++; return 1; } @@ -4060,6 +4062,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, @@ -4080,11 +4094,19 @@ void LLModelPreview::textureLoadedCallback( LLModelPreview* preview = static_cast(handle->get()); preview->refresh(); - if (final && preview->mModelLoader) + if (final) { - if (preview->mModelLoader->mNumOfFetchingTextures > 0) + if (src_vi + && (src_vi->getOriginalWidth() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT + || src_vi->getOriginalHeight() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT)) + { + preview->mTexturesNeedScaling = true; + } + + if (preview->mNumOfFetchingTextures > 0) { - preview->mModelLoader->mNumOfFetchingTextures--; + preview->mNumOfFetchingTextures--; + preview->warnTextureScaling(); } } } diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 0873263587..7b3b699b33 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -204,6 +204,7 @@ public: std::vector mLodsQuery; std::vector 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 handle); + void warnTextureScaling(); void lookupLODModelFiles(S32 lod); private: @@ -242,6 +244,9 @@ private: /// Not read unless mWarnOfUnmatchedPhyicsMeshes is true. LLPointer mDefaultPhysicsShapeP; + S32 mNumOfFetchingTextures; + bool mTexturesNeedScaling; + typedef enum { MESH_OPTIMIZER_FULL, diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index a86b9c7da2..f11d687840 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -39,6 +39,7 @@ Analyzing... Simplifying... TBD + One or more textures in this model were scaled to be within the allowed limits. Skinning disabled due to too many joints: [JOINTS], maximum: [MAX] diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 82e77119ab..a7e892f6f2 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -7135,6 +7135,20 @@ You don't have permission to view this notecard. fail + +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. + + confirm + + + Date: Tue, 22 Jul 2025 19:14:18 +0300 Subject: #4393 Handle unknown exceptions in uploader better Output is highly technical, but better than nothing --- indra/llprimitive/llmodelloader.cpp | 3 ++- indra/newview/gltf/llgltfloader.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 630c109d8f..0383659f62 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -33,6 +33,7 @@ #include "llmatrix4a.h" #include +#include std::list LLModelLoader::sActiveLoaderList; @@ -184,7 +185,7 @@ void LLModelLoader::run() LLSD args; args["Message"] = "UnknownException"; args["FILENAME"] = mFilename; - args["EXCEPTION"] = "Unknown exception"; + args["EXCEPTION"] = boost::current_exception_diagnostic_information(); mWarningsArray.append(args); setLoadState(ERROR_PARSING); } diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 9f6caf4198..950e98c96d 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -59,6 +59,7 @@ #include #include +#include #include static const std::string lod_suffix[LLModel::NUM_LODS] = @@ -154,7 +155,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) LLSD args; args["Message"] = "ParsingErrorException"; args["FILENAME"] = filename; - args["EXCEPTION"] = "Unknown exception"; + args["EXCEPTION"] = boost::current_exception_diagnostic_information(); mWarningsArray.append(args); setLoadState(ERROR_PARSING); return false; -- cgit v1.2.3 From e05b32a0305f57573236c12dc4c1fbeecf2878ed Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 23 Jul 2025 00:01:51 +0300 Subject: #4400 Model Import "use lod above" was not updating --- indra/newview/llfloatermodelpreview.cpp | 16 ++++++++++++---- indra/newview/llfloatermodelpreview.h | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 5aef7f20b4..332a98031f 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -165,7 +165,7 @@ bool LLFloaterModelPreview::postBuild() for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) { LLComboBox* lod_source_combo = getChild("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("lod_browse_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onBrowseLOD, this, lod)); @@ -766,7 +766,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[i]); if (lod_source_combo->getCurrentIndex() == LLModelPreview::USE_LOD_ABOVE) { - onLoDSourceCommit(i); + onLoDSourceCommit(i, false); } else { @@ -1760,7 +1760,7 @@ void LLFloaterModelPreview::toggleCalculateButton(bool visible) } } -void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) +void LLFloaterModelPreview::onLoDSourceCommit(S32 lod, bool refresh_ui) { mModelPreview->updateLodControls(lod); @@ -1769,9 +1769,17 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) if (index == LLModelPreview::MESH_OPTIMIZER_AUTO || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY || index == LLModelPreview::MESH_OPTIMIZER_PRECISE) - { //rebuild LoD to update triangle counts + { + // rebuild LoD to update triangle counts onLODParamCommit(lod, true); } + else if (refresh_ui && index == LLModelPreview::USE_LOD_ABOVE) + { + // Update mUploadData for updateStatusMessages + mModelPreview->rebuildUploadData(); + // Update UI with new triangle values + mModelPreview->updateStatusMessages(); + } } void LLFloaterModelPreview::resetDisplayOptions() diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 6adc084fe8..5d23dc8d8e 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -208,7 +208,7 @@ private: void onClickCalculateBtn(); void onJointListSelection(); - void onLoDSourceCommit(S32 lod); + void onLoDSourceCommit(S32 lod, bool refresh_ui); void modelUpdated(bool calculate_visible); -- cgit v1.2.3 From 5c746ae372a0f4ab249377091bc7cdca4ca474f7 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 25 Jul 2025 01:44:36 +0300 Subject: #4415 fix crash when cancelling large model process on MacOS --- indra/newview/llmodelpreview.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index e819f4dec0..d68fc9c02d 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3084,9 +3084,12 @@ void LLModelPreview::loadedCallback( S32 lod, void* opaque) { + if(LLModelPreview::sIgnoreLoadedCallback) + return; + LLModelPreview* pPreview = static_cast(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(); -- cgit v1.2.3 From 5d4e2c91b8c4f7e14ef3604df9f1050b05c00380 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 24 Jul 2025 19:55:34 +0300 Subject: #3969 Log time it takes to create inventory from cache --- indra/newview/llinventorymodel.cpp | 4 +++- indra/newview/llinventorypanel.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index cf1efd283a..f1ca69416b 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2682,6 +2682,7 @@ bool LLInventoryModel::loadSkeleton( LL_PROFILE_ZONE_SCOPED; LL_DEBUGS(LOG_INV) << "importing inventory skeleton for " << owner_id << LL_ENDL; + LLTimer timer; typedef std::set, InventoryIDPtrLess> cat_set_t; cat_set_t temp_cats; bool rv = true; @@ -2966,7 +2967,8 @@ bool LLInventoryModel::loadSkeleton( } LL_INFOS(LOG_INV) << "Successfully loaded " << cached_category_count - << " categories and " << cached_item_count << " items from cache." + << " categories and " << cached_item_count << " items from cache" + << " after " << timer.getElapsedTimeF32() << " seconds." << LL_ENDL; return rv; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 5a067ba454..443a0022fb 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -364,9 +364,28 @@ void LLInventoryPanel::initializeViewBuilding() if (mInventory->isInventoryUsable() && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) { + LLTimer timer; // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect const F64 max_time = 20.f; initializeViews(max_time); + + if (mViewsInitialized == VIEWS_INITIALIZED) + { + LL_INFOS("Inventory") + << "Fully initialized inventory panel " << getName() + << " with " << (S32)mItemMap.size() + << " views in " << timer.getElapsedTimeF32() << " seconds." + << LL_ENDL; + } + else + { + LL_INFOS("Inventory") + << "Partially initialized inventory panel " << getName() + << " with " << (S32)mItemMap.size() + << " views in " << timer.getElapsedTimeF32() + << " seconds. Pending known views: " << (S32)mBuildViewsQueue.size() + << LL_ENDL; + } } else { -- cgit v1.2.3 From 210f559efe81864c7581677882c867dc14b7f854 Mon Sep 17 00:00:00 2001 From: Darl Date: Fri, 25 Jul 2025 11:00:01 -0500 Subject: Media first click interact self check fix (#4406, #4426) --- indra/newview/lltoolpie.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index 618955c83b..dc27e11a0b 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1541,12 +1541,6 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo return false; } - // Own objects - if((FirstClickPref & MEDIA_FIRST_CLICK_OWN) && object->permYouOwner()) - { - LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_OWN" << LL_ENDL; - return true; - } // HUD attachments if((FirstClickPref & MEDIA_FIRST_CLICK_HUD) && object->isHUDAttachment()) { @@ -1569,6 +1563,13 @@ 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) { -- cgit v1.2.3 From c4204a9687a6f41e49879db1e24dcb53a372d31d Mon Sep 17 00:00:00 2001 From: Darl Date: Wed, 23 Jul 2025 19:07:56 -0500 Subject: Media first click interact large number value fix --- doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md | 6 +++--- indra/newview/app_settings/settings.xml | 2 +- indra/newview/lltoolpie.h | 4 ++-- indra/newview/skins/default/xui/en/panel_preferences_sound.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md index 690146265c..47e90f680d 100644 --- a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md +++ b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md @@ -144,16 +144,16 @@ Note: This requires the avatar that is performing the tests to physically be in ### Case 7 (MEDIA_FIRST_CLICK_ANY) (optional) -Ensure that debug setting `MediaFirstClickInteract` is set to `31` +Ensure that debug setting `MediaFirstClickInteract` is set to `32767` Repeat test cases 1-6. 1. Test case 1 should fail 2. Test cases 2-6 should pass -### Case 8 (MEDIA_FIRST_CLICK_ALL) (optional) +### Case 8 (MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG) (optional) -Ensure that debug setting `MediaFirstClickInteract` is set to `1073741824` +Ensure that debug setting `MediaFirstClickInteract` is set to `65535` Repeat test cases 1-6, there is no pass/fail for this run. diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index ad39008b87..c4f2f679e3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -16237,7 +16237,7 @@ MediaFirstClickInteract Comment - 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. + 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=65535. For complete details see lltoolpie.h enum MediaFirstClickTypes. Persist 1 Type diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index d9daad9515..9914da905e 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -99,10 +99,10 @@ private: MEDIA_FIRST_CLICK_LAND = 1 << 4, // 0b00010000 (16) // Covers any object with PRIM_MEDIA_FIRST_CLICK_INTERACT (combines all previous flags) - MEDIA_FIRST_CLICK_ANY = ~(3<<30), // 0b00111111111111111111111111111111 + MEDIA_FIRST_CLICK_ANY = (1 << 15) - 1, // 0b0111111111111111 (32767) // Covers all media regardless of other rules or PRIM_MEDIA_FIRST_CLICK_INTERACT - MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG = 1 << 30 // 0b01000000000000000000000000000000 (1073741824) + MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG = 1 << 15 // 0b10000000000000000 (65535) }; bool shouldAllowFirstMediaInteraction(const LLPickInfo& info, bool moap_flag); bool handleMediaClick(const LLPickInfo& info); diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml index de6132aec6..52413abe74 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml @@ -422,11 +422,11 @@ + value="32767"/> + value="65535"/> Date: Wed, 23 Jul 2025 21:06:34 -0500 Subject: Fix incorrectly calculated number in comment and where it was referenced --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/lltoolpie.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c4f2f679e3..b0b7430c97 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -16237,7 +16237,7 @@ MediaFirstClickInteract Comment - 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=65535. For complete details see lltoolpie.h enum MediaFirstClickTypes. + 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. Persist 1 Type diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index 9914da905e..ec54e0207d 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -102,7 +102,7 @@ private: 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 << 15 // 0b10000000000000000 (65535) + MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG = 1 << 15 // 0b10000000000000000 (32768) }; bool shouldAllowFirstMediaInteraction(const LLPickInfo& info, bool moap_flag); bool handleMediaClick(const LLPickInfo& info); -- cgit v1.2.3 From 31f8ebf7351598b1d9851bd26e6097e00547ee53 Mon Sep 17 00:00:00 2001 From: Darl Date: Thu, 24 Jul 2025 21:18:05 -0500 Subject: Update test plan to match current functionality per suggested/requested change in #4406 --- doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md index 47e90f680d..afb3d5b337 100644 --- a/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md +++ b/doc/testplans/PRIM_MEDIA_FIRST_CLICK_INTERACT.md @@ -103,13 +103,11 @@ Perform the testing procedure on both sets of cubes. Ensure that debug setting `MediaFirstClickInteract` is set to `4` -This test case requires two pairs of cubes, and the second pair must be deeded or set to a group that your testing account is a member of, but does not have set as active at the beginning of the test. As long as the second set of cubes is set to a group that your primary test account is a member of, the avatar that owns them does not matter. +This test case requires two cubes, and the second cube must be deeded or set to a group that your testing account is a member of. As long as the second set of cubes is set to a group that your test account is a member of, the avatar that owns them does not matter. -1. Perform the testing procedure on both sets of cubes. -2. Activate the group that the second set of cubes is set / deeded to -3. Perform the testing procedure on both sets of cubes once more. +Perform the testing procedure on both sets of cubes. -**Expected observations:** Both cubes owned by your primary testing account will not react to mouse cursor hover events and clicks without needing a focus click. Cube A set to group will react to mouse cursor hover events and clicks without needing a focus click, but Cube B will not. +**Expected observations:** The cube owned by your primary account will not react to mouse cursor hover events and clicks without needing a focus click. The cube set to group will react to mouse cursor hover events and clicks without needing a focus click. ### Case 5 (MEDIA_FIRST_CLICK_FRIEND) -- cgit v1.2.3 From 4799f2e33865cf11b692dd95c594ae4d128d5866 Mon Sep 17 00:00:00 2001 From: Darl Date: Wed, 23 Jul 2025 19:03:15 -0500 Subject: Media first click interact group check fix --- indra/newview/lltoolpie.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index dc27e11a0b..48c8470381 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1580,11 +1580,9 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo // 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; } } -- cgit v1.2.3 From a588addc68e43c0558c5b18560f37fff350dc2f1 Mon Sep 17 00:00:00 2001 From: Darl Date: Wed, 23 Jul 2025 19:07:00 -0500 Subject: Media first click interact friend check fix --- indra/newview/lltoolpie.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index 48c8470381..0fd9faab35 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1573,8 +1573,11 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo // 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 -- cgit v1.2.3 From 288bce100d1239dd98e1d0730c905cc5cbe6ad1b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 28 Jul 2025 19:58:57 +0300 Subject: #4455 Division by zero in updateFrameStats --- indra/newview/llviewerstats.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 4ec7c6dfac..63fb7d4a17 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -277,9 +277,9 @@ private: F64Seconds mLastTimeDiff; // used for time stat updates F64Seconds mTotalFrametimeJitter; - U32 mFrameJitterEvents; - U32 mFrameJitterEventsLastMinute; - U32 mEventMinutes; + U32 mFrameJitterEvents = 0; + U32 mFrameJitterEventsLastMinute = 0; + U32 mEventMinutes = 0; F64Seconds mTotalTime; F64Seconds mLastFrameTimeSample; // used for frame time stats -- cgit v1.2.3 From afcc64cb073eef83f07313bb5f3a0b364d4b6933 Mon Sep 17 00:00:00 2001 From: Erik Kundiman Date: Thu, 31 Jul 2025 23:54:39 +0800 Subject: Rich Presence support using Discord Social SDK (#4457) * Rich Presence support using Discord Social SDK Download DiscordSocialSdk-1.4.9649.zip from https://discord.com/developers/applications/1394782217405862001/social-sdk/downloads Add -DUSE_DISCORD:BOOL=ON to your cmake line. The Discord app needs to be set to be a public client in the OAuth2 tab. All Discord-related code are contained within one file, llstartup.cpp, and other classes access it through some opaque layer, static functions, otherwise we'd get these "duplicate symbol" linking errors. * Move Discord-related code to llappviewer.cpp The doFrame is the one called over and over again, so running the Discord callbacks from there shouldn't have one extra function overhead, while running the Discord initialisation is only once so it's much more okay to have the extra function overhead there. * panel_preferences_privacy tabs Add tab and checkboxes for discord social SDK integration options to panel_preferences_privacy.xml * Shorten Discord-related local variable names * Connect to Discord now through privacy tab Now the access token is saved the way passwords are saved, but without a username, so we can have some persistence without having to implement an OAuth2 backend server cause we would have to store those tokens there anyway still, and it's just simpler to not go that way. Discord Social SDK doesn't have a helper for sending code to a custom server anyway, that we would have to have some asynchronous HTTP requestor ready. Show location check button gets enabled only when Discord integration is enabled, though it's not functioning yet. * Location for Discord Rich Presence Activity State I was going to use LLAgentUI::buildLocationString but there's no location format that shows only region and coords without having to have the parcel name empty, so I copied buildLocationString implementation in the case of LOCATION_FORMAT_NO_MATURITY but when the parcel name is empty. I had to make updateDiscordActivity check agent's ID and the existence of agent avatar pointer first before trying to set Activity Details or State, cause I like the "Show location" button be checkable not only after online when both the ID & pointer will have existed. I think this way is simpler than programmatically enabling the "Show location" button after the user is logged in. I put a trigger to Activity update somewhere after the user is logged in for now, not yet after a TP. The elapsed time gets reset whenever Activity is updated for now, but I'll try to make elapsed time extended instead. No Party for now, because I couldn't find a way to make a Party shown without showing its CurrentSize (I could still get away not showing its MaxSize by setting it to 0), so the State (location) is shown above the elapsed time, not on the right of it. I'll try to figure out to get some representative numbers for its CurrentSize & MaxSize next. Also no privacy on hiding the username for now, until the UI is ready. * Update Rich Presence location on region change I had to find a spot in source code where it doesn't cause a crash (it did in LLAgent::setRegion), but I'm not removing the one in llstartup.cpp because on login, the one in llviewermessage.cpp gets only the placeholder coords (10, 10, 10). * Show display name too on Discord Rich Presence Avatar name cache can be used right away upon login now after I moved the update call to the end of PRECACHE section in llstartup. * Show Discord Rich Presence Activity Party By setting CurrentSize to the number of people within chat radius, and MaxSize to the number of people within near range. * Call updateDiscordActivity too in Discord init so when the user enables the integration after being logged in, the init can show the name and location right away. * Discord Rich Presence: Hide name & connect to llappviewer.cpp Add option to show/hide avatar name in privacy panel & connect rich presense directly to llappviewer.cpp * Discord time elapsed not reset on region change Time elapses right after viewer launch even before login. Plus parameter name change in header to make it the same as in implementation. * Cache bool setting retrievals in updateDiscordActivity As suggested by Andrey Kleschev. getBOOL and getF32 are expensive, so using `static LLCachedControl<>` is the way to do it in llappviewer.cpp. * Check Discord creds existence before getting token as suggested by Andrey Kleshchev, anticipating external factors such as user moving settings from another PC. * Tracy visibility for looped Discord function calls As suggested by Andrey Kleshchev. They likely can get pricey so they need to be visible in the profiler. * Discord-related error handling/logging plus delay saving Discord credentials to only after the access token is successfully updated on Discord, and try to disconnect from Discord when the integration gets disabled regardless whether there are credentials to delete or not and whether there's an access token to revoke or not. * Use getAvatars already called for Discord Party numbers so we don't have to make any extra getAvatars calls just for this, as it's pricy in crowds, and we'll just be piggybacking `updateSpeakerList` and `updateNearbyList`. * Assemble Discord Activity Details only once by saving it to a static global string for reuse. * Remove updateDiscordActivity call in startup loop The State field (region & coords) is updated well enough without it now. * Rename handleDiscordSocial to toggleDiscordIntegration * Update Discord Activity only when integration is enabled No need to check setting for the status change callback one, because getting there would need to be connected to Discord first, which in turn needs the integration to be enabled first. --------- Co-authored-by: Secret Foxtail --- indra/cmake/CMakeLists.txt | 1 + indra/cmake/Discord.cmake | 9 + indra/newview/CMakeLists.txt | 7 + indra/newview/app_settings/settings.xml | 33 ++++ indra/newview/llappviewer.cpp | 194 +++++++++++++++++++++ indra/newview/llappviewer.h | 8 + indra/newview/llfloaterpreference.cpp | 5 + indra/newview/llpanelpeople.cpp | 4 + indra/newview/llspeakers.cpp | 4 + indra/newview/llstartup.cpp | 4 + indra/newview/llviewermessage.cpp | 5 + .../default/xui/en/panel_preferences_privacy.xml | 69 +++++++- 12 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 indra/cmake/Discord.cmake diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index cc217b0563..0a00ccbb5b 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -20,6 +20,7 @@ set(cmake_SOURCE_FILES Copy3rdPartyLibs.cmake DBusGlib.cmake DeploySharedLibs.cmake + Discord.cmake DragDrop.cmake EXPAT.cmake FindAutobuild.cmake diff --git a/indra/cmake/Discord.cmake b/indra/cmake/Discord.cmake new file mode 100644 index 0000000000..2193023a06 --- /dev/null +++ b/indra/cmake/Discord.cmake @@ -0,0 +1,9 @@ +include(Prebuilt) + +add_library(ll::discord INTERFACE IMPORTED) +target_compile_definitions(ll::discord INTERFACE LL_DISCORD=1) + +use_prebuilt_binary(discord) + +target_include_directories(ll::discord SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include) +target_link_libraries(ll::discord INTERFACE discord_partner_sdk) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1672efcf33..7cc26c7716 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -15,6 +15,9 @@ include(CMakeCopyIfDifferent) include(CubemapToEquirectangularJS) include(DBusGlib) include(DragDrop) +if (USE_DISCORD) + include(Discord) +endif () include(EXPAT) include(Hunspell) include(JPEGEncoderBasic) @@ -1995,6 +1998,10 @@ target_link_libraries(${VIEWER_BINARY_NAME} ll::openxr ) +if (USE_DISCORD) + target_link_libraries(${VIEWER_BINARY_NAME} ll::discord ) +endif () + if( TARGET ll::intel_memops ) target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops ) endif() diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index fc878653e4..b9e3ebf502 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1139,6 +1139,39 @@ Value 1 + EnableDiscord + + Comment + When set, connect to Discord to enable Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + + ShowDiscordActivityDetails + + Comment + When set, show avatar name on Discord Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + + ShowDiscordActivityState + + Comment + When set, show location on Discord Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + EnableDiskCacheDebugInfo Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index a896b210f4..a5535d4bcc 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -268,6 +268,16 @@ using namespace LL; #include "glib.h" #endif // (LL_LINUX) && LL_GTK +#ifdef LL_DISCORD +#define DISCORDPP_IMPLEMENTATION +#include +static std::shared_ptr gDiscordClient; +static uint64_t gDiscordTimestampsStart; +static std::string gDiscordActivityDetails; +static int32_t gDiscordPartyCurrentSize; +static int32_t gDiscordPartyMaxSize; +#endif + static LLAppViewerListener sAppViewerListener(LLAppViewer::instance); ////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor @@ -1319,6 +1329,13 @@ bool LLAppViewer::frame() bool LLAppViewer::doFrame() { +#ifdef LL_DISCORD + { + LL_PROFILE_ZONE_NAMED("discord_callbacks"); + discordpp::RunCallbacks(); + } +#endif + LL_RECORD_BLOCK_TIME(FTM_FRAME); { // and now adjust the visuals from previous frame. @@ -5862,3 +5879,180 @@ void LLAppViewer::metricsSend(bool enable_reporting) gViewerAssetStats->restart(); } +#ifdef LL_DISCORD + +void LLAppViewer::initDiscordSocial() +{ + gDiscordPartyCurrentSize = 1; + gDiscordPartyMaxSize = 0; + gDiscordTimestampsStart = time(nullptr); + gDiscordClient = std::make_shared(); + gDiscordClient->SetStatusChangedCallback([](discordpp::Client::Status status, discordpp::Client::Error, int32_t) { + if (status == discordpp::Client::Status::Ready) + { + updateDiscordActivity(); + } + }); + if (gSavedSettings.getBOOL("EnableDiscord")) + { + auto credential = gSecAPIHandler->loadCredential("Discord"); + if (credential.notNull()) + { + gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) { + if (result.Successful()) + gDiscordClient->Connect(); + else + LL_WARNS("Discord") << result.Error() << LL_ENDL; + }); + } + else + { + LL_WARNS("Discord") << "Integration was enabled, but no credentials. Disabling integration." << LL_ENDL; + gSavedSettings.setBOOL("EnableDiscord", false); + } + } +} + +void LLAppViewer::toggleDiscordIntegration(const LLSD& value) +{ + static const uint64_t APPLICATION_ID = 1394782217405862001; + if (value.asBoolean()) + { + discordpp::AuthorizationArgs args{}; + args.SetClientId(APPLICATION_ID); + args.SetScopes(discordpp::Client::GetDefaultPresenceScopes()); + auto codeVerifier = gDiscordClient->CreateAuthorizationCodeVerifier(); + args.SetCodeChallenge(codeVerifier.Challenge()); + gDiscordClient->Authorize(args, [codeVerifier](auto result, auto code, auto redirectUri) { + if (result.Successful()) + { + gDiscordClient->GetToken(APPLICATION_ID, code, codeVerifier.Verifier(), redirectUri, [](discordpp::ClientResult result, std::string accessToken, std::string, discordpp::AuthorizationTokenType, int32_t, std::string) { + if (result.Successful()) + { + gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, accessToken, [accessToken](discordpp::ClientResult result) { + if (result.Successful()) + { + LLSD authenticator = LLSD::emptyMap(); + authenticator["token"] = accessToken; + gSecAPIHandler->saveCredential(gSecAPIHandler->createCredential("Discord", LLSD::emptyMap(), authenticator), true); + gDiscordClient->Connect(); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + } + }); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + } + }); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + gSavedSettings.setBOOL("EnableDiscord", false); + } + }); + } + else + { + gDiscordClient->Disconnect(); + auto credential = gSecAPIHandler->loadCredential("Discord"); + if (credential.notNull()) + { + gDiscordClient->RevokeToken(APPLICATION_ID, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) { + if (result.Successful()) + LL_INFOS("Discord") << "Access token successfully revoked." << LL_ENDL; + else + LL_WARNS("Discord") << "No access token to revoke." << LL_ENDL; + }); + auto cred = new LLCredential("Discord"); + gSecAPIHandler->deleteCredential(cred); + } + else + { + LL_WARNS("Discord") << "Credentials are already nonexistent." << LL_ENDL; + } + } +} + +void LLAppViewer::updateDiscordActivity() +{ + LL_PROFILE_ZONE_SCOPED; + discordpp::Activity activity; + activity.SetType(discordpp::ActivityTypes::Playing); + discordpp::ActivityTimestamps timestamps; + timestamps.SetStart(gDiscordTimestampsStart); + activity.SetTimestamps(timestamps); + + if (gAgent.getID() == LLUUID::null) + { + gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {}); + return; + } + + static LLCachedControl show_details(gSavedSettings, "ShowDiscordActivityDetails", false); + if (show_details) + { + if (gDiscordActivityDetails.empty()) + { + LLAvatarName av_name; + LLAvatarNameCache::get(gAgent.getID(), &av_name); + gDiscordActivityDetails = av_name.getUserName(); + auto displayName = av_name.getDisplayName(); + if (gDiscordActivityDetails != displayName) + gDiscordActivityDetails = displayName + " (" + gDiscordActivityDetails + ")"; + } + activity.SetDetails(gDiscordActivityDetails); + } + + static LLCachedControl show_state(gSavedSettings, "ShowDiscordActivityState", false); + if (show_state) + { + auto agent_pos_region = gAgent.getPositionAgent(); + S32 pos_x = S32(agent_pos_region.mV[VX] + 0.5f); + S32 pos_y = S32(agent_pos_region.mV[VY] + 0.5f); + S32 pos_z = S32(agent_pos_region.mV[VZ] + 0.5f); + F32 velocity_mag_sq = gAgent.getVelocity().magVecSquared(); + const F32 FLY_CUTOFF = 6.f; + const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF; + const F32 WALK_CUTOFF = 1.5f; + const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF; + if (velocity_mag_sq > FLY_CUTOFF_SQ) + { + pos_x -= pos_x % 4; + pos_y -= pos_y % 4; + } + else if (velocity_mag_sq > WALK_CUTOFF_SQ) + { + pos_x -= pos_x % 2; + pos_y -= pos_y % 2; + } + auto location = llformat("%s (%d, %d, %d)", gAgent.getRegion()->getName().c_str(), pos_x, pos_y, pos_z); + activity.SetState(location); + + discordpp::ActivityParty party; + party.SetId(location); + party.SetCurrentSize(gDiscordPartyCurrentSize); + party.SetMaxSize(gDiscordPartyMaxSize); + activity.SetParty(party); + } + + gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {}); +} + +void LLAppViewer::updateDiscordPartyCurrentSize(int32_t size) +{ + gDiscordPartyCurrentSize = size; + updateDiscordActivity(); +} + +void LLAppViewer::updateDiscordPartyMaxSize(int32_t size) +{ + gDiscordPartyMaxSize = size; + updateDiscordActivity(); +} + +#endif diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b4b8e5bac3..0424bdd34f 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -250,6 +250,14 @@ public: // Note: mQuitRequested can be aborted by user. void outOfMemorySoftQuit(); +#ifdef LL_DISCORD + static void initDiscordSocial(); + static void toggleDiscordIntegration(const LLSD& value); + static void updateDiscordActivity(); + static void updateDiscordPartyCurrentSize(int32_t size); + static void updateDiscordPartyMaxSize(int32_t size); +#endif + protected: virtual bool initWindow(); // Initialize the viewer's window. virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index fdac390e8a..2a6360cef8 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -366,6 +366,11 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // Hook up for filtering +#ifdef LL_DISCORD + gSavedSettings.getControl("EnableDiscord")->getCommitSignal()->connect(boost::bind(&LLAppViewer::toggleDiscordIntegration, _2)); + gSavedSettings.getControl("ShowDiscordActivityDetails")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity)); + gSavedSettings.getControl("ShowDiscordActivityState")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity)); +#endif } void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 25672db318..5e6f1e6a0a 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -843,6 +843,10 @@ void LLPanelPeople::updateNearbyList() LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); mNearbyList->setDirty(); +#ifdef LL_DISCORD + if (gSavedSettings.getBOOL("EnableDiscord")) + LLAppViewer::updateDiscordPartyMaxSize(mNearbyList->getIDs().size()); +#endif DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); LLActiveSpeakerMgr::instance().update(true); diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 4956c188fb..12a9d5e9b7 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -1026,6 +1026,10 @@ void LLLocalSpeakerMgr::updateSpeakerList() uuid_vec_t avatar_ids; std::vector positions; LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); +#ifdef LL_DISCORD + if (gSavedSettings.getBOOL("EnableDiscord")) + LLAppViewer::updateDiscordPartyCurrentSize(avatar_ids.size()); +#endif for(U32 i=0; i - + + + log in to change @@ -134,3 +150,48 @@ (People and/or Objects you have blocked) + + + + + + + + + + + + -- cgit v1.2.3 From a33fda4f288859222a1d2409e340f42b5aa5dd20 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 31 Jul 2025 00:08:08 +0300 Subject: #4339 Remade fix for world map's find button --- indra/newview/llfloaterworldmap.cpp | 36 ++++++++++++++++++++++++++++++------ indra/newview/llfloaterworldmap.h | 3 ++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 565642e683..6e6eaa3a20 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -325,6 +325,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), @@ -338,7 +339,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)); @@ -783,6 +784,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) { @@ -922,7 +924,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(); @@ -1196,6 +1201,7 @@ void LLFloaterWorldMap::onGoHome() { gAgent.teleportHome(); closeFloater(); + mProcessingSearchUpdate = false; } @@ -1365,6 +1371,7 @@ void LLFloaterWorldMap::onLocationCommit() { return; } + mProcessingSearchUpdate = true; LLStringUtil::toLower(str); mCompletingRegionName = str; @@ -1386,6 +1393,7 @@ void LLFloaterWorldMap::onCoordinatesCommit() { return; } + mProcessingSearchUpdate = false; S32 x_coord = (S32)mTeleportCoordSpinX->getValue().asReal(); S32 y_coord = (S32)mTeleportCoordSpinY->getValue().asReal(); @@ -1399,6 +1407,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 @@ -1415,6 +1424,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() @@ -1570,6 +1580,12 @@ void LLFloaterWorldMap::teleport() gAgent.teleportViaLocation( pos_global ); } } + + if (mProcessingSearchUpdate) + { + mProcessingSearchUpdate = false; + mTrackedSimName.clear(); + } } void LLFloaterWorldMap::flyToLandmark() @@ -1695,18 +1711,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); } @@ -1720,7 +1738,7 @@ void LLFloaterWorldMap::onTeleportFinished() } } -void LLFloaterWorldMap::onCommitSearchResult() +void LLFloaterWorldMap::onCommitSearchResult(bool from_search) { std::string sim_name = mSearchResults->getSelectedValue().asString(); if (sim_name.empty()) @@ -1751,8 +1769,14 @@ void LLFloaterWorldMap::onCommitSearchResult() pos_global.mdV[VY] += (F64)pos_local.mV[VY]; pos_global.mdV[VZ] = (F64)pos_local.mV[VZ]; - mLocationEditor->setValue(sim_name); + // Commiting search string automatically selects first item in the search list, + // in such case onCommitSearchResult shouldn't modify search string + if (!from_search) + { + mLocationEditor->setValue(sim_name); + } trackLocation(pos_global); + mProcessingSearchUpdate = from_search; mTrackCtrlsPanel->setDefaultBtn(mTeleportButton); break; } diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index bf1650eb7c..9558ca2615 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -176,7 +176,7 @@ protected: void onLocationFocusChanged( LLFocusableElement* ctrl ); void onLocationCommit(); void onCoordinatesCommit(); - void onCommitSearchResult(); + void onCommitSearchResult(bool from_search); void onTeleportFinished(); @@ -213,6 +213,7 @@ private: bool mIsClosing; bool mSetToUserPosition; + bool mProcessingSearchUpdate; // Don't update search string from what user set it to LLVector3d mTrackedLocation; LLTracker::ETrackingStatus mTrackedStatus; -- cgit v1.2.3 From 8240af03b9c64572ce0594052e18d374d6e8941c Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 31 Jul 2025 19:55:28 +0300 Subject: #4465 modify vertex limit threshold for starting splitting --- indra/newview/gltf/llgltfloader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 950e98c96d..dd1d327683 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -87,7 +87,8 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -static const S32 VERTICIES_LIMIT = USHRT_MAX - 2; +static const S32 VERTEX_SPLIT_SAFETY_MARGIN = 3 * 3 + 1; // 10 vertices: 3 complete triangles plus remapping overhead +static const S32 VERTEX_LIMIT = USHRT_MAX - VERTEX_SPLIT_SAFETY_MARGIN; LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, @@ -951,7 +952,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } // Indices handling - if (faceVertices.size() >= VERTICIES_LIMIT) + if (faceVertices.size() >= VERTEX_LIMIT) { // Will have to remap 32 bit indices into 16 bit indices // For the sake of simplicity build vector of 32 bit indices first @@ -1036,7 +1037,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } indices_16.push_back((U16)vert_index); - if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1) + if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTEX_LIMIT) { LLVolumeFace face; face.fillFromLegacyData(face_verts, indices_16); -- cgit v1.2.3 From 7255933d2ac9c2a9101b817e7b880702f453c72b Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 2 Aug 2025 05:31:35 +0300 Subject: #3785 Set default `MediaFirstClickInteract` value to 31 (Landowner objects) --- indra/newview/app_settings/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b0b7430c97..5b5551dbe0 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -16243,7 +16243,7 @@ Type S32 Value - 1 + 31 -- cgit v1.2.3 From f338b91f7c9dbfe999b5ea9a1facb9eaeafb3407 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 4 Aug 2025 17:47:24 +0300 Subject: #4483 Fix gltf not opening unicode paths --- indra/newview/gltf/accessor.cpp | 2 +- indra/newview/gltf/asset.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index d1845605d4..03f7331893 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -159,7 +159,7 @@ bool Buffer::prep(Asset& asset) std::string dir = gDirUtilp->getDirName(asset.mFilename); std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri; - std::ifstream file(bin_file, std::ios::binary); + llifstream file(bin_file.c_str(), std::ios::binary); if (!file.is_open()) { LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL; diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 8c9f77686a..e24aea4a28 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -696,7 +696,7 @@ bool Asset::load(std::string_view filename, bool 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(file)), std::istreambuf_iterator()); -- cgit v1.2.3 From 61d53a8731a0d10e94c4c366c31598b3f12198e1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 4 Aug 2025 22:40:13 +0300 Subject: #4470 Plug in Discord SDK package --- autobuild.xml | 60 +++++++++++++++++++++++++++++++++++ indra/cmake/Copy3rdPartyLibs.cmake | 9 ++++++ indra/cmake/Discord.cmake | 10 +++--- indra/newview/CMakeLists.txt | 15 ++++++++- indra/newview/llfloaterpreference.cpp | 4 +++ indra/newview/llpanelpeople.cpp | 2 +- indra/newview/llspeakers.cpp | 2 +- indra/newview/viewer_manifest.py | 11 +++++++ 8 files changed, 105 insertions(+), 8 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 777d98640f..4787ed168b 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2896,6 +2896,64 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors version 1.0.9-5e8947c + discord_sdk + + platforms + + windows64 + + archive + + creds + github + hash + e11571bf76b27d15c244069988ae372eaa5afae9 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333720 + + name + windows64 + + darwin64 + + archive + + creds + github + hash + dc21df8b051c425163acf3eff8f06e32f407c9e0 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333706 + + name + darwin64 + + + license + discord_sdk + license_file + LICENSES/discord_sdk.txt + copyright + Discord Inc. + version + 1.4.9649.16733550144 + name + discord_sdk + vcs_branch + main + vcs_revision + ef5c7c4a490ceac2df2b2f046788b1daf1bbb392 + vcs_url + https://github.com/secondlife/3p-discord-sdk + canonical_repo + https://github.com/secondlife/3p-discord-sdk + description + Discord Social SDK + package_description @@ -2917,6 +2975,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors -DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE -DROOT_PROJECT_NAME:STRING=SecondLife -DINSTALL_PROPRIETARY=TRUE + -DUSE_DISCORD:BOOL=ON -DUSE_OPENAL:BOOL=ON @@ -2959,6 +3018,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors -DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE -DROOT_PROJECT_NAME:STRING=SecondLife -DINSTALL_PROPRIETARY=TRUE + -DUSE_DISCORD:BOOL=ON -DUSE_OPENAL:BOOL=ON diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 6ac00fd131..de41918a34 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -6,6 +6,7 @@ include(CMakeCopyIfDifferent) include(Linking) +include(Discord) include(OPENAL) # When we copy our dependent libraries, we almost always want to copy them to @@ -75,6 +76,10 @@ if(WINDOWS) endif(ADDRESS_SIZE EQUAL 32) endif (USE_BUGSPLAT) + if (TARGET ll::discord_sdk) + list(APPEND release_files discord_partner_sdk.dll) + endif () + if (TARGET ll::openal) list(APPEND release_files openal32.dll alut.dll) endif () @@ -180,6 +185,10 @@ elseif(DARWIN) ) endif() + if (TARGET ll::discord_sdk) + list(APPEND release_files libdiscord_partner_sdk.dylib) + endif () + if (TARGET ll::openal) list(APPEND release_files libalut.dylib libopenal.dylib) endif () diff --git a/indra/cmake/Discord.cmake b/indra/cmake/Discord.cmake index 2193023a06..d15b4041b1 100644 --- a/indra/cmake/Discord.cmake +++ b/indra/cmake/Discord.cmake @@ -1,9 +1,9 @@ include(Prebuilt) -add_library(ll::discord INTERFACE IMPORTED) -target_compile_definitions(ll::discord INTERFACE LL_DISCORD=1) +add_library(ll::discord_sdk INTERFACE IMPORTED) +target_compile_definitions(ll::discord_sdk INTERFACE LL_DISCORD=1) -use_prebuilt_binary(discord) +use_prebuilt_binary(discord_sdk) -target_include_directories(ll::discord SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include) -target_link_libraries(ll::discord INTERFACE discord_partner_sdk) +target_include_directories(ll::discord_sdk SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/discord_sdk) +target_link_libraries(ll::discord_sdk INTERFACE discord_partner_sdk) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7cc26c7716..035feee898 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1785,6 +1785,12 @@ if (WINDOWS) ) endif (ADDRESS_SIZE EQUAL 64) + if (TARGET ll::discord_sdk) + list(APPEND COPY_INPUT_DEPENDENCIES + ${SHARED_LIB_STAGING_DIR}/discord_partner_sdk.dll + ) + endif () + if (TARGET ll::openal) list(APPEND COPY_INPUT_DEPENDENCIES ${SHARED_LIB_STAGING_DIR}/OpenAL32.dll @@ -1801,6 +1807,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1839,6 +1846,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1903,6 +1911,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1999,7 +2008,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ) if (USE_DISCORD) - target_link_libraries(${VIEWER_BINARY_NAME} ll::discord ) + target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk ) endif () if( TARGET ll::intel_memops ) @@ -2058,6 +2067,7 @@ if (LINUX) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2086,6 +2096,7 @@ if (LINUX) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2162,6 +2173,7 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2197,6 +2209,7 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 2a6360cef8..c84728248c 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -528,6 +528,10 @@ bool LLFloaterPreference::postBuild() getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); } +#ifndef LL_DISCORD + getChild("privacy_tab_container")->childDisable("privacy_preferences_discord"); +#endif + return true; } diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 5e6f1e6a0a..33599357a3 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -845,7 +845,7 @@ void LLPanelPeople::updateNearbyList() mNearbyList->setDirty(); #ifdef LL_DISCORD if (gSavedSettings.getBOOL("EnableDiscord")) - LLAppViewer::updateDiscordPartyMaxSize(mNearbyList->getIDs().size()); + LLAppViewer::updateDiscordPartyMaxSize((S32)mNearbyList->getIDs().size()); #endif DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 12a9d5e9b7..b49c0119ed 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -1028,7 +1028,7 @@ void LLLocalSpeakerMgr::updateSpeakerList() LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); #ifdef LL_DISCORD if (gSavedSettings.getBOOL("EnableDiscord")) - LLAppViewer::updateDiscordPartyCurrentSize(avatar_ids.size()); + LLAppViewer::updateDiscordPartyCurrentSize((S32)avatar_ids.size()); #endif for(U32 i=0; i Date: Tue, 5 Aug 2025 01:34:45 +0300 Subject: #4451 Remove missed first login image --- indra/newview/skins/default/textures/textures.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 97468c3b2e..ebc4beead2 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -655,7 +655,6 @@ with the same filename but different name - -- cgit v1.2.3 From c26749750998c2efaa5208b83bc48d65dfa60274 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 5 Aug 2025 09:33:19 +0300 Subject: #4470 Adjustments and Mac build fixes --- indra/cmake/Copy3rdPartyLibs.cmake | 4 +++- indra/cmake/Discord.cmake | 2 ++ indra/newview/viewer_manifest.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index de41918a34..0153e69d5b 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -6,7 +6,9 @@ include(CMakeCopyIfDifferent) include(Linking) -include(Discord) +if (USE_DISCORD) + include(Discord) +endif () include(OPENAL) # When we copy our dependent libraries, we almost always want to copy them to diff --git a/indra/cmake/Discord.cmake b/indra/cmake/Discord.cmake index d15b4041b1..95cfaacf5b 100644 --- a/indra/cmake/Discord.cmake +++ b/indra/cmake/Discord.cmake @@ -1,5 +1,7 @@ include(Prebuilt) +include_guard() + add_library(ll::discord_sdk INTERFACE IMPORTED) target_compile_definitions(ll::discord_sdk INTERFACE LL_DISCORD=1) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 4760ba5a6f..04c3fea93a 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1022,7 +1022,7 @@ class Darwin_x86_64_Manifest(ViewerManifest): self.path2basename(relpkgdir, libfile) # Discord social SDK - if self.args['DISCORD'] == 'ON': + if self.args['discord'] == 'ON': for libfile in ( "libdiscord_partner_sdk.dylib", ): -- cgit v1.2.3 From c8f1890f963a7c6ec288e3155e553f9411101417 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 5 Aug 2025 18:26:35 +0300 Subject: #4473 Permit zooming to self in people panel --- indra/newview/llpanelpeoplemenus.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp index f8a73ddb46..939b1f12a9 100644 --- a/indra/newview/llpanelpeoplemenus.cpp +++ b/indra/newview/llpanelpeoplemenus.cpp @@ -154,11 +154,15 @@ void PeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) bool PeopleContextMenu::enableContextMenuItem(const LLSD& userdata) { + std::string item = userdata.asString(); if(gAgent.getID() == mUUIDs.front()) { + if (item == std::string("can_zoom_in")) + { + return true; + } return false; } - std::string item = userdata.asString(); // Note: can_block and can_delete is used only for one person selected menu // so we don't need to go over all uuids. -- cgit v1.2.3 From c25df5224f7bf150ddc56014b0389985da2eb542 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 5 Aug 2025 23:13:42 +0300 Subject: #3564 Permit 'labeling' audio streams that play via media plugin People were using fmod's undocumented capability to ignore everything after the url to label their streams. --- indra/newview/llviewermedia_streamingaudio.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/indra/newview/llviewermedia_streamingaudio.cpp b/indra/newview/llviewermedia_streamingaudio.cpp index b68ffbe1a2..4a1c433f8e 100644 --- a/indra/newview/llviewermedia_streamingaudio.cpp +++ b/indra/newview/llviewermedia_streamingaudio.cpp @@ -60,13 +60,26 @@ void LLStreamingAudio_MediaPlugins::start(const std::string& url) if(!mMediaPlugin) return; - if (!url.empty()) { + if (!url.empty()) + { LL_INFOS() << "Starting internet stream: " << url << LL_ENDL; - mURL = url; - mMediaPlugin->loadURI ( url ); + + mURL = url; // keep original url here for comparison purposes + std::string snt_url = url; + LLStringUtil::trim(snt_url); + size_t pos = snt_url.find(' '); + if (pos != std::string::npos) + { + // fmod permited having names after the url and people were using it. + // People label their streams this way, ignore the 'label'. + snt_url = snt_url.substr(0, pos); + } + mMediaPlugin->loadURI(snt_url); mMediaPlugin->start(); LL_INFOS() << "Playing stream..." << LL_ENDL; - } else { + } + else + { LL_INFOS() << "setting stream to NULL"<< LL_ENDL; mURL.clear(); mMediaPlugin->stop(); -- cgit v1.2.3 From 819817f5c9cebad353f5b86996b5426095dd73b8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 6 Aug 2025 14:29:55 +0300 Subject: #4470 Temporarily disable Discord SDK --- autobuild.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 4787ed168b..56c66b163f 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2975,7 +2975,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors -DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE -DROOT_PROJECT_NAME:STRING=SecondLife -DINSTALL_PROPRIETARY=TRUE - -DUSE_DISCORD:BOOL=ON -DUSE_OPENAL:BOOL=ON @@ -3018,7 +3017,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors -DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE -DROOT_PROJECT_NAME:STRING=SecondLife -DINSTALL_PROPRIETARY=TRUE - -DUSE_DISCORD:BOOL=ON -DUSE_OPENAL:BOOL=ON -- cgit v1.2.3