summaryrefslogtreecommitdiff
path: root/indra/newview/llappviewer.cpp
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2026-09-01 18:35:23 +0800
committerErik Kundiman <erik@megapahit.org>2026-09-04 21:58:53 +0800
commit00bb3bb6f07660bd914d29a1389342bb3060d254 (patch)
tree431f574922494d2201718f13085f17bc6bd7fb42 /indra/newview/llappviewer.cpp
parenta8636720129952c10cf49ab80da21bf8b340b96c (diff)
parent4ef9f8f14b35ed388c294d53767a0dca0e890924 (diff)
Merge remote-tracking branch 'secondlife/release/26.4' into 26.4
Diffstat (limited to 'indra/newview/llappviewer.cpp')
-rw-r--r--indra/newview/llappviewer.cpp663
1 files changed, 533 insertions, 130 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index f0df9537fc..39a89a9373 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2026, 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
@@ -66,6 +66,7 @@
#include "llconversationlog.h"
#if LL_WINDOWS
#include "lldxhardware.h"
+#include <shellapi.h>
#endif
#include "lltexturestats.h"
#include "lltrace.h"
@@ -114,6 +115,7 @@
#include "lllocalbitmaps.h"
#include "llperfstats.h"
#include "llgltfmateriallist.h"
+#include "llwebsocketmgr.h"
// Linden library includes
#include "llavatarnamecache.h"
@@ -143,12 +145,12 @@
#include "vlc/libvlc_version.h"
#endif
-#if LL_DARWIN
-#if LL_SDL
-#include "llwindowsdl.h"
-#else
+#if LL_DARWIN && !LL_SDL_WINDOW
#include "llwindowmacosx.h"
-#endif // LL_SDL
+#endif
+
+#if LL_SDL_WINDOW
+#include "llwindowsdl.h"
#endif
// Third party library includes
@@ -192,7 +194,6 @@
#include "lltracker.h"
#include "llviewerparcelmgr.h"
#include "llworldmapview.h"
-#include "llpostprocess.h"
#include "lldebugview.h"
#include "llconsole.h"
@@ -280,12 +281,13 @@ using namespace LL;
// define a self-registering event API object
#include "llappviewerlistener.h"
-#if LL_MSVC
-// disable boost::lexical_cast warning
-#pragma warning (disable:4702)
-#endif
-
#ifdef LL_DISCORD
+#ifdef None
+#undef None
+#endif
+#ifdef BadRequest
+#undef BadRequest
+#endif
#define DISCORDPP_IMPLEMENTATION
#include <discordpp.h>
static std::shared_ptr<discordpp::Client> gDiscordClient;
@@ -304,17 +306,24 @@ static LLAppViewerListener sAppViewerListener(LLAppViewer::instance);
#if LL_DARWIN
extern void init_apple_menu(const char* product);
+extern bool gHDRDisplaySupport;
#endif // LL_DARWIN
extern bool gRandomizeFramerate;
extern bool gPeriodicSlowFrame;
extern bool gDebugGL;
-#if LL_DARWIN
+#if LL_DARWIN || LL_SDL_WINDOW
extern bool gHiDPISupport;
-extern bool gHDRDisplaySupport;
#endif
+#if LL_WINDOWS
+extern bool gGPUBenchmarkMode;
+#else
+static constexpr bool gGPUBenchmarkMode = false;
+#endif // LL_WINDOWS
+
+
////////////////////////////////////////////////////////////
// All from the last globals push...
@@ -399,6 +408,8 @@ const std::string START_MARKER_FILE_NAME("SecondLife.start_marker");
const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker");
const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker");
const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker");
+const std::string INITED_MARKER_FILE_NAME("SecondLife.inited_marker");
+const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker");
static std::string gLaunchFileOnQuit;
//----------------------------------------------------------------------------
@@ -559,15 +570,13 @@ static void settings_to_globals()
{
LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize"));
-//#if LL_DARWIN
+#if LL_DARWIN
LLRender::sGLCoreProfile = true;
-/*
#else
LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLContextCoreProfile");
#endif
-*/
LLRender::sNsightDebugSupport = gSavedSettings.getBOOL("RenderNsightDebugSupport");
- LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic");
+ LLRender::sAnisotropicFilteringLevel = static_cast<F32>(gSavedSettings.getU32("RenderAnisotropicLevel"));
LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures");
LLVOVolume::sLODFactor = llclamp(gSavedSettings.getF32("RenderVolumeLODFactor"), 0.01f, MAX_LOD_FACTOR);
LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f;
@@ -592,14 +601,17 @@ static void settings_to_globals()
LLWorldMapView::setScaleSetting(gSavedSettings.getF32("MapScale"));
#if LL_DARWIN
-#if LL_SDL
+#if LL_SDL_WINDOW
LLWindowSDL::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL");
#else
LLWindowMacOSX::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL");
-#endif // LL_SDL
- gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI");
+#endif
gHDRDisplaySupport = gSavedSettings.getBOOL("MPHDRDisplay");
#endif
+
+#if LL_DARWIN || LL_SDL_WINDOW
+ gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI");
+#endif
}
static void settings_modify()
@@ -655,6 +667,12 @@ bool LLAppViewer::sendURLToOtherInstance(const std::string& url)
return false;
}
+//virtual
+void LLAppViewer::setOSHibernationMode(eHibernationMode mode)
+{
+ // See OS specific files
+}
+
//----------------------------------------------------------------------------
// LLAppViewer definition
@@ -713,6 +731,12 @@ LLAppViewer::LLAppViewer()
gLoggedInTime.stop();
+ // Locking this early is needed to prevent multiple instances and
+ // to log, but it also means that early paths such as SLURL handling
+ // can invoke processMarkerFiles() and potentially clear markers
+ // from previous runs before those stats are reported.
+ // Todo: improve this. Perhaps store stats 'permanently' to be reported
+ // on next login and only login cleans stats up?
processMarkerFiles();
//
// OK to write stuff to logs now, we've now crash reported if necessary
@@ -865,6 +889,8 @@ bool LLAppViewer::init()
// that use findSkinnedFilenames(), will include the localized files.
gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage());
+ loadLocalizedSettingsComments();
+
// Setup LLTrans after LLUI::initClass has been called.
initStrings();
@@ -963,6 +989,16 @@ bool LLAppViewer::init()
LL_WARNS("InitInfo") << "initHardwareTest() failed." << LL_ENDL;
// quit immediately
LL_PROFILER_FRAME_END;
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
+ delete mSettingsLocationList;
+ if (!mSecondInstance)
+ {
+ // Stats from previous session will likely be lost, but this should
+ // be fine as this is likely first run for this version.
+ // Todo: Might be smarter to have an exit code for a cleaner shutdown
+ removeMarkerFiles();
+ }
return false;
}
LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ;
@@ -1022,7 +1058,7 @@ bool LLAppViewer::init()
return false;
}
-#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || _M_X64
+#if defined(LL_X86) || defined(LL_X86_64)
// Without SSE2 support we will crash almost immediately, warn here.
if (!gSysCPU.hasSSE2())
{
@@ -1143,17 +1179,17 @@ bool LLAppViewer::init()
gGLActive = false;
-//#if LL_RELEASE_FOR_DOWNLOAD
- // Launch VVM update check
+#if 0
+ // Launch VVM update check (Linux updater is future work: Velopack port)
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive)
{
initVVMUpdateCheck();
}
else
+#endif
{
LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL;
}
-//#endif //LL_RELEASE_FOR_DOWNLOAD
{
// Iterate over --leap command-line options. But this is a bit tricky: if
@@ -1271,15 +1307,11 @@ void LLAppViewer::initMaxHeapSize()
//------------------------------------------------------------------------------------------
//currently SL is built under 32-bit setting, we set its max heap size no more than 1.6 GB.
-/*
-#ifndef LL_X86_64
+ #if !defined(LL_X86_64) && !defined(LL_ARM64)
F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize") ;
#else
-*/
F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize64");
-/*
#endif
-*/
LLMemory::initMaxHeapSizeGB(max_heap_size_gb);
}
@@ -1790,7 +1822,15 @@ bool LLAppViewer::cleanup()
// Give any remaining SLPlugin instances a chance to exit cleanly.
LLPluginProcessParent::shutdown();
+ if (LLWatchdog::instanceExists())
+ {
+ // Signal a stop early, so that it will be out
+ // of 1s sleep loop by the time we get to clean it.
+ LLWatchdog::getInstance()->shutdown();
+ }
+
disconnectViewer();
+
LLViewerCamera::deleteSingleton();
LL_INFOS() << "Viewer disconnected" << LL_ENDL;
@@ -1901,6 +1941,9 @@ bool LLAppViewer::cleanup()
if( gViewerWindow)
gViewerWindow->shutdownViews();
+ // Model previews release their decomposition handles while the UI is destroyed.
+ gMeshRepo.shutdownDecomposition();
+
LL_INFOS() << "Cleaning up Inventory" << LL_ENDL;
// Cleanup Inventory after the UI since it will delete any remaining observers
@@ -1921,8 +1964,6 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLAvatarAppearance);
- SUBSYSTEM_CLEANUP(LLPostProcess);
-
LLTracker::cleanupInstance();
// *FIX: This is handled in LLAppViewerWin32::cleanup().
@@ -2091,7 +2132,7 @@ bool LLAppViewer::cleanup()
}
LLSplashScreen::show();
- LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+ LLSplashScreen::update(LLTrans::getString("ShutdownCleanup"));
LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL;
@@ -2120,7 +2161,7 @@ bool LLAppViewer::cleanup()
if (sTextureFetch)
{
sTextureFetch->shutdown();
- sTextureFetch->waitOnPending();
+ sTextureFetch->waitOnPending(10.f);
delete sTextureFetch;
sTextureFetch = NULL;
}
@@ -2165,7 +2206,10 @@ bool LLAppViewer::cleanup()
gSavedSettings.cleanup();
LLUIColorTable::instance().clear();
- LLWatchdog::getInstance()->cleanup();
+ if (LLWatchdog::instanceExists())
+ {
+ LLWatchdog::getInstance()->cleanup();
+ }
LLViewerAssetStatsFF::cleanup();
@@ -2190,6 +2234,10 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLProxy);
LLCore::LLHttp::cleanup();
+ LLSplashScreen::update(LLTrans::getString("CompressingInventoryCache"));
+ LLInventoryModel::waitForPendingCacheWrites();
+ LLSplashScreen::update(LLTrans::getString("ShuttingDown"));
+
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
@@ -2201,6 +2249,8 @@ bool LLAppViewer::cleanup()
LLWorld::deleteSingleton();
LLVoiceClient::deleteSingleton();
LLUI::deleteSingleton();
+ LLGridManager::deleteSingleton();
+ LLWatchdog::deleteSingleton();
// It's not at first obvious where, in this long sequence, a generic cleanup
// call OUGHT to go. So let's say this: as we migrate cleanup from
@@ -2317,7 +2367,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string)
LLAppViewer::instance()->writeDebugInfo();
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
- if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB))
+ if (!LLFile::isfile(error_marker_file))
{
// If marker doesn't exist, create a marker with llerror code for next launch
// otherwise don't override existing file
@@ -2345,6 +2395,9 @@ void errorHandler(const std::string& title_string, const std::string& message_st
case LLError::LLUserWarningMsg::ERROR_MISSING_FILES:
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES);
break;
+ case LLError::LLUserWarningMsg::ERROR_INIT_FAILED:
+ LLAppViewer::instance()->createErrorMarker(LAST_EXEC_INIT);
+ break;
default:
break;
}
@@ -2369,6 +2422,12 @@ void errorHandler(const std::string& title_string, const std::string& message_st
}
}
+namespace
+{
+ std::string getStartupLogFileName();
+ std::string getOldLogFileName(const std::string& log_file);
+}
+
void LLAppViewer::initLoggingAndGetLastDuration()
{
//
@@ -2385,22 +2444,25 @@ void LLAppViewer::initLoggingAndGetLastDuration()
if (mSecondInstance)
{
- LLFile::mkdir(gDirUtilp->getDumpLogsDirPath());
+ if (!gGPUBenchmarkMode)
+ {
+ LLFile::mkdir(gDirUtilp->getDumpLogsDirPath());
- LLUUID uid;
- uid.generate();
- LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log"));
+ LLUUID uid;
+ uid.generate();
+ // Is this even useful?
+ // Originally this wa used to store states, but I don't think it's practical with bugsplat attributes.
+ // So it just spams files now.
+ LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log"));
+ }
}
else
{
// Remove the last ".old" log file.
- std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
- "SecondLife.old");
+ std::string log_file = getStartupLogFileName();
+ std::string old_log_file = getOldLogFileName(log_file);
LLFile::remove(old_log_file);
- // Get name of the log file
- std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
- "SecondLife.log");
/*
* Before touching any log files, compute the duration of the last run
* by comparing the ctime of the previous start marker file with the ctime
@@ -2447,7 +2509,7 @@ void LLAppViewer::initLoggingAndGetLastDuration()
// Rename current log file to ".old"
LLFile::rename(log_file, old_log_file);
- // Set the log file to SecondLife.log
+ // Set the log file.
LLError::logToFile(log_file);
LL_INFOS() << "Started logging to " << log_file << LL_ENDL;
if (!duration_log_msg.empty())
@@ -2586,6 +2648,76 @@ namespace
LLStringUtil::null,
OSMB_OK);
}
+
+ std::string getStartupLogFileName()
+ {
+ if (LLControlVariable* user_log_file = gSavedSettings.getControl("UserLogFile"))
+ {
+ std::string log_file = user_log_file->getValue().asString();
+ if (!log_file.empty())
+ {
+ return log_file;
+ }
+ }
+
+#if LL_WINDOWS
+ int argc = 0;
+ LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (argv)
+ {
+ std::string log_file;
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string option = ll_convert_wide_to_string(argv[i]);
+ if ((option == "--logfile" || option == "-logfile" || option == "/logfile") &&
+ i + 1 < argc)
+ {
+ log_file = ll_convert_wide_to_string(argv[i + 1]);
+ }
+ else if (option.compare(0, 10, "--logfile=") == 0)
+ {
+ log_file = option.substr(10);
+ }
+ else if (option.compare(0, 9, "-logfile=") == 0)
+ {
+ log_file = option.substr(9);
+ }
+ else if (option.compare(0, 9, "/logfile:") == 0)
+ {
+ log_file = option.substr(9);
+ }
+ }
+ LocalFree(argv);
+
+ if (!log_file.empty())
+ {
+ return log_file;
+ }
+ }
+#endif
+
+ return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log");
+ }
+
+ std::string getOldLogFileName(const std::string& log_file)
+ {
+ std::string old_log_file = log_file;
+ size_t separator = old_log_file.find_last_of("/\\");
+ size_t basename_start = (separator == std::string::npos) ? 0 : separator + 1;
+ size_t extension = old_log_file.find_last_of('.');
+
+ if (extension != std::string::npos &&
+ extension > basename_start)
+ {
+ old_log_file.replace(extension, std::string::npos, ".old");
+ }
+ else
+ {
+ old_log_file += ".old";
+ }
+
+ return old_log_file;
+ }
} // anonymous namespace
// Set a named control temporarily for this session, as when set via the command line --set option.
@@ -2958,6 +3090,8 @@ bool LLAppViewer::initConfiguration()
}
}
+ LLGridManager::createInstance();
+
LLSLURL start_slurl;
if (!starting_location.empty())
{
@@ -3000,19 +3134,39 @@ bool LLAppViewer::initConfiguration()
{
if (sendURLToOtherInstance(start_slurl.getSLURLString()))
{
- // successfully handed off URL to existing instance, exit
+ // Successfully handed off URL to existing instance.
+ // Returning 'false' gets treated as a failure to init,
+ // without cleanup, so instead clear markers and app here.
+ // Do not save settings.
+ // Might be smarter to have an exit code for a more reliable
+ // "early exit, needs cleanup" case.
+ LLGridManager::deleteSingleton();
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
+ delete mSettingsLocationList;
+ if (!mSecondInstance)
+ {
+ // Todo: Unfortunately, if we are doing this, stats and
+ // markers from previous session were already processed,
+ // cleared yet haven't been reported and will be lost.
+ // Consider a way to save those.
+ removeMarkerFiles();
+ }
return false;
}
}
// Display splash screen. Must be after above check for previous
// crash as this dialog is always frontmost.
- std::string splash_msg;
- LLStringUtil::format_map_t args;
- args["[APP_NAME]"] = LLTrans::getString("SECOND_LIFE");
- splash_msg = LLTrans::getString("StartupLoading", args);
- LLSplashScreen::show();
- LLSplashScreen::update(splash_msg);
+ if (!gGPUBenchmarkMode)
+ {
+ std::string splash_msg;
+ LLStringUtil::format_map_t args;
+ args["[APP_NAME]"] = LLTrans::getString("SECOND_LIFE");
+ splash_msg = LLTrans::getString("StartupLoading", args);
+ LLSplashScreen::show();
+ LLSplashScreen::update(splash_msg);
+ }
//LLVolumeMgr::initClass();
LLVolumeMgr* volume_manager = new LLVolumeMgr();
@@ -3047,6 +3201,12 @@ bool LLAppViewer::initConfiguration()
LLTrans::getString("MBAlreadyRunning"),
LLStringUtil::null,
OSMB_OK);
+
+ // Since returning 'false' is basically an error without cleanup,
+ // do cleanup here. No need to worry about marker files here.
+ LLGridManager::deleteSingleton();
+ LLSingletonBase::deleteAll();
+ cleanupConsole();
return false;
}
@@ -3087,6 +3247,57 @@ bool LLAppViewer::initConfiguration()
return true; // Config was successful.
}
+static void apply_localized_comments(LLControlGroup& group, const LLSD& comments)
+{
+ for (LLSD::map_const_iterator it = comments.beginMap(); it != comments.endMap(); ++it)
+ {
+ LLControlVariablePtr control = group.getControl(it->first);
+ if (control.notNull())
+ {
+ const std::string comment = it->second.asString();
+ if (!comment.empty())
+ {
+ control->setComment(comment);
+ }
+ }
+ }
+}
+
+void LLAppViewer::loadLocalizedSettingsComments()
+{
+ std::string lang = LLUI::getLanguage();
+ if (lang.empty() || lang == "en")
+ {
+ return;
+ }
+
+ std::string path = gDirUtilp->findSkinnedFilename(
+ LLDir::XUI, "settings_comments.xml", LLDir::CURRENT_SKIN);
+ if (path.empty())
+ {
+ return;
+ }
+
+ llifstream infile;
+ infile.open(path.c_str());
+ if (!infile.is_open())
+ {
+ return;
+ }
+
+ LLSD comments;
+ if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(comments, infile))
+ {
+ infile.close();
+ LL_WARNS("Settings") << "Failed to parse localized settings comments file: " << path << LL_ENDL;
+ return;
+ }
+ infile.close();
+
+ apply_localized_comments(gSavedSettings, comments);
+ apply_localized_comments(gSavedPerAccountSettings, comments);
+}
+
// The following logic is replicated in initConfiguration() (to be able to get
// some initial strings before we've finished initializing enough to know the
// current language) and also in init() (to initialize for real). Somehow it
@@ -3106,13 +3317,11 @@ void LLAppViewer::initStrings()
}
else
{
- llstat st;
- int rc = LLFile::stat(strings_path_full, &st);
- if (rc != 0)
+ if (!LLFile::exists(strings_path_full))
{
- crash_reason = "The file '" + strings_path_full + "' failed to get status. Error code: " + std::to_string(rc);
+ crash_reason = "The file '" + strings_path_full + "' doesn't seem to exist";
}
- else if (S_ISDIR(st.st_mode))
+ else if (LLFile::isdir(strings_path_full))
{
crash_reason = "The filename '" + strings_path_full + "' is a directory name";
}
@@ -3502,7 +3711,7 @@ LLSD LLAppViewer::getViewerInfo() const
info["RENDER_QUALITY"] = (F32)gSavedSettings.getU32("RenderQualityPerformance");
info["TEXTURE_MEMORY"] = LLSD::Integer(gGLManager.mVRAM);
-#if LL_DARWIN
+#if LL_DARWIN || LL_SDL_WINDOW
info["HIDPI"] = gHiDPISupport;
#endif
@@ -3558,8 +3767,6 @@ LLSD LLAppViewer::getViewerInfo() const
cef_ver_codec << CHROME_VERSION_PATCH;
info["LIBCEF_VERSION"] = cef_ver_codec.str();
-#else
- info["LIBCEF_VERSION"] = "Undefined";
#endif
#if defined(LL_VLC)
@@ -3570,14 +3777,13 @@ LLSD LLAppViewer::getViewerInfo() const
vlc_ver_codec << ".";
vlc_ver_codec << LIBVLC_VERSION_REVISION;
info["LIBVLC_VERSION"] = vlc_ver_codec.str();
-#else
- info["LIBVLC_VERSION"] = "Undefined";
#endif
- S32 packets_in = (S32)LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN);
+ LLTrace::Recording& recording = LLViewerStats::instance().getRecording();
+ S32 packets_in = (S32)recording.getSum(LLStatViewer::PACKETS_IN);
if (packets_in > 0)
{
- info["PACKETS_LOST"] = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST);
+ info["PACKETS_LOST"] = recording.getSum(LLStatViewer::PACKETS_LOST);
info["PACKETS_IN"] = packets_in;
info["PACKETS_PCT"] = 100.f*info["PACKETS_LOST"].asReal() / info["PACKETS_IN"].asReal();
}
@@ -3630,6 +3836,13 @@ std::string LLAppViewer::getViewerInfoString(bool default_string) const
{
args[ii->first] = LLTrans::getString("none_text", default_string);
}
+ else if (ii->second.isBoolean())
+ {
+ // LLSD intentionally renders false as an empty string to
+ // preserve string-to-boolean round trips. Viewer info is
+ // human-readable, so show both boolean states explicitly.
+ args[ii->first] = ii->second.asBoolean() ? "true" : "false";
+ }
else
{
// don't forget to render value asString()
@@ -3762,8 +3975,12 @@ void LLAppViewer::writeSystemInfo()
gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily();
gDebugInfo["CPUInfo"]["CPUMhz"] = (S32)gSysCPU.getMHz();
gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec();
- gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE();
- gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2();
+ gDebugInfo["CPUInfo"]["CPUSSE42"] = gSysCPU.hasSSE42();
+ gDebugInfo["CPUInfo"]["CPUSSE4a"] = gSysCPU.hasSSE4a();
+ gDebugInfo["CPUInfo"]["CPUAVX"] = gSysCPU.hasAVX();
+ gDebugInfo["CPUInfo"]["CPUAVX2"] = gSysCPU.hasAVX2();
+ gDebugInfo["CPUInfo"]["CPUAVX512F"] = gSysCPU.hasAVX512F();
+
gDebugInfo["RAMInfo"]["Physical"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().value());
gDebugInfo["RAMInfo"]["Allocated"] = LLSD::Integer(gMemoryAllocated.valueInUnits<LLUnits::Kilobytes>());
@@ -4007,18 +4224,30 @@ bool LLAppViewer::getMarkerData(const std::string& marker_name, std::string& dat
void LLAppViewer::processMarkerFiles()
{
+ if (gGPUBenchmarkMode)
+ {
+ // Skipping marker file processing in GPU benchmark mode
+ mSecondInstance = true;
+ initLoggingAndGetLastDuration();
+ return;
+ }
//We've got 4 things to test for here
// - Other Process Running (SecondLife.exec_marker present, locked)
// - Freeze (SecondLife.exec_marker present, not locked)
// - LLError Crash (SecondLife.llerror_marker present)
// - Other Crash (SecondLife.error_marker present)
- // These checks should also remove these files for the last 2 cases if they currently exist
+ // - Watchdog freeze (SecondLife.watchdog_marker present)
+ // - Failed to initialize (SecondLife.inited_marker not present)
+ // - Potentially killed by task manager or computer
+ // didn't recover from hibernation (SecondLife.close_marker present)
+ // These checks should also remove these files for the last 2 cases
+ // if they currently exist
std::ostringstream marker_log_stream;
bool marker_is_same_version = true;
// first, look for the marker created at startup and deleted on a clean exit
mMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,MARKER_FILE_NAME);
- if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB))
+ if (LLFile::isfile(mMarkerFileName))
{
// File exists...
// first, read it to see if it was created by the same version (we need this later)
@@ -4105,7 +4334,7 @@ void LLAppViewer::processMarkerFiles()
// check for any last exec event report based on whether or not it happened during logout
// (the logout marker is created when logout begins)
std::string logout_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LOGOUT_MARKER_FILE_NAME);
- if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB))
+ if(LLFile::isfile(logout_marker_file))
{
if (markerIsSameVersion(logout_marker_file))
{
@@ -4117,14 +4346,16 @@ void LLAppViewer::processMarkerFiles()
{
LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL;
}
- LLAPRFile::remove(logout_marker_file);
+ LLFile::remove(logout_marker_file);
}
// Refine based on whether or not a marker created during
// a crash is found or if wathdog caught a freeze.
// Bugsplat will set correct state in bugsplatSendLog.
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME);
- if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB))
+ std::string inited_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+ std::string close_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
+ if(LLFile::isfile(error_marker_file))
{
S32 marker_code = getMarkerErrorCode(error_marker_file);
if (marker_code >= 0)
@@ -4152,8 +4383,8 @@ void LLAppViewer::processMarkerFiles()
{
LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL;
}
- LLAPRFile::remove(error_marker_file);
- if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB))
+ LLFile::remove(error_marker_file);
+ if (LLFile::isfile(watchdog_marker_file))
{
// If viewer crashed after a freeze was detected,
// crash still takes precendence. Just clear watchdog.
@@ -4162,11 +4393,13 @@ void LLAppViewer::processMarkerFiles()
}
else
{
- // so only check watchdog marker if there is no error marker.
- if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB))
+ if (LAST_EXEC_UNKNOWN == gLastExecEvent
+ || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent)
{
- if (LAST_EXEC_UNKNOWN == gLastExecEvent
- || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent)
+ // If viewer crashed after a freeze was detected,
+ // crash still takes precendence.
+ // So only check watchdog marker if there is no error marker.
+ if (LLFile::isfile(watchdog_marker_file))
{
// watchdog marker gets created if we detect a freeze,
// so if viwer did not stop gracefully, and we know it wasn't a crash,
@@ -4178,9 +4411,49 @@ void LLAppViewer::processMarkerFiles()
<< LL_ENDL;
}
}
- removeWatchdogMarker();
+ // If 'close' marker is found, viewer either started shutdown but
+ // failed, OS did not recover from hibernation or viewer got
+ // killed by task manager.
+ // Marker does not indicate that viewer was closed or is closing,
+ // just that 'close' was requested before viewer died.
+ else if (LLFile::isfile(close_marker_file))
+ {
+ // Unfortunately we can't reliably distinguish
+ // task manager's case from genuine shutdown, so we
+ // have to report all of them as the same thing.
+ // Todo: but we can distinguish hibernation, might want
+ // to simply not report it as an issue.
+ if (markerIsSameVersion(close_marker_file))
+ {
+ gLastExecEvent = LAST_EXEC_OS_EVENT;
+ LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to OS_EVENT"
+ << LL_ENDL;
+ }
+ }
+ else if ((LAST_EXEC_UNKNOWN == gLastExecEvent)
+ && !LLFile::isfile(inited_marker_file))
+ {
+ // Viewer didn't get to a login screen.
+ gLastExecEvent = LAST_EXEC_INIT;
+ LL_INFOS("MarkerFile") << "'Inited' marker '"
+ << inited_marker_file
+ << "' not found, assuming that init crashed."
+ << LL_ENDL;
+ }
}
}
+ if (LLFile::isfile(watchdog_marker_file))
+ {
+ removeWatchdogMarker();
+ }
+ if (LLFile::isfile(inited_marker_file))
+ {
+ removeInitedMarker();
+ }
+ if (LLFile::isfile(close_marker_file))
+ {
+ removeCloseRequestMarker();
+ }
#if LL_DARWIN
if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL)
@@ -4206,7 +4479,7 @@ void LLAppViewer::removeMarkerFiles()
if (mMarkerFile.getFileHandle())
{
mMarkerFile.close() ;
- LLAPRFile::remove( mMarkerFileName );
+ LLFile::remove( mMarkerFileName );
LL_DEBUGS("MarkerFile") << "removed exec marker '"<<mMarkerFileName<<"'"<< LL_ENDL;
}
else
@@ -4217,13 +4490,14 @@ void LLAppViewer::removeMarkerFiles()
if (mLogoutMarkerFile.getFileHandle())
{
mLogoutMarkerFile.close();
- LLAPRFile::remove( mLogoutMarkerFileName );
+ LLFile::remove( mLogoutMarkerFileName );
LL_DEBUGS("MarkerFile") << "removed logout marker '"<<mLogoutMarkerFileName<<"'"<< LL_ENDL;
}
else
{
LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL;
}
+ removeCloseRequestMarker();
removeWatchdogMarker();
}
else
@@ -4283,6 +4557,13 @@ void LLAppViewer::requestQuit()
// This prevents the halfway-logged-in avatar from hanging around inworld for a couple minutes.
if (region)
{
+ // We probably don't have caps to do it, and it might
+ // arrive after logout finishes, but attempt to send stats
+ if (region->capabilitiesReceived())
+ {
+ constexpr bool include_preferences = true;
+ send_viewer_stats(include_preferences);
+ }
sendLogoutRequest();
}
@@ -4383,6 +4664,14 @@ void LLAppViewer::abortQuit()
mClosingFloaters = false;
}
+void LLAppViewer::sendViewerStatistics(bool include_preferences)
+{
+ if (!gDisconnected)
+ {
+ send_viewer_stats(include_preferences);
+ }
+}
+
void LLAppViewer::migrateCacheDirectory()
{
#if LL_WINDOWS || LL_DARWIN
@@ -4444,7 +4733,7 @@ void LLAppViewer::migrateCacheDirectory()
LLFile::remove(ds_store);
}
#endif
- if (LLFile::rmdir(old_cache_dir) != 0)
+ if (LLFile::remove(old_cache_dir) != 0)
{
LL_WARNS() << "could not delete old cache directory " << old_cache_dir << LL_ENDL;
}
@@ -4730,7 +5019,7 @@ void LLAppViewer::purgeCacheImmediate()
std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "objectcache");
LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL;
gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files
- LLFile::rmdir(cache_dir);
+ LLFile::remove(cache_dir);
}
}
@@ -5006,6 +5295,11 @@ void LLAppViewer::idle()
LLMortician::updateClass();
LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify()
LLDirPickerThread::clearDead();
+
+ if (LLWebsocketMgr::instanceExists())
+ {
+ LLWebsocketMgr::instance().update();
+ }
F32 dt_raw = idle_timer.getElapsedTimeAndResetF32();
LLGLTFMaterialList::flushUpdates();
@@ -5013,6 +5307,7 @@ void LLAppViewer::idle()
static LLCachedControl<U32> downscale_method(gSavedSettings, "RenderDownScaleMethod");
gGLManager.mDownScaleMethod = downscale_method;
LLImageGL::updateClass();
+ LLUIImage::updateClass();
// Service the WorkQueue we use for replies from worker threads.
// Use function statics for the timeslice setting so we only have to fetch
@@ -5662,7 +5957,60 @@ void LLAppViewer::createErrorMarker(eLastExecEvent error_code) const
bool LLAppViewer::errorMarkerExists() const
{
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME);
- return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB);
+ return LLFile::isfile(error_marker_file);
+}
+
+void LLAppViewer::createCloseRequestMarker() const
+{
+ // WINDOW THREAD! since we need this to act fast.
+ // This does not indicate that viewer was closed or is closing,
+ // but that 'close' was requested.
+ if (!mSecondInstance)
+ {
+ std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
+
+ LLAPRFile file;
+ file.open(close_marker, LL_APR_WB);
+ if (file.getFileHandle())
+ {
+ recordMarkerVersion(file);
+ file.close();
+ }
+ }
+}
+
+void LLAppViewer::removeCloseRequestMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME);
+ LLFile::remove(close_marker, ENOENT);
+ }
+}
+
+void LLAppViewer::createInitedMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+
+ LLAPRFile file;
+ file.open(inited_marker, LL_APR_WB);
+ if (file.getFileHandle())
+ {
+ recordMarkerVersion(file);
+ file.close();
+ }
+ }
+}
+
+void LLAppViewer::removeInitedMarker() const
+{
+ if (!mSecondInstance)
+ {
+ std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME);
+ LLFile::remove(inited_marker, ENOENT);
+ }
}
void LLAppViewer::createWatchdogMarker() const
@@ -5680,12 +6028,13 @@ void LLAppViewer::createWatchdogMarker() const
}
}
}
+
void LLAppViewer::removeWatchdogMarker() const
{
if (!mSecondInstance)
{
std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME);
- LLFile::remove(error_marker_file);
+ LLFile::remove(error_marker_file, ENOENT);
}
}
@@ -5707,6 +6056,29 @@ void LLAppViewer::outOfMemorySoftQuit()
}
}
+void LLAppViewer::setPermitOSHibernation(bool permit)
+{
+ if (permit)
+ {
+ if (mCurrentHibernationMode != LL_HIBERNATE_MODE_DEFAULT)
+ {
+ // Will call OS specific code to let OS hibernate when idle
+ setOSHibernationMode(LL_HIBERNATE_MODE_DEFAULT);
+ mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT;
+ }
+ }
+ else
+ {
+ static LLCachedControl<S32> os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0);
+ eHibernationMode mode = static_cast<eHibernationMode>(os_hibernation_mode());
+ if (mode != LL_HIBERNATE_MODE_DEFAULT && mCurrentHibernationMode != mode)
+ {
+ setOSHibernationMode(mode);
+ mCurrentHibernationMode = mode;
+ }
+ }
+}
+
void LLAppViewer::idleNameCache()
{
// Neither old nor new name cache can function before agent has a region
@@ -5751,7 +6123,6 @@ void LLAppViewer::idleNetwork()
pingMainloopTimeout("idleNetwork");
gObjectList.mNumNewObjects = 0;
- S32 total_decoded = 0;
static LLCachedControl<bool> speed_test(gSavedSettings, "SpeedTest", false);
if (!speed_test())
@@ -5759,64 +6130,56 @@ void LLAppViewer::idleNetwork()
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode
LLTimer check_message_timer;
- // Read all available packets from network
const S64 frame_count = gFrameCount; // U32->S64
- F32 total_time = 0.0f;
+ S32 total_decoded = 0;
+ // Process packets from network
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(frame_count, gServicePump))
{
- bool needs_drain = false;
- LockMessageChecker lmc(gMessageSystem);
- while (lmc.checkAllMessages(frame_count, gServicePump))
- {
- if (gDoDisconnect)
- {
- // We're disconnecting, don't process any more messages from the server
- // We're usually disconnecting due to either network corruption or a
- // server going down, so this is OK.
- break;
- }
-
- total_decoded++;
+ ++total_decoded;
- if (total_decoded > MESSAGE_MAX_PER_FRAME)
+ // Time-box processing of network packets to prevent framerate catastrophe
+ if (check_message_timer.getElapsedTimeF32() >= CheckMessagesMaxTime)
+ {
+ // Drain the socket buffer so we know how many messages remain to process
+ S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
+ if (num_buffered_packets > total_decoded)
{
- needs_drain = true;
- break;
+ // Grow CheckMessagesMaxTime until we process more packets each frame than arrive.
+ // This might spiral out of control on very slow computers on fast networks when
+ // the bandwidth settings are too high. There is a mechanism for providing backpressure
+ // to network bandwidth but it may be inadequate for the task
+ // (see LLViewerThrottle::updateDynamicThrottle() for more details).
+ CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
}
-
- // Prevent slow packets from completely destroying the frame rate.
- // This usually happens due to clumps of avatars taking huge amount
- // of network processing time (which needs to be fixed, but this is
- // a good limit anyway).
- total_time = check_message_timer.getElapsedTimeF32();
- if (total_time >= CheckMessagesMaxTime)
+ else if (num_buffered_packets == 0)
{
- needs_drain = true;
- break;
+ // Reset CheckMessagesMaxTime to default value
+ CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
}
+ break;
}
- if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0)
+
+ if (total_decoded > MESSAGE_MAX_PER_FRAME)
{
- // Rather than allow packets to silently backup on the socket
- // we drain them into our own buffer so we know how many exist.
- S32 num_buffered_packets = gMessageSystem->drainUdpSocket();
- if (num_buffered_packets > 0)
- {
- // Increase CheckMessagesMaxTime so that we will eventually catch up
- CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames
- }
+ // MESSAGE_MAX_PER_FRAME is very high (400)
+ // We expect to run out of time before reaching here, but just in case...
+ gMessageSystem->drainUdpSocket();
+ break;
}
- else
+
+ if (gDoDisconnect)
{
- // Reset CheckMessagesMaxTime to default value
- CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME;
+ // We're disconnecting so no need to process packets.
+ break;
}
+ }
- // Handle per-frame message system processing.
+ // Handle per-frame message system processing.
- static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
- lmc.processAcks(ack_collection_time());
- }
+ static LLCachedControl<F32> ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f);
+ lmc.processAcks(ack_collection_time());
}
add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects);
@@ -5923,6 +6286,9 @@ void LLAppViewer::disconnectViewer()
// Pass the connection state to LLUrlEntryParcel not to attempt
// parcel info requests while disconnected.
LLUrlEntryParcel::setDisconnected(gDisconnected);
+
+ // Restore default OS hibernation mode
+ setPermitOSHibernation(true);
}
void LLAppViewer::forceErrorLLError()
@@ -5943,11 +6309,13 @@ void LLAppViewer::forceErrorBreakpoint()
LL_WARNS() << "Forcing a deliberate breakpoint" << LL_ENDL;
#ifdef LL_WINDOWS
DebugBreak();
-#elif __i386__ || __x86_64__
+#else
+#if defined(LL_X86) || defined(LL_X86_64)
asm ("int $3");
#else
__builtin_trap();
#endif
+#endif
return;
}
@@ -6135,6 +6503,32 @@ F32 LLAppViewer::getMainloopTimeoutSec() const
}
}
+std::string LLAppViewer::getMainloopWatchdogState() const
+{
+ if (!mMainloopTimeout)
+ {
+ return std::string();
+ }
+ std::string state = mMainloopTimeout->getState();
+
+ if (mMainloopTimeout->hasExpired())
+ {
+ return "Expired at " + state;
+ }
+
+ // Check if the watchdog is currently active (timer started)
+ if (!mMainloopTimeout->isAlive())
+ {
+ // Timer is not running, meaning watchdog is paused/stopped
+ if (state.empty())
+ {
+ return "Paused";
+ }
+ return "Paused at " + state;
+ }
+ return state;
+}
+
void LLAppViewer::handleLoginComplete()
{
gLoggedInTime.start();
@@ -6186,6 +6580,15 @@ void LLAppViewer::handleLoginComplete()
// we logged in successfully, so save settings on logout
LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL;
mSavePerAccountSettings=true;
+
+ // Don't allow hibernation while we're running
+ setPermitOSHibernation(false);
+ // Track 'hibernation' mode changes
+ mOSHibernationModeChangeConnection = gSavedSettings.getControl("OSHibernationMode")->getSignal()->connect([](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val)
+ {
+ // setPermitOSHibernation will sort itself out based on new mode.
+ LLAppViewer::instance()->setPermitOSHibernation(false);
+ });
}
//virtual