summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-08-25 20:18:58 +0300
committerGitHub <noreply@github.com>2026-08-25 20:18:58 +0300
commit60b7e3b78cc401318e4f895bb64b5688645b3462 (patch)
treebe4775270cfb66e6cd414321fc3f0632dc511f14
parenta402fd4da91bbce03340e174505c06feede65866 (diff)
#6153 Make hardware test more robust
-rw-r--r--indra/llcommon/llprocess.cpp5
-rw-r--r--indra/llcommon/llprocess.h4
-rw-r--r--indra/newview/app_settings/cmd_line.xml6
-rw-r--r--indra/newview/llappviewer.cpp43
-rw-r--r--indra/newview/llappviewerwin32.cpp18
-rw-r--r--indra/newview/llcommandlineparser.cpp2
-rw-r--r--indra/newview/llfeaturemanager.cpp114
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