diff options
| author | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2026-08-25 20:18:58 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-25 20:18:58 +0300 |
| commit | 60b7e3b78cc401318e4f895bb64b5688645b3462 (patch) | |
| tree | be4775270cfb66e6cd414321fc3f0632dc511f14 | |
| parent | a402fd4da91bbce03340e174505c06feede65866 (diff) | |
#6153 Make hardware test more robust
| -rw-r--r-- | indra/llcommon/llprocess.cpp | 5 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.h | 4 | ||||
| -rw-r--r-- | indra/newview/app_settings/cmd_line.xml | 6 | ||||
| -rw-r--r-- | indra/newview/llappviewer.cpp | 43 | ||||
| -rw-r--r-- | indra/newview/llappviewerwin32.cpp | 18 | ||||
| -rw-r--r-- | indra/newview/llcommandlineparser.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llfeaturemanager.cpp | 114 |
7 files changed, 178 insertions, 14 deletions
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 @@ <string>ConnectAsGod</string> </map> + <key>gpubenchmark</key> + <map> + <key>desc</key> + <string>Run GPU memory bandwidth benchmark, print result to stdout, and exit. Used internally by the viewer to isolate benchmark from the main process.</string> + </map> + <key>graphicslevel</key> <map> <key>desc</key> 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<std::string> unmapped_options = { "help", "set", "setdefault", "settings", "sessionsettings", "usersessionsettings" }; + const std::set<std::string> 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 |
