From 64eea0d2f2497468c8577323bdaa9fb711639d5c Mon Sep 17 00:00:00 2001 From: Roger Luedecke Date: Sat, 5 Sep 2026 11:52:28 -0700 Subject: [PATCH 1/2] Audio: Pin UI sounds in memory and preload UISndNewIncomingIMSession to prevent frame hitching - In LLAudioEngine, audio buffers were subject to aggressive purging in idle() after 30 seconds of inactivity, and to LRU reclamation in getFreeBuffer(). - Preloaded UI sounds that hadn't fired recently had their decoded audio buffers deleted from memory. When a notification arrived (e.g. object IM or chat alert), LLAudioSource::play() was forced to synchronously read and parse the WAV file from disk on the main thread via loadWAV(), causing noticeable frame hitches. - Furthermore, UISndNewIncomingIMSession was never preloaded in init_audio(), meaning incoming object IM sessions always incurred full on-demand asset decode and disk I/O. - Increase LL_MAX_AUDIO_BUFFERS from 40 to 80 to guarantee headroom for pinned UI sounds alongside the 30 audio channels. - Add pinned buffer tracking (mPinned) to LLAudioData and LLAudioBuffer to exempt UI sounds from 30s stale purging and LRU reuse. - Ensure preloadSound() immediately loads decoded WAV data into a buffer if available on disk and marks it pinned. - In tryFinishAudio(), if an audio asset is pinned, immediately load it into a pinned buffer upon decode completion. - In LLAudioSource::play(), dynamically pin buffers for AUDIO_TYPE_UI. - In init_audio(), preload UISndNewIncomingIMSession and UISndChatPing, pinning all UI sounds into memory. --- indra/llaudio/llaudiodecodemgr.cpp | 8 ++++ indra/llaudio/llaudioengine.cpp | 39 +++++++++++++++--- indra/llaudio/llaudioengine.h | 20 ++++++++- indra/newview/llvieweraudio.cpp | 66 +++++++++++++++--------------- 4 files changed, 94 insertions(+), 39 deletions(-) diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 232b4291305..2c66710e13a 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -756,6 +756,14 @@ bool tryFinishAudio(const LLUUID &decode_id, LLPointer deco if (valid) { adp->setHasWAVLoadFailed(false); + if (adp->isPinned() && !adp->getBuffer()) + { + adp->load(); + if (adp->getBuffer()) + { + adp->getBuffer()->setPinned(true); + } + } } return true; diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index 6f2f7eae61d..14787e79165 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -449,7 +449,7 @@ void LLAudioEngine::idle() { if (mBuffers[i]) { - if (!mBuffers[i]->mInUse && mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > 30.f) + if (!mBuffers[i]->mInUse && !mBuffers[i]->isPinned() && mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > 30.f) { //LL_INFOS() << "Flushing unused buffer!" << LL_ENDL; mBuffers[i]->mAudioDatap->mBufferp = NULL; @@ -550,7 +550,7 @@ LLAudioBuffer * LLAudioEngine::getFreeBuffer() { if (mBuffers[i]) { - if (!mBuffers[i]->mInUse) + if (!mBuffers[i]->mInUse && !mBuffers[i]->isPinned()) { if (mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > max_age) { @@ -643,12 +643,28 @@ void LLAudioEngine::cleanupBuffer(LLAudioBuffer *bufferp) } -bool LLAudioEngine::preloadSound(const LLUUID &uuid) +bool LLAudioEngine::preloadSound(const LLUUID &uuid, bool pin_buffer) { LL_DEBUGS("AudioEngine")<<"( "<setPinned(true); + } + + if (adp->hasDecodedData() && !adp->getBuffer()) + { + adp->load(); + if (adp->getBuffer() && pin_buffer) + { + adp->getBuffer()->setPinned(true); + } + return true; + } if (LLAudioDecodeMgr::getInstance()->addDecodeRequest(uuid)) { @@ -1418,6 +1434,15 @@ bool LLAudioSource::play(const LLUUID &audio_uuid) LLAudioData *adp = gAudiop->getAudioData(audio_uuid); addAudioData(adp); + if (mType == LLAudioEngine::AUDIO_TYPE_UI) + { + adp->setPinned(true); + if (adp->getBuffer()) + { + adp->getBuffer()->setPinned(true); + } + } + if (isMuted()) { return false; @@ -1843,5 +1868,9 @@ bool LLAudioData::load() return false; } mBufferp->mAudioDatap = this; + if (mPinned) + { + mBufferp->setPinned(true); + } return true; } diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index a9a229c0a5e..36361458db5 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -49,7 +49,7 @@ const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f; const F32 DEFAULT_MIN_DISTANCE = 2.0f; #define LL_MAX_AUDIO_CHANNELS 30 -#define LL_MAX_AUDIO_BUFFERS 40 // Some extra for preloading, maybe? +#define LL_MAX_AUDIO_BUFFERS 80 // Some extra for preloading and pinning UI sounds class LLAudioSource; class LLAudioData; @@ -137,7 +137,7 @@ class LLAudioEngine const LLVector3d &pos_global = LLVector3d::zero); void triggerSound(SoundData& soundData); - bool preloadSound(const LLUUID &id); + bool preloadSound(const LLUUID &id, bool pin_buffer = true); void addAudioSource(LLAudioSource *asp); void cleanupAudioSource(LLAudioSource *asp); @@ -373,12 +373,14 @@ class LLAudioData bool hasCompletedDecode() const { return mHasCompletedDecode; } bool hasDecodeFailed() const { return mHasDecodeFailed; } bool hasWAVLoadFailed() const { return mHasWAVLoadFailed; } + bool isPinned() const { return mPinned; } void setHasLocalData(const bool hld) { mHasLocalData = hld; } void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; } void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; } void setHasDecodeFailed(const bool hdf) { mHasDecodeFailed = hdf; } void setHasWAVLoadFailed(const bool hwlf) { mHasWAVLoadFailed = hwlf; } + void setPinned(const bool pinned); friend class LLAudioEngine; // Severe laziness, bad. @@ -391,6 +393,7 @@ class LLAudioData bool mHasDecodeFailed; // Set true if decoding failed, meaning the sound asset is bad bool mHasWAVLoadFailed; // Set true if loading the decoded WAV file failed, meaning the sound asset should be decoded instead if // possible + bool mPinned{ false }; }; @@ -449,15 +452,28 @@ class LLAudioBuffer virtual bool loadWAV(const std::string& filename) = 0; virtual U32 getLength() = 0; + void setPinned(bool pinned) { mPinned = pinned; } + bool isPinned() const { return mPinned; } + friend class LLAudioEngine; friend class LLAudioChannel; friend class LLAudioData; protected: bool mInUse; + bool mPinned{ false }; LLAudioData *mAudioDatap; LLFrameTimer mLastUseTimer; }; +inline void LLAudioData::setPinned(const bool pinned) +{ + mPinned = pinned; + if (mBufferp) + { + mBufferp->setPinned(pinned); + } +} + struct SoundData { LLUUID audio_uuid; diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index a7441febd95..f94dc9f8fe2 100644 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -359,38 +359,40 @@ void init_audio() if (!mute_audio && false == gSavedSettings.getBOOL("NoPreload")) { - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndAlert"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndBadKeystroke"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatFromObject"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClick"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClickRelease"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionF"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingChat"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingIM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvApplyToObject"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvalidOp"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInventoryCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeDown"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeUp"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCreate"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectDelete"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezIn"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezOut"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndSnapshot"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartAutopilot"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartFollowpilot"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartIM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStopAutopilot"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTeleportOut"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureApplyToObject"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatMention"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndAlert")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndBadKeystroke")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatFromObject")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClick")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClickRelease")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionF")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionM")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingChat")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingIM")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvApplyToObject")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvalidOp")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInventoryCopyToInv")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeDown")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeUp")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndNewIncomingIMSession")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCopyToInv")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCreate")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectDelete")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezIn")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezOut")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndSnapshot")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartAutopilot")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartFollowpilot")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartIM")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStopAutopilot")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTeleportOut")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureApplyToObject")), true); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureCopyToInv")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatMention")), true); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatPing")), true); } audio_update_volume(true); From 926cbe592c917ae4836a90117e21824eabc0b9f7 Mon Sep 17 00:00:00 2001 From: Roger Luedecke Date: Sat, 5 Sep 2026 14:01:34 -0700 Subject: [PATCH 2/2] Audio: Default preloadSound pin_buffer to false and restrict pinning to preloaded UI sounds - Change preloadSound() default parameter pin_buffer from true to false to prevent inventory sound previews and decode retries from unintentionally pinning buffers into RAM. - Remove dynamic UI pinning from LLAudioSource::play(); active playback is already protected from idle eviction and LRU reclamation by mInUse, and fixed UI sounds are preloaded with pin_buffer = true in init_audio(). - Preserve mPinned state on decode reload retry in LLAudioData::load(). --- indra/llaudio/llaudioengine.cpp | 11 +---------- indra/llaudio/llaudioengine.h | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index 14787e79165..124cc31855a 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -1434,15 +1434,6 @@ bool LLAudioSource::play(const LLUUID &audio_uuid) LLAudioData *adp = gAudiop->getAudioData(audio_uuid); addAudioData(adp); - if (mType == LLAudioEngine::AUDIO_TYPE_UI) - { - adp->setPinned(true); - if (adp->getBuffer()) - { - adp->getBuffer()->setPinned(true); - } - } - if (isMuted()) { return false; @@ -1862,7 +1853,7 @@ bool LLAudioData::load() mHasCompletedDecode = false; mHasDecodeFailed = false; mHasWAVLoadFailed = false; - gAudiop->preloadSound(mID); + gAudiop->preloadSound(mID, mPinned); } return false; diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 36361458db5..9762d8928ef 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -137,7 +137,7 @@ class LLAudioEngine const LLVector3d &pos_global = LLVector3d::zero); void triggerSound(SoundData& soundData); - bool preloadSound(const LLUUID &id, bool pin_buffer = true); + bool preloadSound(const LLUUID &id, bool pin_buffer = false); void addAudioSource(LLAudioSource *asp); void cleanupAudioSource(LLAudioSource *asp);