summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorJonathan "Geenz" Goodman <geenz@geenzo.com>2026-08-12 16:09:42 -0400
committerJonathan "Geenz" Goodman <geenz@geenzo.com>2026-08-12 16:09:42 -0400
commit480af3ea32856007b166a06ca4932078c9f557c6 (patch)
tree010a3b43a75dae55d6a66d3cdece6277dc97a1d8 /indra
parent8cabc33e960ca93fdc1a32c77cf611269d95d51b (diff)
parent932e8ac318eb2e29ea519249348315b50c7d1d58 (diff)
Merge branch 'release/26.3' into geenz/linux-to-develop
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/llmemory.cpp119
-rw-r--r--indra/llcommon/llmemory.h17
-rw-r--r--indra/llcommon/llsys.cpp80
-rw-r--r--indra/llcommon/llsys.h9
-rw-r--r--indra/llcommon/llwatchdog.cpp5
-rw-r--r--indra/llcommon/llwatchdog.h2
-rw-r--r--indra/llrender/llimagegl.cpp2
-rw-r--r--indra/llrender/llrender.cpp15
-rw-r--r--indra/llui/llfloater.cpp2
-rw-r--r--indra/llui/llpanel.cpp3
-rw-r--r--indra/llwebrtc/CMakeLists.txt9
-rw-r--r--indra/llwebrtc/llwebrtc.cpp565
-rw-r--r--indra/llwebrtc/llwebrtc.h8
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h80
-rw-r--r--indra/llwindow/llwindowcallbacks.cpp2
-rw-r--r--indra/llwindow/llwindowcallbacks.h2
-rw-r--r--indra/llwindow/llwindowwin32.cpp72
-rw-r--r--indra/llxml/llcontrol.cpp45
-rw-r--r--indra/llxml/llcontrol.h8
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/Info-SecondLife.plist2
-rw-r--r--indra/newview/app_settings/settings.xml18
-rw-r--r--indra/newview/app_settings/shaders/class1/deferred/CASF.glsl8
-rw-r--r--indra/newview/llappviewer.cpp20
-rw-r--r--indra/newview/llappviewerwin32.cpp11
-rw-r--r--indra/newview/lldebugview.cpp2
-rw-r--r--indra/newview/lldrawpoolwater.cpp8
-rw-r--r--indra/newview/llfloaterimcontainer.cpp16
-rw-r--r--indra/newview/llfloaterimcontainer.h2
-rw-r--r--indra/newview/llfloatersettingsdebug.cpp42
-rw-r--r--indra/newview/llfloatersimplesnapshot.cpp197
-rw-r--r--indra/newview/llinventorymodel.cpp12
-rw-r--r--indra/newview/llinventorymodel.h4
-rw-r--r--indra/newview/llinventorymodelbackgroundfetch.cpp35
-rw-r--r--indra/newview/llinventorymodelbackgroundfetch.h6
-rw-r--r--indra/newview/llpanelprofile.cpp158
-rw-r--r--indra/newview/llpanelvoicedevicesettings.cpp7
-rw-r--r--indra/newview/llsidepanelinventory.cpp2
-rw-r--r--indra/newview/llstatslistener.cpp200
-rw-r--r--indra/newview/llstatslistener.h41
-rw-r--r--indra/newview/lltextureview.cpp2
-rw-r--r--indra/newview/lltoastnotifypanel.cpp2
-rw-r--r--indra/newview/llviewerassetupload.cpp95
-rw-r--r--indra/newview/llviewerdisplay.cpp5
-rw-r--r--indra/newview/llviewermessage.cpp10
-rw-r--r--indra/newview/llvieweroctree.cpp2
-rw-r--r--indra/newview/llviewerstats.cpp2
-rw-r--r--indra/newview/llviewerstats.h39
-rw-r--r--indra/newview/llviewertexture.cpp16
-rw-r--r--indra/newview/llviewertexture.h1
-rw-r--r--indra/newview/llviewertexturelist.cpp32
-rw-r--r--indra/newview/llviewerwindow.cpp9
-rw-r--r--indra/newview/llviewerwindow.h4
-rw-r--r--indra/newview/llvoavatar.cpp10
-rw-r--r--indra/newview/llvocache.cpp6
-rw-r--r--indra/newview/llvoicewebrtc.cpp22
-rw-r--r--indra/newview/skins/default/xui/en/floater_profile.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_script.xml4
-rwxr-xr-xindra/newview/viewer_manifest.py2
59 files changed, 1503 insertions, 600 deletions
diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp
index 9617e30f0b..6e6597431d 100644
--- a/indra/llcommon/llmemory.cpp
+++ b/indra/llcommon/llmemory.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, 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
@@ -52,6 +52,9 @@
//static
+// On windows commit charge information is vital for OOM diagnosis.
+U32Megabytes LLMemory::sAvailCommitMemInMB(U32_MAX);
+
// most important memory metric for texture streaming
// On Windows, this should agree with resource monitor -> performance -> memory -> available
// On OS X, this should be activity monitor -> memory -> (physical memory - memory used)
@@ -70,6 +73,12 @@ U32Kilobytes LLMemory::sAllocatedMemInKB(0);
U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0);
+LLFrameTimer LLMemory::sMemoryCheckTimer;
+F32 LLMemory::sSysMemoryFactor = 1.f;
+U32 LLMemory::sFactorLastFrameCount = 0;
+
+static const S32Megabytes MEM_LOW_THRESHOLD = S32Megabytes(256);
+
static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application");
static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application");
@@ -102,23 +111,14 @@ void LLMemory::updateMemoryInfo()
{
LL_PROFILE_ZONE_SCOPED;
+ sMemoryCheckTimer.reset();
sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB();
- U32Kilobytes avail_mem;
- LLMemoryInfo::getAvailableMemoryKB(avail_mem);
- sAvailPhysicalMemInKB = avail_mem;
+ LLMemoryInfo::updateAvailableMemory();
#if LL_WINDOWS
- PROCESS_MEMORY_COUNTERS counters;
-
- if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
- {
- LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
- return ;
- }
-
- sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize));
- sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage));
+ // On windows updateAvailableMemory fills sAvailPhysicalMemInKB,
+ //sAllocatedMemInKB and sAllocatedPageSizeInKB
sample(sVirtualMem, sAllocatedPageSizeInKB);
#elif defined(LL_DARWIN)
@@ -198,6 +198,97 @@ void LLMemory::logMemoryInfo(bool update)
LL_INFOS() << llformat("Current max usable memory: %.2f MB", sMaxPhysicalMemInKB / 1024.0) << LL_ENDL;
}
+void LLMemory::updateFreeSystemMemory()
+{
+ if (sMemoryCheckTimer.getElapsedTimeF32() >= 1.f) //once per second.
+ {
+ LLMemory::updateMemoryInfo(); // resets the timer
+ }
+}
+
+F32 LLMemory::getSystemMemoryBudgetFactor()
+{
+ // Only update once per frame
+ U32 current_frame = LLFrameTimer::getFrameCount();
+ if (sFactorLastFrameCount == current_frame)
+ {
+ return sSysMemoryFactor;
+ }
+ sFactorLastFrameCount = current_frame;
+
+ updateFreeSystemMemory();
+#if LL_WINDOWS
+ S32Megabytes free_sys_mem = getAvailableCommitMemMB();
+#else
+ S32Megabytes free_sys_mem = getAvailableMemKB();
+#endif
+ bool is_sys_low = free_sys_mem < MEM_LOW_THRESHOLD;
+ static bool was_low = false;
+
+ // sSysMemoryFactor affects draw distance
+ //
+ // We only decrement when more than 406MB is free, but increment
+ // when below 256MB free. This should provide a stable value
+ // in the 256-406MB range to avoid draw range fluctuations.
+ //
+ // Draw range reduction is a last resort, texture bias is supposed
+ // to free at least some memory before we get here.
+ // Note: textures were mostly moved to vram, we might want to
+ // detach texture bias from system memory.
+ if (is_sys_low)
+ {
+ // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory.
+ S32Megabytes sys_budget_debt = free_sys_mem - MEM_LOW_THRESHOLD;
+
+ // Leave some padding, otherwise we will crash out of memory before hitting factor 2.
+ const S32Megabytes PAD_BUFFER(32);
+ S32Megabytes budget_target = MEM_LOW_THRESHOLD - PAD_BUFFER;
+ if (!was_low)
+ {
+ // Result should range from 1 at 0 debt to 2 at -224 debt, 2.14 at -256MB
+ F32 new_factor = 1.f - (F32)sys_budget_debt.value() / (F32)budget_target.value();
+ sSysMemoryFactor = llmax(sSysMemoryFactor, new_factor);
+ }
+ else
+ {
+ // Slowly ramp up factor to free memory (increasing factor decreases draw range)
+ constexpr F32 MAX_INCREMENT = 0.05f;
+ F32 increment = MAX_INCREMENT * llmax(-(F32)sys_budget_debt.value() / (F32)budget_target.value(), 0.f);
+ sSysMemoryFactor += increment * LLFrameTimer::getFrameDeltaTimeF32();
+ }
+ sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f);
+ }
+ else
+ {
+ // Only start ramping down when we have breathing room.
+ // This should be under the value of isSystemMemoryLow to not throw texture
+ // bias into 1.5+ territory each time we fluctuate around isSystemMemoryLow's
+ // threshold.
+ const S32Megabytes MEM_THRESHOLD = MEM_LOW_THRESHOLD + S32Megabytes(150);
+ if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f)
+ {
+ // Ramp down factor over time.
+ constexpr F32 DECREMENT = 0.02f;
+ sSysMemoryFactor -= DECREMENT * LLFrameTimer::getFrameDeltaTimeF32();
+ sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f);
+ }
+ }
+ was_low = is_sys_low;
+
+ return sSysMemoryFactor;
+}
+
+#if LL_WINDOWS
+//static
+U32Megabytes LLMemory::getAvailableCommitMemMB()
+{
+ // Commit charge combines page file and ram,
+ // theoretical limit is 128TB on 64bit windows.
+ // Store as MB instead of KB to prevent overflow.
+ return sAvailCommitMemInMB;
+}
+#endif
+
//static
U32Kilobytes LLMemory::getAvailableMemKB()
{
diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h
index a25387d321..5f9339bbb7 100644
--- a/indra/llcommon/llmemory.h
+++ b/indra/llcommon/llmemory.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, 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
@@ -27,6 +27,7 @@
#define LLMEMORY_H
#include "linden_common.h"
+#include "llframetimer.h"
#include "llunits.h"
#include "stdtypes.h"
#if !LL_WINDOWS
@@ -420,17 +421,31 @@ public:
static void initMaxHeapSizeGB(F32Gigabytes max_heap_size);
static void updateMemoryInfo() ;
static void logMemoryInfo(bool update = false);
+ static F32 getSystemMemoryBudgetFactor();
+#if LL_WINDOWS
+ // Commit charge is a Windows-only concept, combines page file and ram
+ static U32Megabytes getAvailableCommitMemMB();
+#endif
static U32Kilobytes getAvailableMemKB() ;
static U32Kilobytes getMaxMemKB() ;
static U32Kilobytes getAllocatedMemKB() ;
private:
+ static void updateFreeSystemMemory();
+ // LLMemoryInfo directly updates memory stats
+ friend class LLMemoryInfo;
+
+ static U32Megabytes sAvailCommitMemInMB;
static U32Kilobytes sAvailPhysicalMemInKB ;
static U32Kilobytes sMaxPhysicalMemInKB ;
static U32Kilobytes sAllocatedMemInKB;
static U32Kilobytes sAllocatedPageSizeInKB ;
static U32Kilobytes sMaxHeapSizeInKB;
+
+ static LLFrameTimer sMemoryCheckTimer;
+ static F32 sSysMemoryFactor;
+ static U32 sFactorLastFrameCount;
};
// LLRefCount moved to llrefcount.h
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index 5ef042b5a7..b1b281001e 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, 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
@@ -43,6 +43,7 @@
#include "llerrorcontrol.h"
#include "llevents.h"
#include "llformat.h"
+#include "llmemory.h"
#include "llregex.h"
#include "lltimer.h"
#include "llsdserialize.h"
@@ -791,15 +792,13 @@ U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
}
//static
-void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb)
+void LLMemoryInfo::updateAvailableMemory()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
#if LL_WINDOWS
- // Sigh, this shouldn't be a static method, then we wouldn't have to
- // reload this data separately from refresh()
- LLSD statsMap(loadStatsMap());
-
- avail_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger();
+ // On windows loadStatsMap will fill sAvailPhysicalMemInKB,
+ // sAvailCommitMemInMB, sAllocatedMemInKB and sAllocatedPageSizeInKB
+ loadStatsMap();
#elif LL_DARWIN
// use host_statistics64 to get memory info
@@ -811,21 +810,21 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb)
kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
if (result == KERN_SUCCESS)
{
- avail_mem_kb = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size);
+ LLMemory::sAvailPhysicalMemInKB = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size);
}
else
{
- avail_mem_kb = (U32Kilobytes)-1;
+ LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1;
}
#elif LL_LINUX
U64 phys = U64(getpagesize()) * U64(get_avphys_pages());
- avail_mem_kb = U64Bytes(phys);
+ LLMemory::sAvailPhysicalMemInKB = U64Bytes(phys);
#else
//do not know how to collect available memory info for other systems.
//leave it blank here for now.
- avail_mem_kb = (U32Kilobytes)-1 ;
+ LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1 ;
#endif
}
@@ -896,15 +895,24 @@ LLSD LLMemoryInfo::loadStatsMap()
state.dwLength = sizeof(state);
GlobalMemoryStatusEx(&state);
- DWORDLONG div = 1024;
+ static constexpr DWORDLONG div = 1024;
- stats.add("Percent Memory use", state.dwMemoryLoad/div);
+ stats.add("Percent Memory use", state.dwMemoryLoad);
stats.add("Total Physical KB", state.ullTotalPhys/div);
stats.add("Avail Physical KB", state.ullAvailPhys/div);
+
+ // Despite the confusing naming "PageFile" , these values
+ // actually represent the committed memory limit for
+ // the system or the current process, whichever is smaller.
stats.add("Total page KB", state.ullTotalPageFile/div);
stats.add("Avail page KB", state.ullAvailPageFile/div);
- stats.add("Total Virtual KB", state.ullTotalVirtual/div);
- stats.add("Avail Virtual KB", state.ullAvailVirtual/div);
+
+ static constexpr DWORDLONG mb_div = 1024 * 1024;
+ stats.add("Total Virtual MB", state.ullTotalVirtual/mb_div); // ~134 million MB
+ stats.add("Avail Virtual MB", state.ullAvailVirtual/mb_div);
+
+ LLMemory::sAvailPhysicalMemInKB = U32Kilobytes::convert(U64Bytes(state.ullAvailPhys));
+ LLMemory::sAvailCommitMemInMB = U32Megabytes::convert(U64Bytes(state.ullAvailPageFile));
// SL-12122 - Call to GetPerformanceInfo() was removed here. Took
// on order of 10 ms, causing unacceptable frame time spike every
@@ -920,18 +928,38 @@ LLSD LLMemoryInfo::loadStatsMap()
// specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a
// classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the
// pointer.
- GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*) &pmem, sizeof(pmem));
+ if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmem, sizeof(pmem)))
+ {
+ LLMemory::sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(pmem.WorkingSetSize));
+ LLMemory::sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(pmem.PagefileUsage));
- stats.add("Page Fault Count", pmem.PageFaultCount);
- stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div);
- stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div);
- stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div);
- stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div);
- stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div);
- stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div);
- stats.add("PagefileUsage KB", pmem.PagefileUsage/div);
- stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div);
- stats.add("PrivateUsage KB", pmem.PrivateUsage/div);
+ stats.add("Page Fault Count", pmem.PageFaultCount);
+ stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize / div);
+ stats.add("WorkingSetSize KB", pmem.WorkingSetSize / div);
+ stats.add("QuotaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage / div);
+ stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage / div);
+ stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage / div);
+ stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage / div);
+ stats.add("PagefileUsage KB", pmem.PagefileUsage / div);
+ stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage / div);
+ stats.add("PrivateUsage KB", pmem.PrivateUsage / div);
+ }
+ else
+ {
+ LLMemory::sAllocatedMemInKB = U32Kilobytes(0);
+ LLMemory::sAllocatedPageSizeInKB = U32Kilobytes(0);
+
+ stats.add("Page Fault Count", 0);
+ stats.add("PeakWorkingSetSize KB", 0);
+ stats.add("WorkingSetSize KB", 0);
+ stats.add("QuotaPeakPagedPoolUsage KB", 0);
+ stats.add("QuotaPagedPoolUsage KB", 0);
+ stats.add("QuotaPeakNonPagedPoolUsage KB", 0);
+ stats.add("QuotaNonPagedPoolUsage KB", 0);
+ stats.add("PagefileUsage KB", 0);
+ stats.add("PeakPagefileUsage KB", 0);
+ stats.add("PrivateUsage KB", 0);
+ }
#elif LL_DARWIN
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h
index 827b0dc048..0abbe047ad 100644
--- a/indra/llcommon/llsys.h
+++ b/indra/llcommon/llsys.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, 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
@@ -134,11 +134,12 @@ public:
static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
#endif
- //get the available memory in KiloBytes.
- static void getAvailableMemoryKB(U32Kilobytes& avail_mem_kb);
+ // Updates LLMemory's values.
+ static void updateAvailableMemory();
// Retrieve a map of memory statistics. The keys of the map are platform-
- // dependent. The values are in kilobytes to try to avoid integer overflow.
+ // dependent.
+ // On Windows updates LLMemory values.
LLSD getStatsMap() const;
// Re-fetch memory data (as reported by stream() and getStatsMap()) from the
diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp
index b17b68dbe7..ad7c2db62e 100644
--- a/indra/llcommon/llwatchdog.cpp
+++ b/indra/llcommon/llwatchdog.cpp
@@ -122,6 +122,11 @@ bool LLWatchdogTimeout::hasExpired() const
return mTimer.hasExpired();
}
+bool LLWatchdogTimeout::started() const
+{
+ return mTimer.getStarted();
+}
+
void LLWatchdogTimeout::reset()
{
mTimer.setTimerExpirySec(mTimeout);
diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h
index 3e82fd36b9..a3a51113b0 100644
--- a/indra/llcommon/llwatchdog.h
+++ b/indra/llcommon/llwatchdog.h
@@ -48,6 +48,7 @@ public:
// isAlive and other method may need synchronization.
virtual bool isAlive() const = 0;
virtual bool hasExpired() const = 0;
+ virtual bool started() const = 0;
virtual void reset() = 0;
virtual void start();
virtual void stop();
@@ -68,6 +69,7 @@ public:
bool isAlive() const override;
bool hasExpired() const override;
+ bool started() const override;
void reset() override;
void start() override { start(""); }
void stop() override;
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index c004b36fef..fafc92b5d0 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -1853,7 +1853,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
LLGLint is_compressed = 0;
if (compressed_ok)
{
- glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed);
+ glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed);
}
//-----------------------------------------------------------------------------------------------
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 94b5e7d767..6a39a34c4e 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -193,6 +193,7 @@ void LLTexUnit::bindFast(LLTexture* texture)
glActiveTexture(GL_TEXTURE0 + mIndex);
gGL.mCurrTextureUnitIndex = mIndex;
mCurrTexture = gl_tex->getTexName();
+ mCurrTexType = gl_tex->getTarget();
if (!mCurrTexture)
{
LL_PROFILE_ZONE_NAMED("MISSING TEXTURE");
@@ -1642,7 +1643,16 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count)
LLVertexBuffer * vb = new LLVertexBuffer(attribute_mask);
vb->allocateBuffer(count, 0);
- vb->setBuffer();
+ // Non-Apple path uses glBufferSubData inside setXxxData, so the VBO
+ // must already be bound. On Apple, the VBO is lazily created in
+ // _unmapBuffer (LLAppleVBOPool); calling setBuffer() here would bind
+ // mGLBuffer == 0 and then setupVertexBuffer would issue
+ // glVertexAttribIPointer with a non-null offset against no bound
+ // GL_ARRAY_BUFFER -> GL_INVALID_OPERATION in core profile.
+ if (!gGLManager.mIsApple)
+ {
+ vb->setBuffer();
+ }
vb->setPositionData(mVerticesp.get());
@@ -1657,6 +1667,9 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count)
}
#if LL_DARWIN
+ // unmapBuffer creates the GL buffer, uploads, and leaves it bound,
+ // drawBuffer's later setBuffer() then runs setupVertexBuffer against
+ // a valid VBO.
vb->unmapBuffer();
#endif
vb->unbind();
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index f1a97ee074..b5e26db1b5 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -570,6 +570,8 @@ void LLFloater::storeRectControl()
void LLFloater::storeVisibilityControl()
{
+ // Todo: this is a bit pricey, gets called each frame
+ // on LLAppViewer::idle(), optimize!
if( !sQuitting && mVisibilityControl.size() > 1 )
{
getControlGroup()->setBOOL( mVisibilityControl, getVisible() );
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index d09fc3e22d..ff5a28b250 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -489,10 +489,9 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP);
LLXMLNodePtr referenced_xml;
- const std::string& xml_filename = mXMLFilename;
// if the panel didn't provide a filename, check the node
- if (xml_filename.empty())
+ if (mXMLFilename.empty())
{
std::string temp_filename;
node->getAttributeString("filename", temp_filename);
diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt
index eb10f4eee4..9d5cde0e6a 100644
--- a/indra/llwebrtc/CMakeLists.txt
+++ b/indra/llwebrtc/CMakeLists.txt
@@ -47,7 +47,14 @@ if (WINDOWS)
set_target_properties(llwebrtc PROPERTIES PDB_OUTPUT_DIRECTORY "${SYMBOLS_STAGING_DIR}")
endif (USE_BUGSPLAT)
elseif (DARWIN)
- target_link_libraries(llwebrtc PRIVATE ll::webrtc)
+ target_link_libraries(llwebrtc PRIVATE ll::webrtc
+ "-framework AVFoundation"
+ "-framework AudioToolbox"
+ "-framework CoreAudio"
+ "-framework CoreMedia"
+ "-framework CoreVideo"
+ "-framework Foundation"
+ )
if (USE_BUGSPLAT)
set_target_properties(llwebrtc PROPERTIES XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
XCODE_ATTRIBUTE_DWARF_DSYM_FOLDER_PATH "${SYMBOLS_STAGING_DIR}/dSYMs")
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index f4ecce63a6..bdb629172d 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -26,8 +26,11 @@
#include "llwebrtc_impl.h"
#include <algorithm>
+#include <chrono>
+#include <future>
+#include <thread>
#include <string.h>
-
+#include "api/audio/create_audio_device_module.h"
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
@@ -42,8 +45,8 @@
namespace llwebrtc
{
#if WEBRTC_WIN
-static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
-static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
+static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
+static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
#else
static int16_t PLAYOUT_DEVICE_DEFAULT = 0;
static int16_t RECORD_DEVICE_DEFAULT = 0;
@@ -134,7 +137,9 @@ int32_t LLWebRTCAudioTransport::NeedMorePlayData(size_t number_of_frames,
if (!engine)
{
// No engine sink; output silence to be safe.
- const size_t bytes = number_of_frames * bytes_per_frame * number_of_channels;
+ // bytes_per_frame already accounts for all channels, so do not multiply
+ // by number_of_channels again (that would overrun the playout buffer).
+ const size_t bytes = number_of_frames * bytes_per_frame;
memset(audio_data, 0, bytes);
number_of_samples_out = bytes_per_frame;
return 0;
@@ -250,17 +255,47 @@ void LLCustomProcessor::Process(webrtc::AudioBuffer *audio)
mState->setMicrophoneEnergy(std::sqrt(totalSum / (audio->num_channels() * audio->num_frames() * buffer_size)));
}
+
+//
+// LLWebRTCImpl implementation
+//
+
+void LLWebRTCAudioDeviceModule::SetTuning(bool tuning, bool mute)
+{
+ tuning_ = tuning;
+ if (tuning)
+ {
+ // Ensure capture is running (it's normally already running -- capture is
+ // session-long) so the mic-level meter works, and stop rendering the
+ // call while tuning. The recording calls are no-ops if capture is
+ // already active, so this won't cold-start it.
+ inner_->InitMicrophone();
+ inner_->InitRecording();
+ inner_->StartRecording();
+ inner_->StopPlayout();
+ }
+ // On exit, capture is deliberately left running (mute is handled by gain,
+ // not by stopping the device, so there's no AEC cold-start hiss). Playout
+ // is restored by the caller via workerOpenPlayout(), keeping it gated on
+ // there being a connection to render.
+}
+
//
// LLWebRTCImpl implementation
//
LLWebRTCImpl::LLWebRTCImpl(LLWebRTCLogCallback* logCallback) :
+ mEnv(webrtc::CreateEnvironment(webrtc::CreateDefaultTaskQueueFactory())),
mLogSink(new LLWebRTCLogSink(logCallback)),
mPeerCustomProcessor(nullptr),
mMute(true),
+ mVoiceEnabled(false),
mTuningMode(false),
mDevicesDeploying(0),
- mGain(0.0f)
+ mGain(0.0f),
+ mBuiltinNS(false),
+ mBuiltinAGC(false),
+ mBuiltinAEC(false)
{
}
@@ -273,8 +308,6 @@ void LLWebRTCImpl::init()
webrtc::LogMessage::SetLogToStderr(true);
webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE);
- mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
-
// Create the native threads.
mNetworkThread = webrtc::Thread::CreateWithSocketServer();
mNetworkThread->SetName("WebRTCNetworkThread", nullptr);
@@ -290,9 +323,17 @@ void LLWebRTCImpl::init()
[this]()
{
webrtc::scoped_refptr<webrtc::AudioDeviceModule> realADM =
- webrtc::AudioDeviceModule::Create(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, mTaskQueueFactory.get());
+ webrtc::CreateAudioDeviceModule(mEnv, webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio);
mDeviceModule = webrtc::make_ref_counted<LLWebRTCAudioDeviceModule>(realADM);
mDeviceModule->SetObserver(this);
+ mDeviceModule->Init();
+
+ mBuiltinNS = mDeviceModule->BuiltInNSIsAvailable();
+ mBuiltinAEC = mDeviceModule->BuiltInAECIsAvailable();
+ mBuiltinAGC = mDeviceModule->BuiltInAGCIsAvailable();
+ // All audio processing is done by WebRTC's software APM (configured
+ // below); make sure the hardware processors stay off.
+ workerDisableBuiltInAudioProcessing();
});
// The custom processor allows us to retrieve audio data (and levels)
@@ -302,17 +343,22 @@ void LLWebRTCImpl::init()
apb.SetCapturePostProcessing(std::make_unique<LLCustomProcessor>(mPeerCustomProcessor));
mAudioProcessingModule = apb.Build(webrtc::CreateEnvironment());
+ // Initial software-APM state, matching setAudioConfig() so there's no
+ // window where processing differs before the viewer's first config call.
+ // All processing is done here in software (the hardware AEC/AGC/NS is kept
+ // disabled), so enable echo cancellation from the very first frame.
webrtc::AudioProcessing::Config apm_config;
- apm_config.echo_canceller.enabled = false;
- apm_config.echo_canceller.mobile_mode = false;
- apm_config.gain_controller1.enabled = false;
- apm_config.gain_controller2.enabled = true;
- apm_config.high_pass_filter.enabled = true;
- apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
- apm_config.transient_suppression.enabled = true;
- apm_config.pipeline.multi_channel_render = true;
- apm_config.pipeline.multi_channel_capture = false;
+ apm_config.echo_canceller.enabled = true;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = false;
+ apm_config.gain_controller2.enabled = true;
+ apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
mAudioProcessingModule->ApplyConfig(apm_config);
@@ -344,7 +390,6 @@ void LLWebRTCImpl::init()
{
if (mDeviceModule)
{
- mDeviceModule->EnableBuiltInAEC(false);
updateDevices();
}
});
@@ -353,8 +398,23 @@ void LLWebRTCImpl::init()
void LLWebRTCImpl::terminate()
{
- mWorkerThread->BlockingCall(
- [this]()
+ // Run all blocking WebRTC shutdown calls on a separate thread so that a
+ // hung BlockingCall cannot block the viewer shutdown indefinitely.
+ // Webrtc is not mission critical, we need to save personal data.
+ auto done_promise = std::make_shared<std::promise<void> >();
+ std::future<void> done_future = done_promise->get_future();
+
+ // Hand ownership of the connections to the shutdown thread. Nothing on
+ // this thread may touch them afterwards -- in the timeout case below the
+ // shutdown thread is detached and may still be working through them.
+ std::vector<webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> connections;
+ connections.swap(mPeerConnections);
+
+ std::thread shutdown_thread(
+ [this, connections = std::move(connections), done_promise]() mutable
+ {
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
@@ -363,79 +423,155 @@ void LLWebRTCImpl::terminate()
}
});
- for (auto &connection : mPeerConnections)
- {
- connection->terminate();
- }
+ // Close the connections inline on the signaling thread. This can't be
+ // connection->terminate(), which only *posts* the close: that queues the
+ // real work behind everything below, so the connections would be closed
+ // after the factory and the device module are gone -- or not at all, if
+ // the thread is destroyed with the task still queued.
+ //
+ // It matters that the close completes here because closing a peer
+ // connection flushes any in-flight GetStats request and runs its
+ // callback inline, and that callback calls back into the viewer's
+ // signaling observers. Those observers are only valid until
+ // llwebrtc::terminate() returns.
+ mSignalingThread->BlockingCall(
+ [&connections]()
+ {
+ for (auto& connection : connections)
+ {
+ connection->closeOnSignalingThread();
+ }
+ // Destroy the connections here, on the signaling thread, while
+ // it's still running.
+ connections.clear();
+ });
- // connection->terminate() above spawns a number of Signaling thread calls to
- // shut down the connection. The following Blocking Call will wait
- // until they're done before it's executed, allowing time to clean up.
+ // Drain anything the closes posted before dropping the factory.
+ mSignalingThread->BlockingCall([]() {});
- mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; });
+ mSignalingThread->BlockingCall([this]() {
+ mPeerConnectionFactory = nullptr;
+ });
- mWorkerThread->BlockingCall(
- [this]()
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
- mDeviceModule->Terminate();
+ mDeviceModule->ForceTerminate();
}
- mDeviceModule = nullptr;
- mTaskQueueFactory = nullptr;
+ mDeviceModule = nullptr;
});
- // In case peer connections still somehow have jobs in workers,
- // only clear connections up after clearing workers.
+ done_promise->set_value();
+ });
+
+ constexpr auto WEBRTC_TERMINATE_TIMEOUT = std::chrono::seconds(10);
+ if (done_future.wait_for(WEBRTC_TERMINATE_TIMEOUT) == std::future_status::timeout)
+ {
+ RTC_LOG(LS_WARNING) << __FUNCTION__
+ << ": timed out waiting for WebRTC thread shutdown."
+ " Detaching — some WebRTC resources will be leaked.";
+ shutdown_thread.detach();
+
+ // Release the unique_ptrs WITHOUT joining/deleting: the detached thread
+ // may still be using these thread objects.
+ // The raw pointers are intentionally leaked — the process is exiting anyway
+ // and our priority is saving cache and personal data.
+ (void)mNetworkThread.release();
+ (void)mWorkerThread.release();
+ (void)mSignalingThread.release();
+
+ // mPeerConnections is already empty -- the detached thread owns the
+ // connections now and must be left to finish with them.
+ webrtc::LogMessage::RemoveLogToStream(mLogSink);
+ return;
+ }
+
+ shutdown_thread.join();
+
+ // The connections were closed and destroyed on the signaling thread before
+ // the shutdown thread finished, so it's safe to drop the threads now.
mNetworkThread = nullptr;
mWorkerThread = nullptr;
mSignalingThread = nullptr;
- mPeerConnections.clear();
webrtc::LogMessage::RemoveLogToStream(mLogSink);
}
+
void LLWebRTCImpl::setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config)
{
+ // All audio processing is handled by WebRTC's software APM here. The
+ // platform/hardware AEC/AGC/NS is always disabled (see
+ // workerDisableBuiltInAudioProcessing), so these are enabled purely on the
+ // requested config without deferring to any built-in processor.
webrtc::AudioProcessing::Config apm_config;
- apm_config.echo_canceller.enabled = config.mEchoCancellation;
- apm_config.echo_canceller.mobile_mode = false;
- apm_config.gain_controller1.enabled = false;
- apm_config.gain_controller2.enabled = config.mAGC;
+ apm_config.echo_canceller.enabled = config.mEchoCancellation;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = false;
+ apm_config.gain_controller2.enabled = config.mAGC;
apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech
- apm_config.high_pass_filter.enabled = true;
- apm_config.transient_suppression.enabled = true;
- apm_config.pipeline.multi_channel_render = true;
- apm_config.pipeline.multi_channel_capture = true;
- apm_config.pipeline.multi_channel_capture = true;
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
switch (config.mNoiseSuppressionLevel)
{
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_NONE:
apm_config.noise_suppression.enabled = false;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_LOW:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_MODERATE:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_HIGH:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_VERY_HIGH:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
break;
default:
apm_config.noise_suppression.enabled = false;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
}
mAudioProcessingModule->ApplyConfig(apm_config);
+
+ // Keep the hardware processors off; the APM above is the only processing.
+ PostWorkerTask([this]() { workerDisableBuiltInAudioProcessing(); });
+}
+
+void LLWebRTCImpl::workerDisableBuiltInAudioProcessing()
+{
+ if (!mDeviceModule)
+ {
+ return;
+ }
+
+ // We always use WebRTC's internal (software APM) audio processing. Running
+ // the platform/hardware AEC, AGC, or NS alongside it causes the two to
+ // fight -- pumping levels, double noise suppression, and mismatched AEC
+ // references -- so disable any that the device exposes.
+ if (mBuiltinNS)
+ {
+ mDeviceModule->EnableBuiltInNS(false);
+ }
+ if (mBuiltinAGC)
+ {
+ mDeviceModule->EnableBuiltInAGC(false);
+ }
+ if (mBuiltinAEC)
+ {
+ mDeviceModule->EnableBuiltInAEC(false);
+ }
}
void LLWebRTCImpl::refreshDevices()
@@ -455,20 +591,25 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
}
}
-// must be run in the worker thread.
-void LLWebRTCImpl::workerDeployDevices()
+// must be run in the worker thread. Selects the configured capture device and
+// starts recording. Capture runs the whole time voice is enabled (it's never
+// stopped for mute or between calls, so the AEC never cold-starts -- there's no
+// hiss on unmute), so this is a no-op when already recording. Device changes
+// go through workerDeployDevices(), which stops recording first to force a
+// clean re-select; voice off goes through setVoiceEnabled(false).
+void LLWebRTCImpl::workerStartRecording()
{
- if (!mDeviceModule)
+ // Only run capture while voice is enabled, and never cold-start it when
+ // it's already running (that would cause the unmute hiss).
+ if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording())
{
return;
}
int16_t recordingDevice = RECORD_DEVICE_DEFAULT;
- int16_t recording_device_start = 0;
-
if (mRecordingDevice != "Default")
{
- for (int16_t i = recording_device_start; i < mRecordingDeviceList.size(); i++)
+ for (int16_t i = 0; i < mRecordingDeviceList.size(); i++)
{
if (mRecordingDeviceList[i].mID == mRecordingDevice)
{
@@ -484,8 +625,6 @@ void LLWebRTCImpl::workerDeployDevices()
}
}
- mDeviceModule->StopPlayout();
- mDeviceModule->ForceStopRecording();
#if WEBRTC_WIN
if (recordingDevice < 0)
{
@@ -500,13 +639,32 @@ void LLWebRTCImpl::workerDeployDevices()
#endif
mDeviceModule->InitMicrophone();
mDeviceModule->SetStereoRecording(false);
+ // A newly-selected capture device may default its hardware AEC/AGC/NS on;
+ // disable before InitRecording so the recording stream is configured to
+ // use only WebRTC's software APM.
+ workerDisableBuiltInAudioProcessing();
mDeviceModule->InitRecording();
+ mDeviceModule->ForceStartRecording();
+}
+
+// must be run in the worker thread. Selects the configured playout device and
+// starts playout. Playout only runs while there's a connection to render
+// (running the output device with no engine data is heard as a buzz), so this
+// is a no-op when there are no connections or when already playing. Device
+// changes go through workerDeployDevices(), which stops playout first.
+void LLWebRTCImpl::workerStartPlayout()
+{
+ // Only run playout while voice is enabled and there's a connection to
+ // render (running the output device otherwise is heard as a buzz).
+ if (!mDeviceModule || !mVoiceEnabled || mTuningMode || mDeviceModule->Playing() || mPeerConnections.empty())
+ {
+ return;
+ }
int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT;
- int16_t playout_device_start = 0;
if (mPlayoutDevice != "Default")
{
- for (int16_t i = playout_device_start; i < mPlayoutDeviceList.size(); i++)
+ for (int16_t i = 0; i < mPlayoutDeviceList.size(); i++)
{
if (mPlayoutDeviceList[i].mID == mPlayoutDevice)
{
@@ -537,16 +695,29 @@ void LLWebRTCImpl::workerDeployDevices()
mDeviceModule->InitSpeaker();
mDeviceModule->SetStereoPlayout(true);
mDeviceModule->InitPlayout();
+ mDeviceModule->StartPlayout();
+}
- if ((!mMute && mPeerConnections.size()) || mTuningMode)
+// must be run in the worker thread. Used for device changes and tuning: forces
+// a clean re-select of both devices, then re-applies per-connection mute/track
+// state. To merely bring playout up when a connection is established (without
+// disturbing the connection's own mute/track management) call
+// workerOpenPlayout() directly -- see startPlayout().
+void LLWebRTCImpl::workerDeployDevices()
+{
+ if (!mDeviceModule)
{
- mDeviceModule->ForceStartRecording();
+ return;
}
- if (!mTuningMode)
- {
- mDeviceModule->StartPlayout();
- }
+ // Stop first so the start helpers (which no-op when already running) will
+ // re-select the now-current device.
+ mDeviceModule->StopPlayout();
+ mDeviceModule->ForceStopRecording();
+
+ workerStartRecording();
+ workerStartPlayout();
+
mSignalingThread->PostTask(
[this]
{
@@ -588,6 +759,35 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id)
}
}
+void LLWebRTCImpl::setVoiceEnabled(bool enable)
+{
+ mVoiceEnabled = enable;
+ mWorkerThread->PostTask(
+ [this, enable]()
+ {
+ if (!mDeviceModule)
+ {
+ return;
+ }
+ if (enable)
+ {
+ // Voice on: start the capture device (it then stays running
+ // across calls and mute/unmute), and start playout if there's
+ // already a connection to render.
+ mDeviceModule->Init();
+ workerDeployDevices();
+ }
+ else
+ {
+ // Voice off: release both devices so the OS mic/speaker aren't
+ // held open.
+ mDeviceModule->ForceStopRecording();
+ mDeviceModule->StopPlayout();
+ mDeviceModule->ForceTerminate();
+ }
+ });
+}
+
// updateDevices needs to happen on the worker thread.
void LLWebRTCImpl::updateDevices()
{
@@ -611,6 +811,7 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->PlayoutDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: playout device [" << index << "] name='" << name << "' guid='" << guid << "'";
mPlayoutDeviceList.emplace_back(name, guid);
}
@@ -629,13 +830,18 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->RecordingDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: recording device [" << index << "] name='" << name << "' guid='" << guid << "'";
mRecordingDeviceList.emplace_back(name, guid);
}
+ RTC_LOG(LS_INFO) << "updateDevices, playout count: " << renderDeviceCount << "; capture count: " << captureDeviceCount;
+
for (auto &observer : mVoiceDevicesObserverList)
{
observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList);
}
+
+ deployDevices();
}
void LLWebRTCImpl::OnDevicesUpdated()
@@ -658,6 +864,13 @@ void LLWebRTCImpl::setTuningMode(bool enable)
[this]
{
mDeviceModule->SetTuning(mTuningMode, mMute);
+ if (!mTuningMode)
+ {
+ // Restore playout after tuning, gated on there being a
+ // connection to render (so the output device isn't left
+ // spinning with no engine data).
+ workerStartPlayout();
+ }
mSignalingThread->PostTask(
[this]
{
@@ -729,39 +942,16 @@ void LLWebRTCImpl::setMute(bool mute, int delay_ms)
void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
{
+ // Mute by zeroing the captured (post-APM) gain; the sender track is also
+ // disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). The
+ // capture device deliberately stays running for the whole session, so
+ // muting/unmuting never stops or starts it -- that's what avoids the AEC
+ // cold-start hiss on unmute. Capture start/stop is tied to device
+ // selection (workerStartRecording) and shutdown, not to mute.
if (mPeerCustomProcessor)
{
mPeerCustomProcessor->setGain(mMute ? 0.0f : mGain);
}
-
- // Sequence counter to prevent race conditions from rapid requests to mute/unmute
- static std::atomic<uint32_t> mute_sequence(0);
- uint32_t current_sequence = ++mute_sequence;
-
- if (mMute)
- {
- mWorkerThread->PostDelayedTask(
- [this, current_sequence]
- {
- if (mDeviceModule && (current_sequence == mute_sequence.load()))
- {
- mDeviceModule->ForceStopRecording();
- }
- },
- webrtc::TimeDelta::Millis(delay_ms));
- }
- else
- {
- mWorkerThread->PostTask(
- [this, current_sequence]
- {
- if (mDeviceModule && (current_sequence == mute_sequence.load()))
- {
- mDeviceModule->InitRecording();
- mDeviceModule->ForceStartRecording();
- }
- });
- }
}
//
@@ -770,8 +960,7 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
{
- bool empty = mPeerConnections.empty();
- webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>());
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>(mEnv));
peerConnection->init(this);
if (mPeerConnections.empty())
{
@@ -779,6 +968,13 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
}
mPeerConnections.emplace_back(peerConnection);
+ // Playout is intentionally NOT started here. This runs when the connection
+ // is created/connecting; starting the output device now leaves it spinning
+ // with no decoded audio during the handshake, which is heard as a buzz.
+ // Playout is started from OnConnectionChange(kConnected) instead, once audio
+ // is actually established (see startPlayout()). Capture follows
+ // voice-enabled state, so it's not touched here either.
+
peerConnection->enableSenderTracks(false);
peerConnection->resetMute();
return peerConnection.get();
@@ -795,10 +991,45 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
if (mPeerConnections.empty())
{
intSetMute(true);
+ // Last connection gone: stop playout (there's nothing to render).
+ // Capture stays running while voice is enabled so it's ready -- with
+ // no cold-start hiss -- when the next call comes up. But if voice
+ // has been disabled, stop capture now: setVoiceEnabled(false) tried
+ // to, but the engine's send stream was still active then (and the
+ // engine's own StopRecording is intentionally a no-op), so the stop
+ // only sticks once the connection -- and its stream -- is gone.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ if (mDeviceModule)
+ {
+ mDeviceModule->StopPlayout();
+ if (!mVoiceEnabled)
+ {
+ mDeviceModule->ForceStopRecording();
+ }
+ }
+ });
}
}
}
+void LLWebRTCImpl::startPlayout()
+{
+ // Called when a connection's audio is established. Only playout is started
+ // here: it's gated on there being a connection to render, because running
+ // the output device with no engine data is heard as a buzz. Capture is
+ // NOT touched here -- it follows voice-enabled state (setVoiceEnabled), so
+ // it's already running if voice is on and must stay off if voice is off.
+ // Starting it here would also let a stray kConnected during voice-disable
+ // teardown re-open the mic.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ workerStartPlayout();
+ });
+}
+
//
// LLWebRTCPeerConnectionImpl implementation.
@@ -806,13 +1037,16 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
// Most peer connection (signaling) happens on
// the signaling thread.
-LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() :
+LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl(const webrtc::Environment& env) :
+ mEnv(env),
mWebRTCImpl(nullptr),
mPeerConnection(nullptr),
mMute(MUTE_INITIAL),
mAnswerReceived(false),
mPeerConnectionState(webrtc::PeerConnectionInterface::PeerConnectionState::kNew),
mDisconnectCount(0),
+ mStatsRequestPending(false),
+ mShuttingDown(false),
mPendingJobs(0)
{
}
@@ -845,47 +1079,64 @@ void LLWebRTCPeerConnectionImpl::terminate()
mWebRTCImpl->PostSignalingTask(
[self]()
{
- if (self->mPeerConnection)
- {
- if (self->mDataChannel)
- {
- {
- self->mDataChannel->Close();
- self->mDataChannel = nullptr;
- }
- }
+ self->closeOnSignalingThread();
+ self->mPendingJobs--;
+ });
+}
- // to remove 'Secondlife is recording' icon from taskbar
- // if user was speaking
- auto senders = self->mPeerConnection->GetSenders();
- for (auto& sender : senders)
- {
- auto track = sender->track();
- if (track)
- {
- track->set_enabled(false);
- }
- }
+// Signaling thread only.
+void LLWebRTCPeerConnectionImpl::closeOnSignalingThread()
+{
+ // Stop issuing stats requests; one may already be in flight, and
+ // Close() below will flush it.
+ mShuttingDown = true;
- self->mPeerConnection->Close();
- if (self->mLocalStream)
- {
- auto tracks = self->mLocalStream->GetAudioTracks();
- for (auto& track : tracks)
- {
- self->mLocalStream->RemoveTrack(track);
- }
- self->mLocalStream = nullptr;
- }
- self->mPeerConnection = nullptr;
+ if (mPeerConnection)
+ {
+ if (mDataChannel)
+ {
+ mDataChannel->Close();
+ mDataChannel = nullptr;
+ }
- for (auto &observer : self->mSignalingObserverList)
- {
- observer->OnPeerConnectionClosed();
- }
+ // to remove 'Secondlife is recording' icon from taskbar
+ // if user was speaking
+ auto senders = mPeerConnection->GetSenders();
+ for (auto& sender : senders)
+ {
+ auto track = sender->track();
+ if (track)
+ {
+ track->set_enabled(false);
}
- self->mPendingJobs--;
- });
+ }
+
+ // NOTE: Close() delivers any pending GetStats report inline, before it
+ // returns, so the observer list below must still be valid here.
+ mPeerConnection->Close();
+ if (mLocalStream)
+ {
+ auto tracks = mLocalStream->GetAudioTracks();
+ for (auto& track : tracks)
+ {
+ mLocalStream->RemoveTrack(track);
+ }
+ mLocalStream = nullptr;
+ }
+ mPeerConnection = nullptr;
+
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnPeerConnectionClosed();
+ }
+ }
+
+ // Nothing may call back into the viewer past this point. On shutdown the
+ // viewer's connection objects are torn down as soon as llwebrtc::terminate()
+ // returns and they deliberately don't unset themselves as observers, so any
+ // late callback would be reaching into freed memory.
+ mSignalingObserverList.clear();
+ mDataObserverList.clear();
}
void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); }
@@ -1255,6 +1506,12 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
{
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
{
+ // Audio is established now -- start playout for this connection.
+ // (Capture follows voice-enabled state, so it's already running and
+ // isn't touched here.) Doing playout here rather than at connection
+ // creation avoids running the output device with no decoded audio
+ // during the handshake (heard as a buzz).
+ mWebRTCImpl->startPlayout();
mPendingJobs++;
webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
mWebRTCImpl->PostWorkerTask([self]()
@@ -1267,6 +1524,7 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
});
break;
}
+
case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
{
for (auto &observer : mSignalingObserverList)
@@ -1605,16 +1863,41 @@ void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
return;
}
- auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
- [this](const LLWebRTCStatsMap& generic_stats)
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
+ mWebRTCImpl->PostSignalingTask(
+ [self]()
+ {
+ if (!self->mPeerConnection
+ || self->mShuttingDown
+ || self->mPeerConnectionState != webrtc::PeerConnectionInterface::PeerConnectionState::kConnected
+ || self->mStatsRequestPending) // signaling thread only
+ {
+ return;
+ }
+
+ self->mStatsRequestPending = true;
+
+ auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
+ [self](const LLWebRTCStatsMap& generic_stats)
{
- for (auto& observer : mSignalingObserverList)
+ self->mStatsRequestPending = false;
+
+ // This can be delivered inline from PeerConnection::Close(), which
+ // flushes pending stats requests as it tears down. Don't call out
+ // to the observers in that case -- we're on our way out.
+ if (!self->mPeerConnection || self->mShuttingDown)
+ {
+ return;
+ }
+
+ for (auto& observer : self->mSignalingObserverList)
{
observer->OnStatsDelivered(generic_stats);
}
});
- mPeerConnection->GetStats(stats_callback.get());
+ self->mPeerConnection->GetStats(stats_callback.get());
+ });
}
LLWebRTCImpl * gWebRTCImpl = nullptr;
diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h
index e76e708f0c..821400cfe8 100644
--- a/indra/llwebrtc/llwebrtc.h
+++ b/indra/llwebrtc/llwebrtc.h
@@ -153,6 +153,14 @@ class LLWebRTCDeviceInterface
virtual void setCaptureDevice(const std::string& id) = 0;
virtual void setRenderDevice(const std::string& id) = 0;
+ // Enable/disable the audio devices, set when voice is enabled/disabled.
+ // The capture (microphone) and playout (speaker) devices only run while this
+ // is enabled, so neither is held open when the user has voice off. While
+ // enabled, capture stays running across calls and mute/unmute so the AEC
+ // never cold-starts (no unmute hiss); playout still only runs when there's a
+ // connection to render.
+ virtual void setVoiceEnabled(bool enable) = 0;
+
// Device observers for device change callbacks.
virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
virtual void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index bd7a2e0bcf..cee42cf19a 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -180,7 +180,7 @@ private:
class LLWebRTCAudioDeviceModule : public webrtc::AudioDeviceModule
{
public:
- explicit LLWebRTCAudioDeviceModule(webrtc::scoped_refptr<webrtc::AudioDeviceModule> inner) : inner_(std::move(inner)), tuning_(false)
+ explicit LLWebRTCAudioDeviceModule(webrtc::scoped_refptr<webrtc::AudioDeviceModule> inner) : inner_(inner), tuning_(false)
{
RTC_CHECK(inner_);
}
@@ -197,9 +197,15 @@ public:
}
int32_t Init() override { return inner_->Init(); }
- int32_t Terminate() override { return inner_->Terminate(); }
+ int32_t Terminate() override {
+ // libwebrtc attempts to terminate the adm when peer connections go to zero, but we don't want that,
+ // now that we're keeping the adm active throughout the session.
+ return 0;
+ }
bool Initialized() const override { return inner_->Initialized(); }
+ int32_t ForceTerminate() { return inner_->Terminate(); }
+
// --- Device enumeration/selection (forward) ---
int16_t PlayoutDevices() override { return inner_->PlayoutDevices(); }
int16_t RecordingDevices() override { return inner_->RecordingDevices(); }
@@ -323,30 +329,8 @@ public:
// tuning microphone energy calculations
float GetMicrophoneEnergy() { return audio_transport_.GetMicrophoneEnergy(); }
void SetTuningMicGain(float gain) { audio_transport_.SetGain(gain); }
- void SetTuning(bool tuning, bool mute)
- {
- tuning_ = tuning;
- if (tuning)
- {
- inner_->InitRecording();
- inner_->StartRecording();
- inner_->StopPlayout();
- }
- else
- {
- if (mute)
- {
- inner_->StopRecording();
- }
- else
- {
- inner_->InitRecording();
- inner_->StartRecording();
- }
- inner_->InitPlayout();
- inner_->StartPlayout();
- }
- }
+
+ void SetTuning(bool tuning, bool mute);
protected:
~LLWebRTCAudioDeviceModule() override = default;
@@ -436,7 +420,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
//
void setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config = LLWebRTCDeviceInterface::AudioConfig()) override;
-
void refreshDevices() override;
void setDevicesObserver(LLWebRTCDevicesObserver *observer) override;
@@ -445,6 +428,8 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
void setCaptureDevice(const std::string& id) override;
void setRenderDevice(const std::string& id) override;
+ void setVoiceEnabled(bool enable) override;
+
void setTuningMode(bool enable) override;
float getTuningAudioLevel() override;
float getPeerConnectionAudioLevel() override;
@@ -522,9 +507,23 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
LLWebRTCPeerConnectionInterface* newPeerConnection();
void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection);
+ // Start playout once a connection's audio is established (playout is gated
+ // on there being a connection to render). Capture is not touched here --
+ // it follows voice-enabled state, not connection state. Safe to call from
+ // any thread (work is posted to the worker thread).
+ void startPlayout();
+
protected:
+ const webrtc::Environment mEnv;
+ void workerStartRecording();
+ void workerStartPlayout();
void workerDeployDevices();
+ // We always rely on WebRTC's internal (software APM) audio processing, so
+ // any platform/hardware AEC/AGC/NS must be kept disabled.
+ void workerDisableBuiltInAudioProcessing();
+
+
LLWebRTCLogSink* mLogSink;
// The native webrtc threads
@@ -537,10 +536,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
webrtc::scoped_refptr<webrtc::AudioProcessing> mAudioProcessingModule;
- // more native webrtc stuff
- std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory;
-
-
// Devices
void updateDevices();
void deployDevices();
@@ -548,6 +543,10 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
webrtc::scoped_refptr<LLWebRTCAudioDeviceModule> mDeviceModule;
std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList;
+ bool mBuiltinNS;
+ bool mBuiltinAGC;
+ bool mBuiltinAEC;
+
// accessors in native webrtc for devices aren't apparently implemented yet.
bool mTuningMode;
std::string mRecordingDevice;
@@ -557,6 +556,8 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
LLWebRTCVoiceDeviceList mPlayoutDeviceList;
bool mMute;
+ // Whether voice is enabled; gates whether the capture/playout devices run.
+ bool mVoiceEnabled;
float mGain;
LLCustomProcessorStatePtr mPeerCustomProcessor;
@@ -580,11 +581,16 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
{
public:
- LLWebRTCPeerConnectionImpl();
+ LLWebRTCPeerConnectionImpl(const webrtc::Environment& env);
~LLWebRTCPeerConnectionImpl();
void init(LLWebRTCImpl * webrtc_impl);
+ // Posts closeOnSignalingThread() and returns immediately.
void terminate();
+ // The actual close. Signaling thread only. Callable directly (via a
+ // BlockingCall) when the caller needs the connection to be fully closed
+ // before it continues -- see LLWebRTCImpl::terminate().
+ void closeOnSignalingThread();
virtual void AddRef() const override = 0;
virtual webrtc::RefCountReleaseStatus Release() const override = 0;
@@ -659,7 +665,7 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
void gatherConnectionStats() override;
protected:
-
+ const webrtc::Environment mEnv;
LLWebRTCImpl * mWebRTCImpl;
webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
@@ -687,6 +693,14 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
webrtc::PeerConnectionInterface::PeerConnectionState mPeerConnectionState;
uint32_t mDisconnectCount;
+ // Accessed only on the WebRTC signaling thread.
+ bool mStatsRequestPending;
+
+ // Set by closeOnSignalingThread() so that no new stats request (or other
+ // callback into the viewer) is issued while we're tearing down.
+ // Accessed only on the WebRTC signaling thread.
+ bool mShuttingDown;
+
std::atomic<int> mPendingJobs;
};
diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp
index 8d1eebe33d..f997aebb9d 100644
--- a/indra/llwindow/llwindowcallbacks.cpp
+++ b/indra/llwindow/llwindowcallbacks.cpp
@@ -202,7 +202,7 @@ bool LLWindowCallbacks::handleTimerEvent(LLWindow *window)
return false;
}
-bool LLWindowCallbacks::handleDeviceChange(LLWindow *window)
+bool LLWindowCallbacks::handleDeviceChange(LLWindow *window, const std::string& change_type)
{
return false;
}
diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h
index 390e3ff93a..cd54ccd7c9 100644
--- a/indra/llwindow/llwindowcallbacks.h
+++ b/indra/llwindow/llwindowcallbacks.h
@@ -72,7 +72,7 @@ public:
virtual void handleWindowUnblock(LLWindow *window); // window coming back after taking over CPU for a while
virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data);
virtual bool handleTimerEvent(LLWindow *window);
- virtual bool handleDeviceChange(LLWindow *window);
+ virtual bool handleDeviceChange(LLWindow *window, const std::string& change_type);
virtual bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height);
virtual bool handleDisplayChanged();
virtual bool handleWindowDidChangeScreen(LLWindow *window);
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index e947948c57..3fccc1fbff 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -466,6 +466,16 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
}
});
}
+
+ // For mainWindowProc, it should not unpause watchdog if it was paused
+ void pingWindowTimeout(std::string_view state)
+ {
+ if (mWindowTimeout && mWindowTimeout->started())
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->ping(state);
+ }
+ }
private:
// These timeout related functions are strictly for the thread.
void resumeTimeout(std::string_view state)
@@ -2430,18 +2440,45 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DEVICECHANGE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DEVICECHANGE");
+
+ // Log detailed device change information
+ std::string change_type = "UNKNOWN";
+ switch (w_param)
+ {
+ case DBT_DEVICEARRIVAL: change_type = "DBT_DEVICEARRIVAL"; break;
+ case DBT_DEVICEREMOVECOMPLETE: change_type = "DBT_DEVICEREMOVECOMPLETE"; break;
+ case DBT_DEVNODES_CHANGED: change_type = "DBT_DEVNODES_CHANGED"; break;
+ case DBT_DEVICEQUERYREMOVE: change_type = "DBT_DEVICEQUERYREMOVE"; break;
+ case DBT_DEVICEQUERYREMOVEFAILED: change_type = "DBT_DEVICEQUERYREMOVEFAILED"; break;
+ case DBT_DEVICEREMOVEPENDING: change_type = "DBT_DEVICEREMOVEPENDING"; break;
+ case DBT_CONFIGCHANGED: change_type = "DBT_CONFIGCHANGED"; break;
+ }
+
if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL)
{
- WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp));
+ WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp, change_type));
return 1;
}
+ else if (l_param)
+ {
+ const auto* hdr = reinterpret_cast<const DEV_BROADCAST_HDR*>(l_param);
+ if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+ {
+ // Might need to register for monitor device notifications
+ // to get this message when monitor is suspended or resumed.
+ // TODO: log monitor suspending and resuming.
+ LL_INFOS("Window") << "DEVICEINTERFACE: " << change_type << LL_ENDL;
+ }
+ }
break;
}
case WM_PAINT:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_PAINT");
+ window_imp->mWindowThread->pingWindowTimeout("WM_PAINT");
GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE);
update_width = update_rect.right - update_rect.left + 1;
update_height = update_rect.bottom - update_rect.top + 1;
@@ -2586,6 +2623,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_CLOSE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE");
+ window_imp->mWindowThread->pingWindowTimeout("WM_CLOSE");
window_imp->mCallbacks->handlePreCloseRequest(); // mark app as potentially closing
if (!window_imp->mReceivedSCClose)
@@ -2654,6 +2692,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// Comes after WM_QUERYENDSESSION
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENDSESSION");
LL_INFOS("Window") << "Received WM_ENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL;
+ window_imp->mWindowThread->pingWindowTimeout("WM_ENDSESSION");
unsigned int end_session_flags = (U32)l_param;
if (w_param == TRUE // if true, session is ending
@@ -2722,6 +2761,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
return TRUE;
default:
+ LL_INFOS("Window") << "Received WM_POWERBROADCAST with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL;
break;
}
break;
@@ -3377,6 +3417,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DPICHANGED:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DPICHANGED");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DPICHANGED");
LPRECT lprc_new_scale;
F32 new_scale = F32(LOWORD(w_param)) / F32(USER_DEFAULT_SCREEN_DPI);
lprc_new_scale = (LPRECT)l_param;
@@ -3398,6 +3439,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DISPLAYCHANGE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DISPLAYCHANGE");
+ window_imp->mWindowThread->pingWindowTimeout("WM_DISPLAYCHANGE");
window_imp->post([=]() {
window_imp->mCallbacks->handleDisplayChanged();
// Note: WM_DISPLAYCHANGE was passing to WM_SETFOCUS
@@ -3446,6 +3488,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_SETTINGCHANGE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETTINGCHANGE");
+ // Can be called on OS user switching
+ LL_INFOS("Window") << "WM_SETTINGCHANGE, with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL;
+ window_imp->mWindowThread->pingWindowTimeout("WM_SETTINGCHANGE");
if (w_param == SPI_SETMOUSEVANISH)
{
if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &window_imp->mMouseVanish, 0))
@@ -4980,6 +5025,16 @@ void LLWindowWin32::requestHighPerformanceGPU() const
<< ", Vendor: 0x" << std::hex << desc.VendorId << std::dec
<< ", Flags: " << desc.Flags << LL_ENDL;
}
+ // Skip Microsoft Basic Render Driver, it's a placeholder for missing drivers
+ else if (description.find("Microsoft Basic Render Driver") != std::string::npos)
+ {
+ // User is likely missing drivers, so log a warning.
+ // Don't consider this adapter as a valid selection.
+ LL_WARNS("Window") << "Adapter " << adapterIndex << ": " << description
+ << ", Dedicated VRAM: " << (desc.DedicatedVideoMemory / 1024 / 1024) << " MB"
+ << ", Vendor: 0x" << std::hex << desc.VendorId << std::dec
+ << ", Flags: " << desc.Flags << LL_ENDL;
+ }
else
{
LL_INFOS("Window") << "Adapter " << adapterIndex << ": " << description
@@ -5292,6 +5347,13 @@ inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
: LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, false)
{
LL::ThreadPool::start();
+
+ // Set thread name for the window thread
+ // This will make it distinguishable in Visual Studio debugger
+ post([this]()
+ {
+ SetThreadDescription(GetCurrentThread(), L"LLWindowWin32 Thread");
+ });
}
/**
@@ -5473,7 +5535,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
}
// Normally won't exist yet, but in case of re-init, make sure it's cleaned up
- resumeTimeout("WindowThread");
+ resumeTimeout("Window:WindowThread");
while (! getQueue().done())
{
@@ -5484,23 +5546,25 @@ void LLWindowWin32::LLWindowWin32Thread::run()
if (mWindowHandleThrd != 0)
{
- pingTimeout("messages");
MSG msg;
BOOL status;
if (mhDCThrd == 0)
{
+ pingTimeout("Window:PeekMessage");
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - PeekMessage");
logger.onChange("PeekMessage(", std::hex, mWindowHandleThrd, ")");
status = PeekMessage(&msg, mWindowHandleThrd, 0, 0, PM_REMOVE);
}
else
{
+ pingTimeout("Window:GetMessage");
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - GetMessage");
logger.always("GetMessage(", std::hex, mWindowHandleThrd, ")");
status = GetMessage(&msg, NULL, 0, 0);
}
if (status > 0)
{
+ pingTimeout("Window:TranslateMessage");
logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message,
", ", msg.wParam, ")");
TranslateMessage(&msg);
@@ -5512,7 +5576,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue");
- pingTimeout("queue");
+ pingTimeout("Window:Queue");
logger.onChange("runPending()");
//process any pending functions
getQueue().runPending();
diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp
index 5d353e64f9..84d03a6d0c 100644
--- a/indra/llxml/llcontrol.cpp
+++ b/indra/llxml/llcontrol.cpp
@@ -37,7 +37,6 @@
#include "llstring.h"
#include "v3math.h"
#include "v3dmath.h"
-#include "v4math.h"
#include "v4coloru.h"
#include "v4color.h"
#include "v3color.h"
@@ -64,7 +63,6 @@ template <> eControlType get_control_type<std::string>();
template <> eControlType get_control_type<LLVector3>();
template <> eControlType get_control_type<LLVector3d>();
-template <> eControlType get_control_type<LLVector4>();
template <> eControlType get_control_type<LLRect>();
template <> eControlType get_control_type<LLColor4>();
template <> eControlType get_control_type<LLColor3>();
@@ -74,7 +72,6 @@ template <> eControlType get_control_type<LLSD>();
template <> LLSD convert_to_llsd<U32>(const U32& in);
template <> LLSD convert_to_llsd<LLVector3>(const LLVector3& in);
template <> LLSD convert_to_llsd<LLVector3d>(const LLVector3d& in);
-template <> LLSD convert_to_llsd<LLVector4>(const LLVector4& in);
template <> LLSD convert_to_llsd<LLRect>(const LLRect& in);
template <> LLSD convert_to_llsd<LLColor4>(const LLColor4& in);
template <> LLSD convert_to_llsd<LLColor3>(const LLColor3& in);
@@ -88,7 +85,6 @@ template <> std::string convert_from_llsd<std::string>(const LLSD& sd, eControlT
template <> LLWString convert_from_llsd<LLWString>(const LLSD& sd, eControlType type, std::string_view control_name);
template <> LLVector3 convert_from_llsd<LLVector3>(const LLSD& sd, eControlType type, std::string_view control_name);
template <> LLVector3d convert_from_llsd<LLVector3d>(const LLSD& sd, eControlType type, std::string_view control_name);
-template <> LLVector4 convert_from_llsd<LLVector4>(const LLSD& sd, eControlType type, std::string_view control_name);
template <> LLRect convert_from_llsd<LLRect>(const LLSD& sd, eControlType type, std::string_view control_name);
template <> LLColor4 convert_from_llsd<LLColor4>(const LLSD& sd, eControlType type, std::string_view control_name);
template <> LLColor4U convert_from_llsd<LLColor4U>(const LLSD& sd, eControlType type, std::string_view control_name);
@@ -128,9 +124,6 @@ bool LLControlVariable::llsd_compare(const LLSD& a, const LLSD & b)
case TYPE_VEC3D:
result = LLVector3d(a) == LLVector3d(b);
break;
- case TYPE_VEC4:
- result = LLVector4(a) == LLVector4(b);
- break;
case TYPE_QUAT:
result = LLQuaternion(a) == LLQuaternion(b);
break;
@@ -380,7 +373,6 @@ const std::string LLControlGroup::mTypeString[TYPE_COUNT] = { "U32"
,"Rect"
,"Color4"
,"Color3"
- ,"Vector4"
,"LLSD"
};
@@ -541,11 +533,6 @@ LLControlVariable* LLControlGroup::declareVec3d(const std::string& name, const L
return declareControl(name, TYPE_VEC3D, initial_val.getValue(), comment, persist);
}
-LLControlVariable* LLControlGroup::declareVec4(const std::string& name, const LLVector4 &initial_val, const std::string& comment, LLControlVariable::ePersist persist)
-{
- return declareControl(name, TYPE_VEC4, initial_val.getValue(), comment, persist);
-}
-
LLControlVariable* LLControlGroup::declareQuat(const std::string& name, const LLQuaternion &initial_val, const std::string& comment, LLControlVariable::ePersist persist)
{
return declareControl(name, TYPE_QUAT, initial_val.getValue(), comment, persist);
@@ -628,11 +615,6 @@ LLVector3d LLControlGroup::getVector3d(std::string_view name)
return get<LLVector3d>(name);
}
-LLVector4 LLControlGroup::getVector4(std::string_view name)
-{
- return get<LLVector4>(name);
-}
-
LLQuaternion LLControlGroup::getQuaternion(std::string_view name)
{
return get<LLQuaternion>(name);
@@ -733,11 +715,6 @@ void LLControlGroup::setVector3d(std::string_view name, const LLVector3d &val)
set(name, val);
}
-void LLControlGroup::setVector4(std::string_view name, const LLVector4 &val)
-{
- set(name, val);
-}
-
void LLControlGroup::setQuaternion(std::string_view name, const LLQuaternion &val)
{
set(name, val);
@@ -1287,11 +1264,6 @@ template <> eControlType get_control_type<LLVector3d>()
return TYPE_VEC3D;
}
-template <> eControlType get_control_type<LLVector4>()
-{
- return TYPE_VEC4;
-}
-
template <> eControlType get_control_type<LLQuaternion>()
{
return TYPE_QUAT;
@@ -1332,11 +1304,6 @@ template <> LLSD convert_to_llsd<LLVector3d>(const LLVector3d& in)
{
return in.getValue();
}
-template <> LLSD convert_to_llsd<LLVector4>(const LLVector4& in)
-{
- return in.getValue();
-}
-
template <> LLSD convert_to_llsd<LLQuaternion>(const LLQuaternion& in)
{
return in.getValue();
@@ -1454,18 +1421,6 @@ LLVector3d convert_from_llsd<LLVector3d>(const LLSD& sd, eControlType type, std:
}
template<>
-LLVector4 convert_from_llsd<LLVector4>(const LLSD& sd, eControlType type, std::string_view control_name)
-{
- if (type == TYPE_VEC4)
- return LLVector4(sd);
- else
- {
- CONTROL_ERRS << "Invalid LLVector4 value for " << control_name << ": " << LLControlGroup::typeEnumToString(type) << " " << sd << LL_ENDL;
- return LLVector4();
- }
-}
-
-template<>
LLQuaternion convert_from_llsd<LLQuaternion>(const LLSD& sd, eControlType type, std::string_view control_name)
{
if (type == TYPE_QUAT)
diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h
index c2a7c22bbc..ad283c89b1 100644
--- a/indra/llxml/llcontrol.h
+++ b/indra/llxml/llcontrol.h
@@ -43,7 +43,6 @@
class LLVector3;
class LLVector3d;
-class LLVector4;
class LLQuaternion;
class LLColor4;
class LLColor3;
@@ -62,7 +61,6 @@ typedef enum e_control_type
TYPE_RECT,
TYPE_COL4,
TYPE_COL3,
- TYPE_VEC4,
TYPE_LLSD,
TYPE_COUNT
} eControlType;
@@ -198,7 +196,6 @@ public:
LLControlVariable* declareString(const std::string& name, const std::string &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
LLControlVariable* declareVec3(const std::string& name, const LLVector3 &initial_val,const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
LLControlVariable* declareVec3d(const std::string& name, const LLVector3d &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
- LLControlVariable* declareVec4(const std::string& name, const LLVector4 &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
LLControlVariable* declareQuat(const std::string& name, const LLQuaternion &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
LLControlVariable* declareRect(const std::string& name, const LLRect &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
LLControlVariable* declareColor4(const std::string& name, const LLColor4 &initial_val, const std::string& comment, LLControlVariable::ePersist persist = LLControlVariable::PERSIST_NONDFT);
@@ -215,7 +212,6 @@ public:
LLWString getWString(std::string_view name);
LLVector3 getVector3(std::string_view name);
LLVector3d getVector3d(std::string_view name);
- LLVector4 getVector4(std::string_view name);
LLRect getRect(std::string_view name);
LLSD getLLSD(std::string_view name);
LLQuaternion getQuaternion(std::string_view name);
@@ -254,7 +250,6 @@ public:
void setString(std::string_view name, const std::string& val);
void setVector3(std::string_view name, const LLVector3 &val);
void setVector3d(std::string_view name, const LLVector3d &val);
- void setVector4(std::string_view name, const LLVector4 &val);
void setQuaternion(std::string_view name, const LLQuaternion &val);
void setRect(std::string_view name, const LLRect &val);
void setColor4(std::string_view name, const LLColor4 &val);
@@ -421,7 +416,6 @@ template <> eControlType get_control_type<bool>();
template <> eControlType get_control_type<std::string>();
template <> eControlType get_control_type<LLVector3>();
template <> eControlType get_control_type<LLVector3d>();
-template <> eControlType get_control_type<LLVector4>();
template <> eControlType get_control_type<LLQuaternion>();
template <> eControlType get_control_type<LLRect>();
template <> eControlType get_control_type<LLColor4>();
@@ -431,7 +425,6 @@ template <> eControlType get_control_type<LLSD>();
template <> LLSD convert_to_llsd<U32>(const U32& in);
template <> LLSD convert_to_llsd<LLVector3>(const LLVector3& in);
template <> LLSD convert_to_llsd<LLVector3d>(const LLVector3d& in);
-template <> LLSD convert_to_llsd<LLVector4>(const LLVector4& in);
template <> LLSD convert_to_llsd<LLQuaternion>(const LLQuaternion& in);
template <> LLSD convert_to_llsd<LLRect>(const LLRect& in);
template <> LLSD convert_to_llsd<LLColor4>(const LLColor4& in);
@@ -441,7 +434,6 @@ template<> std::string convert_from_llsd<std::string>(const LLSD& sd, eControlTy
template<> LLWString convert_from_llsd<LLWString>(const LLSD& sd, eControlType type, std::string_view control_name);
template<> LLVector3 convert_from_llsd<LLVector3>(const LLSD& sd, eControlType type, std::string_view control_name);
template<> LLVector3d convert_from_llsd<LLVector3d>(const LLSD& sd, eControlType type, std::string_view control_name);
-template<> LLVector4 convert_from_llsd<LLVector4>(const LLSD& sd, eControlType type, std::string_view control_name);
template<> LLQuaternion convert_from_llsd<LLQuaternion>(const LLSD& sd, eControlType type, std::string_view control_name);
template<> LLRect convert_from_llsd<LLRect>(const LLSD& sd, eControlType type, std::string_view control_name);
template<> bool convert_from_llsd<bool>(const LLSD& sd, eControlType type, std::string_view control_name);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 2807ab25b1..7e771de0f7 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -589,6 +589,7 @@ set(viewer_SOURCE_FILES
llslurl.cpp
llsnapshotlivepreview.cpp
llspatialpartition.cpp
+ llstatslistener.cpp
llspeakers.cpp
llspeakingindicatormanager.cpp
llsplitbutton.cpp
@@ -1260,6 +1261,7 @@ set(viewer_HEADER_FILES
llsnapshotlivepreview.h
llsnapshotmodel.h
llspatialpartition.h
+ llstatslistener.h
llspeakers.h
llspeakingindicatormanager.h
llsplitbutton.h
diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist
index 09b0e1ec1b..506b2b8a60 100644
--- a/indra/newview/Info-SecondLife.plist
+++ b/indra/newview/Info-SecondLife.plist
@@ -34,6 +34,8 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSMicrophoneUsageDescription</key>
<string>For voice chat, you must grant permission for Second Life to use the microphone.</string>
+ <key>NSLocalNetworkUsageDescription</key>
+ <string>Second Life uses WebRTC for voice chat, which may require local network access while establishing the voice connection.</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index d29e006fdd..e50234d26a 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -5903,7 +5903,7 @@
<key>Type</key>
<string>S32</string>
<key>Value</key>
- <integer>110</integer>
+ <integer>127</integer>
</map>
<key>NotificationChannelRightMargin</key>
<map>
@@ -11904,22 +11904,6 @@
<key>Value</key>
<real>20.0</real>
</map>
- <key>TextureChannelPriority</key>
- <map>
- <key>Comment</key>
- <string>Per-channel texture streaming aggressiveness. X=normals, Y=diffuse, Z=specular/metallic, W=emissive. 1.0=baseline, higher=more aggressive downrez.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Vector4</string>
- <key>Value</key>
- <array>
- <real>5</real>
- <real>7.5</real>
- <real>20</real>
- <real>7.5</real>
- </array>
- </map>
<key>TextureCameraBoost</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl b/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl
index 8e12d09443..df58f68556 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl
@@ -1171,12 +1171,12 @@ vec3 linear_to_srgb(vec3 cl);
// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage).
// Burns 3 ops for conversion {shift,or,xor}.
//==============================================================================================================================
- AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));}
- AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));}
+ AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000u));}
+ AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000u));}
//------------------------------------------------------------------------------------------------------------------------------
// Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value).
- AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));}
- AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));}
+ AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000u));}
+ AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000u));}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//_____________________________________________________________/\_______________________________________________________________
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 46e21ce16d..e9d01c6735 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -153,6 +153,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/throw_exception.hpp>
+#include <chrono>
#if LL_WINDOWS
# include <share.h> // For _SH_DENYWR in processMarkerFiles
@@ -1315,7 +1316,11 @@ bool LLAppViewer::frame()
{
try
{
+ const auto start = std::chrono::steady_clock::now();
ret = doFrame();
+ const auto end = std::chrono::steady_clock::now();
+ const U64 doframe_time_us = (U64)std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
+ LLTrace::sample(LLStatViewer::DOFRAME_TIME_US, doframe_time_us);
}
catch (const LLContinueError&)
{
@@ -3122,13 +3127,20 @@ bool LLAppViewer::initConfiguration()
if (mSecondInstance)
{
- // This is the second instance of SL. Mute voice,
- // but make sure the setting is *not* persisted.
+ // This is the second concurrent instance of SL.
+ // Disable voice for this session only, user should
+ // be able to enable voice manually, after that it
+ // works the same way as on primary instance.
LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat");
- if (enable_voice)
+ if (enable_voice && enable_voice->getValue().asBoolean())
{
+ LL_DEBUGS("AppInit") << "Disabling voice for this session only" << LL_ENDL;
+ // Will be saved as mValues[2] which does not get written to the file.
+ // This feels like a hack, but otherwise way too many controls have to
+ // be tracked manually instead of using xmls' control_name.
const bool DO_NOT_PERSIST = false;
- enable_voice->setValue(LLSD(false), DO_NOT_PERSIST);
+ LLSD::Boolean new_value = false;
+ enable_voice->setValue(new_value, DO_NOT_PERSIST);
}
}
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index 430445631f..159c3af8a0 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -83,6 +83,7 @@
#include "BugSplat.h"
#include "boost/json.hpp" // Boost.Json
#include "llagent.h" // for agent location
+#include "llmemory.h"
#include "llstartup.h"
#include "llviewerregion.h"
#include "llvoavatarself.h" // for agent name
@@ -182,6 +183,16 @@ namespace
sBugSplatSender->setAttribute(WCSTR(L"VRAM"), WCSTR(STRINGIZE(gGLManager.mVRAM)));
sBugSplatSender->setAttribute(WCSTR(L"RAM"), WCSTR(STRINGIZE(gSysMemory.getPhysicalMemoryKB().value())));
+ const U32 avail_kb = LLMemory::getAvailableMemKB().value();
+ if (avail_kb != U32_MAX) // filter out initial values, if one is not set, all are not set
+ {
+ // Memory usage at crash time (can be 1s obsolete)
+ sBugSplatSender->setAttribute(WCSTR(L"MemAllocatedKB"), WCSTR(std::to_string(LLMemory::getAllocatedMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemAvailableKB"), WCSTR(std::to_string(LLMemory::getAvailableMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemMaxPhysicalKB"), WCSTR(std::to_string(LLMemory::getMaxMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemAvailCommitMB"), WCSTR(std::to_string(LLMemory::getAvailableCommitMemMB().value())));
+ }
+
if (gAgent.getRegion())
{
// region location, when we have it
diff --git a/indra/newview/lldebugview.cpp b/indra/newview/lldebugview.cpp
index 3941b82e75..246a9d28e4 100644
--- a/indra/newview/lldebugview.cpp
+++ b/indra/newview/lldebugview.cpp
@@ -101,7 +101,7 @@ void LLDebugView::init()
addChild(gSceneMonitorView);
gSceneMonitorView->setRect(rect);
- r.set(150, rect.getHeight() - 60, 820, 110);
+ r.set(150, rect.getHeight() - 60, 835, 110);
LLTextureView::Params tvp;
tvp.name("gTextureView");
tvp.rect(r);
diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp
index cdf3244389..fbecaa1583 100644
--- a/indra/newview/lldrawpoolwater.cpp
+++ b/indra/newview/lldrawpoolwater.cpp
@@ -204,22 +204,22 @@ void LLDrawPoolWater::renderPostDeferred(S32 pass)
if (tex_a && (!tex_b || (tex_a == tex_b)))
{
- shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a);
tex_a->setFilteringOption(filter_mode);
+ shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a);
blend_factor = 0; // only one tex provided, no blending
}
else if (tex_b && !tex_a)
{
- shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b);
tex_b->setFilteringOption(filter_mode);
+ shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b);
blend_factor = 0; // only one tex provided, no blending
}
else if (tex_b != tex_a)
{
- shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a);
tex_a->setFilteringOption(filter_mode);
- shader->bindTexture(LLViewerShaderMgr::BUMP_MAP2, tex_b);
tex_b->setFilteringOption(filter_mode);
+ shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a);
+ shader->bindTexture(LLViewerShaderMgr::BUMP_MAP2, tex_b);
}
shader->bindTexture(LLShaderMgr::WATER_EXCLUSIONTEX, &gPipeline.mWaterExclusionMask);
diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp
index c2a9008420..c2a1a306fe 100644
--- a/indra/newview/llfloaterimcontainer.cpp
+++ b/indra/newview/llfloaterimcontainer.cpp
@@ -155,6 +155,11 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const
{
// The general strategy when a session id is modified is to delete all related objects and create them anew.
+ // The conversation floater can be active while participant widgets are selected.
+ // Preserve that state separately so the new conversation widget can be selected
+ // after the server replaces an outgoing ad-hoc session id.
+ const bool was_active = old_session_id == getSelectedSession();
+
// Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived()
// and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress
// its related mSessions record as it's indexed with the wrong id.
@@ -175,6 +180,11 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const
{
mSessions[new_session_id] = conversp;
}
+
+ if (was_active)
+ {
+ selectConversationPair(new_session_id, true, false, true);
+ }
}
@@ -1734,7 +1744,7 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata)
void LLFloaterIMContainer::showConversation(const LLUUID& session_id)
{
setVisibleAndFrontmost(false);
- selectConversationPair(session_id, true);
+ selectConversationPair(session_id, true, true, true);
LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
if (session_floater)
@@ -1780,13 +1790,13 @@ void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid)
}
// Synchronous select the conversation item and the conversation floater
-bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/)
+bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/, bool force_select_widget/*=false*/)
{
bool handled = true;
LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
/* widget processing */
- if (select_widget && mConversationsRoot->getSelectedCount() <= 1)
+ if (select_widget && (force_select_widget || mConversationsRoot->getSelectedCount() <= 1))
{
LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id);
if (widget && widget->getParentFolder())
diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h
index 9f1690a9b9..4cb243aa64 100644
--- a/indra/newview/llfloaterimcontainer.h
+++ b/indra/newview/llfloaterimcontainer.h
@@ -74,7 +74,7 @@ public:
void showConversation(const LLUUID& session_id);
void selectConversation(const LLUUID& session_id);
void selectNextConversationByID(const LLUUID& session_id);
- bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true);
+ bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true, bool force_select_widget = false);
void clearAllFlashStates();
bool selectAdjacentConversation(bool focus_selected);
bool selectNextorPreviousConversation(bool select_next, bool focus_selected = true);
diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp
index 92b3c92165..01108b5cfa 100644
--- a/indra/newview/llfloatersettingsdebug.cpp
+++ b/indra/newview/llfloatersettingsdebug.cpp
@@ -106,7 +106,6 @@ void LLFloaterSettingsDebug::onCommitSettings()
LLVector3 vector;
LLVector3d vectord;
- LLVector4 vector4;
LLQuaternion quat;
LLRect rect;
LLColor4 col4;
@@ -143,13 +142,6 @@ void LLFloaterSettingsDebug::onCommitSettings()
vectord.mdV[VZ] = mValSpinner3->getValue().asReal();
controlp->set(vectord.getValue());
break;
- case TYPE_VEC4:
- vector4.mV[VX] = (F32)mValSpinner1->getValue().asReal();
- vector4.mV[VY] = (F32)mValSpinner2->getValue().asReal();
- vector4.mV[VZ] = (F32)mValSpinner3->getValue().asReal();
- vector4.mV[VW] = (F32)mValSpinner4->getValue().asReal();
- controlp->set(vector4.getValue());
- break;
case TYPE_QUAT:
quat.mQ[VX] = mValSpinner1->getValueF32();
quat.mQ[VY] = mValSpinner2->getValueF32();
@@ -360,40 +352,6 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp)
}
break;
}
- case TYPE_VEC4:
- {
- LLVector4 v;
- v.setValue(sd);
- mValSpinner1->setVisible(true);
- mValSpinner1->setLabel(std::string("X"));
- mValSpinner2->setVisible(true);
- mValSpinner2->setLabel(std::string("Y"));
- mValSpinner3->setVisible(true);
- mValSpinner3->setLabel(std::string("Z"));
- mValSpinner4->setVisible(true);
- mValSpinner4->setLabel(std::string("W"));
- if (!mValSpinner1->hasFocus())
- {
- mValSpinner1->setPrecision(3);
- mValSpinner1->setValue(v[VX]);
- }
- if (!mValSpinner2->hasFocus())
- {
- mValSpinner2->setPrecision(3);
- mValSpinner2->setValue(v[VY]);
- }
- if (!mValSpinner3->hasFocus())
- {
- mValSpinner3->setPrecision(3);
- mValSpinner3->setValue(v[VZ]);
- }
- if (!mValSpinner4->hasFocus())
- {
- mValSpinner4->setPrecision(3);
- mValSpinner4->setValue(v[VW]);
- }
- break;
- }
case TYPE_QUAT:
{
LLQuaternion q;
diff --git a/indra/newview/llfloatersimplesnapshot.cpp b/indra/newview/llfloatersimplesnapshot.cpp
index 55b39d9193..4671f74d6d 100644
--- a/indra/newview/llfloatersimplesnapshot.cpp
+++ b/indra/newview/llfloatersimplesnapshot.cpp
@@ -54,87 +54,162 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
- httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("post_profile_image_coro", httpPolicy);
+ httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("post_thumbnail_image_coro", httpPolicy);
LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>();
LLCore::HttpHeaders::ptr_t httpHeaders;
LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>();
httpOpts->setFollowRedirects(true);
- LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders);
+ // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total)
+ const S32 MAX_UPLOAD_RETRIES = 2;
+ S32 upload_retry_count = 0;
+ LLUUID result_uuid;
- LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
- LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
-
- if (!status)
- {
- // todo: notification?
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL;
- return;
- }
- if (!result.has("uploader"))
+ while (upload_retry_count <= MAX_UPLOAD_RETRIES)
{
- // todo: notification?
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL;
- return;
- }
- std::string uploader_cap = result["uploader"].asString();
- if (uploader_cap.empty())
- {
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL;
- return;
- }
+ // Stage 1: Request uploader URL
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders);
- // Upload the image
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>();
- LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>();
- LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>();
- S64 length;
+ if (!status)
+ {
+ LL_WARNS("Thumbnail") << "Failed to get uploader cap " << status.toString() << LL_ENDL;
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
+ return;
+ }
- {
- llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate);
- if (!instream.is_open())
+ if (!result.has("uploader"))
{
- LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL;
+ LL_WARNS("Thumbnail") << "Failed to get uploader cap, response contains no data." << LL_ENDL;
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
return;
}
- length = instream.tellg();
- }
- uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional
- uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required!
- uploaderhttpOpts->setFollowRedirects(true);
+ std::string uploader_cap = result["uploader"].asString();
+ if (uploader_cap.empty())
+ {
+ LL_WARNS("Thumbnail") << "Failed to get uploader cap, cap invalid." << LL_ENDL;
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
+ return;
+ }
- result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders);
+ // Stage 2: Upload the image
+ LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>();
+ LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>();
+ LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>();
+ S64 length;
- httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
- status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+ {
+ llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate);
+ if (!instream.is_open())
+ {
+ LL_WARNS("Thumbnail") << "Failed to open file " << path_to_image << LL_ENDL;
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
+ return;
+ }
+ length = instream.tellg();
+ }
- LL_DEBUGS("Thumbnail") << result << LL_ENDL;
+ uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2");
+ uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length));
+ uploaderhttpOpts->setFollowRedirects(true);
- if (!status)
- {
- LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL;
- return;
- }
+ result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders);
- if (result["state"].asString() != "complete")
- {
- if (result.has("message"))
- {
- LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL;
- }
- else
+ httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ LL_DEBUGS("Thumbnail") << result << LL_ENDL;
+
+ if (!status)
{
- LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL;
+ if (upload_retry_count < MAX_UPLOAD_RETRIES)
+ {
+ upload_retry_count++;
+ LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count
+ << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString()
+ << ", re-requesting uploader..." << LL_ENDL;
+ llcoro::suspendUntilTimeout(1.0f);
+ continue;
+ }
+ else
+ {
+ LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts: " << status.toString() << LL_ENDL;
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
+ return;
+ }
}
- if (callback)
+ if (result["state"].asString() != "complete")
{
- callback(LLUUID());
+ if (upload_retry_count < MAX_UPLOAD_RETRIES)
+ {
+ upload_retry_count++;
+ if (result.has("message"))
+ {
+ LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"]
+ << " message: " << result["message"] << " (attempt "
+ << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1)
+ << "), re-requesting uploader..." << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count
+ << " of " << (MAX_UPLOAD_RETRIES + 1)
+ << "), re-requesting uploader..." << LL_ENDL;
+ }
+ llcoro::suspendUntilTimeout(1.0f);
+ continue;
+ }
+ else
+ {
+ if (result.has("message"))
+ {
+ LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts, state " << result["state"]
+ << " message: " << result["message"] << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts" << LL_ENDL;
+ }
+ if (callback)
+ {
+ callback(LLUUID());
+ }
+ LLFile::remove(path_to_image);
+ return;
+ }
}
- return;
+
+ // Success!
+ result_uuid = result["new_asset"].asUUID();
+ break;
}
if (first_data.has("category_id"))
@@ -143,7 +218,7 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
if (cat)
{
- cat->setThumbnailUUID(result["new_asset"].asUUID());
+ cat->setThumbnailUUID(result_uuid);
}
gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id);
}
@@ -153,16 +228,18 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L
LLViewerInventoryItem* item = gInventory.getItem(item_id);
if (item)
{
- item->setThumbnailUUID(result["new_asset"].asUUID());
+ item->setThumbnailUUID(result_uuid);
}
- // Are we supposed to get BulkUpdateInventory?
gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id);
}
if (callback)
{
- callback(result["new_asset"].asUUID());
+ callback(result_uuid);
}
+
+ // Cleanup
+ LLFile::remove(path_to_image);
}
///----------------------------------------------------------------------------
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 5adde21e3e..d0d4d85993 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -396,6 +396,8 @@ LLInventoryModel::LLInventoryModel()
: // These are now ordered, keep them that way.
mBacklinkMMap(),
mIsAgentInvUsable(false),
+ mLibrarySkeletonLoadTime(0.f),
+ mAgentSkeletonLoadTime(0.f),
mRootFolderID(),
mLibraryRootFolderID(),
mLibraryOwnerID(),
@@ -3100,6 +3102,16 @@ bool LLInventoryModel::loadSkeleton(
<< " after " << timer.getElapsedTimeF32() << " seconds."
<< LL_ENDL;
+ const F32 elapsed = timer.getElapsedTimeF32();
+ if (owner_id == mLibraryOwnerID)
+ {
+ mLibrarySkeletonLoadTime = elapsed;
+ }
+ else
+ {
+ mAgentSkeletonLoadTime = elapsed;
+ }
+
return rv;
}
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index 2c516f6a86..6ac307dd68 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -158,8 +158,12 @@ protected:
public:
// The inventory model usage is sensitive to the initial construction of the model
bool isInventoryUsable() const;
+ F32 getLibrarySkeletonLoadTime() const { return mLibrarySkeletonLoadTime; }
+ F32 getAgentSkeletonLoadTime() const { return mAgentSkeletonLoadTime; }
private:
bool mIsAgentInvUsable; // used to handle an invalid inventory state
+ F32 mLibrarySkeletonLoadTime;
+ F32 mAgentSkeletonLoadTime;
// One-time initialization of HTTP system.
void initHttpRequest();
diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp
index 82eefb50ac..b02b2e2eb9 100644
--- a/indra/newview/llinventorymodelbackgroundfetch.cpp
+++ b/indra/newview/llinventorymodelbackgroundfetch.cpp
@@ -192,6 +192,8 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch():
mFetchCount(0),
mLastFetchCount(0),
mFetchFolderCount(0),
+ mInitialFetchDuration(0.f),
+ mInitialFetchDurationCaptured(false),
mAllRecursiveFoldersFetched(false),
mRecursiveInventoryFetchStarted(false),
mRecursiveLibraryFetchStarted(false),
@@ -199,6 +201,15 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch():
mMinTimeBetweenFetches(0.3f)
{}
+void LLInventoryModelBackgroundFetch::markFetchStarted()
+{
+ if (!mBackgroundFetchActive)
+ {
+ mFetchStartTimer.reset();
+ }
+ mBackgroundFetchActive = true;
+}
+
LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch()
{
gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
@@ -289,7 +300,7 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive)
// it's a folder, do a bulk fetch
LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL;
- mBackgroundFetchActive = true;
+ markFetchStarted();
mFolderFetchActive = true;
EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT;
if (id.isNull())
@@ -376,7 +387,7 @@ void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id,
{
if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id)
{
- mBackgroundFetchActive = true;
+ markFetchStarted();
mFolderFetchActive = true;
if (forced)
@@ -403,7 +414,7 @@ void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, b
{
if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id)
{
- mBackgroundFetchActive = true;
+ markFetchStarted();
if (forced)
{
// check if already requested
@@ -447,7 +458,7 @@ void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id,
});
// start idle loop to track completion
- mBackgroundFetchActive = true;
+ markFetchStarted();
mFolderFetchActive = true;
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
}
@@ -489,14 +500,14 @@ void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback)
});
// start idle loop to track completion
- mBackgroundFetchActive = true;
+ markFetchStarted();
mFolderFetchActive = true;
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
}
void LLInventoryModelBackgroundFetch::findLostItems()
{
- mBackgroundFetchActive = true;
+ markFetchStarted();
mFolderFetchActive = true;
mFetchFolderQueue.emplace_back(LLUUID::null, FT_RECURSIVE);
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
@@ -515,13 +526,18 @@ void LLInventoryModelBackgroundFetch::setAllFoldersFetched()
mFolderFetchActive = false;
if (isBulkFetchProcessingComplete())
{
+ if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured)
+ {
+ mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32();
+ mInitialFetchDurationCaptured = true;
+ }
mBackgroundFetchActive = false;
}
// For now only informs about initial fetch being done
mFoldersFetchedSignal();
- LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL;
+ LL_INFOS(LOG_INV) << "Inventory background fetch completed after: " << mFetchStartTimer.getElapsedTimeF32() << LL_ENDL;
}
boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb)
@@ -843,6 +859,11 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis()
if (isBulkFetchProcessingComplete())
{
+ if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured)
+ {
+ mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32();
+ mInitialFetchDurationCaptured = true;
+ }
mBackgroundFetchActive = false;
}
}
diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h
index ef6fa06e9f..6a5e0f66ff 100644
--- a/indra/newview/llinventorymodelbackgroundfetch.h
+++ b/indra/newview/llinventorymodelbackgroundfetch.h
@@ -70,6 +70,7 @@ public:
bool inventoryFetchStarted() const;
bool inventoryFetchCompleted() const;
bool inventoryFetchInProgress() const;
+ F32 getInitialFetchDuration() const { return mInitialFetchDuration; }
void findLostItems();
void incrFetchCount(S32 fetching);
@@ -120,6 +121,8 @@ protected:
bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const;
private:
+ void markFetchStarted();
+
bool mRecursiveInventoryFetchStarted;
bool mRecursiveLibraryFetchStarted;
bool mRecursiveMarketplaceFetchStarted; // AIS3 specific
@@ -133,6 +136,9 @@ private:
S32 mLastFetchCount; // for debug
S32 mFetchFolderCount;
+ LLFrameTimer mFetchStartTimer;
+ F32 mInitialFetchDuration;
+ bool mInitialFetchDurationCaptured;
LLFrameTimer mFetchTimer;
F32 mMinTimeBetweenFetches;
fetch_queue_t mFetchFolderQueue;
diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp
index 34d2d4d6a5..b33696244d 100644
--- a/indra/newview/llpanelprofile.cpp
+++ b/indra/newview/llpanelprofile.cpp
@@ -113,77 +113,131 @@ LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::stri
LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>();
httpOpts->setFollowRedirects(true);
- LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders);
+ // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total)
+ const S32 MAX_UPLOAD_RETRIES = 2;
+ S32 upload_retry_count = 0;
+ LLUUID result_uuid;
- LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
- LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
-
- if (!status)
- {
- // todo: notification?
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL;
- return LLUUID::null;
- }
- if (!result.has("uploader"))
+ while (upload_retry_count <= MAX_UPLOAD_RETRIES)
{
- // todo: notification?
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL;
- return LLUUID::null;
- }
- std::string uploader_cap = result["uploader"].asString();
- if (uploader_cap.empty())
- {
- LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL;
- return LLUUID::null;
- }
+ // Stage 1: Request uploader URL
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders);
- // Upload the image
- LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>();
- LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>();
- LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>();
- S64 length;
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- {
- llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate);
- if (!instream.is_open())
+ if (!status)
{
- LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL;
+ // todo: notification?
+ LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL;
+ return LLUUID::null;
+ }
+
+ if (!result.has("uploader"))
+ {
+ // todo: notification?
+ LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL;
return LLUUID::null;
}
- length = instream.tellg();
- }
- uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional
- uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required!
- uploaderhttpOpts->setFollowRedirects(true);
+ std::string uploader_cap = result["uploader"].asString();
+ if (uploader_cap.empty())
+ {
+ LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL;
+ return LLUUID::null;
+ }
+
+ // Stage 2: Upload the image
+ LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>();
+ LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>();
+ LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>();
+ S64 length;
+
+ {
+ llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate);
+ if (!instream.is_open())
+ {
+ LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL;
+ return LLUUID::null;
+ }
+ length = instream.tellg();
+ }
- result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders);
+ uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2");
+ uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length));
+ uploaderhttpOpts->setFollowRedirects(true);
- httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
- status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+ result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders);
- LL_DEBUGS("AvatarProperties") << result << LL_ENDL;
+ httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- if (!status)
- {
- LL_WARNS("AvatarProperties") << "Failed to upload image " << status.toString() << LL_ENDL;
- return LLUUID::null;
- }
+ LL_DEBUGS("AvatarProperties") << result << LL_ENDL;
- if (result["state"].asString() != "complete")
- {
- if (result.has("message"))
+ if (!status)
{
- LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL;
+ if (upload_retry_count < MAX_UPLOAD_RETRIES)
+ {
+ upload_retry_count++;
+ LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count
+ << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString()
+ << ", re-requesting uploader..." << LL_ENDL;
+ llcoro::suspendUntilTimeout(1.0f);
+ continue;
+ }
+ else
+ {
+ LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts: " << status.toString() << LL_ENDL;
+ return LLUUID::null;
+ }
}
- else
+
+ // Todo: should we really repeat if 'complete' not set?
+ if (result["state"].asString() != "complete")
{
- LL_WARNS("AvatarProperties") << "Failed to upload image " << result << LL_ENDL;
+ if (upload_retry_count < MAX_UPLOAD_RETRIES)
+ {
+ upload_retry_count++;
+ if (result.has("message"))
+ {
+ LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"]
+ << " message: " << result["message"] << " (attempt "
+ << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1)
+ << "), re-requesting uploader..." << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count
+ << " of " << (MAX_UPLOAD_RETRIES + 1)
+ << "), re-requesting uploader..." << LL_ENDL;
+ }
+ llcoro::suspendUntilTimeout(1.0f);
+ continue;
+ }
+ else
+ {
+ if (result.has("message"))
+ {
+ LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts, state " << result["state"]
+ << " message: " << result["message"] << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1)
+ << " attempts" << LL_ENDL;
+ }
+ return LLUUID::null;
+ }
}
- return LLUUID::null;
+
+ // Success!
+ result_uuid = result["new_asset"].asUUID();
+ break;
}
- return result["new_asset"].asUUID();
+ return result_uuid;
}
enum EProfileImageType
diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp
index 0e6b61f316..609177046e 100644
--- a/indra/newview/llpanelvoicedevicesettings.cpp
+++ b/indra/newview/llpanelvoicedevicesettings.cpp
@@ -338,8 +338,12 @@ void LLPanelVoiceDeviceSettings::initialize()
// put voice client in "tuning" mode
if (mUseTuningMode)
{
+ // WebRTC tuning only affects the local audio device (mic-level
+ // monitoring and device selection); the peer connection stays up and
+ // its send/receive tracks are disabled for the duration. Unlike Vivox,
+ // there's no need to suspend (and tear down) the voice channel, which
+ // previously dropped the call and failed to reconnect on resume.
LLVoiceClient::getInstance()->tuningStart();
- LLVoiceChannel::suspend();
}
}
@@ -348,7 +352,6 @@ void LLPanelVoiceDeviceSettings::cleanup()
if (mUseTuningMode)
{
LLVoiceClient::getInstance()->tuningStop();
- LLVoiceChannel::resume();
}
}
diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp
index b48417bd71..623db82939 100644
--- a/indra/newview/llsidepanelinventory.cpp
+++ b/indra/newview/llsidepanelinventory.cpp
@@ -402,8 +402,6 @@ void LLSidepanelInventory::onOpen(const LLSD& key)
gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", (U32)time_corrected());
}
#endif
-
- gAgent.showLatestFeatureNotification("inventory");
}
void LLSidepanelInventory::performActionOnSelection(const std::string &action)
diff --git a/indra/newview/llstatslistener.cpp b/indra/newview/llstatslistener.cpp
new file mode 100644
index 0000000000..b0f8d54231
--- /dev/null
+++ b/indra/newview/llstatslistener.cpp
@@ -0,0 +1,200 @@
+/**
+ * @file llstatslistener.cpp
+ * @brief EventAPI interface for querying performance statistics
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * 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
+ * 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 "llstatslistener.h"
+
+#include "llinventorymodel.h"
+#include "llinventorymodelbackgroundfetch.h"
+#include "llimagegl.h"
+#include "llviewerstats.h"
+
+namespace
+{
+template <typename STAT, typename EXTRACTOR>
+LLSD collectPeriodArray(LLTrace::PeriodicRecording& recording,
+ size_t num_periods,
+ const STAT& stat,
+ EXTRACTOR extractor)
+{
+ LLSD values = LLSD::emptyArray();
+ for (size_t i = 1; i <= num_periods; ++i)
+ {
+ LLTrace::Recording& period = recording.getPrevRecording(i);
+ if (period.hasValue(stat))
+ {
+ values.append((F64)extractor(period, stat));
+ }
+ }
+ return values;
+}
+
+template <typename STAT, typename EXTRACTOR>
+void setPeriodArray(LLSD& out,
+ const char* key,
+ LLTrace::Recording& last,
+ LLTrace::PeriodicRecording& recording,
+ size_t num_periods,
+ const STAT& stat,
+ EXTRACTOR extractor)
+{
+ if (last.hasValue(stat))
+ {
+ out[key] = collectPeriodArray(recording, num_periods, stat, extractor);
+ }
+}
+}
+
+LLStatsListener::LLStatsListener()
+ : LLEventAPI("LLStats", "Query performance statistics")
+{
+ add("getPerfData",
+ "Get performance data from the frame recording buffer, plus texture memory\n"
+ "and inventory loading timing information.\n"
+ "Reply contains [\"stats\"] with nested group maps.",
+ &LLStatsListener::getPerfData,
+ llsd::map("reply", LLSD()));
+}
+
+void LLStatsListener::getPerfData(LLSD const & evt)
+{
+ LLEventAPI::Response response(LLSD(), evt);
+
+ // get_frame_recording() is a PeriodicRecording with 200 periods
+ LLTrace::PeriodicRecording& recording = LLTrace::get_frame_recording();
+ LLTrace::Recording& last = recording.getLastRecording();
+
+ size_t num_periods = recording.getNumRecordedPeriods();
+ F64 total_duration = recording.getDuration().value();
+
+ LLSD stats;
+ stats["total_periods_duration"] = total_duration;
+ stats["num_periods"] = (LLSD::Integer)num_periods;
+
+ LLSD frametime;
+
+ setPeriodArray(frametime, "fps", last, recording, num_periods, LLStatViewer::FPS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); });
+
+ setPeriodArray(frametime, "frame_time_ms", last, recording, num_periods, LLStatViewer::FRAMETIME,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ setPeriodArray(frametime, "do_frame_time_us", last, recording, num_periods, LLStatViewer::DOFRAME_TIME_US,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ setPeriodArray(frametime, "frame_time_jitter_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Normalized jitter - scalar samples updated per frame
+ // report the current (last) value since they are session/period rolling stats
+ if (last.hasValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION))
+ {
+ frametime["normalized_jitter_session"] = last.getLastValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION);
+ }
+ if (last.hasValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD))
+ {
+ frametime["normalized_jitter_period"] = last.getLastValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD);
+ }
+ if (last.hasValue(LLStatViewer::NFTV))
+ {
+ frametime["normalized_frametime_variation"] = last.getLastValue(LLStatViewer::NFTV);
+ }
+
+ // Jitter event minute counters: running avg per minute and count in last completed minute
+ // These are already minute-window aggregates, so sending single value, not arrays
+ if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE))
+ {
+ frametime["frame_time_jitter_events_per_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE);
+ }
+ if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE))
+ {
+ frametime["frame_time_jitter_events_last_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE);
+ }
+
+ // Jitter percentiles / cumulative
+ setPeriodArray(frametime, "jitter_cumulative_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_CUMULATIVE,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_99TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_95TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_STDDEV,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Frame time percentiles
+ setPeriodArray(frametime, "frametime_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_99TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "frametime_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_95TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "frametime_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_STDDEV,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Frametime jitter event count
+ setPeriodArray(frametime, "frametime_jitter_events", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_EVENTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ stats["frametime"] = frametime;
+
+ LLSD other;
+
+ // Packet loss
+ setPeriodArray(other, "packet_loss_percent", last, recording, num_periods, LLStatViewer::PACKETS_LOST_PERCENT,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Sim ping
+ setPeriodArray(other, "sim_ping_ms", last, recording, num_periods, LLStatViewer::SIM_PING,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Triangle rendering
+ setPeriodArray(other, "ktris_per_frame", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getSum(stat); });
+ setPeriodArray(other, "ktris_per_sec", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); });
+
+ // Object counts
+ setPeriodArray(other, "num_objects", last, recording, num_periods, LLStatViewer::NUM_OBJECTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(other, "num_active_objects", last, recording, num_periods, LLStatViewer::NUM_ACTIVE_OBJECTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ stats["other"] = other;
+
+ // texture memory usage
+ LLSD memory;
+ memory["texture_bytes_alloc_mb"] = (F64)LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0;
+ stats["memory"] = memory;
+
+ LLSD inventory;
+ // library skeleton cache load duration
+ inventory["skeleton_load_time_library_seconds"] = gInventory.getLibrarySkeletonLoadTime();
+ // agent skeleton cache load duration
+ inventory["skeleton_load_time_agent_seconds"] = gInventory.getAgentSkeletonLoadTime();
+ // initial recursive inventory/library background fetch duration
+ inventory["initial_fetch_time_seconds"] = LLInventoryModelBackgroundFetch::instance().getInitialFetchDuration();
+ inventory["fetch_completed"] = LLInventoryModelBackgroundFetch::instance().isEverythingFetched();
+ stats["inventory"] = inventory;
+
+ response["stats"] = stats;
+}
diff --git a/indra/newview/llstatslistener.h b/indra/newview/llstatslistener.h
new file mode 100644
index 0000000000..77777034c7
--- /dev/null
+++ b/indra/newview/llstatslistener.h
@@ -0,0 +1,41 @@
+/**
+ * @file llstatslistener.h
+ * @brief EventAPI interface for querying performance statistics
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * 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
+ * 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_LLSTATSLISTENER_H
+#define LL_LLSTATSLISTENER_H
+
+#include "lleventapi.h"
+
+class LLStatsListener : public LLEventAPI
+{
+public:
+ LLStatsListener();
+
+private:
+ void getPerfData(LLSD const & evt);
+};
+
+#endif // LL_LLSTATSLISTENER_H
diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp
index 8cbede8303..cbe2dac52a 100644
--- a/indra/newview/lltextureview.cpp
+++ b/indra/newview/lltextureview.cpp
@@ -250,7 +250,7 @@ void LLTextureBar::draw()
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
// Draw the progress bar.
- S32 bar_width = 100;
+ S32 bar_width = 110;
S32 bar_left = 260;
left = bar_left;
right = left + bar_width;
diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp
index 3de61dffc7..e905a879cb 100644
--- a/indra/newview/lltoastnotifypanel.cpp
+++ b/indra/newview/lltoastnotifypanel.cpp
@@ -301,7 +301,7 @@ void LLToastNotifyPanel::init( LLRect rect, bool show_images )
mIsScriptDialog = (notif_name == "ScriptDialog" || notif_name == "ScriptDialogGroup");
static LLCachedControl<S32> btn_width(gSavedSettings, "ToastButtonWidth", 90);
- static LLCachedControl<S32> script_button_width(gSavedSettings, "ScriptToastButtonWidth", 110);
+ static LLCachedControl<S32> script_button_width(gSavedSettings, "ScriptToastButtonWidth", 127);
mButtonWidth = mIsScriptDialog ? script_button_width : btn_width;
bool is_content_trusted = (notif_name != "LoadWebPage");
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index d93e900519..bc87e862b9 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -884,6 +884,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti
if (uploadInfo->showUploadDialog())
{
+ // todo: localize this string
std::string uploadMessage = "Uploading...\n\n";
uploadMessage.append(uploadInfo->getDisplayName());
LLUploadDialog::modalUploadDialog(uploadMessage);
@@ -891,66 +892,90 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti
LLSD body = uploadInfo->generatePostBody();
- result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions);
+ // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total)
+ const S32 MAX_UPLOAD_RETRIES = 2;
+ S32 upload_retry_count = 0;
+ LLCore::HttpStatus status;
- LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
- LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
-
- if ((!status) || (result.has("error")))
+ while (upload_retry_count <= MAX_UPLOAD_RETRIES)
{
- HandleUploadError(status, result, uploadInfo);
- if (uploadInfo->showUploadDialog())
- LLUploadDialog::modalUploadFinished();
- return;
- }
+ // Stage 1: Request uploader URL
+ result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions);
- std::string uploader = result["uploader"].asString();
-
- bool success = false;
- if (!uploader.empty() && uploadInfo->getAssetId().notNull())
- {
- result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions);
- httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- std::string ulstate = result["state"].asString();
-
- if ((!status) || (ulstate != "complete"))
+ if ((!status) || (result.has("error")))
{
HandleUploadError(status, result, uploadInfo);
if (uploadInfo->showUploadDialog())
LLUploadDialog::modalUploadFinished();
return;
}
- if (!result.has("success"))
+
+ std::string uploader = result["uploader"].asString();
+ if (uploader.empty() || uploadInfo->getAssetId().isNull())
{
- result["success"] = LLSD::Boolean((ulstate == "complete") && status);
+ LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL;
+ break;
}
- S32 uploadPrice = result["upload_price"].asInteger();
+ // Stage 2: Upload to the uploader URL
+ result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions);
+ httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- if (uploadPrice > 0)
- {
- // this upload costed us L$, update our balance
- // and display something saying that it cost L$
- LLStatusBar::sendMoneyBalanceRequest();
+ std::string ulstate = result["state"].asString();
- LLSD args;
- args["AMOUNT"] = llformat("%d", uploadPrice);
- LLNotificationsUtil::add("UploadPayment", args);
+ if ((!status) || (ulstate != "complete"))
+ {
+ if (upload_retry_count < MAX_UPLOAD_RETRIES)
+ {
+ upload_retry_count++;
+ LL_WARNS() << "Upload to uploader failed (attempt " << upload_retry_count
+ << " of " << (MAX_UPLOAD_RETRIES + 1) << "), re-requesting uploader..." << LL_ENDL;
+ llcoro::suspendUntilTimeout(1.0f);
+ continue;
+ }
+ else
+ {
+ HandleUploadError(status, result, uploadInfo);
+ if (uploadInfo->showUploadDialog())
+ LLUploadDialog::modalUploadFinished();
+ return;
+ }
}
+
+ // Success!
+ break;
}
- else
+
+ if (!result.has("success"))
+ {
+ result["success"] = LLSD::Boolean((result["state"].asString() == "complete") && status);
+ }
+
+ S32 uploadPrice = result["upload_price"].asInteger();
+
+ if (uploadPrice > 0)
{
- LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL;
+ // this upload costed us L$, update our balance
+ // and display something saying that it cost L$
+ LLStatusBar::sendMoneyBalanceRequest();
+
+ LLSD args;
+ args["AMOUNT"] = llformat("%d", uploadPrice);
+ LLNotificationsUtil::add("UploadPayment", args);
}
+
LLUUID serverInventoryItem = uploadInfo->finishUpload(result);
+ bool succeeded = false;
if (uploadInfo->showInventoryPanel())
{
if (serverInventoryItem.notNull())
{
- success = true;
+ succeeded = true;
LLFocusableElement* focus = gFocusMgr.getKeyboardFocus();
@@ -976,7 +1001,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti
LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot");
if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot && floater_snapshot->isShown())
{
- floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory")));
+ floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", succeeded).with("msg", "inventory")));
}
}
diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index e85c7db48d..3f06fe3320 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -216,9 +216,10 @@ void display_update_camera()
{
final_far *= 0.5f;
}
- else if (LLViewerTexture::sDesiredDiscardBias > 2.f)
+ // When system memory is critically low or recovering, shrink draw distance.
+ else if (const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor(); mem_factor > 1.f)
{
- final_far = llmax(32.f, final_far / (LLViewerTexture::sDesiredDiscardBias - 1.f));
+ final_far = llmax(32.f, final_far / mem_factor);
}
LLViewerCamera::getInstance()->setFar(final_far);
LLVOAvatar::sRenderDistance = llclamp(final_far, 16.f, 256.f);
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 8df56f8ef1..fcfddb091e 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -40,6 +40,7 @@
#include "llinventorydefines.h"
#include "lllslconstants.h"
#include "llmaterialtable.h"
+#include "llmemory.h"
#include "llregionhandle.h"
#include "llsd.h"
#include "llsdserialize.h"
@@ -3390,11 +3391,12 @@ void send_agent_update(bool force_send, bool send_reliable)
static F32 last_draw_disatance_step = 1024;
F32 memory_limited_draw_distance = gAgentCamera.mDrawDistance;
-
- if (LLViewerTexture::isSystemMemoryCritical())
+ const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor();
+ if (mem_factor > 1.f)
{
- // If we are low on memory, reduce requested draw distance
- memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / LLViewerTexture::getSystemMemoryBudgetFactor(), gAgentCamera.mDrawDistance / 2.f);
+ // We are critically low on memory or recovering,
+ // limit requested draw distance
+ memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / mem_factor, gAgentCamera.mDrawDistance / 2.f);
}
if (tp_state == LLAgent::TELEPORT_ARRIVING || LLStartUp::getStartupState() < STATE_MISC)
diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp
index b1673d2232..3b79666f7c 100644
--- a/indra/newview/llvieweroctree.cpp
+++ b/indra/newview/llvieweroctree.cpp
@@ -1143,6 +1143,8 @@ void LLOcclusionCullingGroup::checkOcclusion()
GLuint query_result; // Will be # samples drawn, or a boolean depending on mHasOcclusionQuery2 (both are type GLuint)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query result");
+ // Ping watchdog before a blocking GL_QUERY_RESULT GPU call
+ LLAppViewer::instance()->pingMainloopTimeout("Display:CullQueryResult");
glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT, &query_result);
}
#if LL_TRACK_PENDING_OCCLUSION_QUERIES
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 3b2796572b..45f7058acb 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -232,6 +232,8 @@ LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS("frametimeevents", "Numbe
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<U64> DOFRAME_TIME_US("doframetimeus", "doFrame wall time in microseconds.");
+
LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session.");
LLTrace::SampleStatHandle<F64> NFTV("nftv", "Normalized frametime variation.");
LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD("normalizedframetimejitterperiod", "Normalized frametime jitter over the last 5 seconds.");
diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h
index 8ec0dd0024..7da74e716b 100644
--- a/indra/newview/llviewerstats.h
+++ b/indra/newview/llviewerstats.h
@@ -209,29 +209,44 @@ extern SimMeasurement<F64Megabytes > SIM_PHYSICS_MEM;
extern LLTrace::SampleStatHandle<F64Milliseconds > FRAMETIME_JITTER,
- SIM_PING;
+ FRAMETIME,
+ SIM_PING,
+ FRAMETIME_JITTER_99TH,
+ FRAMETIME_JITTER_95TH,
+ FRAMETIME_99TH,
+ FRAMETIME_95TH,
+ FRAMETIME_JITTER_CUMULATIVE,
+ FRAMETIME_JITTER_STDDEV,
+ FRAMETIME_STDDEV;
+
+extern LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS,
+ FRAMETIME_JITTER_EVENTS_PER_MINUTE,
+ FRAMETIME_JITTER_EVENTS_LAST_MINUTE;
+
+extern LLTrace::SampleStatHandle<U64> DOFRAME_TIME_US;
+
+extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
+extern LLTrace::SampleStatHandle<F64> NFTV;
+extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
extern LLTrace::EventStatHandle<LLUnit<F64, LLUnits::Meters> > AGENT_POSITION_SNAP;
extern LLTrace::EventStatHandle<> LOADING_WEARABLES_LONG_DELAY;
extern LLTrace::EventStatHandle<F64Milliseconds > REGION_CROSSING_TIME,
- FRAME_STACKTIME,
- UPDATE_STACKTIME,
- NETWORK_STACKTIME,
- IMAGE_STACKTIME,
- REBUILD_STACKTIME,
- RENDER_STACKTIME;
+ FRAME_STACKTIME,
+ UPDATE_STACKTIME,
+ NETWORK_STACKTIME,
+ IMAGE_STACKTIME,
+ REBUILD_STACKTIME,
+ RENDER_STACKTIME;
extern LLTrace::EventStatHandle<F64Seconds > AVATAR_EDIT_TIME,
- TOOLBOX_TIME,
- MOUSELOOK_TIME;
+ TOOLBOX_TIME,
+ MOUSELOOK_TIME;
extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT_RATE;
-extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
-extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
-
extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST;
extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER;
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 0f23596c9a..6e05555c45 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -533,7 +533,7 @@ void LLViewerTexture::updateClass()
if (is_sys_low)
{
// Not having system memory is more serious, so discard harder
- sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * getSystemMemoryBudgetFactor());
+ sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * LLMemory::getSystemMemoryBudgetFactor());
}
else
{
@@ -679,20 +679,6 @@ bool LLViewerTexture::isSystemMemoryCritical()
return getFreeSystemMemory() < get_render_free_main_memory_treshold() / 2;
}
-F32 LLViewerTexture::getSystemMemoryBudgetFactor()
-{
- const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2);
- S32 free_budget = (S32Megabytes)getFreeSystemMemory() - MIN_FREE_MAIN_MEMORY;
- if (free_budget < 0)
- {
- // Leave some padding, otherwise we will crash out of memory before hitting factor 2.
- const S32Megabytes PAD_BUFFER(32);
- // Result should range from 1 at 0 free budget to 2 at -224 free budget, 2.14 at -256MB
- return 1.f - free_budget / (MIN_FREE_MAIN_MEMORY - PAD_BUFFER);
- }
- return 1.f;
-}
-
//end of static functions
//-------------------------------------------------------------------------------------------
const U32 LLViewerTexture::sCurrentFileVersion = 1;
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index 2937651995..20aaa8a058 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -116,7 +116,6 @@ public:
static void updateClass();
static bool isSystemMemoryLow();
static bool isSystemMemoryCritical();
- static F32 getSystemMemoryBudgetFactor();
LLViewerTexture(bool usemipmaps = true);
LLViewerTexture(const LLUUID& id, bool usemipmaps) ;
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index 4bee11b6f7..4d64f897b1 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -1014,38 +1014,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag
}
imagep->addTextureStats(max_vsize);
-
- // Derive stream priority channel from face lists.
- // Map render texture channels to priority channels:
- // 0 = normal, 1 = diffuse, 2 = specular, 3 = emissive
- {
- static const S32 render_to_priority[] = {
- 1, // DIFFUSE_MAP (0)
- 0, // NORMAL_MAP / ALTERNATE_DIFFUSE_MAP (1)
- 2, // SPECULAR_MAP (2)
- 1, // BASECOLOR_MAP (3)
- 2, // METALLIC_ROUGHNESS_MAP (4)
- 0, // GLTF_NORMAL_MAP (5)
- 3, // EMISSIVE_MAP (6)
- };
-
- S32 priority_channel = 1; // default to diffuse
- for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i)
- {
- if (imagep->getNumFaces(i) > 0)
- {
- priority_channel = llmin(priority_channel, render_to_priority[i]);
- }
- }
-
- static LLCachedControl<LLVector4> channel_priority(gSavedSettings, "TextureChannelPriority",
- LLVector4(10.0f, 20.0f, 40.0f, 20.0f));
- F32 factor = llmax(channel_priority().mV[priority_channel], 0.1f);
- if (factor != 1.0f)
- {
- imagep->mMaxVirtualSize /= factor;
- }
- }
}
#if 0
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 98de2422d8..8730b14614 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -201,6 +201,7 @@
#include "llwindowlistener.h"
#include "llviewerwindowlistener.h"
+#include "llstatslistener.h"
#include "llcleanup.h"
#if LL_WINDOWS
@@ -1833,7 +1834,7 @@ bool LLViewerWindow::handleTimerEvent(LLWindow *window)
return false;
}
-bool LLViewerWindow::handleDeviceChange(LLWindow *window)
+bool LLViewerWindow::handleDeviceChange(LLWindow *window, const std::string& change_type)
{
// give a chance to use a joystick after startup (hot-plugging)
if (!LLViewerJoystick::getInstance()->isJoystickInitialized() )
@@ -1841,6 +1842,10 @@ bool LLViewerWindow::handleDeviceChange(LLWindow *window)
LLViewerJoystick::getInstance()->init(true);
return true;
}
+ else
+ {
+ LL_INFOS("Window") << "Device change event: " << change_type << LL_ENDL;
+ }
return false;
}
@@ -1862,6 +1867,7 @@ bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32
bool LLViewerWindow::handleDisplayChanged()
{
+ LL_INFOS("Window") << "Display change event" << LL_ENDL;
LLFontGL::sResolutionGeneration++;
return false;
}
@@ -1943,6 +1949,7 @@ LLViewerWindow::LLViewerWindow(const Params& p)
LLWindowListener::KeyboardGetter getter = [](){ return gKeyboard; };
mWindowListener = std::make_unique<LLWindowListener>(this, getter);
mViewerWindowListener = std::make_unique<LLViewerWindowListener>(this);
+ mStatsListener = std::make_unique<LLStatsListener>();
mSystemChannel.reset(new LLNotificationChannel("System", "Visible", LLNotificationFilters::includeEverything));
mCommunicationChannel.reset(new LLCommunicationChannel("Communication", "Visible"));
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index 936eb275cf..06d49df56d 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -66,6 +66,7 @@ class LLWindow;
class LLRootView;
class LLWindowListener;
class LLViewerWindowListener;
+class LLStatsListener;
class LLVOPartGroup;
class LLPopupView;
class LLCubeMap;
@@ -233,7 +234,7 @@ public:
/*virtual*/ void handleWindowUnblock(LLWindow *window);
/*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data);
/*virtual*/ bool handleTimerEvent(LLWindow *window);
- /*virtual*/ bool handleDeviceChange(LLWindow *window);
+ /*virtual*/ bool handleDeviceChange(LLWindow *window, const std::string& change_type);
/*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height);
/*virtual*/ bool handleDisplayChanged();
/*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window);
@@ -565,6 +566,7 @@ private:
std::unique_ptr<LLWindowListener> mWindowListener;
std::unique_ptr<LLViewerWindowListener> mViewerWindowListener;
+ std::unique_ptr<LLStatsListener> mStatsListener;
// Object temporarily hovered over while dragging
LLPointer<LLViewerObject> mDragHoveredObject;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 5befad314e..85d32e19cb 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -8617,6 +8617,10 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading)
if (changed && isSelf())
{
+ // Agent's own avatar doesn't track bakes the same way as other avatars.
+ // So just update here, on cloud removal.
+ markBodyPartsComplexityDirty();
+
// to know about outfit switching
LLAvatarRenderNotifier::getInstance()->updateNotificationState();
}
@@ -10213,6 +10217,10 @@ void LLVOAvatar::onInitialBakedTextureLoaded( bool success, LLViewerFetchedTextu
}
if (final || !success )
{
+ if (selfp)
+ {
+ selfp->markBodyPartsComplexityDirty();
+ }
delete avatar_idp;
}
}
@@ -11530,7 +11538,7 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
// Store results
mVisualComplexity = total_cost;
- // Call the existing reporting function with the aggregated lists
+ // Call the reporting function with the aggregated lists
processComplexityCostChange(hud_complexity_list, object_complexity_list);
// Stop processing until something changes
diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp
index a26579f9ec..df14b46acd 100644
--- a/indra/newview/llvocache.cpp
+++ b/indra/newview/llvocache.cpp
@@ -32,6 +32,7 @@
#include "lldrawable.h"
#include "llviewerregion.h"
#include "llagentcamera.h"
+#include "llmemory.h"
#include "llsdserialize.h"
#include "llworld.h" // For LLWorld::getInstance()
//static variables
@@ -488,11 +489,12 @@ void LLVOCacheEntry::updateDebugSettings()
static const F32 MIN_RADIUS = 1.0f;
F32 draw_radius = gAgentCamera.mDrawDistance;
- if (LLViewerTexture::isSystemMemoryCritical())
+ const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor();
+ if (mem_factor > 1.f)
{
// Factor is intended to go from 1.0 to 2.0
// For safety cap reduction at 50%, we don't want to go below half of draw distance
- draw_radius = llmax(draw_radius / LLViewerTexture::getSystemMemoryBudgetFactor(), draw_radius / 2.f);
+ draw_radius = llmax(draw_radius / mem_factor, draw_radius / 2.f);
}
const F32 clamped_min_radius = llclamp((F32) min_radius, MIN_RADIUS, draw_radius); // [1, mDrawDistance]
sNearRadius = MIN_RADIUS + ((clamped_min_radius - MIN_RADIUS) * adjust_factor);
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 1ab3586e3a..9b4a371cec 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -1711,6 +1711,14 @@ void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled)
mVoiceEnabled = enabled;
LLVoiceClientStatusObserver::EStatusType status;
+ // Gate the audio devices on voice being enabled: the capture mic and
+ // playout speaker only run while voice is on, and the mic isn't held
+ // open when voice is off.
+ if (mWebRTCDeviceInterface)
+ {
+ mWebRTCDeviceInterface->setVoiceEnabled(enabled);
+ }
+
if (enabled)
{
LL_DEBUGS("Voice") << "enabling" << LL_ENDL;
@@ -3460,9 +3468,10 @@ void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap&
{
if (attributes.contains("packetsLost"))
{
- U32 out_packets_lost = 0;
- LLStringUtil::convertToU32(attributes.at("packetsLost"), out_packets_lost);
- sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, out_packets_lost);
+ // packetsLost may be negative, clamp to zero for unsigned Viewer stats
+ S32 out_packets_lost = 0;
+ LLStringUtil::convertToS32(attributes.at("packetsLost"), out_packets_lost);
+ sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, static_cast<U32>(llmax(out_packets_lost, 0)));
}
if (attributes.contains("jitter"))
{
@@ -3476,9 +3485,10 @@ void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap&
{
if (attributes.contains("packetsLost"))
{
- U32 in_packets_lost = 0;
- LLStringUtil::convertToU32(attributes.at("packetsLost"), in_packets_lost);
- sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, in_packets_lost);
+ // packetsLost may be negative, clamp to zero for unsigned Viewer stats
+ S32 in_packets_lost = 0;
+ LLStringUtil::convertToS32(attributes.at("packetsLost"), in_packets_lost);
+ sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, static_cast<U32>(llmax(in_packets_lost, 0)));
}
if (attributes.contains("packetsReceived"))
{
diff --git a/indra/newview/skins/default/xui/en/floater_profile.xml b/indra/newview/skins/default/xui/en/floater_profile.xml
index 32ab811a6e..3a33572785 100644
--- a/indra/newview/skins/default/xui/en/floater_profile.xml
+++ b/indra/newview/skins/default/xui/en/floater_profile.xml
@@ -31,7 +31,7 @@
follows="all"
layout="topleft"
halign="center"
- tab_min_width="81"
+ tab_min_width="80"
tab_height="30"
tab_position="top"
>
diff --git a/indra/newview/skins/default/xui/en/floater_script.xml b/indra/newview/skins/default/xui/en/floater_script.xml
index ae6e68de17..20feec615c 100644
--- a/indra/newview/skins/default/xui/en/floater_script.xml
+++ b/indra/newview/skins/default/xui/en/floater_script.xml
@@ -8,8 +8,8 @@
can_dock="true"
can_minimize="true"
visible="false"
- width="350"
+ width="402"
can_resize="false"
- min_width="350"
+ min_width="402"
min_height="200">
</floater>
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index f86dd81913..96fb3f7a17 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -764,7 +764,7 @@ class Windows_x86_64_Manifest(ViewerManifest):
pack_version = '.'.join(self.args['version'][:3])
if len(self.args['version']) > 3 and self.args['version'][3]:
pack_version += '-' + self.args['version'][3]
- pack_title = self.app_name() # Display name with spaces
+ pack_title = pack_id #Wrapper exe, don't use spaces
pack_dir = self.get_dst_prefix()
main_exe = self.final_exe()
installer_base = self.installer_base_name()