diff options
| author | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2026-08-12 16:09:42 -0400 |
|---|---|---|
| committer | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2026-08-12 16:09:42 -0400 |
| commit | 480af3ea32856007b166a06ca4932078c9f557c6 (patch) | |
| tree | 010a3b43a75dae55d6a66d3cdece6277dc97a1d8 /indra | |
| parent | 8cabc33e960ca93fdc1a32c77cf611269d95d51b (diff) | |
| parent | 932e8ac318eb2e29ea519249348315b50c7d1d58 (diff) | |
Merge branch 'release/26.3' into geenz/linux-to-develop
Diffstat (limited to 'indra')
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() |
