From b33d4053eca36480098f764c7b7ad8f3516641ea Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:30:13 +0300 Subject: #1841 Fix viewer attempting to parse joystick key string into an llsd Key already is an LLSD::String and needs no further parsing. This parsing was added for command line handling and should be done explicitly in command line's parser. Also extended event listeners, there should be no ambiguity in what kind of data we are passing. --- indra/newview/llcommandlineparser.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra/newview/llcommandlineparser.cpp') diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp index 0734b12531..84d3ff90d5 100644 --- a/indra/newview/llcommandlineparser.cpp +++ b/indra/newview/llcommandlineparser.cpp @@ -611,6 +611,11 @@ void setControlValueCB(const LLCommandLineParser::token_vector_t& value, ctrl->setValue(llsdArray, false); } + else if (ctrl->isType(TYPE_LLSD)) + { + // Command-line LLSD should support a notation format string + ctrl->setValueFromNotation(onevalue(option, value), false); + } else { ctrl->setValue(onevalue(option, value), false); -- cgit v1.3 From 60b7e3b78cc401318e4f895bb64b5688645b3462 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:18:58 +0300 Subject: #6153 Make hardware test more robust --- indra/llcommon/llprocess.cpp | 5 ++ indra/llcommon/llprocess.h | 4 ++ indra/newview/app_settings/cmd_line.xml | 6 ++ indra/newview/llappviewer.cpp | 43 +++++++++--- indra/newview/llappviewerwin32.cpp | 18 ++++- indra/newview/llcommandlineparser.cpp | 2 +- indra/newview/llfeaturemanager.cpp | 114 +++++++++++++++++++++++++++++++- 7 files changed, 178 insertions(+), 14 deletions(-) (limited to 'indra/newview/llcommandlineparser.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index e43f386af2..86e1f48377 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1109,6 +1109,11 @@ bool LLProcess::kill(const LLProcessPtr& ptr, const std::string& who) return !ptr || ptr->kill(who); } +void LLProcess::pump() +{ + tick(); +} + LLProcess::id LLProcess::getProcessID() const { if (!mChild) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 26f9a27837..a016b6a258 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -234,6 +234,10 @@ public: bool kill(const std::string& who = ""); static bool kill(const LLProcessPtr& p, const std::string& who = ""); + /// Manually drive pending I/O and check process state. + /// Use this when the mainloop is not yet running or was terminated. + void pump(); + #if LL_WINDOWS typedef int id; typedef HANDLE handle; diff --git a/indra/newview/app_settings/cmd_line.xml b/indra/newview/app_settings/cmd_line.xml index e16a5c7e76..bf7f9bab1f 100644 --- a/indra/newview/app_settings/cmd_line.xml +++ b/indra/newview/app_settings/cmd_line.xml @@ -90,6 +90,12 @@ ConnectAsGod + gpubenchmark + + desc + Run GPU memory bandwidth benchmark, print result to stdout, and exit. Used internally by the viewer to isolate benchmark from the main process. + + graphicslevel desc diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 35a2cc1852..d155e7340b 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -300,6 +300,13 @@ extern bool gDebugGL; extern bool gHiDPISupport; #endif +#if LL_WINDOWS +extern bool gGPUBenchmarkMode; +#else +static constexpr bool gGPUBenchmarkMode = false; +#endif // LL_WINDOWS + + //////////////////////////////////////////////////////////// // All from the last globals push... @@ -2373,11 +2380,17 @@ 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 { @@ -3081,12 +3094,15 @@ bool LLAppViewer::initConfiguration() // 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(); @@ -4139,6 +4155,13 @@ 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) diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 159c3af8a0..2080d0b7bf 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -234,6 +234,8 @@ namespace } #endif // LL_BUGSPLAT +extern bool gGPUBenchmarkMode; + namespace { void (*gOldTerminateHandler)() = NULL; @@ -515,12 +517,21 @@ int APIENTRY WINMAIN(HINSTANCE hInstance, gIconResource = MAKEINTRESOURCE(IDI_LL_ICON); gIconSmallResource = MAKEINTRESOURCE(IDI_LL_ICON_SMALL); + // Benchmark subprocess mode before full init for LLFeatureManager::loadGPUClass(). + { + std::wstring cmdLineStr(pCmdLine ? pCmdLine : L""); + if (cmdLineStr.find(L"--gpubenchmark") != std::wstring::npos) + { + gGPUBenchmarkMode = true; + } + } + LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str()); gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler); // Set a debug info flag to indicate if multiple instances are running. - bool found_other_instance = !create_app_mutex(); + bool found_other_instance = gGPUBenchmarkMode || !create_app_mutex(); gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance); bool ok = viewer_app_ptr->init(); @@ -940,7 +951,10 @@ void LLAppViewerWin32::initLoggingAndGetLastDuration() void LLAppViewerWin32::initConsole() { // pop up debug console - mIsConsoleAllocated = create_console(); + if (!gGPUBenchmarkMode) + { + mIsConsoleAllocated = create_console(); + } return LLAppViewer::initConsole(); } diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp index 84d3ff90d5..7e7d528199 100644 --- a/indra/newview/llcommandlineparser.cpp +++ b/indra/newview/llcommandlineparser.cpp @@ -60,7 +60,7 @@ namespace // List of command-line switches that can't map-to settings variables. // Going forward, we want every new command-line switch to map-to some // settings variable. This list is used to validate that. - const std::set unmapped_options = { "help", "set", "setdefault", "settings", "sessionsettings", "usersessionsettings" }; + const std::set unmapped_options = { "help", "set", "setdefault", "settings", "sessionsettings", "usersessionsettings", "gpubenchmark" }; po::options_description gOptionsDesc; po::positional_options_description gPositionalOptions; diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 8140aaab22..3dbb93c453 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -377,6 +377,83 @@ F32 gpu_benchmark(); #if LL_WINDOWS +bool gGPUBenchmarkMode = false; + +// Runs gpu_benchmark() in a subprocess (exe with --gpubenchmark). +static F32 subprocess_gpu_benchmark() +{ + LLProcess::Params params; + params.executable = gDirUtilp->getExecutablePathAndName(); + params.args.add("--gpubenchmark"); + params.desc = "GPU benchmark"; + params.autokill = true; // killed via job object if parent crashes + params.attached = true; // killed on LLProcessPtr destruction (timeout) + params.files.add(LLProcess::FileParam()); // stdin: default + params.files.add(LLProcess::FileParam().type("pipe")); // stdout: pipe + params.files.add(LLProcess::FileParam()); // stderr: default + + LLProcessPtr child; + try + { + child = LLProcess::create(params); + } + catch (const std::exception& e) + { + LL_WARNS("RenderInit") << "subprocess_gpu_benchmark: failed to launch: " + << e.what() << LL_ENDL; + return -1.f; + } + + if (!child) + { + LL_WARNS("RenderInit") << "subprocess_gpu_benchmark: LLProcess::create returned null." << LL_ENDL; + return -1.f; + } + + LLProcess::ReadPipe& out = child->getReadPipe(LLProcess::STDOUT); + + const F32 POLL_INTERVAL_S = 0.25f; + const F32 TOTAL_TIMEOUT_S = 120.f; // covers full viewer init + benchmark + LLTimer timer; + timer.start(); + + while (timer.getElapsedTimeF32() < TOTAL_TIMEOUT_S) + { + child->pump(); + + // Result is a single float followed by '\n' + if (out.contains('\n')) + { + std::string line = out.getline(); + float parsed = 0.f; + if (sscanf_s(line.c_str(), "%f", &parsed) == 1 && parsed > 0.f) + { + LL_INFOS("RenderInit") << "subprocess_gpu_benchmark: result = " + << parsed << " GB/sec" << LL_ENDL; + // Let LLProcessPtr destructor handle cleanup + return parsed; + } + LL_WARNS("RenderInit") << "subprocess_gpu_benchmark: unparseable output: '" + << line << "'" << LL_ENDL; + return -1.f; + } + + // If child already exited without writing anything, bail + if (!child->isRunning()) + { + LL_WARNS("RenderInit") << "subprocess_gpu_benchmark: process exited without result." << LL_ENDL; + return -1.f; + } + + ms_sleep((U32)(POLL_INTERVAL_S * 1000)); + } + + // Timeout: attached=true means LLProcessPtr destructor kills it + LL_WARNS("RenderInit") << "GPU benchmark subprocess timed out after " + << (int)TOTAL_TIMEOUT_S << " seconds; killing." << LL_ENDL; + return -1.f; +} + F32 logExceptionBenchmark() { // FIXME: gpu_benchmark uses many C++ classes on the stack to control state. @@ -457,7 +534,27 @@ bool LLFeatureManager::loadGPUClass() try { #if LL_WINDOWS - gbps = logExceptionBenchmark(); + if (gGPUBenchmarkMode) + { + // We ARE the benchmark subprocess; run directly in-process. + // logExceptionBenchmark wraps with SEH so structured exceptions + // (e.g. access violations inside the driver) are still caught. + gbps = logExceptionBenchmark(); + } + else + { + // Normal path: run benchmark in an isolated subprocess so a + // driver hang can be killed without freezing the main viewer. + gbps = subprocess_gpu_benchmark(); + if (gbps == -1.f + && gGLManager.getRawGLString().find("Radeon") != std::string::npos + && checkRDNA35()) + { + // Certain AMD GPUs have known issues with shader profiling and occlusion queries that can lead to hangs. + // If test returned -1, we are likely on a bad driver. + gSavedSettings.setBOOL("UseOcclusion", false); + } + } #else gbps = gpu_benchmark(); #endif @@ -468,6 +565,21 @@ bool LLFeatureManager::loadGPUClass() LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL; } +#if LL_WINDOWS + // If we are the benchmark subprocess, write the raw result to stdout + // so the parent process can read it, then exit immediately. + if (gGPUBenchmarkMode) + { + LL_WARNS("RenderInit") << "Passing " << gbps << " to parent" << LL_ENDL; + char buf[64]; + int len = snprintf(buf, sizeof(buf), "%.6f\n", gbps); + DWORD written = 0; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, (DWORD)len, &written, NULL); + FlushFileBuffers(GetStdHandle(STD_OUTPUT_HANDLE)); + ExitProcess(0); + } +#endif + mGPUMemoryBandwidth = gbps; // bias by CPU speed -- cgit v1.3