From 5a7a5272a07935aae544767abc700e8fabf3cbbd Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 27 Mar 2026 19:46:38 +0200 Subject: #4298 Handle various out of memory cases --- indra/llrender/llfontfreetype.cpp | 43 +++++++++++++++++++++++---------------- indra/llrender/llshadermgr.cpp | 19 +++++++++++++++-- 2 files changed, 43 insertions(+), 19 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index d37b16ce0c..d9857a7ad5 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -871,30 +871,39 @@ namespace ll U8 const* LLFontManager::loadFont( std::string const &aFilename, long &a_Size) { - a_Size = 0; - std::map< std::string, std::shared_ptr >::iterator itr = m_LoadedFonts.find( aFilename ); - if( itr != m_LoadedFonts.end() ) + try { - ++itr->second->mRefs; - // A possible overflow cannot happen here, as it is asserted that the size is less than std::numeric_limits::max() a few lines below. - a_Size = static_cast(itr->second->mSize); - return reinterpret_cast(itr->second->mAddress.c_str()); - } + a_Size = 0; + std::map< std::string, std::shared_ptr >::iterator itr = m_LoadedFonts.find(aFilename); + if (itr != m_LoadedFonts.end()) + { + ++itr->second->mRefs; + // A possible overflow cannot happen here, as it is asserted that the size is less than std::numeric_limits::max() a few lines below. + a_Size = static_cast(itr->second->mSize); + return reinterpret_cast(itr->second->mAddress.c_str()); + } - auto strContent = LLFile::getContents(aFilename); + auto strContent = LLFile::getContents(aFilename); - if( strContent.empty() ) - return nullptr; + if (strContent.empty()) + return nullptr; - // For fontconfig a type of long is required, std::string::size() returns size_t. I think it is safe to limit this to 2GiB and not support fonts that huge (can that even be a thing?) - llassert_always( strContent.size() < std::numeric_limits::max() ); + // For fontconfig a type of long is required, std::string::size() returns size_t. I think it is safe to limit this to 2GiB and not support fonts that huge (can that even be a thing?) + llassert_always(strContent.size() < std::numeric_limits::max()); - a_Size = static_cast(strContent.size()); + a_Size = static_cast(strContent.size()); - auto pCache = std::make_shared( aFilename, strContent, a_Size ); - itr = m_LoadedFonts.insert( std::make_pair( aFilename, pCache ) ).first; + auto pCache = std::make_shared(aFilename, strContent, a_Size); + itr = m_LoadedFonts.insert(std::make_pair(aFilename, pCache)).first; - return reinterpret_cast(itr->second->mAddress.c_str()); + return reinterpret_cast(itr->second->mAddress.c_str()); + } + catch (const std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Failed to load font. Out of memory." << LL_ENDL; + } + return nullptr; } void LLFontManager::unloadAllFonts() diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index 2c35a6acae..b8545b3ed9 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1024,8 +1024,23 @@ void LLShaderMgr::initShaderCache(bool enabled, const LLUUID& old_cache_version, llifstream instream(meta_out_path, std::ifstream::in | std::ifstream::binary); LLSD in_data; - // todo: this is likely very expensive to parse, should use binary - LLSDSerialize::fromBinary(in_data, instream, LLSDSerialize::SIZE_UNLIMITED); + try + { + LLSDSerialize::fromBinary(in_data, instream, LLSDSerialize::SIZE_UNLIMITED); + } + catch( std::bad_alloc& ) + { + // Try to get a bit more memory back before we try to clear the cache. + in_data.clear(); + // Just in case it was somehow the cause, clear cache. + clearShaderCache(); + // If user run out of memory this early in init, + // we don't want to keep going just to crash again. + // Notify user and close. + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("ShaderMgr") << "Failed to parse shader cache metadata, potentially due to size. Purged cache." << LL_ENDL; + return; + } instream.close(); if (old_cache_version == current_cache_version -- cgit v1.3 From 5f91a3faf3ef0b9d0a6c3424fff119992dc0822a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:53:56 +0300 Subject: #5626 LLTextBase optimization by caching string width --- indra/llrender/llfontvertexbuffer.cpp | 77 +++++++++++++++++++++++++++++++++++ indra/llrender/llfontvertexbuffer.h | 46 +++++++++++++++++++++ indra/llui/llscrollcontainer.cpp | 1 + indra/llui/lltextbase.cpp | 29 +++++++------ indra/llui/lltextbase.h | 13 +++--- indra/newview/llexpandabletextbox.cpp | 2 +- indra/newview/llviewertexteditor.cpp | 12 ++++-- indra/newview/pipeline.cpp | 8 +++- 8 files changed, 164 insertions(+), 24 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llfontvertexbuffer.cpp b/indra/llrender/llfontvertexbuffer.cpp index a223509d30..2a0115265f 100644 --- a/indra/llrender/llfontvertexbuffer.cpp +++ b/indra/llrender/llfontvertexbuffer.cpp @@ -237,3 +237,80 @@ void LLFontVertexBuffer::renderBuffers() gGL.popUIMatrix(); } +// LLFontWidthBuffer +bool LLFontWidthBuffer::sEnableBufferCollection = true; + +LLFontWidthBuffer::LLFontWidthBuffer() +{ +} + +LLFontWidthBuffer::~LLFontWidthBuffer() +{ +} + +void LLFontWidthBuffer::reset() +{ + mLastFont = nullptr; + mLastOffset = 0; + mLastMaxChars = 0; + mLastNoPadding = false; + mWidth = -1.f; + mLastScaleX = 1.f; + mLastScaleY = 1.f; + mLastVertDPI = 0.f; + mLastHorizDPI = 0.f; + mLastResGeneration = 0; + mLastFontCacheGen = 0; +} + +F32 LLFontWidthBuffer::getWidth( + const LLFontGL* fontp, + const llwchar* wchars, + S32 begin_offset, + S32 max_chars, + bool no_padding) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (!fontp || !wchars) + { + return 0.f; + } + + if (!sEnableBufferCollection) + { + return fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding); + } + + // Check if we can use cached width + bool needs_recalc = (mWidth < 0.f) + || (mLastFont != fontp) + || (mLastOffset != begin_offset) + || (mLastMaxChars != max_chars) + || (mLastNoPadding != no_padding) + || (mLastScaleX != LLFontGL::sScaleX) + || (mLastScaleY != LLFontGL::sScaleY) + || (mLastVertDPI != LLFontGL::sVertDPI) + || (mLastHorizDPI != LLFontGL::sHorizDPI) + || (mLastResGeneration != LLFontGL::sResolutionGeneration) + || (mLastFontCacheGen != fontp->getCacheGeneration()); + + if (needs_recalc) + { + // Calculate width using the font + mWidth = fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding); + + // Cache the parameters + mLastFont = fontp; + mLastOffset = begin_offset; + mLastMaxChars = max_chars; + mLastNoPadding = no_padding; + mLastScaleX = LLFontGL::sScaleX; + mLastScaleY = LLFontGL::sScaleY; + mLastVertDPI = LLFontGL::sVertDPI; + mLastHorizDPI = LLFontGL::sHorizDPI; + mLastResGeneration = LLFontGL::sResolutionGeneration; + mLastFontCacheGen = fontp->getCacheGeneration(); + } + + return mWidth; +} diff --git a/indra/llrender/llfontvertexbuffer.h b/indra/llrender/llfontvertexbuffer.h index a9e1e2337c..94b833d227 100644 --- a/indra/llrender/llfontvertexbuffer.h +++ b/indra/llrender/llfontvertexbuffer.h @@ -32,6 +32,11 @@ class LLVertexBufferData; +// Rendering fonts is expensive, this class is intended to store +// vertex buffers for rendered text, so that they can be reused. +// LLFontVertexBuffer tracks font and rendering parameters, but +// expects caller to track text changes and call reset() when +// text changes. class LLFontVertexBuffer { public: @@ -127,4 +132,45 @@ private: static bool sEnableBufferCollection; }; +// Extracting width from a font is expensive, and due to +// mechanics of font rendering, we need width separately +// and usually before rendering. +// LLFontWidthBuffer tracks font and rendering parameters, +// but expects caller to track text changes and call reset() +// when text changes. +class LLFontWidthBuffer +{ +public: + LLFontWidthBuffer(); + ~LLFontWidthBuffer(); + + void reset(); + + F32 getWidth(const LLFontGL* fontp, + const llwchar* wchars, + S32 begin_offset, + S32 max_chars, + bool no_padding); + + static void enableBufferCollection(bool enable) { sEnableBufferCollection = enable; } +private: + const LLFontGL* mLastFont = nullptr; + S32 mLastOffset = 0; + S32 mLastMaxChars = 0; + bool mLastNoPadding = false; + F32 mWidth = -1.f; + + // LLFontGL's values that affect width calculation + F32 mLastScaleX = 1.f; + F32 mLastScaleY = 1.f; + F32 mLastVertDPI = 0.f; + F32 mLastHorizDPI = 0.f; + S32 mLastResGeneration = 0; + + // Cache generation tracking + S32 mLastFontCacheGen = 0; + + static bool sEnableBufferCollection; +}; + #endif diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index df99c4f636..e36fd45bb4 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -480,6 +480,7 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height void LLScrollContainer::draw() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c084b400f6..9fcf2a5f10 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1640,7 +1640,7 @@ void LLTextBase::deselect() bool LLTextBase::getSpellCheck() const { - return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); + return (!mReadOnly) && (LLSpellChecker::getUseSpellCheck()) && (mSpellCheck); } const std::string& LLTextBase::getSuggestion(U32 index) const @@ -2855,7 +2855,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, line_seg_iter != mSegments.end(); ++line_seg_iter, line_seg_offset = 0) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; S32 segment_line_start = segmentp->getStart() + line_seg_offset; S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; @@ -2946,7 +2946,7 @@ LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const while(line_seg_iter != mSegments.end()) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; if (line_seg_iter == cursor_seg_iter) { @@ -3497,8 +3497,8 @@ LLStyleSP LLTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source) } -bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } -bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = 0; return false; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) { F32 fwidth = 0; bool result = getDimensionsF32(first_char, num_chars, fwidth, height); @@ -3595,6 +3595,7 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } return draw_rect.mLeft; } @@ -3620,6 +3621,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } const LLFontGL* font = mStyle->getFont(); @@ -3851,17 +3853,19 @@ LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const return new LLNormalTextSegment(sp, mStart, mEnd, target); } -bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { height = 0; width = 0; if (num_chars > 0 && (mStart + first_char >= 0)) { height = mFontHeight; - const LLWString &text = getWText(); - // if last character is a newline, then return true, forcing line break - width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); + + const LLWString& text = getWText(); + const LLFontGL* font = mStyle->getFont(); + width += mFontWidthBuffer.getWidth(font, text.c_str(), mStart + first_char, num_chars, true); } + // if last character is a newline, then return true, forcing line break return false; } @@ -3943,6 +3947,7 @@ void LLNormalTextSegment::updateLayout(const class LLTextBase& editor) mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } void LLNormalTextSegment::dump() const @@ -4094,7 +4099,7 @@ LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const return nullptr; } -bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { if (first_char == 0 && num_chars == 0) { @@ -4186,7 +4191,7 @@ LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const copy->mFontHeight = mFontHeight; return copy; } -bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mFontHeight; @@ -4223,7 +4228,7 @@ LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const static const S32 IMAGE_HPAD = 3; // virtual -bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mStyle->getFont()->getLineHeight(); diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 21134fd7c9..9206b3facf 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -68,10 +68,10 @@ public: virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); } static LLStyleSP cloneStyle(LLTextBase& target, const LLStyle* source); - bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; + bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height); bool getPermitsEmoji() const { return mPermitsEmoji; }; - virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /** @@ -139,7 +139,7 @@ public: virtual ~LLNormalTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); @@ -182,6 +182,7 @@ protected: LLFontVertexBuffer mFontBufferPreSelection; LLFontVertexBuffer mFontBufferSelection; LLFontVertexBuffer mFontBufferPostSelection; + LLFontWidthBuffer mFontWidthBuffer; S32 mLastGeneration = -1; }; @@ -254,7 +255,7 @@ public: ~LLInlineViewSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -280,7 +281,7 @@ public: LLLineBreakTextSegment(S32 pos); ~LLLineBreakTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -295,7 +296,7 @@ public: ~LLImageTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp index 5c46eb9d80..41ae47b645 100644 --- a/indra/newview/llexpandabletextbox.cpp +++ b/indra/newview/llexpandabletextbox.cpp @@ -52,7 +52,7 @@ public: return copy; } - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { // more label always spans width of text box if (num_chars == 0) diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 210cd62d6f..95e34b4df1 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -189,7 +189,12 @@ public: return new LLEmbeddedItemSegment(mStart, mImage, mItem, *editor); } - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) + { + return getSegmentDimensionsF32(first_char, num_chars, width, height); + } + + inline bool getSegmentDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { if (num_chars == 0) { @@ -213,8 +218,9 @@ public: } else { - S32 width, height; - getDimensions(mStart, 1, width, height); + F32 width; + S32 height; + getSegmentDimensionsF32(mStart, 1, width, height); if (width > num_pixels) { return 0; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index c9d53bbcbc..4ef88f5deb 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -609,7 +609,9 @@ void LLPipeline::init() { cntrl_ptr->getCommitSignal()->connect([](LLControlVariable* control, const LLSD& value, const LLSD& previous) { - LLFontVertexBuffer::enableBufferCollection(control->getValue().asBoolean()); + bool enable_buffers = control->getValue().asBoolean(); + LLFontVertexBuffer::enableBufferCollection(enable_buffers); + LLFontWidthBuffer::enableBufferCollection(enable_buffers); }); } } @@ -1146,7 +1148,9 @@ void LLPipeline::refreshCachedSettings() LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); } - LLFontVertexBuffer::enableBufferCollection(gSavedSettings.getBOOL("CollectFontVertexBuffers")); + bool enable_buffers = gSavedSettings.getBOOL("CollectFontVertexBuffers"); + LLFontVertexBuffer::enableBufferCollection(enable_buffers); + LLFontWidthBuffer::enableBufferCollection(enable_buffers); } void LLPipeline::releaseGLBuffers() -- cgit v1.3 From 82db4943e011fc65e9a0b87990abb49b898a7782 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 12 May 2026 20:23:44 -0400 Subject: Rework texture streaming and tracking. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a big one: - Reworks the discard signal almost entirely. Now has a normalized 0..1 discard signal: distance x size x channel exponent, floored by staleness and background app state. Shaped by VRAM pressure. - Textures can now scale down to the smallest GPU mip (1×1), independent of the codec's encoded mip count. - Terrain texture LOD now works. Useful for 2K textures and PBR on terrain. Based upon camera distance to nearest terrain patch. - New texture quality setting. Low/Medium/High/Ultra - Caps texture resolution on Low to 1024, and otherwise shifts the discard signal around. Makes distance based texture LOD work a lot more predictably. - We now track last bind state for textures, and discard accordingly. We progressively discard based upon last bind time. - Avatar textures get a residency boost to stay loaded in VRAM longer under pressure. --- indra/llrender/llimagegl.cpp | 84 ++++++++-- indra/llrender/llimagegl.h | 16 +- indra/llrender/llrender.cpp | 14 ++ indra/newview/app_settings/settings.xml | 144 +++++++++++++++-- indra/newview/llface.cpp | 30 +++- indra/newview/llface.h | 7 +- indra/newview/llsurface.cpp | 14 +- indra/newview/llsurface.h | 6 + indra/newview/lltexturefetch.cpp | 12 +- indra/newview/lltexturefetch.h | 3 +- indra/newview/llviewercontrol.cpp | 21 ++- indra/newview/llviewertexture.cpp | 276 +++++++++++++++++++++++--------- indra/newview/llviewertexture.h | 50 +++++- indra/newview/llviewertexturelist.cpp | 176 ++++++++++++++------ indra/newview/llviewertexturelist.h | 5 + indra/newview/llvlcomposition.cpp | 18 +-- 16 files changed, 702 insertions(+), 174 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 4a3d32c7ff..d79d13dc8b 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -66,8 +66,8 @@ static LLMutex sTexMemMutex; static std::unordered_map sTextureAllocs; static U64 sTextureBytes = 0; -// track a texture alloc on the currently bound texture. -// asserts that no currently tracked alloc exists +// Per-mip upload paths call this once per level; only free_tex_image +// removes a texture's accounting entirely. void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count) { U32 texUnit = gGL.getCurrentTexUnitIndex(); @@ -80,15 +80,46 @@ void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32 sTexMemMutex.lock(); - // it is a precondition that no existing allocation exists for this texture - llassert(sTextureAllocs.find(texName) == sTextureAllocs.end()); - - sTextureAllocs[texName] = size; + auto iter = sTextureAllocs.find(texName); + if (iter != sTextureAllocs.end()) + { + iter->second += size; + } + else + { + sTextureAllocs[texName] = size; + } sTextureBytes += size; sTexMemMutex.unlock(); } +// Add mip 1..N bytes to existing accounting. Use after glGenerateMipmap. +void LLImageGLMemory::account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat) +{ + U64 extra = 0; + U32 w = base_width; + U32 h = base_height; + while (w > 1 || h > 1) + { + w = w > 1 ? w >> 1 : 1; + h = h > 1 ? h >> 1 : 1; + extra += LLImageGL::dataFormatBytes(intformat, w, h); + } + + U32 texUnit = gGL.getCurrentTexUnitIndex(); + U32 texName = gGL.getTexUnit(texUnit)->getCurrTexture(); + + sTexMemMutex.lock(); + auto iter = sTextureAllocs.find(texName); + if (iter != sTextureAllocs.end()) + { + iter->second += extra; + sTextureBytes += extra; + } + sTexMemMutex.unlock(); +} + // track texture free on given texName void LLImageGLMemory::free_tex_image(U32 texName) { @@ -838,7 +869,7 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 mMipLevels = wpo2(llmax(w, h)); //use legacy mipmap generation mode (note: making this condional can cause rendering issues) - // -- but making it not conditional triggers deprecation warnings when core profile is enabled + // - but making it not conditional triggers deprecation warnings when core profile is enabled // (some rendering issues while core profile is enabled are acceptable at this point in time) if (!LLRender::sGLCoreProfile) { @@ -864,6 +895,7 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 { LL_PROFILE_GPU_ZONE("generate mip map"); glGenerateMipmap(mTarget); + account_extra_mip_bytes(w, h, mFormatInternal); } stop_glerror(); } @@ -1461,7 +1493,12 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt LL_PROFILE_ZONE_NUM(width); LL_PROFILE_ZONE_NUM(height); - free_cur_tex_image(); + // Release prior accounting only on the base mip; per-mip iteration + // accumulates the rest via the additive alloc_tex_image. + if (miplevel == 0) + { + free_cur_tex_image(); + } const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) { @@ -2039,6 +2076,28 @@ S32 LLImageGL::getWidth(S32 discard_level) const return width; } +// static +S32 LLImageGL::dimDerivedMaxDiscard(S32 width, S32 height) +{ + if (width <= 0 || height <= 0) + { + return 0; + } + // max(w,h) - min() caps short on rectangular textures + // (1024x512 reaches 1x1 at discard 10, not 9). + return (S32)floorf(log2f((F32)llmax(width, height))); +} + +void LLImageGL::stampBound() const +{ + // Skip the store on same-frame re-binds - bindFast is per-draw and + // would dirty this cache line per bind per texture otherwise. + if (mLastBindTime != sLastFrameTime) + { + mLastBindTime = sLastFrameTime; + } +} + S64 LLImageGL::getBytes(S32 discard_level) const { if (discard_level < 0) @@ -2468,7 +2527,12 @@ bool LLImageGL::scaleDown(S32 desired_discard) return false; } - desired_discard = llmin(desired_discard, mMaxDiscardLevel); + // GL pyramid reaches 1x1 regardless of codec levels; + // mMaxDiscardLevel is hardcapped at MAX_DISCARD_LEVEL. + S32 dim_max_discard = (mWidth > 0 && mHeight > 0) + ? dimDerivedMaxDiscard(mWidth, mHeight) + : (S32)mMaxDiscardLevel; + desired_discard = llmin(desired_discard, dim_max_discard); if (desired_discard <= mCurrentDiscardLevel) { @@ -2501,6 +2565,7 @@ bool LLImageGL::scaleDown(S32 desired_discard) LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap"); gGL.getTexUnit(0)->bind(this); glGenerateMipmap(mTarget); + account_extra_mip_bytes(desired_width, desired_height, mFormatInternal); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } } @@ -2546,6 +2611,7 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap"); glGenerateMipmap(mTarget); + account_extra_mip_bytes(desired_width, desired_height, mFormatInternal); } gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 6b4492c09e..a02c320738 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -51,6 +51,11 @@ class LLWindow; namespace LLImageGLMemory { void alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count); + + // Add mip 1..N bytes to existing accounting. Call after glGenerateMipmap + // when only the base mip was accounted; without this the bytes counter + // undercounts mipmap-generated textures by ~25%. + void account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat); void free_tex_image(U32 texName); void free_tex_images(U32 count, const U32* texNames); void free_cur_tex_image(); @@ -151,6 +156,15 @@ public: S32 getDiscardLevel() const { return mCurrentDiscardLevel; } S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; } + // floor(log2(max(w, h))) - deepest GL pyramid level (down to 1x1). + // Returns 0 for non-positive inputs. + static S32 dimDerivedMaxDiscard(S32 width, S32 height); + + // Record the wall-clock bind time - every bind path that touches a + // streaming-managed texture must call this, or the staleness signal + // sees the texture as never-bound and ramps it toward eviction. + void stampBound() const; + // override the current discard level // should only be used for local textures where you know exactly what you're doing void setDiscardLevel(S32 level) { mCurrentDiscardLevel = level; } @@ -224,7 +238,7 @@ public: public: // Various GL/Rendering options S64Bytes mTextureMemory; - mutable F32 mLastBindTime; // last time this was bound, by discard level + mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives streaming staleness private: U32 createPickMask(S32 pWidth, S32 pHeight); diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 1a3a499b20..696a0d145f 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -197,6 +197,9 @@ void LLTexUnit::bindFast(LLTexture* texture) glActiveTexture(GL_TEXTURE0 + mIndex); gGL.mCurrTextureUnitIndex = mIndex; mCurrTexture = gl_tex->getTexName(); + // bindFast bypasses updateBindStats(); stamp directly so the staleness + // signal sees per-frame use of batched textures. + gl_tex->stampBound(); if (!mCurrTexture) { LL_PROFILE_ZONE_NAMED("MISSING TEXTURE"); @@ -249,11 +252,17 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) setTextureFilteringOption(gl_tex->mFilterOption); } } + else + { + // Already current - still being used, keep it fresh. + gl_tex->stampBound(); + } } else { //if deleted, will re-generate it immediately texture->forceImmediateUpdate() ; + gl_tex->stampBound(); gl_tex->forceUpdateBindStats() ; return texture->bindDefaultImage(mIndex); @@ -325,6 +334,11 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 stop_glerror(); } } + else + { + // Already current - still being used, keep it fresh. + texture->stampBound(); + } stop_glerror(); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9de8892c5e..0f1b704dc9 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9221,7 +9221,7 @@ RenderReflectionDetail Comment - DEPRECATED -- use RenderTransparentWater and RenderReflectionProbeDetail -- Detail of reflection render pass. + DEPRECATED - use RenderTransparentWater and RenderReflectionProbeDetail - Detail of reflection render pass. Persist 1 Type @@ -11819,30 +11819,152 @@ TextureChannelPriority Comment - Per-channel texture streaming aggressiveness. X=normals, Y=diffuse, Z=specular/metallic, W=emissive. 1.0=baseline, higher=more aggressive downrez. + Per-channel exponent on the combined discard factor. X=normals, Y=diffuse, Z=spec, W=emissive. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type Vector4 Value - 5 - 7.5 - 20 - 7.5 + 1.0 + 0.75 + 0.5 + 0.75 - TextureCameraBoost + TextureMaxDiscardOverride Comment - Amount to boost resolution of textures that are important to the camera. + When non-zero, overrides the per-texture codec-derived max discard cap. 0 = use codec-reported levels. Higher lets the streaming math push past the codec ceiling; scaleDown handles the GL side. Persist + 1 + Type + S32 + Value 0 + + TextureMemoryPressureRampRate + + Comment + Feedback rate (per second) for the VRAM-pressure factor (0..1). Higher = faster convergence on the budget; lower = gentler. + Persist + 1 Type F32 Value - 8.0 + 3.0 + + TextureMemoryPressureBackoffStart + + Comment + Fraction of the VRAM target at which the pressure ramp starts (0..1). Lower = earlier headroom-building; 1.0 disables backoff (ramp only above target). + Persist + 1 + Type + F32 + Value + 0.85 + + TextureTerrainDistanceFloor + + Comment + Minimum distance factor for BOOST_TERRAIN textures. Keeps combined > 0 so VRAM pressure can evict terrain. Lower = higher idle quality, less pressure response. + Persist + 1 + Type + F32 + Value + 0.01 + + TextureTerrainCoverageFraction + + Comment + Synthetic on-screen coverage fraction for BOOST_TERRAIN textures (no faces are registered). Higher = higher idle quality, less pressure response. + Persist + 1 + Type + F32 + Value + 0.99 + + TextureAgentAvatarBoost + + Comment + Quality boost (0..1) for textures on the agent's avatar (rigged mesh / animated objects). Lower = higher quality. Preference, not exemption - pressure can still evict. + Persist + 1 + Type + F32 + Value + 0.5 + + TextureBackgroundFactorRatePerSec + + Comment + Per-second ramp rate of the background-window discard floor (0..1). Snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. + Persist + 1 + Type + F32 + Value + 0.011 + + TextureDistanceDiscardPower + + Comment + Exponent on the distance factor (face_distance / draw_distance). 1.0 = linear; lower = textures hit max discard sooner with distance. Default 0.5 = sqrt. + Persist + 1 + Type + F32 + Value + 0.5 + TextureSizeDiscardPower + + Comment + Exponent on the on-screen size factor (1 - coverage). 1.0 = linear; lower = small-on-screen textures attenuate sooner. + Persist + 1 + Type + F32 + Value + 1.0 + + TextureStalenessIntervalSeconds + + Comment + Seconds per staleness step. Per-interval increment is 1/max_discard so any texture saturates after interval * max_discard seconds idle. + Persist + 1 + Type + F32 + Value + 5.0 + + TextureBindDecaySeconds + + Comment + Grace seconds after a bind during which the staleness factor stays at 0. Prevents intermittently-bound textures from ramping. 0 disables the grace period. + Persist + 1 + Type + F32 + Value + 5.0 + + TextureFetchPressureScale + + Comment + Pending-fetch divisor for the bias floor: bias floor = 1 + clamp(pending / scale, 0, 3). Lower = bias rises sooner under load floods. + Persist + 1 + Type + F32 + Value + 1000.0 + + TextureDecodeDisabled Comment @@ -16383,7 +16505,7 @@ EmulateCoreCount Comment - For debugging -- number of cores to restrict the main process to, or 0 for no limit. Requires restart. + For debugging - number of cores to restrict the main process to, or 0 for no limit. Requires restart. Persist 1 Type @@ -16537,7 +16659,7 @@ MultiModeDoubleClickFolder Comment - Sets the action for Double-click on folder in multi-folder view (0 - expands and collapses folder, 1 - opens a new window, 2 – stays in current floater but switches to SFV) + Sets the action for Double-click on folder in multi-folder view (0 - expands and collapses folder, 1 - opens a new window, 2 - stays in current floater but switches to SFV) Persist 1 Type diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index 018d4c4bba..e34bea63ef 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -1577,7 +1577,7 @@ bool LLFace::getGeometryVolume(const LLVolume& volume, skin = mSkinInfo; } - //TODO -- cache this (check profile marker above)? + //TODO - cache this (check profile marker above)? glm::mat4 m = glm::make_mat4((F32*)skin->mBindShapeMatrix.getF32ptr()); m = glm::transpose(glm::inverse(m)); mat_normal.loadu(glm::value_ptr(m)); @@ -2265,7 +2265,18 @@ F32 LLFace::getTextureVirtualSize() face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); } - face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area); + // Diffuse source area as the dim-aware hint for adjustPixelArea. + S32 source_area = 0; + if (mTexture[LLRender::DIFFUSE_MAP].notNull()) + { + S32 sw = mTexture[LLRender::DIFFUSE_MAP]->getFullWidth(); + S32 sh = mTexture[LLRender::DIFFUSE_MAP]->getFullHeight(); + if (sw > 0 && sh > 0) + { + source_area = sw * sh; + } + } + face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area, source_area); if(face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. { if(mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->isLargeImage()) @@ -2389,6 +2400,7 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) F32 dist = lookAt.getLength3().getF32(); dist = llmax(dist-size.getLength3().getF32(), 0.001f); + mDistanceToCamera = dist; lookAt.normalize3fast() ; @@ -2513,8 +2525,16 @@ F32 LLFace::calcImportanceToCamera(F32 cos_angle_to_view_dir, F32 dist) } //static -F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area) +F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area, S32 source_area) { + // Dim-aware floor: source_area/256 lowers the "large but unimportant" + // clamp proportionally so smaller sources can be pushed past discard 4. + F32 large_floor = (F32)LLViewerTexture::sMinLargeImageSize; + if (source_area > 0) + { + large_floor = llmin(large_floor, (F32)source_area / 256.f); + } + if(pixel_area > LLViewerTexture::sMaxSmallImageSize) { if(importance < LEAST_IMPORTANCE) //if the face is not important, do not load hi-res. @@ -2522,11 +2542,11 @@ F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area) static const F32 MAX_LEAST_IMPORTANCE_IMAGE_SIZE = 128.0f * 128.0f ; pixel_area = llmin(pixel_area * 0.5f, MAX_LEAST_IMPORTANCE_IMAGE_SIZE) ; } - else if(pixel_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. + else if(pixel_area > large_floor) //if is large image, shrink face_area by considering the partial overlapping. { if(importance < LEAST_IMPORTANCE_FOR_LARGE_IMAGE)//if the face is not important, do not load hi-res. { - pixel_area = (F32)LLViewerTexture::sMinLargeImageSize ; + pixel_area = large_floor ; } } } diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 6e9d23c3a2..71ba3d0f2f 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -242,7 +242,9 @@ private: bool calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) ; public: static F32 calcImportanceToCamera(F32 to_view_dir, F32 dist); - static F32 adjustPixelArea(F32 importance, F32 pixel_area) ; + // source_area > 0 lowers the "large but unimportant" floor for + // moderate sources; 0 keeps the legacy sMinLargeImageSize floor. + static F32 adjustPixelArea(F32 importance, F32 pixel_area, S32 source_area = 0) ; public: @@ -251,6 +253,9 @@ public: LLVector2 mTexExtents[2]; F32 mDistance; + // Camera-to-face distance, cached by calcPixelArea; read by the + // streaming math's distance signal. + F32 mDistanceToCamera = 0.f; F32 mLastUpdateTime; F32 mLastSkinTime; F32 mLastMoveTime; diff --git a/indra/newview/llsurface.cpp b/indra/newview/llsurface.cpp index 64359b6cbe..fe77d585c8 100644 --- a/indra/newview/llsurface.cpp +++ b/indra/newview/llsurface.cpp @@ -57,6 +57,8 @@ namespace LLColor4U MAX_WATER_COLOR(0, 48, 96, 240); S32 LLSurface::sTextureSize = 256; +F32 LLSurface::sNearestVisiblePatchDistance = FLT_MAX; +U32 LLSurface::sNearestVisiblePatchFrame = 0; // ---------------- LLSurface:: Public Members --------------- @@ -122,7 +124,7 @@ LLSurface::~LLSurface() else if (poolp->mReferences.empty()) { gPipeline.removePool(poolp); - // Don't enable this until we blitz the draw pool for it as well. -- djs + // Don't enable this until we blitz the draw pool for it as well. - djs mSTexturep = nullptr; } else @@ -583,6 +585,13 @@ void LLSurface::updatePatchVisibilities(LLAgent &agent) LLSurfacePatch *patchp; + // Reset the cross-region accumulator at the start of each frame. + if (sNearestVisiblePatchFrame != gFrameCount) + { + sNearestVisiblePatchDistance = FLT_MAX; + sNearestVisiblePatchFrame = gFrameCount; + } + mVisiblePatchCount = 0; for (S32 i=0; iupdateCameraDistanceRegion(pos_region); + sNearestVisiblePatchDistance = llmin(sNearestVisiblePatchDistance, patchp->getDistance()); } } } @@ -961,7 +971,7 @@ std::ostream& operator<<(std::ostream &s, const LLSurface &S) void LLSurface::createPatchData() { // Assumes mGridsPerEdge, mGridsPerPatchEdge, and mPatchesPerEdge have been properly set - // TODO -- check for create() called when surface is not empty + // TODO - check for create() called when surface is not empty S32 i, j; LLSurfacePatch *patchp; diff --git a/indra/newview/llsurface.h b/indra/newview/llsurface.h index a599019ca5..4bdb90b102 100644 --- a/indra/newview/llsurface.h +++ b/indra/newview/llsurface.h @@ -163,6 +163,12 @@ public: F32 mDetailTextureScale; // Number of times to repeat detail texture across this surface + // Closest visible patch distance across all surfaces this frame + // (meters), or FLT_MAX if none visible. Drives BOOST_TERRAIN + // streaming - terrain has no faces registered with its texture. + static F32 sNearestVisiblePatchDistance; + static U32 sNearestVisiblePatchFrame; + private: void createSTexture(); void initTextures(); diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 51ade60827..574c200eb4 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -542,6 +542,7 @@ private: S32 mRequestedDiscard; S32 mLoadedDiscard; S32 mDecodedDiscard; + S32 mCodecLevels = 0; LLFrameTimer mRequestedDeltaTimer; LLFrameTimer mFetchDeltaTimer; LLTimer mCacheReadTimer; @@ -1843,6 +1844,10 @@ bool LLTextureFetchWorker::doWork(S32 param) else { llassert_always(mRawImage.notNull()); + if (mFormattedImage.notNull()) + { + mCodecLevels = (S32)mFormattedImage->getLevels(); + } LL_DEBUGS(LOG_TXT) << mID << ": Decoded. Discard: " << mDecodedDiscard << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; setState(WRITE_TO_CACHE); @@ -2774,10 +2779,12 @@ LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id) // Threads: T* bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, S32& worker_state, LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status) + LLCore::HttpStatus& last_http_get_status, + S32& codec_levels) { LL_PROFILE_ZONE_SCOPED; bool res = false; + codec_levels = 0; LLTextureFetchWorker* worker = getWorker(id); if (worker) { @@ -2809,6 +2816,9 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, S3 discard_level = worker->mDecodedDiscard; raw = worker->mRawImage; aux = worker->mAuxImage; + // Cached on the worker so the value survives mFormattedImage + // clears (cache-retry, decode-abort, write-to-cache complete). + codec_levels = worker->mCodecLevels; decode_time = worker->mDecodeTime; fetch_time = worker->mFetchTime; diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 851d6c11a0..d75e16ab7c 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -106,7 +106,8 @@ public: // keep in mind that if fetcher isn't done, it still might need original raw image bool getRequestFinished(const LLUUID& id, S32& discard_level, S32& worker_state, LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status); + LLCore::HttpStatus& last_http_get_status, + S32& codec_levels); // Threads: T* bool updateRequestPriority(const LLUUID& id, F32 priority); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index d4d07f24ea..3a6e4e2e2d 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -114,32 +114,37 @@ static bool handleRenderAvatarMouselookChanged(const LLSD& newvalue) static bool handleRenderTextureQualityChanged(const LLSD& newvalue) { - // 0=Low, 1=Medium, 2=High, 3=Ultra. Drives max-resolution and per-channel - // streaming aggressiveness. Channel order is X=normals, Y=diffuse, - // Z=specular/metallic, W=emissive (matches TextureChannelPriority). + // 0=Low, 1=Medium, 2=High, 3=Ultra. Drives max-resolution and the + // per-channel TextureChannelPriority + TextureDistanceDiscardPower + // exponents. Channel order: X=normals, Y=diffuse, Z=spec, W=emissive. U32 quality = (U32)newvalue.asInteger(); U32 max_res = 2048; - LLVector4 channel_priority(5.f, 7.5f, 20.f, 7.5f); + LLVector4 channel_priority(1.f, 0.75f, 0.5f, 0.75f); + F32 distance_power = 0.5f; switch (quality) { case 0: // Low max_res = 1024; - channel_priority.setVec(20.f, 30.f, 80.f, 30.f); + channel_priority.setVec(0.5f, 0.75f, 0.1f, 0.5f); + distance_power = 0.15f; break; case 1: // Medium - channel_priority.setVec(10.f, 15.f, 40.f, 15.f); + channel_priority.setVec(0.75f, 0.75f, 0.3f, 0.75f); + distance_power = 0.25f; break; case 2: // High - // defaults above + // channel defaults above (1, 0.75, 0.5, 0.75) + distance_power = 0.35f; break; case 3: // Ultra default: - if (quality > 3) quality = 3; channel_priority.setVec(1.f, 1.f, 1.f, 1.f); + distance_power = 0.5f; break; } gSavedSettings.setU32("RenderMaxTextureResolution", max_res); gSavedSettings.setVector4("TextureChannelPriority", channel_priority); + gSavedSettings.setF32("TextureDistanceDiscardPower", distance_power); return true; } diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 0f23596c9a..79439b4d58 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -87,6 +87,8 @@ S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; F32 LLViewerTexture::sDesiredDiscardBias = 0.f; +F32 LLViewerTexture::sBackgroundFactor = 0.f; +F32 LLViewerTexture::sMemoryPressureFactor = 0.f; U32 LLViewerTexture::sBiasTexturesUpdated = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size @@ -97,12 +99,12 @@ constexpr S32 DEFAULT_ICON_DIMENSIONS = 32; constexpr S32 DEFAULT_THUMBNAIL_DIMENSIONS = 256; U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; -bool LLViewerTexture::sFreezeImageUpdates = false; F32 LLViewerTexture::sCurrentTime = 0.0f; constexpr F32 MEMORY_CHECK_WAIT_TIME = 1.0f; constexpr F32 MIN_VRAM_BUDGET = 768.f; F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; +F32 LLViewerTexture::sWindowPixelArea = 1.f; LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; @@ -120,7 +122,7 @@ LLLoadedCallbackEntry::LLLoadedCallbackEntry(loaded_callback_func cb, LLViewerFetchedTexture* target, bool pause) : mCallback(cb), - mLastUsedDiscard(MAX_DISCARD_LEVEL+1), + mLastUsedDiscard(S32_MAX), mDesiredDiscard(discard_level), mNeedsImageRaw(need_imageraw), mUserData(userdata), @@ -485,6 +487,13 @@ void LLViewerTexture::updateClass() LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; sCurrentTime = gFrameTimeSeconds; + if (gViewerWindow) + { + F32 w = (F32)gViewerWindow->getWindowWidthRaw(); + F32 h = (F32)gViewerWindow->getWindowHeightRaw(); + sWindowPixelArea = llmax(w * h, 1.f); + } + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); if (tester) { @@ -523,6 +532,28 @@ void LLViewerTexture::updateClass() F32 over_pct = (used - target) / target; + // VRAM-pressure feedback loop with progressive backoff. Ramp starts at + // backoff_start x target, not at the budget cliff. Proportional in both + // directions: ramp = (over-1)(1-factor), decay = factor; converges at + // factor = 1 - 1/over_at_backoff_target. + { + static LLCachedControl backoff_start(gSavedSettings, "TextureMemoryPressureBackoffStart", 0.85f); + F32 backoff_target = target * llclamp((F32)backoff_start, 0.05f, 1.f); + F32 over = used / llmax(backoff_target, 1.f); + static LLCachedControl pressure_ramp_rate(gSavedSettings, "TextureMemoryPressureRampRate", 3.0f); + F32 dt = gFrameIntervalSeconds; + if (over > 1.f) + { + sMemoryPressureFactor += + (over - 1.f) * (1.f - sMemoryPressureFactor) * (F32)pressure_ramp_rate * dt; + } + else + { + sMemoryPressureFactor -= sMemoryPressureFactor * (F32)pressure_ramp_rate * dt; + } + sMemoryPressureFactor = llclampf(sMemoryPressureFactor); + } + bool is_sys_low = isSystemMemoryLow(); bool is_low = is_sys_low || over_pct > 0.f; @@ -569,6 +600,10 @@ void LLViewerTexture::updateClass() // don't execute above until the slam to 1.5 has a chance to take effect sEvaluationTimer.reset(); + // Don't decay bias while downscale is still draining - those bytes + // are about to free and the loop would oscillate. + bool eviction_in_flight = !gTextureList.mDownScaleQueue.empty(); + // lower discard bias over time when at least 10% of budget is free constexpr F32 FREE_PERCENTAGE_TRESHOLD = -0.1f; constexpr U32 FREE_SYS_MEM_TRESHOLD = 100; @@ -576,7 +611,8 @@ void LLViewerTexture::updateClass() const S32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory() + FREE_SYS_MEM_TRESHOLD); if (sDesiredDiscardBias > 1.f && over_pct < FREE_PERCENTAGE_TRESHOLD - && getFreeSystemMemory() > MIN_FREE_MAIN_MEMORY) + && getFreeSystemMemory() > MIN_FREE_MAIN_MEMORY + && !eviction_in_flight) { static LLCachedControl high_mem_discard_decrement(gSavedSettings, "RenderHighMemMinDiscardDecrement", .1f); @@ -622,6 +658,33 @@ void LLViewerTexture::updateClass() } } + // Background-window ramp: 0 -> 1 at rate per second while backgrounded, + // snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. + { + static LLCachedControl bg_factor_rate(gSavedSettings, "TextureBackgroundFactorRatePerSec", 0.011f); + if (in_background) + { + sBackgroundFactor += (F32)bg_factor_rate * gFrameIntervalSeconds; + sBackgroundFactor = llclampf(sBackgroundFactor); + } + else + { + sBackgroundFactor = 0.f; + } + } + + // Fetch-queue depth as a one-way bias floor (decay path still drops + // bias when the queue drains). Pushes bias up before VRAM overflows + // during teleport/scene-change floods. + if (LLTextureFetch* fetcher = LLAppViewer::getTextureFetch()) + { + S32 pending = fetcher->getNumRequests(); + static LLCachedControl fetch_pressure_scale(gSavedSettings, "TextureFetchPressureScale", 1000.f); + F32 scale = llmax((F32)fetch_pressure_scale, 1.f); + F32 fetch_pressure = llclamp((F32)pending / scale, 0.f, 3.f); + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + fetch_pressure); + } + sDesiredDiscardBias = llclamp(sDesiredDiscardBias, 1.f, 4.f); if (last_texture_update_count_bias < sDesiredDiscardBias) { @@ -638,8 +701,6 @@ void LLViewerTexture::updateClass() // a problem. last_texture_update_count_bias = sDesiredDiscardBias; } - - LLViewerTexture::sFreezeImageUpdates = false; } //static @@ -1156,8 +1217,10 @@ void LLViewerFetchedTexture::init(bool firstinit) mRequestedDownloadPriority = 0.f; mFullyLoaded = false; mCanUseHTTP = true; - mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; - mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; + mDesiredDiscardLevel = S8_MAX; + // S8_MAX = no cap. setMinDiscardLevel takes min(current, new), so + // explicit caps from terrain / avatar self / thumbnails still apply. + mMinDesiredDiscardLevel = S8_MAX; mDecodingAux = false; @@ -1977,21 +2040,10 @@ bool LLViewerFetchedTexture::processFetchResults(S32& desired_discard, S32 curre setIsMissingAsset(); desired_discard = -1; } - else - { - //LL_WARNS() << mID << ": Setting min discard to " << current_discard << LL_ENDL; - if (current_discard >= 0) - { - mMinDiscardLevel = current_discard; - //desired_discard = current_discard; - } - else - { - S32 dis_level = getDiscardLevel(); - mMinDiscardLevel = dis_level; - //desired_discard = dis_level; - } - } + // Transient failure (decoder OOM, network blip): don't latch + // mMinDiscardLevel - that would block all future fetches via + // the make_request gate. Permanent failures are caught above + // (getDiscardLevel()<0 -> setIsMissingAsset). destroyRawImage(); } else if (mRawImage.notNull()) @@ -2072,8 +2124,18 @@ bool LLViewerFetchedTexture::updateFetch() if (mRawImage.notNull()) sRawCount--; if (mAuxRawImage.notNull()) sAuxCount--; // keep in mind that fetcher still might need raw image, don't modify original + S32 codec_levels = 0; bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mFetchState, mRawImage, mAuxRawImage, - mLastHttpGetStatus); + mLastHttpGetStatus, codec_levels); + if (codec_levels > 0) + { + mCodecMaxDiscardLevel = (S8)llmin(codec_levels, (S32)S8_MAX); + if (codec_levels > 5) + { + LL_DEBUGS("TextureStream") << "Texture " << mID << " codec-reported max discard " + << codec_levels << " (above the historical hardcoded cap of 5)" << LL_ENDL; + } + } if (mRawImage.notNull()) sRawCount++; if (mAuxRawImage.notNull()) { @@ -2108,7 +2170,9 @@ bool LLViewerFetchedTexture::updateFetch() } } - desired_discard = llmin(desired_discard, getMaxDiscardLevel()); + // Clamp the fetch request to what the codestream encodes; deeper + // discards are served from the GL mip pyramid via scaleDown. + desired_discard = llmin(desired_discard, (S32)mCodecMaxDiscardLevel); bool make_request = true; if (decode_priority <= 0) @@ -2116,9 +2180,14 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - priority <= 0"); make_request = false; } - else if (mDesiredDiscardLevel > getMaxDiscardLevel()) + else if (mDesiredDiscardLevel > (S32)mCodecMaxDiscardLevel && + current_discard >= 0 && + current_discard <= (S32)mCodecMaxDiscardLevel) { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > max"); + // scaleDown can serve this from the GL pyramid. (If current is + // already past codec_max, fall through so a zoom-in can rebuild — + // scaleDown only goes deeper.) + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > codec max"); make_request = false; } else if (mNeedsCreateTexture || mIsMissingAsset) @@ -2570,20 +2639,21 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() S32 gl_discard = getDiscardLevel(); - // If we don't have a legit GL image, set it to be lower than the worst discard level + // S32_MAX is the "no data" sentinel; real discards can now exceed + // MAX_DISCARD_LEVEL via dimDerivedMaxDiscard. if (gl_discard == -1) { - gl_discard = MAX_DISCARD_LEVEL + 1; + gl_discard = S32_MAX; } // // Determine the quality levels of textures that we can provide to callbacks // and whether we need to do decompression/readback to get it // - S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard + S32 current_raw_discard = S32_MAX; // We can always do a readback to get a raw discard S32 best_raw_discard = gl_discard; // Current GL quality level - S32 current_aux_discard = MAX_DISCARD_LEVEL + 1; - S32 best_aux_discard = MAX_DISCARD_LEVEL + 1; + S32 current_aux_discard = S32_MAX; + S32 best_aux_discard = S32_MAX; LLImageRaw *current_raw_image = nullptr; if (mIsRawImageValid) @@ -2683,7 +2753,7 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() // // Run raw/auxiliary data callbacks // - if (run_raw_callbacks && current_raw_image != nullptr && (current_raw_discard <= getMaxDiscardLevel())) + if (run_raw_callbacks && current_raw_image != nullptr && current_raw_discard != S32_MAX) { // Do callbacks which require raw image data. //LL_INFOS() << "doLoadedCallbacks raw for " << getID() << LL_ENDL; @@ -2723,7 +2793,7 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() // // Run GL callbacks // - if (run_gl_callbacks && (gl_discard <= getMaxDiscardLevel())) + if (run_gl_callbacks && gl_discard != S32_MAX) { //LL_INFOS() << "doLoadedCallbacks GL for " << getID() << LL_ENDL; @@ -3026,11 +3096,6 @@ S8 LLViewerLODTexture::getType() const return LLViewerTexture::LOD_TEXTURE; } -bool LLViewerLODTexture::isUpdateFrozen() -{ - return LLViewerTexture::sFreezeImageUpdates; -} - // This is gauranteed to get called periodically for every texture //virtual void LLViewerLODTexture::processTextureStats() @@ -3056,18 +3121,15 @@ void LLViewerLODTexture::processTextureStats() { mDesiredDiscardLevel = 0; } - // Generate the request priority and render priority - else if (mDontDiscard || !mUseMipMaps) + // HUD/UI/preview and mDontDiscard textures bypass streaming - no + // face_distance signal applies, they need native resolution. + else if (mBoostLevel >= LLGLTexture::BOOST_HIGH + || mDontDiscard + || !mUseMipMaps) { mDesiredDiscardLevel = 0; if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) - mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 2048 and max size ever is 4096 - } - else if (mBoostLevel < LLGLTexture::BOOST_HIGH && mMaxVirtualSize <= 10.f) - { - // If the image has not been significantly visible in a while, we don't want it - mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1)); - mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); + mDesiredDiscardLevel = 1; // 4096^2 source can't be loaded full res } else if (!mFullWidth || !mFullHeight) { @@ -3076,28 +3138,86 @@ void LLViewerLODTexture::processTextureStats() } else { - //static const F64 log_2 = log(2.0); - static const F64 log_4 = log(4.0); - F32 discard_level = 0.f; - // If we know the output width and height, we can force the discard - // level to the correct value, and thus not decode more texture - // data than we need to. + // floor(log2(max(w, h))) - both the multiplier on the normalized + // factor and the cap clamp at the bottom of this function. + S32 dim_max_for_image_i = (mFullWidth > 0 && mFullHeight > 0) + ? LLImageGL::dimDerivedMaxDiscard(mFullWidth, mFullHeight) + : (S32)mCodecMaxDiscardLevel; + F32 dim_max_for_image = (F32)dim_max_for_image_i; + if (mKnownDrawWidth && mKnownDrawHeight) { + // UI-pinned target dimensions - use pixel-area math. + static const F64 log_4 = log(4.0); S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); - - // Use log_4 because we're in square-pixel space, so an image - // with twice the width and twice the height will have mTexelsPerImage - // 4 * draw_size discard_level = (F32)(log(mTexelsPerImage / draw_texels) / log_4); } else { - // Calculate the required scale factor of the image using pixels per texel - discard_level = (F32)(log(mTexelsPerImage / mMaxVirtualSize) / log_4); + // Two 0..1 signals composed multiplicatively: + // discard = distance_factor * size_factor * max_discard + // distance_factor: face_dist / draw_dist, shaped by + // TextureDistanceDiscardPower (default 0.5 = sqrt). + // size_factor: 1 - (mMaxOnScreenSize / window_pixels), shaped + // by TextureSizeDiscardPower. + // Either factor near 0 keeps the result fine - both have to + // be high for the texture to go deep. + static LLCachedControl distance_power(gSavedSettings, "TextureDistanceDiscardPower", 0.5f); + F32 power = llmax((F32)distance_power, 0.0001f); + F32 distance_factor = (power == 1.f) ? mMinDistanceFactor : powf(mMinDistanceFactor, power); + + static LLCachedControl size_power(gSavedSettings, "TextureSizeDiscardPower", 1.f); + F32 sz_power = llmax((F32)size_power, 0.0001f); + F32 coverage = llclampf(mMaxOnScreenSize / sWindowPixelArea); + F32 inv_cov = 1.f - coverage; + F32 size_factor = (sz_power == 1.f) ? inv_cov : powf(inv_cov, sz_power); + + F32 combined = distance_factor * size_factor; + + // Per-channel exponent. 1.0 = baseline; <1.0 pushes combined + // toward 1 (max attenuation) faster. Edges are preserved: + // pow(0, p) = 0, pow(1, p) = 1. + S32 priority_channel = (mPriorityChannel >= 0 && mPriorityChannel < 4) ? (S32)mPriorityChannel : 1; + static LLCachedControl channel_priority(gSavedSettings, "TextureChannelPriority", + LLVector4(1.f, 1.f, 1.f, 1.f)); + F32 channel_power = llmax(channel_priority().mV[priority_channel], 0.0001f); + if (channel_power != 1.f) + { + combined = powf(combined, channel_power); + } + + // Own-avatar boost: shave combined for rigged/animated faces + // on gAgentAvatarp. Preference, not exemption - applied + // before the staleness/background/pressure floors so heavy + // pressure can still evict. + if (mOnAgentAvatar) + { + static LLCachedControl agent_avatar_boost(gSavedSettings, "TextureAgentAvatarBoost", 0.5f); + combined *= llclampf((F32)agent_avatar_boost); + } + + // Staleness / background floors. Avatar bakes exempt from + // background to avoid the universal-cloud bug when re-foregrounding. + combined = llmax(combined, mStalenessFactor); + if (!isAgentAvatarBoost(mBoostLevel)) + { + combined = llmax(combined, sBackgroundFactor); + } + + // VRAM pressure: pow(combined, 1 - factor) bends the curve + // without flattening it - pow(0, p) = 0 so close textures + // (combined ~ 0) stay near 0 while mid/far push toward 1. + // Avatar bakes exempt. + if (!isAgentAvatarBoost(mBoostLevel) && sMemoryPressureFactor > 0.f) + { + F32 pressure_exp = llmax(1.f - sMemoryPressureFactor, 0.0001f); + combined = powf(combined, pressure_exp); + } + + discard_level = combined * dim_max_for_image; } discard_level = floorf(discard_level); @@ -3106,12 +3226,29 @@ void LLViewerLODTexture::processTextureStats() if (mFullWidth > max_tex_res || mFullHeight > max_tex_res) min_discard = 1.f; - discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL); + // dim_max_for_image_i is the per-texture cap. TextureMaxDiscardOverride + // raises it (debug). Codec_max applies only to fetches, not here. + static LLCachedControl max_discard_override(gSavedSettings, "TextureMaxDiscardOverride", 0); + S32 effective_cap = (max_discard_override > 0) ? (S32)max_discard_override : dim_max_for_image_i; + discard_level = llclamp(discard_level, min_discard, (F32)effective_cap); + + mDesiredDiscardLevel = llmin(effective_cap, (S32)discard_level); + + // Apply the setMinDiscardLevel cap, relaxed proportionally under + // VRAM pressure - at factor=1 the cap reaches dim_max so capped + // textures (terrain, etc.) participate fully in eviction. Caps of + // 0 (thumbnails) and avatar bakes are preserved. + S32 effective_min_cap = mMinDesiredDiscardLevel; + if (sMemoryPressureFactor > 0.f && + mMinDesiredDiscardLevel > 0 && mMinDesiredDiscardLevel < S8_MAX && + !isAgentAvatarBoost(mBoostLevel)) + { + F32 room = (F32)dim_max_for_image_i - (F32)mMinDesiredDiscardLevel; + effective_min_cap += (S32)(sMemoryPressureFactor * room); + effective_min_cap = llmin(effective_min_cap, dim_max_for_image_i); + } + mDesiredDiscardLevel = llmin((S8)effective_min_cap, mDesiredDiscardLevel); - // Can't go higher than the max discard level - mDesiredDiscardLevel = llmin(getMaxDiscardLevel() + 1, (S32)discard_level); - // Clamp to min desired discard - mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel); // // At this point we've calculated the quality level that we want, @@ -3120,7 +3257,9 @@ void LLViewerLODTexture::processTextureStats() // S32 current_discard = getDiscardLevel(); - if (mBoostLevel < LLGLTexture::BOOST_AVATAR_BAKED) + // Avatar bakes exempt: shrinking mid-bake can leave the avatar + // stuck as a cloud until the next bake completes. + if (!isAgentAvatarBoost(mBoostLevel)) { if (current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { // should scale down @@ -3128,13 +3267,6 @@ void LLViewerLODTexture::processTextureStats() } } - if (isUpdateFrozen() // we are out of memory and nearing max allowed bias - && mBoostLevel < LLGLTexture::BOOST_SCULPTED - && mDesiredDiscardLevel < current_discard) - { - // stop requesting more - mDesiredDiscardLevel = current_discard; - } mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); } diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 2937651995..f4770e5fac 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -203,6 +203,26 @@ protected: mutable S32 mMaxVirtualSizeResetInterval; LLFrameTimer mLastReferencedTimer; + // Index into TextureChannelPriority Vector4 (X=normals, Y=diffuse, + // Z=spec, W=emissive). -1 -> fall back to diffuse. + S8 mPriorityChannel = -1; + + // Bind-staleness floor, 0..1. Per-interval increment is 1/max_discard + // so any texture saturates after interval x max_discard seconds idle. + F32 mStalenessFactor = 0.f; + + // Closest face's face_distance / draw_distance, clamped 0..1. + // Defaults to 1 so never-measured textures resolve to deepest discard. + F32 mMinDistanceFactor = 1.f; + + // Largest per-face screen-space coverage in pixels. Raw - no bias or + // channel-priority contamination. + F32 mMaxOnScreenSize = 0.f; + + // Any face on the agent's avatar (rigged / animated). Drives the + // own-avatar quality boost in processTextureStats. + bool mOnAgentAvatar = false; + ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; LLFrameTimer mLastFaceListUpdateTimer ; @@ -224,16 +244,27 @@ public: static S32 sAuxCount; static LLFrameTimer sEvaluationTimer; static F32 sDesiredDiscardBias; + // Backgrounded-window discard floor, 0..1. Ramps while backgrounded, + // snaps to 0 in foreground. Avatar bakes exempt. + static F32 sBackgroundFactor; + + // VRAM-pressure factor, 0..1. Applied in processTextureStats as + // combined = pow(combined, 1 - factor) - bends the curve without + // flattening the distance gradient. + static F32 sMemoryPressureFactor; static U32 sBiasTexturesUpdated; static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; static U32 sMaxSmallImageSize ; - static bool sFreezeImageUpdates; static F32 sCurrentTime ; // estimated free memory for textures, by bias calculation static F32 sFreeVRAMMegabytes; + // Viewport pixel area, refreshed once per frame. Hoisted to keep the + // per-texture hot path out of gViewerWindow. + static F32 sWindowPixelArea; + enum EDebugTexels { DEBUG_TEXELS_OFF, @@ -279,6 +310,16 @@ public: LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps); LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); + // Avatar bake/skin textures - exempt from non-visibility-driven discard + // (staleness, background, pressure) to avoid the universal-cloud bug. + static bool isAgentAvatarBoost(S32 boost_level) + { + return boost_level == BOOST_AVATAR + || boost_level == BOOST_AVATAR_BAKED + || boost_level == BOOST_AVATAR_SELF + || boost_level == BOOST_AVATAR_BAKED_SELF; + } + public: struct Compare @@ -461,6 +502,12 @@ protected: S8 mDesiredDiscardLevel; // The discard level we'd LIKE to have - if we have it and there's space S8 mMinDesiredDiscardLevel; // The minimum discard level we'd like to have + // Fetch-side discard cap from the J2C codestream's DWT level count + // (populated by the fetcher). Distinct from LLImageGL::mMaxDiscardLevel - + // scaleDown can still trim the GL pyramid past this. + static constexpr S8 sFallbackCodecMaxDiscardLevel = 5; // MIN_DECOMPOSITION_LEVELS + S8 mCodecMaxDiscardLevel = sFallbackCodecMaxDiscardLevel; + bool mNeedsAux; // We need to decode the auxiliary channels bool mHasAux; // We have aux channels bool mDecodingAux; // Are we decoding high components @@ -540,7 +587,6 @@ public: S8 getType() const override; // Process image stats to determine priority/quality requirements. void processTextureStats() override; - bool isUpdateFrozen() ; bool scaleDown() override; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 7dd32074cf..235888e2d1 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -47,6 +47,7 @@ #include "message.h" #include "lldrawpoolbump.h" // to init bumpmap images +#include "llagentcamera.h" #include "lltexturecache.h" #include "lltexturefetch.h" #include "llviewercontrol.h" @@ -61,6 +62,8 @@ #include "lltracerecording.h" #include "llviewerdisplay.h" #include "llviewerwindow.h" +#include "llsurface.h" +#include "llvoavatarself.h" #include "llprogressview.h" //////////////////////////////////////////////////////////////////////////// @@ -93,6 +96,20 @@ LLTextureKey::LLTextureKey(LLUUID id, ETexListType tex_type) /////////////////////////////////////////////////////////////////////////////// +// eTexIndex -> TextureChannelPriority component index (X=normals, Y=diffuse, +// Z=spec, W=emissive). Single source of truth - route all channel-priority +// lookups through this table. +const S32 LLViewerTextureList::sChannelToPriority[LLRender::NUM_TEXTURE_CHANNELS] = +{ + 1, // DIFFUSE_MAP (0) -> Y (diffuse) + 0, // NORMAL_MAP / ALT_DIFFUSE (1) -> X (normals) + 2, // SPECULAR_MAP (2) -> Z (specular/metallic) + 1, // BASECOLOR_MAP (3) -> Y (diffuse) + 2, // METALLIC_ROUGHNESS_MAP (4) -> Z (specular/metallic) + 0, // GLTF_NORMAL_MAP (5) -> X (normals) + 3, // EMISSIVE_MAP (6) -> W (emissive) +}; + LLViewerTextureList::LLViewerTextureList() : mForceResetTextureStats(false), mInitialized(false) @@ -899,8 +916,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag { llassert(!gCubeSnapshot); - constexpr F32 BIAS_TRS_OUT_OF_SCREEN = 1.5f; - constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; + constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; // perf gate for face-loop early exit if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { @@ -910,9 +926,35 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 max_vsize = 0.f; bool on_screen = false; + // Accumulators for the per-texture signals published below. + // Defaults map to "deepest discard wanted" until evidence updates them. + F32 min_distance_factor = 1.f; + F32 max_on_screen_size = 0.f; + bool on_agent_avatar = false; + F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 0.001f); + U32 face_count = 0; U32 max_faces_to_check = 1024; + // Pick the least-aggressive channel across all uses, so a texture + // used as both diffuse and normal isn't penalized by its harshest + // role. -1 sentinel keeps emissive-only textures (W=3) from being + // clobbered by a smaller init value. + S32 priority_channel = -1; + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + if (imagep->getNumFaces(i) > 0) + { + S32 mapped = sChannelToPriority[i]; + priority_channel = (priority_channel < 0) ? mapped : llmin(priority_channel, mapped); + } + } + if (priority_channel < 0) + { + priority_channel = 1; // no faces - default to diffuse + } + imagep->mPriorityChannel = (S8)priority_channel; + // get adjusted bias based on image resolution LLImageGL* img = imagep->getGLTexture(); F32 max_discard = F32(img ? img->getMaxDiscardLevel() : MAX_DISCARD_LEVEL); @@ -948,6 +990,14 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag on_screen |= face->mInFrustum; + F32 dist_factor = llclampf(face->mDistanceToCamera / draw_distance); + min_distance_factor = llmin(min_distance_factor, dist_factor); + + if (face->mAvatar && face->mAvatar == gAgentAvatarp) + { + on_agent_avatar = true; + } + // Scale desired texture resolution higher or lower depending on texture scale // // Minimum usage examples: a 1024x1024 texture with aplhabet (texture atlas), @@ -963,6 +1013,10 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag min_scale = llclamp(min_scale * min_scale, texture_scale_min(), texture_scale_max()); vsize /= min_scale; + // Raw screen-space coverage - taken before the bias / + // camera-boost mutations below so the size signal is clean. + max_on_screen_size = llmax(max_on_screen_size, vsize); + // apply bias to offscreen faces all the time, but only to onscreen faces when bias is large // use mImportanceToCamera to make bias switch a bit more gradual if (!face->mInFrustum || LLViewerTexture::sDesiredDiscardBias > 1.9f + face->mImportanceToCamera / 2.f) @@ -970,13 +1024,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag vsize /= bias; } - // boost resolution of textures that are important to the camera - if (face->mInFrustum) - { - static LLCachedControl texture_camera_boost(gSavedSettings, "TextureCameraBoost", 8.f); - vsize *= llmax(face->mImportanceToCamera*texture_camera_boost, 1.f); - } - max_vsize = llmax(max_vsize, vsize); // addTextureStats limits size to sMaxVirtualSize @@ -995,57 +1042,89 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag } } - if (face_count > max_faces_to_check) + bool used_face_fast_path = (face_count > max_faces_to_check); + if (used_face_fast_path) { // this texture is used in so many places we should just boost it and not bother checking its vsize // this is especially important because the above is not time sliced and can hit multiple ms for a single texture max_vsize = MAX_IMAGE_AREA; } - if (imagep->getType() == LLViewerTexture::LOD_TEXTURE && imagep->getBoostLevel() == LLViewerTexture::BOOST_NONE) - { // conditionally reset max virtual size for unboosted LOD_TEXTURES - // this is an alternative to decaying mMaxVirtualSize over time - // that keeps textures from continously downrezzing and uprezzing in the background + imagep->addTextureStats(max_vsize); - if (LLViewerTexture::sDesiredDiscardBias > BIAS_TRS_OUT_OF_SCREEN || - (!on_screen && LLViewerTexture::sDesiredDiscardBias > BIAS_TRS_ON_SCREEN)) - { - imagep->mMaxVirtualSize = 0.f; - } + // Publish per-texture signals for processTextureStats. Closest face + // wins for distance (min); biggest face wins for size (max). Default + // distance=1, size=0 maps to "deepest discard wanted" - never- + // measured textures stay coarse until distance/size evidence arrives. + if (used_face_fast_path) + { + // Fast path saw only a prefix of faces - force best-quality + // sentinels to match the MAX_IMAGE_AREA vsize boost above. + imagep->mMinDistanceFactor = 0.f; + imagep->mMaxOnScreenSize = (F32)MAX_IMAGE_AREA; + } + else if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) + { + // Terrain detail textures don't register faces with the texture + // (LLVOSurfacePatch addFace(NULL)). Drive distance from the LOD + // system; floor at a small nonzero value so pressure has + // something to bite into (pow(0, p) = 0). + static LLCachedControl terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); + static LLCachedControl terrain_coverage(gSavedSettings, "TextureTerrainCoverageFraction", 0.99f); + F32 nearest = LLSurface::sNearestVisiblePatchDistance; + F32 dist = (nearest < FLT_MAX) ? llclampf(nearest / draw_distance) : 1.f; + imagep->mMinDistanceFactor = llmax(dist, llclampf((F32)terrain_distance_floor)); + imagep->mMaxOnScreenSize = LLViewerTexture::sWindowPixelArea * llclampf((F32)terrain_coverage); } + else + { + imagep->mMinDistanceFactor = min_distance_factor; + imagep->mMaxOnScreenSize = max_on_screen_size; + } + imagep->mOnAgentAvatar = on_agent_avatar; + + // Bind-staleness. Avatar bakes exempt (cloud-bug protection). + // Per-interval increment is 1/max_discard so saturation time is + // interval * max_discard seconds regardless of texture size. + // Never-bound textures defer to distance/size or initial fetch + // could never start. + if (LLViewerFetchedTexture::isAgentAvatarBoost(imagep->getBoostLevel())) + { + imagep->mStalenessFactor = 0.f; + } + else if (LLImageGL* gli = imagep->getGLTexture()) + { + static LLCachedControl bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); + static LLCachedControl staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); + F32 grace = llmax((F32)bind_decay_seconds, 0.f); + F32 interval = llmax((F32)staleness_interval, 0.0001f); - imagep->addTextureStats(max_vsize); + bool ever_bound = (gli->mLastBindTime > 0.f); + F32 time_since_bind = ever_bound ? (LLImageGL::sLastFrameTime - gli->mLastBindTime) : 0.f; - // 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 (!ever_bound || time_since_bind <= grace) { - if (imagep->getNumFaces(i) > 0) - { - priority_channel = llmin(priority_channel, render_to_priority[i]); - } + imagep->mStalenessFactor = 0.f; } - - static LLCachedControl 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) + else { - imagep->mMaxVirtualSize /= factor; + S32 full_w = imagep->getFullWidth(); + S32 full_h = imagep->getFullHeight(); + S32 max_discard = (full_w > 0 && full_h > 0) + ? LLImageGL::dimDerivedMaxDiscard(full_w, full_h) + : (S32)gli->getMaxDiscardLevel(); + if (max_discard > 0) + { + F32 steps = (time_since_bind - grace) / interval; + F32 step_size = 1.f / (F32)max_discard; + imagep->mStalenessFactor = llclampf(steps * step_size); + } + else + { + imagep->mStalenessFactor = 0.f; + } } } + } #if 0 @@ -1154,8 +1233,7 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) imagep->postCreateTexture(); imagep->mCreatePending = false; - if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel() && - (imagep->getDesiredDiscardLevel() <= MAX_DISCARD_LEVEL)) + if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel()) { // NOTE: this may happen if the desired discard reduces while a decode is in progress and does not // necessarily indicate a problem, but if log occurrences excede that of dsiplay_stats: FPS, @@ -1180,7 +1258,7 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) gCopyProgram.bind(); gPipeline.mScreenTriangleVB->setBuffer(); - // give time to downscaling first -- if mDownScaleQueue is not empty, we're running out of memory and need + // give time to downscaling first - if mDownScaleQueue is not empty, we're running out of memory and need // to free up memory by discarding off screen textures quickly // do at least 5 and make sure we don't get too far behind even if it violates diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 7c7112f4cf..dd8655cd6f 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -30,6 +30,7 @@ #include "lluuid.h" //#include "message.h" #include "llgl.h" +#include "llrender.h" #include "llviewertexture.h" #include "llui.h" #include @@ -92,6 +93,10 @@ class LLViewerTextureList friend class LLLocalBitmap; public: + // eTexIndex -> TextureChannelPriority component (X=normals, Y=diffuse, + // Z=spec, W=emissive). Single source of truth. + static const S32 sChannelToPriority[LLRender::NUM_TEXTURE_CHANNELS]; + static bool createUploadFile(LLPointer raw_image, const std::string& out_filename, const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp index 3441e25c6a..eb0261b5e5 100644 --- a/indra/newview/llvlcomposition.cpp +++ b/indra/newview/llvlcomposition.cpp @@ -173,7 +173,10 @@ LLPointer fetch_terrain_texture(const LLUUID& id) return nullptr; } - LLPointer tex = LLViewerTextureManager::getFetchedTexture(id); + // LOD_TEXTURE so streaming math runs (the base-class processTextureStats + // pins mDesiredDiscardLevel at 0). + LLPointer tex = LLViewerTextureManager::getFetchedTexture( + id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); return tex; } @@ -343,18 +346,9 @@ bool LLTerrainMaterials::makeTextureReady(LLPointer& tex { if (boost) { + // Quality is driven by the streaming math via synthetic signals + // for BOOST_TERRAIN textures in updateImageDecodePriority. boost_minimap_texture(tex, BASE_SIZE*BASE_SIZE); - - S32 width = tex->getFullWidth(); - S32 height = tex->getFullHeight(); - S32 min_dim = llmin(width, height); - S32 ddiscard = 0; - while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) - { - ddiscard++; - min_dim /= 2; - } - tex->setMinDiscardLevel(ddiscard); } return false; } -- cgit v1.3 From e7263854de98868fd2b3205e09849fd6f04fb35b Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 19 May 2026 12:19:11 -0400 Subject: More high pressure and quality changes. New "high res" bubble near the camera, minimum discard settings, and discard scaling. --- indra/llrender/llimagegl.cpp | 10 +- indra/llrender/llimagegl.h | 1 + indra/newview/app_settings/settings.xml | 116 ++++++++++++++++++- indra/newview/lltextureview.cpp | 8 +- indra/newview/llviewertexture.cpp | 195 ++++++++++++++++++++++++++------ indra/newview/llviewertexture.h | 14 ++- indra/newview/llviewertexturelist.cpp | 70 +++++++++--- indra/newview/llviewertexturelist.h | 4 + 8 files changed, 355 insertions(+), 63 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index d79d13dc8b..a1396aba20 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -715,7 +715,10 @@ void LLImageGL::dump() //---------------------------------------------------------------------------- void LLImageGL::forceUpdateBindStats(void) const { - mLastBindTime = sLastFrameTime; + // Intentionally a no-op: mLastBindTime is written only by real bind + // paths so the staleness signal reflects actual GPU use. Callers that + // still invoke this (avatar "keep alive" sites, deleted-texture + // fallback) no longer falsely refresh staleness. } bool LLImageGL::updateBindStats() const @@ -1650,7 +1653,6 @@ bool LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S { destroyGLTexture(); mCurrentDiscardLevel = discard_level; - mLastBindTime = sLastFrameTime; mGLTextureCreated = false; return true ; } @@ -1766,9 +1768,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_ mTextureMemory = (S64Bytes)getMipBytes(mCurrentDiscardLevel); - - // mark this as bound at this point, so we don't throw it out immediately - mLastBindTime = sLastFrameTime; + mGLCreateTime = sLastFrameTime; checkActiveThread(); return true; diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index a02c320738..0c85446b84 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -239,6 +239,7 @@ public: // Various GL/Rendering options S64Bytes mTextureMemory; mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives streaming staleness + F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; staleness fallback for never-bound textures private: U32 createPickMask(S32 pWidth, S32 pHeight); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 8559fa4ac3..0c4df7a805 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11874,13 +11874,13 @@ TextureMemoryPressureRampRate Comment - Feedback rate (per second) for the VRAM-pressure factor (0..1). Higher = faster convergence on the budget; lower = gentler. + Geometric ramp/decay rate (per second) for the VRAM-pressure distance multiplier (>= 1). Higher = faster convergence on the budget; lower = gentler. Multiplier compresses the streaming distance signal: at mult=N, faces past (ramp_range / N) hit max discard. Persist 1 Type F32 Value - 3.0 + 1.0 TextureMemoryPressureBackoffStart @@ -11893,6 +11893,83 @@ Value 0.85 + TextureMemoryPressureMaxMultiplier + + Comment + Upper bound on the VRAM-pressure distance multiplier (>= 1). Mostly defensive -- at mult=64 the streaming ramp collapses to ~ramp_range/64, already extreme. Higher allows even more aggressive compression in tight-budget scenes. + Persist + 1 + Type + F32 + Value + 64.0 + + TextureLastDitchEngageProgress + + Comment + mult_progress (0..1) at which the last-ditch floor starts creeping up. The floor only advances when mult is at or above this fraction of its cap AND prediction is still over budget. Decays back toward 0 whenever prediction is under budget. + Persist + 1 + Type + F32 + Value + 0.95 + + TextureLastDitchRampRate + + Comment + Rate (discard levels/sec) at which sLastDitchMinDiscard creeps up while engaged. 0.5 = takes ~2 sec to add one discard level. Mirrors sDesiredDiscardBias ramp shape. + Persist + 1 + Type + F32 + Value + 0.5 + + TextureLastDitchDecayRate + + Comment + Rate (discard levels/sec) at which sLastDitchMinDiscard decays back to 0 when prediction is under budget. + Persist + 1 + Type + F32 + Value + 0.5 + + TextureLastDitchMinDiscardMax + + Comment + Hard ceiling on sLastDitchMinDiscard. At 13 the floor can climb all the way to the deepest meaningful mip; lower values cap how aggressive the last-ditch escalation can get before we are simply out of discards. + Persist + 1 + Type + F32 + Value + 13.0 + + TextureMemoryPressurePredictionGain + + Comment + Power exponent mapping predicted-over-budget ratio to target multiplier. target_mult = pred_over^gain. Higher gain saturates faster. + Persist + 1 + Type + F32 + Value + 10.0 + + TextureMemoryPressureSmoothingRate + + Comment + Lerp rate (1/sec) at which the pressure multiplier converges to its prediction-driven target. Higher = faster response, lower = smoother. Default 4 reaches ~63% in 0.25s. + Persist + 1 + Type + F32 + Value + 4.0 + TextureTerrainDistanceFloor Comment @@ -11952,7 +12029,7 @@ TextureCloseBubbleMeters Comment - Close-camera bubble (meters). Faces inside this distance get dist_factor = 0 (no discard contribution); the ramp to 1 spans (bubble, draw_distance]. + Close-camera bubble (meters). Faces inside this distance get dist_factor = 0 (no discard contribution); the ramp to 1 spans (bubble, draw_distance]. Shrinks toward TextureCloseBubbleMinMeters as VRAM pressure ramps the multiplier toward its cap. Persist 1 Type @@ -11960,6 +12037,39 @@ Value 5.0 + TextureCloseBubbleMinMeters + + Comment + Floor (meters) for the close-camera bubble under maximum VRAM pressure. At sMemoryPressureMultiplier = TextureMemoryPressureMaxMultiplier the bubble collapses to this value, allowing eviction of even close textures when nothing else fits. + Persist + 1 + Type + F32 + Value + 3.0 + + TextureCloseBubbleShrinkThreshold + + Comment + Bubble stays at full size until mult_progress exceeds this fraction (0..1) of its range to the cap. Above that, bubble lerps from full to TextureCloseBubbleMinMeters. Keeps the bubble out of the normal feedback loop. + Persist + 1 + Type + F32 + Value + 0.8 + + TextureCloseBubbleTrackRate + + Comment + Rate (1/sec) at which the actual bubble tracks its target. Lower = smoother, slower to react. Damps short-term multiplier swings so close textures don't yo-yo. + Persist + 1 + Type + F32 + Value + 0.5 + TextureDistanceDiscardPower Comment diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 8cbede8303..4534db958f 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -572,9 +572,13 @@ void LLGLTexMemBar::draw() LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*8, text_color, LLFontGL::LEFT, LLFontGL::TOP); - text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB)", image_count, raw_image_count, raw_image_bytes_MB, + text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB) Bubble: %.1fm PressMult: %.1fx LDMin: %.1f", + image_count, raw_image_count, raw_image_bytes_MB, saved_raw_image_count, saved_raw_image_bytes_MB, - aux_raw_image_count, aux_raw_image_bytes_MB); + aux_raw_image_count, aux_raw_image_bytes_MB, + LLViewerTextureList::sCurrentBubbleMeters, + LLViewerTexture::sMemoryPressureMultiplier, + LLViewerTexture::sLastDitchMinDiscard); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height * 7, text_color, LLFontGL::LEFT, LLFontGL::TOP); diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 727c0510f6..e55e4016ec 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -88,7 +88,8 @@ S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; F32 LLViewerTexture::sDesiredDiscardBias = 0.f; F32 LLViewerTexture::sBackgroundFactor = 0.f; -F32 LLViewerTexture::sMemoryPressureFactor = 0.f; +F32 LLViewerTexture::sMemoryPressureMultiplier = 1.f; +F32 LLViewerTexture::sLastDitchMinDiscard = 0.f; U32 LLViewerTexture::sBiasTexturesUpdated = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size @@ -532,26 +533,119 @@ void LLViewerTexture::updateClass() F32 over_pct = (used - target) / target; - // VRAM-pressure feedback loop with progressive backoff. Ramp starts at - // backoff_start x target, not at the budget cliff. Proportional in both - // directions: ramp = (over-1)(1-factor), decay = factor; converges at - // factor = 1 - 1/over_at_backoff_target. + // VRAM-pressure controller. Drives sMemoryPressureMultiplier from + // PREDICTED VRAM (used + in-flight refetch growth - in-flight downscale + // shrinkage) rather than instantaneous used. The feedback loop chasing + // instantaneous VRAM sawtooths because eviction is fast but refetch is + // slow; the prediction lets the controller see the equilibrium directly, + // so mult converges to a target value instead of cycling. { static LLCachedControl backoff_start(gSavedSettings, "TextureMemoryPressureBackoffStart", 0.85f); + static LLCachedControl max_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); + static LLCachedControl prediction_gain(gSavedSettings, "TextureMemoryPressurePredictionGain", 10.f); + static LLCachedControl smoothing_rate(gSavedSettings, "TextureMemoryPressureSmoothingRate", 4.f); + F32 backoff_target = target * llclamp((F32)backoff_start, 0.05f, 1.f); - F32 over = used / llmax(backoff_target, 1.f); - static LLCachedControl pressure_ramp_rate(gSavedSettings, "TextureMemoryPressureRampRate", 3.0f); - F32 dt = gFrameIntervalSeconds; - if (over > 1.f) + + // Walk the texture list once per frame, summing pending size deltas. + // Approximation: bytes(d) = (w>>d) * (h>>d) * 4 * 4/3. Units match + // `used` after the /1024/512 conversion that produced it. Coarse but + // proportionally correct - the gain knob tunes absolute magnitude. + S64 pending_bytes_increase = 0; + S64 pending_bytes_decrease = 0; { - sMemoryPressureFactor += - (over - 1.f) * (1.f - sMemoryPressureFactor) * (F32)pressure_ramp_rate * dt; + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vt - in-flight predict"); + for (auto& imagep : gTextureList) + { + if (imagep.isNull()) continue; + S32 fw = imagep->getFullWidth(); + S32 fh = imagep->getFullHeight(); + if (fw <= 0 || fh <= 0) continue; + S32 desired = imagep->getDesiredDiscardLevel(); + S32 current = imagep->getDiscardLevel(); + if (desired < 0 || current < 0 || desired == current) continue; + + S32 wd = llmax(1, fw >> desired); + S32 hd = llmax(1, fh >> desired); + S32 wc = llmax(1, fw >> current); + S32 hc = llmax(1, fh >> current); + // bpp=4, mip pyramid overhead 4/3 + S64 size_d = (S64)wd * hd * 4 * 4 / 3; + S64 size_c = (S64)wc * hc * 4 * 4 / 3; + + if (desired < current) + pending_bytes_increase += (size_d - size_c); + else + pending_bytes_decrease += (size_c - size_d); + } } - else + + // Match the unit conversion used to produce `used` (texture_bytes_alloc + // divided by 1024/512). 1024*512 = 524288. + constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; + F32 predicted_used = used + + (F32)pending_bytes_increase * BYTES_TO_USED_UNITS + - (F32)pending_bytes_decrease * BYTES_TO_USED_UNITS; + F32 predicted_over = predicted_used / llmax(backoff_target, 1.f); + + // Direct target: at over=1, mult=1; growth governed by gain power. + // Smooth toward target so per-frame prediction noise doesn't jolt + // the controller. Lerp rate set so a step change in target reaches + // ~63% in 1/smoothing_rate seconds (default 0.25s). + F32 cap = llmax((F32)max_mult, 1.0001f); + F32 target_mult = llclamp(powf(llmax(predicted_over, 1.f), llmax((F32)prediction_gain, 0.0001f)), 1.f, cap); + F32 dt = (F32)gFrameIntervalSeconds; + F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)smoothing_rate, 0.f)); + sMemoryPressureMultiplier += (target_mult - sMemoryPressureMultiplier) * alpha; + sMemoryPressureMultiplier = llclamp(sMemoryPressureMultiplier, 1.f, cap); + + // Last-ditch global discard floor. Ramps up when mult is pegged near + // cap and we are still over budget; decays toward 0 when fitting. Once + // the normal compression knobs are exhausted, this creeps the discard + // floor up integer step by step, mirroring how sDesiredDiscardBias + // pushes background textures. + { + static LLCachedControl ld_engage(gSavedSettings, "TextureLastDitchEngageProgress", 0.95f); + static LLCachedControl ld_ramp(gSavedSettings, "TextureLastDitchRampRate", 0.5f); + static LLCachedControl ld_decay(gSavedSettings, "TextureLastDitchDecayRate", 0.5f); + static LLCachedControl ld_max(gSavedSettings, "TextureLastDitchMinDiscardMax", 13.f); + F32 progress = llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); + bool mult_saturated = progress >= llclampf((F32)ld_engage); + if (mult_saturated && predicted_over > 1.f) + { + sLastDitchMinDiscard += llmax((F32)ld_ramp, 0.f) * dt; + } + else if (predicted_over < 1.f) + { + sLastDitchMinDiscard -= llmax((F32)ld_decay, 0.f) * dt; + } + sLastDitchMinDiscard = llclamp(sLastDitchMinDiscard, 0.f, llmax((F32)ld_max, 0.f)); + } + + F32 over = used / llmax(backoff_target, 1.f); // legacy alias used below + + // Throttled bisection log. Once per second. + static LLFrameTimer s_pressure_log_timer; + if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) { - sMemoryPressureFactor -= sMemoryPressureFactor * (F32)pressure_ramp_rate * dt; + s_pressure_log_timer.reset(); + F32 mult_progress = llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); + LL_INFOS("TextureStream") << "pressure" + << " mult=" << sMemoryPressureMultiplier + << " target_mult=" << target_mult + << " progress=" << mult_progress + << " used=" << used + << " predicted=" << predicted_used + << " target=" << target + << " over=" << over + << " pred_over=" << predicted_over + << " in+=" << (S32)(pending_bytes_increase / 1024 / 1024) + << "MB in-=" << (S32)(pending_bytes_decrease / 1024 / 1024) + << "MB bias=" << sDesiredDiscardBias + << " ldmin=" << sLastDitchMinDiscard + << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() + << LL_ENDL; } - sMemoryPressureFactor = llclampf(sMemoryPressureFactor); } bool is_sys_low = isSystemMemoryLow(); @@ -2181,12 +2275,11 @@ bool LLViewerFetchedTexture::updateFetch() make_request = false; } else if (mDesiredDiscardLevel > (S32)mCodecMaxDiscardLevel && - current_discard >= 0 && - current_discard <= (S32)mCodecMaxDiscardLevel) + current_discard >= 0) { - // scaleDown can serve this from the GL pyramid. (If current is - // already past codec_max, fall through so a zoom-in can rebuild — - // scaleDown only goes deeper.) + // Desired is past codec_max. Only scaleDown can satisfy it. + // Applies even when current is also past codec_max (post-scaleDown); + // re-fetching at codec_max then scaleDown-ing again is pure thrash. LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > codec max"); make_request = false; } @@ -3177,6 +3270,16 @@ void LLViewerLODTexture::processTextureStats() F32 combined = distance_factor * size_factor; + // VRAM pressure: multiply the combined signal and clamp to 0..1. + // Compresses the effective draw range and picks up close-coverage + // textures (small combined) too. Applied before the channel + // exponent so subsequent transforms see a normalized 0..1 value. + // Avatar bakes exempt. + if (!isAgentAvatarBoost(mBoostLevel) && sMemoryPressureMultiplier > 1.f) + { + combined = llmin(combined * sMemoryPressureMultiplier, 1.f); + } + // Per-channel exponent. 1.0 = baseline; <1.0 pushes combined // toward 1 (max attenuation) faster. Edges are preserved: // pow(0, p) = 0, pow(1, p) = 1. @@ -3225,16 +3328,6 @@ void LLViewerLODTexture::processTextureStats() combined = llmax(combined, bg); } - // VRAM pressure: pow(combined, 1 - factor) bends the curve - // without flattening it - pow(0, p) = 0 so close textures - // (combined ~ 0) stay near 0 while mid/far push toward 1. - // Avatar bakes exempt. - if (!isAgentAvatarBoost(mBoostLevel) && sMemoryPressureFactor > 0.f) - { - F32 pressure_exp = llmax(1.f - sMemoryPressureFactor, 0.0001f); - combined = powf(combined, pressure_exp); - } - discard_level = combined * dim_max_for_image; } @@ -3252,21 +3345,36 @@ void LLViewerLODTexture::processTextureStats() mDesiredDiscardLevel = llmin(effective_cap, (S32)discard_level); - // Apply the setMinDiscardLevel cap, relaxed proportionally under - // VRAM pressure - at factor=1 the cap reaches dim_max so capped - // textures (terrain, etc.) participate fully in eviction. Caps of - // 0 (thumbnails) and avatar bakes are preserved. + // Apply the setMinDiscardLevel cap, relaxed under VRAM pressure + // (cap_relax = 1 - 1/mult: 0 at mult=1, ~0.5 at mult=2, ~0.9 at + // mult=10). Caps of 0 (thumbnails) and avatar bakes are preserved. S32 effective_min_cap = mMinDesiredDiscardLevel; - if (sMemoryPressureFactor > 0.f && + if (sMemoryPressureMultiplier > 1.f && mMinDesiredDiscardLevel > 0 && mMinDesiredDiscardLevel < S8_MAX && !isAgentAvatarBoost(mBoostLevel)) { + F32 cap_relax = 1.f - 1.f / sMemoryPressureMultiplier; F32 room = (F32)dim_max_for_image_i - (F32)mMinDesiredDiscardLevel; - effective_min_cap += (S32)(sMemoryPressureFactor * room); + effective_min_cap += (S32)(cap_relax * room); effective_min_cap = llmin(effective_min_cap, dim_max_for_image_i); } mDesiredDiscardLevel = llmin((S8)effective_min_cap, mDesiredDiscardLevel); + // Last-ditch global discard floor, driven by sLastDitchMinDiscard. + // That state creeps up step-by-step while mult is pegged and we are + // still over budget (see updateClass), and decays when we fit. Force + // every non-avatar-bake texture to at least floor(sLastDitchMinDiscard) + // discard, capped at the per-texture max. + if (!isAgentAvatarBoost(mBoostLevel)) + { + S32 forced = (S32)floorf(sLastDitchMinDiscard); + forced = llclamp(forced, 0, dim_max_for_image_i); + if (forced > mDesiredDiscardLevel) + { + mDesiredDiscardLevel = (S8)forced; + } + } + // // At this point we've calculated the quality level that we want, @@ -3310,6 +3418,23 @@ bool LLViewerLODTexture::scaleDown() return false; } + // Hard structural blocks only. Per-texture policy (icons pinned to full + // res, etc.) lives in processTextureStats; if that policy is later + // relaxed (e.g. honor mKnownDrawWidth for icons rendered at 8x8 in a + // friend list) the scaleDown path stays open. + if (!mUseMipMaps || mDontDiscard || mBoostLevel >= LLGLTexture::BOOST_HIGH) + { + // No mip pyramid to drop into, texture is explicitly pinned full res, + // or BOOST_HIGH+ emergency-out (currently only the GLTF "force full + // res" hack hits this). + return false; + } + // Avatar bakes are exempt from mid-bake eviction (cloud avatar risk). + if (isAgentAvatarBoost(mBoostLevel)) + { + return false; + } + if (!mDownScalePending) { mDownScalePending = true; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index e2215700c0..d40d3bc5ee 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -247,10 +247,14 @@ public: // snaps to 0 in foreground. Avatar bakes exempt. static F32 sBackgroundFactor; - // VRAM-pressure factor, 0..1. Applied in processTextureStats as - // combined = pow(combined, 1 - factor) - bends the curve without - // flattening the distance gradient. - static F32 sMemoryPressureFactor; + // VRAM-pressure distance multiplier, >= 1. Compresses the distance + // signal: dist_factor = clamp(mMinDistanceFactor * mult, 0, 1). + // Grows geometrically while over budget; decays back to 1 when fitting. + static F32 sMemoryPressureMultiplier; + // Last-ditch global discard floor. Creeps up when mult is pegged at cap + // and we are still over budget; decays back to 0 when fitting. Applied + // as a floor on mDesiredDiscardLevel for non-avatar-bake textures. + static F32 sLastDitchMinDiscard; static U32 sBiasTexturesUpdated; static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; @@ -379,7 +383,7 @@ public: void updateVirtualSize() ; - S32 getDesiredDiscardLevel() { return mDesiredDiscardLevel; } + S32 getDesiredDiscardLevel() const { return mDesiredDiscardLevel; } void setMinDiscardLevel(S32 discard) { mMinDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel,(S8)discard); } void setBoostLevel(S32 level) override; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 2e66dc2a11..ee21d6a4dd 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -71,6 +71,7 @@ void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; S32 LLViewerTextureList::sNumImages = 0; +F32 LLViewerTextureList::sCurrentBubbleMeters = 0.f; LLViewerTextureList gTextureList; @@ -937,8 +938,40 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag // dist_factor = 0 (no discard contribution). The ramp from 0 -> 1 // spans (bubble, draw_distance] rather than (0, draw_distance]. static LLCachedControl close_bubble(gSavedSettings, "TextureCloseBubbleMeters", 5.f); - F32 bubble = llclamp((F32)close_bubble, 0.f, draw_distance - 0.001f); + static LLCachedControl close_bubble_min(gSavedSettings, "TextureCloseBubbleMinMeters", 0.1f); + static LLCachedControl max_pressure_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); + static LLCachedControl bubble_shrink_threshold(gSavedSettings, "TextureCloseBubbleShrinkThreshold", 0.8f); + static LLCachedControl bubble_track_rate(gSavedSettings, "TextureCloseBubbleTrackRate", 0.5f); + F32 bubble_full = llmax((F32)close_bubble, 0.f); + F32 bubble_min = llclamp((F32)close_bubble_min, 0.f, bubble_full); + // Target bubble: stay at full size until pressure multiplier is + // deep into its range, then collapse toward bubble_min. The bubble + // is an emergency response, not part of the normal feedback loop. + F32 mult_cap = llmax((F32)max_pressure_mult, 1.0001f); + F32 mult_progress = llclampf((LLViewerTexture::sMemoryPressureMultiplier - 1.f) / (mult_cap - 1.f)); + F32 shrink_thresh = llclampf((F32)bubble_shrink_threshold); + F32 shrink_t = (mult_progress > shrink_thresh) + ? (mult_progress - shrink_thresh) / llmax(1.f - shrink_thresh, 0.0001f) + : 0.f; + F32 target_bubble = bubble_full - (bubble_full - bubble_min) * shrink_t; + // Slow-track the actual bubble toward target so short-term multiplier + // swings don't yo-yo close textures in and out. Advance the state + // ONCE per frame, not per texture - this function runs once per + // texture so a naive per-call lerp converges in a single frame. + static F32 s_tracked_bubble = -1.f; + static U32 s_tracked_bubble_frame = 0; + if (s_tracked_bubble < 0.f) s_tracked_bubble = bubble_full; + if (s_tracked_bubble_frame != LLFrameTimer::getFrameCount()) + { + s_tracked_bubble_frame = LLFrameTimer::getFrameCount(); + F32 dt = (F32)gFrameIntervalSeconds; + F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)bubble_track_rate, 0.f)); + s_tracked_bubble += (target_bubble - s_tracked_bubble) * alpha; + s_tracked_bubble = llclamp(s_tracked_bubble, bubble_min, bubble_full); + } + F32 bubble = llclamp(s_tracked_bubble, 0.f, draw_distance - 0.001f); F32 ramp_range = llmax(draw_distance - bubble, 0.001f); + sCurrentBubbleMeters = bubble; U32 face_count = 0; U32 max_faces_to_check = 1024; @@ -1107,10 +1140,15 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 grace = llmax((F32)bind_decay_seconds, 0.f); F32 interval = llmax((F32)staleness_interval, 0.0001f); - bool ever_bound = (gli->mLastBindTime > 0.f); - F32 time_since_bind = ever_bound ? (LLImageGL::sLastFrameTime - gli->mLastBindTime) : 0.f; + // Clock starts at whichever is later: the last real bind or + // the GL-create time. The latter is the fallback for textures + // decoded into GL but never actually rendered - without it, + // mLastBindTime stays 0 forever and staleness can't evict. + F32 clock_time = llmax(gli->mLastBindTime, gli->mGLCreateTime); + bool has_clock = (clock_time > 0.f); + F32 time_since = has_clock ? (LLImageGL::sLastFrameTime - clock_time) : 0.f; - if (!ever_bound || time_since_bind <= grace) + if (!has_clock || time_since <= grace) { imagep->mStalenessFactor = 0.f; } @@ -1123,7 +1161,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag : (S32)gli->getMaxDiscardLevel(); if (max_discard > 0) { - F32 steps = (time_since_bind - grace) / interval; + F32 steps = (time_since - grace) / interval; F32 step_size = 1.f / (F32)max_discard; imagep->mStalenessFactor = llclampf(steps * step_size); } @@ -1270,10 +1308,13 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) // give time to downscaling first - if mDownScaleQueue is not empty, we're running out of memory and need // to free up memory by discarding off screen textures quickly - // do at least 5 and make sure we don't get too far behind even if it violates - // the time limit. If we don't downscale quickly the viewer will hit swap and may - // freeze. - S32 min_count = (S32)mCreateTextureList.size() / 20 + 5; + // Drain rate scales with both pending creates and the downscale + // queue itself. Without the queue term, a backlog of evictions + // could only drain 5/frame regardless of size, and the system + // can't actually free VRAM fast enough under pressure. + S32 min_count = (S32)mCreateTextureList.size() / 20 + + (S32)mDownScaleQueue.size() / 5 + + 5; create_timer.reset(); while (!mDownScaleQueue.empty()) @@ -1375,12 +1416,15 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); - if (LLViewerTexture::sDesiredDiscardBias > 1.f + // Scale up the per-frame update window under VRAM pressure so eviction + // candidates get re-evaluated quickly. Both the legacy bias and the + // new pressure multiplier widen the window. + F32 pressure_scale = llmax(LLViewerTexture::sDesiredDiscardBias, + LLViewerTexture::sMemoryPressureMultiplier); + if (pressure_scale > 1.f && LLViewerTexture::sBiasTexturesUpdated < (U32)mUUIDMap.size()) { - // We are over memory target. Bias affects discard rates, so update - // existing textures agresively to free memory faster. - update_count = (S32)(update_count * LLViewerTexture::sDesiredDiscardBias); + update_count = (S32)(update_count * pressure_scale); // This isn't particularly precise and can overshoot, but it doesn't need // to be, just making sure it did a full circle and doesn't get stuck updating diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 931f2ed50e..dbed8b5c2f 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -244,6 +244,10 @@ private: bool mInitialized ; LLFrameTimer mForceDecodeTimer; +public: + // Current close-camera bubble in meters (frame-coherent, slow-tracked). + static F32 sCurrentBubbleMeters; + private: static S32 sNumImages; static void (*sUUIDCallback)(void**, const LLUUID &); -- cgit v1.3 From dad44ba5d67d04a73708a9a25bbe1ddec29a6a9a Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 22 May 2026 13:12:58 -0400 Subject: A few OpenGL state fixes provided by Rye from the Alchemy Viewer. --- indra/llrender/llimagegl.cpp | 2 +- indra/llrender/llrender.cpp | 15 ++++++++++++++- indra/newview/lldrawpoolwater.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index a1396aba20..6bcc34938c 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1895,7 +1895,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 696a0d145f..8ac906b5a1 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -197,6 +197,7 @@ void LLTexUnit::bindFast(LLTexture* texture) glActiveTexture(GL_TEXTURE0 + mIndex); gGL.mCurrTextureUnitIndex = mIndex; mCurrTexture = gl_tex->getTexName(); + mCurrTexType = gl_tex->getTarget(); // bindFast bypasses updateBindStats(); stamp directly so the staleness // signal sees per-frame use of batched textures. gl_tex->stampBound(); @@ -1732,7 +1733,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()); @@ -1747,6 +1757,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/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); -- cgit v1.3 From b55a7038511a6ed3d97e17b13c9d83bc2017ba61 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 6 Jul 2026 02:34:15 -0400 Subject: Move over to a pixels to texels ratio to drive texture resolution. --- indra/llrender/llimagegl.h | 4 +- indra/newview/app_settings/settings.xml | 270 ++------------- indra/newview/lltextureview.cpp | 13 +- indra/newview/llviewercontrol.cpp | 76 ++--- indra/newview/llviewertexture.cpp | 568 +++++++++----------------------- indra/newview/llviewertexture.h | 108 ++---- indra/newview/llviewertexturelist.cpp | 292 ++++------------ indra/newview/llviewertexturelist.h | 4 - 8 files changed, 314 insertions(+), 1021 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 0c85446b84..0869ae54fe 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -238,8 +238,8 @@ public: public: // Various GL/Rendering options S64Bytes mTextureMemory; - mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives streaming staleness - F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; staleness fallback for never-bound textures + mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives the streaming cooldown + F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; cooldown fallback for never-bound textures private: U32 createPickMask(S32 pWidth, S32 pHeight); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 519041bbeb..cfd92e03ca 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8060,7 +8060,7 @@ RenderTextureQuality Comment - Texture quality preset: 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, TextureDiscardBiasMax, TextureCloseBubbleMeters/MinMeters/ShrinkThreshold, TextureChannelOffset* (four), TexturePressureDiscardScale, TextureMinCapPressureRelaxScale, TextureUpdateCountPressureMaxMultiplier, TextureScreenSizeOversample/UnderPressure, TextureAgentAvatarOversampleMultiplier, TextureBackgroundFactorRatePerSec, TextureDiscardBackgroundedTime. Watermarks (TextureWatermarkHigh/Low) are constant across tiers. + Texture quality preset: 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, TexturePixelToTexelRatio, TexturePressureTightenRate, TexturePressureRelaxRate, and TextureChannelRatio* (Normal/BaseColor/Specular/Emissive). Watermarks (TextureWatermarkHigh/Low) are constant across tiers. Persist 1 Type @@ -11871,131 +11871,10 @@ Value 20.0 - TextureChannelOffsetNormal + TextureChannelRatioNormal Comment - Additive discard offset for normal maps (integer mip levels). 0 = no shift; positive = N more discard levels (worse quality). Driven by the RenderTextureQuality preset. - Persist - 1 - Type - S32 - Value - 0 - - TextureChannelOffsetBaseColor - - Comment - Additive discard offset for base color / diffuse (integer mip levels). 0 = no shift; positive = N more discard levels. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - S32 - Value - 0 - - TextureChannelOffsetSpecular - - Comment - Additive discard offset for specular / metallic-roughness (integer mip levels). Higher = more aggressive (specular detail is usually less perceptible than diffuse). Driven by the RenderTextureQuality preset. - Persist - 1 - Type - S32 - Value - 1 - - TextureChannelOffsetEmissive - - Comment - Additive discard offset for emissive (integer mip levels). Higher = more aggressive. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - S32 - Value - 1 - - TextureMaxDiscardOverride - - Comment - When non-zero, overrides the per-texture codec-derived max discard cap. 0 = use codec-reported levels. Higher lets the streaming math push past the codec ceiling; scaleDown handles the GL side. - Persist - 1 - Type - S32 - Value - 0 - - TextureWatermarkHigh - - Comment - High watermark as a fraction of the VRAM budget. When used VRAM crosses this, the global discard bias climbs (backs off detail). Held at 0.90 across quality tiers - it's a physical "crossed the budget" threshold, not a tier preference. - Persist - 1 - Type - F32 - Value - 0.90 - - TextureWatermarkLow - - Comment - Low watermark as a fraction of the VRAM budget. When used VRAM drops below this, the global discard bias relaxes (restores detail). The band between low and high is the hysteresis zone where the bias holds steady - wide enough to prevent sawtooth without prediction/smoothing. - Persist - 1 - Type - F32 - Value - 0.70 - - TextureDiscardBiasMax - - Comment - Upper bound on the watermark-driven global discard bias. Caps how steep the distance-floor ramp can get under sustained pressure: at the cap with TexturePressureDiscardScale=1.0 the floor forces max discard at ~1/(1+max) of draw distance. Also the denominator for getMemoryPressureProgress. - Persist - 1 - Type - F32 - Value - 12.0 - - TextureDiscardBiasRampRate - - Comment - Rate (bias units/sec) at which the global discard bias climbs while used VRAM is above the high watermark. Deliberately slow so eviction (scaleDown draining) catches up before the next step - prevents over-shoot / thrash. - Persist - 1 - Type - F32 - Value - 2.0 - - TextureDiscardBiasDecayRate - - Comment - Rate (bias units/sec) at which the global discard bias relaxes while used VRAM is below the low watermark. Typically slower than the ramp so detail returns gradually as headroom appears. - Persist - 1 - Type - F32 - Value - 1.0 - - TexturePressureDiscardScale - - Comment - Modulates the distance-floor pressure response. compression = 1 + sDiscardBias * scale; the floor forces max discard at ~1/compression of draw distance. At scale=1.0 with bias=12, max discard is forced at ~8% of draw distance; higher scale = steeper / closer. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - F32 - Value - 1.0 - - TextureMinCapPressureRelaxScale - - Comment - Modulates the relaxation of mMinDesiredDiscardLevel under pressure. add = floor(progress * room * scale), where room is the gap to max discard and progress is sDiscardBias/TextureDiscardBiasMax. At 0 disables relaxation entirely. + Per-channel pixel:texel ratio multiplier for normal maps (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 1.0 = full quality; lower = coarser. Driven by the RenderTextureQuality preset. Persist 1 Type @@ -12003,21 +11882,10 @@ Value 1.0 - TextureUpdateCountPressureMaxMultiplier - - Comment - Hard cap on per-frame update_count scale-up factor in updateImagesFetchTextures under pressure. Replaces unbounded max(bias, mult) blow-up. At default 6, the full-list sweep takes at minimum mUUIDMap.size()/6 frames per round-trip under high pressure - bounded CPU cost even at peak. - Persist - 1 - Type - F32 - Value - 6.0 - - TextureFetchBiasTrackRate + TextureChannelRatioBaseColor Comment - First-order low-pass rate for the fetch-pressure bias contribution (1/sec). The bias floor decays independently of VRAM headroom, so queue-driven bias releases naturally as fetches drain. Default ~1s convergence. + Per-channel pixel:texel ratio multiplier for base color / diffuse (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 1.0 = full quality. Driven by the RenderTextureQuality preset. Persist 1 Type @@ -12025,162 +11893,116 @@ Value 1.0 - TextureTerrainDistanceFloor + TextureChannelRatioSpecular Comment - Minimum distance factor for BOOST_TERRAIN textures. Keeps combined > 0 so VRAM pressure can evict terrain. Lower = higher idle quality, less pressure response. + Per-channel pixel:texel ratio multiplier for specular / metallic-roughness (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 0.5 = half resolution (specular detail is usually less perceptible than diffuse). Driven by the RenderTextureQuality preset. Persist 1 Type F32 Value - 0.01 - - TextureTerrainCoverageFraction - - Comment - Synthetic on-screen coverage fraction for BOOST_TERRAIN textures (no faces are registered). Higher = higher idle quality, less pressure response. - Persist - 1 - Type - F32 - Value - 0.99 - - TextureAgentAvatarOversampleMultiplier - - Comment - Oversample multiplier for textures on the agent's avatar (rigged mesh / animated objects). 1.0 = no boost; 2.0 = 4x texels per pixel = exactly -1 discard level. Preference, not exemption - pressure can still evict via the distance floor. - Persist - 1 - Type - F32 - Value - 2.0 + 0.5 - TextureBackgroundFactorRatePerSec + TextureChannelRatioEmissive Comment - Per-second ramp rate of the background-window discard floor (0..1). Snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. + Per-channel pixel:texel ratio multiplier for emissive (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 0.5 = half resolution. Driven by the RenderTextureQuality preset. Persist 1 Type F32 Value - 0.011 + 0.5 - TextureBackgroundDiscardOffset + TextureMaxDiscardOverride Comment - Backgrounded textures will only discard up to (dim_max - offset). e.g. a 2048 texture (dim_max 11) with offset 2 caps the background floor at discard 9. 0 disables the cap (background can drive to max discard). + When non-zero, overrides the per-texture codec-derived max discard cap. 0 = use codec-reported levels. Higher lets the streaming math push past the codec ceiling; scaleDown handles the GL side. Persist 1 Type S32 Value - 2 - - - TextureCloseBubbleMeters - - Comment - Close-camera bubble (meters). Faces inside this distance get dist_factor = 0 (no discard contribution); the ramp to 1 spans (bubble, draw_distance]. Shrinks toward TextureCloseBubbleMinMeters as VRAM pressure ramps the multiplier toward its cap. - Persist - 1 - Type - F32 - Value - 5.0 - - TextureCloseBubbleMinMeters - - Comment - Floor (meters) for the close-camera bubble under maximum VRAM pressure. As the discard bias approaches TextureDiscardBiasMax the bubble collapses toward this value, allowing eviction of even close textures when nothing else fits. - Persist - 1 - Type - F32 - Value - 3.0 + 0 - TextureCloseBubbleShrinkThreshold + TexturePixelToTexelRatio Comment - Bubble stays at full size until mult_progress exceeds this fraction (0..1) of its range to the cap. Above that, bubble lerps from full to TextureCloseBubbleMinMeters. Keeps the bubble out of the normal feedback loop. + Global maximum pixel:texel ratio, expressed as texels per screen pixel (the "R" in 1:R). 1.0 = one texel per pixel, the best quality the streamer will allocate. A texture is sized so its most-demanding on-screen face stays at or below this many texels per pixel; everything coarser falls out by distance. VRAM pressure walks the effective ratio down from here toward 0 (no floor). Persist 1 Type F32 Value - 0.8 + 1.0 - TextureCloseBubbleTrackRate + TextureWatermarkHigh Comment - Rate (1/sec) at which the actual bubble tracks its target. Lower = smoother, slower to react. Damps short-term multiplier swings so close textures don't yo-yo. + High watermark as a fraction of the VRAM budget. When used VRAM crosses this, the global pixel:texel ratio tightens (backs off detail). Held at 0.90 - it's a physical "crossed the budget" threshold, not a tier preference. Persist 1 Type F32 Value - 0.5 + 0.90 - TextureScreenSizeOversample + TextureWatermarkLow Comment - Texels per screen pixel kept by the per-texture pixel-area discard cap. 1.0 = 1:1 with on-screen contribution; higher = sharper distant textures, more VRAM. Driven by RenderTextureQuality. + Low watermark as a fraction of the VRAM budget. When used VRAM drops below this, the global pixel:texel ratio relaxes (restores detail). The band between low and high is the hysteresis zone where the ratio holds steady - wide enough to prevent sawtooth. Persist 1 Type F32 Value - 1.5 + 0.70 - TextureScreenSizeOversampleUnderPressure + TexturePressureTightenRate Comment - Floor that TextureScreenSizeOversample collapses toward as the discard bias walks up toward TextureDiscardBiasMax. Lower = allow distant textures to drop below their on-screen contribution under pressure. + Rate (ratio units/sec) at which the global pixel:texel ratio drops while used VRAM is above the high watermark. Deliberately slow so eviction (scaleDown draining) frees bytes before the next step - prevents over-shoot / thrash. Persist 1 Type F32 Value - 0.5 + 0.30 - TextureStalenessIntervalSeconds + TexturePressureRelaxRate Comment - Seconds per staleness step. Per-interval increment is 1/max_discard so any texture saturates after interval * max_discard seconds idle. + Rate (ratio units/sec) at which the global pixel:texel ratio climbs back toward TexturePixelToTexelRatio while used VRAM is below the low watermark. Typically slower than the tighten rate so detail returns gradually as headroom appears. Persist 1 Type F32 Value - 5.0 + 0.10 - TextureBindDecaySeconds + TextureUpRezMargin Comment - Grace seconds after a bind during which the staleness factor stays at 0. Prevents intermittently-bound textures from ramping. 0 disables the grace period. + Hysteresis dead-band (in mip levels) around a texture's current discard. A texture only fetches a finer mip when its ideal discard falls more than this below the current level, and only evicts when it rises more than this above - prevents fetch/scaleDown thrash at mip boundaries when an object slowly recedes. Persist 1 Type F32 Value - 5.0 + 0.2 - TextureFetchPressureScale + TextureCooldownStepSeconds Comment - Pending-fetch divisor for the bias floor: bias floor = 1 + clamp(pending / scale, 0, 3). Lower = bias rises sooner under load floods. + Seconds an unseen texture waits per mip level before stepping down. When a texture stops being bound (occluded / off-screen) or the window is backgrounded, its discard rises one level every this many seconds until it reaches the deepest mip, instead of snapping there immediately - so briefly-unseen content isn't thrown away and refetched (cache thrash) if it reappears. Resets the moment the texture is bound again. Persist 1 Type F32 Value - 1000.0 + 1.0 - TextureDecodeDisabled Comment @@ -12214,28 +12036,6 @@ Value 0 - TextureDiscardBackgroundedTime - - Comment - Specify how long to wait before discarding texture data after viewer is backgrounded. (zero or negative to disable) - Persist - 1 - Type - F32 - Value - 60.0 - - TextureDiscardMinimizedTime - - Comment - Specify how long to wait before discarding texture data after viewer is minimized. (zero or negative to disable) - Persist - 1 - Type - F32 - Value - 1.0 - TextureFetchConcurrency Comment diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 1c341a06cd..10b57f47da 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -476,7 +476,7 @@ private: void LLGLTexMemBar::draw() { - F32 discard_bias = LLViewerTexture::sDesiredDiscardBias; + F32 pixel_to_texel_ratio = LLViewerTexture::sPixelToTexelRatio; F32 cache_usage = (F32)LLAppViewer::getTextureCache()->getUsage().valueInUnits(); F32 cache_max_usage = (F32)LLAppViewer::getTextureCache()->getMaxUsage().valueInUnits(); S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); @@ -560,25 +560,22 @@ void LLGLTexMemBar::draw() gGL.color4f(0.f, 0.f, 0.f, 0.25f); gl_rect_2d(-10, getRect().getHeight() + line_height*2 + 1, getRect().getWidth()+2, getRect().getHeight()+2); - text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Probe#: %d Probe Mem: %d MB Bias: %.2f Cache: %.1f/%.1f MB", + text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Probe#: %d Probe Mem: %d MB Px:Texel 1:%.2f Cache: %.1f/%.1f MB", (S32)LLViewerTexture::sFreeVRAMMegabytes, LLMemory::getAvailableMemKB()/1024, LLRenderTarget::sBytesAllocated/(1024*1024), gPipeline.mReflectionMapManager.probeCount(), gPipeline.mReflectionMapManager.probeMemory(), - discard_bias, + pixel_to_texel_ratio, cache_usage, cache_max_usage); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*8, text_color, LLFontGL::LEFT, LLFontGL::TOP); - text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB) Bubble: %.1fm DiscardBias: %.2f Progress: %.2f", + text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB)", image_count, raw_image_count, raw_image_bytes_MB, saved_raw_image_count, saved_raw_image_bytes_MB, - aux_raw_image_count, aux_raw_image_bytes_MB, - LLViewerTextureList::sCurrentBubbleMeters, - LLViewerTexture::sDiscardBias, - LLViewerTexture::getMemoryPressureProgress()); + aux_raw_image_count, aux_raw_image_bytes_MB); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height * 7, text_color, LLFontGL::LEFT, LLFontGL::TOP); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 19c28b12f1..e2af54539a 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -121,43 +121,32 @@ namespace { const char* name; U32 max_resolution; - F32 discard_bias_max; // TextureDiscardBiasMax: ceiling on the watermark bias - F32 close_bubble_meters; - F32 close_bubble_min_meters; - F32 close_bubble_shrink_threshold; - F32 screen_size_oversample; - F32 screen_size_oversample_under_pressure; - F32 background_factor_rate_per_sec; - F32 discard_backgrounded_time; - F32 pressure_discard_scale; - F32 min_cap_relax_scale; - F32 update_count_max_mult; - F32 avatar_oversample_mult; - S32 channel_off_normal; - S32 channel_off_basecolor; - S32 channel_off_specular; - S32 channel_off_emissive; + F32 pixel_to_texel_ratio; // TexturePixelToTexelRatio (R_max, texels per pixel) + F32 pressure_tighten_rate; // TexturePressureTightenRate (ratio units/sec) + F32 pressure_relax_rate; // TexturePressureRelaxRate (ratio units/sec) + F32 channel_ratio_normal; // TextureChannelRatioNormal + F32 channel_ratio_basecolor; // TextureChannelRatioBaseColor + F32 channel_ratio_specular; // TextureChannelRatioSpecular + F32 channel_ratio_emissive; // TextureChannelRatioEmissive }; // Tier values: Low (2-4GB), Medium (4-8GB), High (8-16GB), Ultra (16+GB). - // Pressure-aggression ladder: Low burns hot, Ultra barely sweats. - // + // Quality ladder, expressed in pixel:texel (texels per pixel): + // - pixel_to_texel_ratio is the baseline quality: how many texels per + // screen pixel the tier allocates when VRAM is comfortable (1.0 = 1:1). + // Under pressure the runtime drives the global ratio below this with no + // floor (down to 0 = deepest mips), so there is no per-tier minimum. + // - the channel ratios coarsen specular/emissive/normal relative to base + // color (each is a multiplier on the global ratio). // The watermarks (TextureWatermarkHigh/Low) are NOT tiered - they're a // physical "crossed the budget" threshold (0.90 / 0.70), constant across - // tiers. Tier aggression is expressed via discard_bias_max (how steep the - // ramp can get) and pressure_discard_scale (how fast it steepens). At the - // cap, compression = 1 + bias_max*scale; the floor forces max discard at - // ~1/compression of draw distance: - // Low: 1+16*1.5 = 25 -> ~4% of draw distance - // Medium: 1+12*1.2 = 15 -> ~7% - // High: 1+10*1.0 = 11 -> ~9% - // Ultra: 1+ 8*0.75 = 7 -> ~14% (the old "mult 8" feel) - // max_res biasMax bub bub_min sh_th oS oSp bgRt bgT pScl relax uMul avO N BC S E + // tiers. Lower tiers start blurrier (lower R_max) and tighten faster. + // max_res Rmax tight relax N BC S E static constexpr TexturePreset TEXTURE_PRESETS[4] = { - /* 0 Low */ { "Low", 1024, 16.0f, 3.0f, 0.0f, 0.70f, 0.75f, 0.50f, 0.020f, 30.0f, 1.5f, 1.5f, 8.0f, 1.5f, 7, 0, 4, 1 }, - /* 1 Medium */ { "Medium", 2048, 12.0f, 5.0f, 1.0f, 0.80f, 1.00f, 0.50f, 0.011f, 60.0f, 1.2f, 1.2f, 6.0f, 1.75f, 0, 0, 1, 1 }, - /* 2 High */ { "High", 2048, 10.0f, 8.0f, 2.0f, 0.90f, 1.50f, 0.75f, 0.005f, 120.0f, 1.0f, 1.0f, 4.0f, 2.0f, 0, 0, 1, 0 }, - /* 3 Ultra */ { "Ultra", 2048, 8.0f, 12.0f, 4.0f, 0.95f, 2.00f, 1.00f, 0.002f, 300.0f, 0.75f, 0.75f, 2.0f, 2.0f, 0, 0, 0, 0 }, + /* 0 Low */ { "Low", 1024, 0.10f, 0.50f, 0.05f, 0.50f, 1.00f, 0.25f, 0.25f }, + /* 1 Medium */ { "Medium", 2048, 0.40f, 0.35f, 0.08f, 1.00f, 1.00f, 0.50f, 0.50f }, + /* 2 High */ { "High", 2048, 0.80f, 0.25f, 0.10f, 1.00f, 1.00f, 0.50f, 1.00f }, + /* 3 Ultra */ { "Ultra", 2048, 1.00f, 0.15f, 0.12f, 1.00f, 1.00f, 1.00f, 1.00f }, }; } @@ -167,23 +156,14 @@ static bool handleRenderTextureQualityChanged(const LLSD& newvalue) if (quality > 3) quality = 3; const TexturePreset& p = TEXTURE_PRESETS[quality]; - gSavedSettings.setU32("RenderMaxTextureResolution", p.max_resolution); - gSavedSettings.setF32("TextureDiscardBiasMax", p.discard_bias_max); - gSavedSettings.setF32("TextureCloseBubbleMeters", p.close_bubble_meters); - gSavedSettings.setF32("TextureCloseBubbleMinMeters", p.close_bubble_min_meters); - gSavedSettings.setF32("TextureCloseBubbleShrinkThreshold", p.close_bubble_shrink_threshold); - gSavedSettings.setF32("TextureScreenSizeOversample", p.screen_size_oversample); - gSavedSettings.setF32("TextureScreenSizeOversampleUnderPressure", p.screen_size_oversample_under_pressure); - gSavedSettings.setF32("TextureBackgroundFactorRatePerSec", p.background_factor_rate_per_sec); - gSavedSettings.setF32("TextureDiscardBackgroundedTime", p.discard_backgrounded_time); - gSavedSettings.setF32("TexturePressureDiscardScale", p.pressure_discard_scale); - gSavedSettings.setF32("TextureMinCapPressureRelaxScale", p.min_cap_relax_scale); - gSavedSettings.setF32("TextureUpdateCountPressureMaxMultiplier", p.update_count_max_mult); - gSavedSettings.setF32("TextureAgentAvatarOversampleMultiplier", p.avatar_oversample_mult); - gSavedSettings.setS32("TextureChannelOffsetNormal", p.channel_off_normal); - gSavedSettings.setS32("TextureChannelOffsetBaseColor", p.channel_off_basecolor); - gSavedSettings.setS32("TextureChannelOffsetSpecular", p.channel_off_specular); - gSavedSettings.setS32("TextureChannelOffsetEmissive", p.channel_off_emissive); + gSavedSettings.setU32("RenderMaxTextureResolution", p.max_resolution); + gSavedSettings.setF32("TexturePixelToTexelRatio", p.pixel_to_texel_ratio); + gSavedSettings.setF32("TexturePressureTightenRate", p.pressure_tighten_rate); + gSavedSettings.setF32("TexturePressureRelaxRate", p.pressure_relax_rate); + gSavedSettings.setF32("TextureChannelRatioNormal", p.channel_ratio_normal); + gSavedSettings.setF32("TextureChannelRatioBaseColor", p.channel_ratio_basecolor); + gSavedSettings.setF32("TextureChannelRatioSpecular", p.channel_ratio_specular); + gSavedSettings.setF32("TextureChannelRatioEmissive", p.channel_ratio_emissive); LL_INFOS("TextureStream") << "Applied texture quality preset: " << p.name << LL_ENDL; return true; diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 545264b980..ef49e0e67f 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -86,33 +86,8 @@ S32 LLViewerTexture::sImageCount = 0; S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; -F32 LLViewerTexture::sDesiredDiscardBias = 0.f; -F32 LLViewerTexture::sBackgroundFactor = 0.f; -F32 LLViewerTexture::sDiscardBias = 0.f; - -//static -F32 LLViewerTexture::getMemoryPressureProgress() -{ - static LLCachedControl bias_max(gSavedSettings, "TextureDiscardBiasMax", 12.f); - F32 cap = llmax((F32)bias_max, 0.0001f); - return llclampf(sDiscardBias / cap); -} - -// Effective oversample factor for the per-texture pixel-area discard cap. -// Trends from TextureScreenSizeOversample toward -// TextureScreenSizeOversampleUnderPressure as the pressure multiplier -// walks its 0..1 range. Progress = 1 (mult at cap, including the high- -// water-mark slam in updateClass) lands on the floor. -static F32 pixelCapOversampleForPressure() -{ - static LLCachedControl over_base(gSavedSettings, "TextureScreenSizeOversample", 1.5f); - static LLCachedControl over_pressure(gSavedSettings, "TextureScreenSizeOversampleUnderPressure", 0.5f); - F32 base = llmax((F32)over_base, 0.1f); - F32 floor = llclamp((F32)over_pressure, 0.1f, base); - F32 progress = LLViewerTexture::getMemoryPressureProgress(); - return base + (floor - base) * progress; -} -U32 LLViewerTexture::sBiasTexturesUpdated = 0; +F32 LLViewerTexture::sPixelToTexelRatio = 1.f; +F32 LLViewerTexture::sBackgroundSeconds = 0.f; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size constexpr S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; @@ -562,96 +537,71 @@ void LLViewerTexture::updateClass() sFreeVRAMMegabytes = vram_target - vram_used; const S32Megabytes free_sys_mem = getFreeSystemMemory(); - F32 over_pct = (vram_used - vram_target) / vram_target; - - // Watermark-driven discard-bias controller. One clean signal: the bias - // climbs while used VRAM is above the high watermark, relaxes below the - // low watermark, and holds steady in the hysteresis band between. The - // band is what prevents sawtooth - no prediction scan or smoothing - // needed (the prediction apparatus existed only to damp the old - // multiplier's oscillation). The dt-based ramp is deliberately slow so - // eviction (scaleDown draining mDownScaleQueue) has time to actually - // free bytes before the next step, matching the id/Granite "back off - // without thrashing" pattern. Subsumes the old last-ditch floor: when - // the bias saturates, the distance-weighted floor in processTextureStats - // forces everything outside the bubble to its deepest mip. + // Single VRAM-pressure knob: the global maximum pixel:texel ratio (texels + // per screen pixel, the "R" in 1:R). It tightens (drops toward 0, no floor) + // while used VRAM is above the high watermark, + // relaxes back toward TexturePixelToTexelRatio below the low watermark, and + // holds steady in the hysteresis band between - the band is what prevents + // sawtooth. The ramp is deliberately slow so eviction (scaleDown draining + // mDownScaleQueue) frees bytes before the next step. Lowering the ratio + // raises every texture's desired discard, which is what actually evicts. { + static LLCachedControl ratio_max(gSavedSettings, "TexturePixelToTexelRatio", 1.0f); static LLCachedControl wm_high(gSavedSettings, "TextureWatermarkHigh", 0.90f); static LLCachedControl wm_low(gSavedSettings, "TextureWatermarkLow", 0.70f); - static LLCachedControl bias_max(gSavedSettings, "TextureDiscardBiasMax", 12.f); - static LLCachedControl bias_ramp(gSavedSettings, "TextureDiscardBiasRampRate", 2.0f); - static LLCachedControl bias_decay(gSavedSettings, "TextureDiscardBiasDecayRate", 1.0f); - - F32 backoff_target = vram_target * llclamp((F32)backoff_start, 0.05f, 1.f); - F32 cap = llmax((F32)max_mult, 1.0001f); - F32 dt = (F32)gFrameIntervalSeconds; - - // Skip the full-list iteration when there is no pressure to react to: - // mult already at baseline, last-ditch at zero, and used well clear of - // the backoff target. Worst case the controller picks up the spike one - // frame later, from `used` alone. - bool need_predict = sMemoryPressureMultiplier > 1.001f - || sLastDitchMinDiscard > 0.f - || vram_used > backoff_target * 0.5f; - - S64 pending_bytes_increase = 0; - S64 pending_bytes_decrease = 0; - if (need_predict) + static LLCachedControl tighten_rate(gSavedSettings, "TexturePressureTightenRate", 0.30f); + static LLCachedControl relax_rate(gSavedSettings, "TexturePressureRelaxRate", 0.10f); + + // Unbounded downward: pressure drives the ratio all the way to 0 if it + // has to. There is no quality floor - at 0 every texture resolves to + // its deepest mip (computeDesiredDiscard treats zero allowed texels as + // dim_max), and desired discard is clamped to dim_max anyway, so it + // saturates on its own. Only the top is bounded, by the configured max. + F32 r_max = llmax((F32)ratio_max, 0.f); + F32 high_frac = llclamp((F32)wm_high, 0.1f, 1.f); + F32 high = vram_budget * high_frac; + F32 low = vram_budget * llclamp((F32)wm_low, 0.05f, high_frac); + F32 dt = (F32)gFrameIntervalSeconds; + + if (vram_used > high) { - sDiscardBias += llmax((F32)bias_ramp, 0.f) * dt; + sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * dt; } - - // 1024 * 512 = 524288: matches the unit reduction at line 513. - constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; - F32 predicted_used = vram_used - + (F32)pending_bytes_increase * BYTES_TO_USED_UNITS - - (F32)pending_bytes_decrease * BYTES_TO_USED_UNITS; - F32 predicted_over = predicted_used / llmax(backoff_target, 1.f); - - // High water mark: when used crosses budget * high_water, skip the - // smoothed convergence and slam the controller into hard-cap state. - // Recovers the historical 90% behavior - immediate aggressive - // response instead of waiting for the lerp to chase the target. - static LLCachedControl high_water(gSavedSettings, "TextureMemoryHighWaterMark", 0.8f); - bool above_high_water = vram_used >= vram_budget * llclamp((F32)high_water, 0.5f, 1.f); - - F32 target_mult = llclamp(powf(llmax(predicted_over, 1.f), llmax((F32)prediction_gain, 0.0001f)), 1.f, cap); - if (above_high_water) + else if (vram_used < low) { - sDiscardBias -= llmax((F32)bias_decay, 0.f) * dt; + sPixelToTexelRatio += llmax((F32)relax_rate, 0.f) * dt; } - // In the hysteresis band [low, high]: hold steady. - sDiscardBias = llclamp(sDiscardBias, 0.f, cap); + // else: hold in the hysteresis band. + sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max); + + // Background cooldown clock: grows while backgrounded/minimized, resets in + // foreground. Feeds the per-texture cooldown in computeDesiredDiscard so + // backgrounding steps every texture up its mip chain over time instead of + // snapping straight to the deepest mip. + bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); + sBackgroundSeconds = in_background ? (sBackgroundSeconds + dt) : 0.f; // 1 Hz pressure log. static LLFrameTimer s_pressure_log_timer; if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) { s_pressure_log_timer.reset(); - F32 over = vram_used / llmax(backoff_target, 1.f); LL_INFOS("TextureStream") << "pressure" - << " mult=" << sMemoryPressureMultiplier - << " target_mult=" << target_mult - << " progress=" << progress + << " ratio=" << sPixelToTexelRatio << " used=" << vram_used - << " predicted=" << predicted_used - << " target=" << vram_target - << " over=" << over - << " pred_over=" << predicted_over - << " in+=" << (S32)(pending_bytes_increase / 1024 / 1024) - << "MB in-=" << (S32)(pending_bytes_decrease / 1024 / 1024) - << "MB bias=" << sDesiredDiscardBias - << " ldmin=" << sLastDitchMinDiscard + << " budget=" << vram_budget + << " high=" << high + << " low=" << low << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() << LL_ENDL; } } - bool is_sys_low = isSystemMemoryLow(); + // System-memory -> draw-distance factor. Separate from the VRAM ratio above: + // this is a last-resort response to running low on *system* RAM and only + // affects draw distance (via getSystemMemoryBudgetFactor, consumed by + // llviewerdisplay). Textures were mostly moved to VRAM, so this rarely fires. bool is_sys_critically_low = isSystemMemoryCritical(); - bool is_low = is_sys_low || over_pct > 0.f; - - static bool was_low = false; static bool sys_was_low = false; // System memory factor @@ -706,169 +656,6 @@ void LLViewerTexture::updateClass() } } sys_was_low = is_sys_critically_low; - - // VRAM memory bias - if (is_low && !was_low) - { - if (is_sys_low) - { - // Not having system memory is more serious, so discard harder - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * getSystemMemoryBudgetFactor()); - } - else - { - // Slam to 1.5 bias the moment we hit low memory (discards off screen textures immediately) - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f); - } - - if (is_sys_low || over_pct > 2.f) - { // if we're low on system memory, emergency purge off screen textures to avoid a death spiral - LL_WARNS() << "Low system memory detected, emergency downrezzing off screen textures" << LL_ENDL; - for (auto& image : gTextureList) - { - gTextureList.updateImageDecodePriority(image, false /*will modify gTextureList otherwise!*/); - } - } - } - - was_low = is_low; - - if (is_low) - { - // ramp up discard bias over time to free memory - if (sEvaluationTimer.getElapsedTimeF32() > MEMORY_CHECK_WAIT_TIME) - { - static LLCachedControl low_mem_min_discard_increment(gSavedSettings, "RenderLowMemMinDiscardIncrement", .1f); - - F32 increment = low_mem_min_discard_increment + llmax(over_pct, 0.f); - sDesiredDiscardBias += increment * gFrameIntervalSeconds; - } - } - else - { - // don't execute above until the slam to 1.5 has a chance to take effect - sEvaluationTimer.reset(); - - // Don't decay bias while downscale is still draining - those bytes - // are about to free and the loop would oscillate. - bool eviction_in_flight = !gTextureList.mDownScaleQueue.empty(); - - // lower discard bias over time when at least 10% of budget is free - constexpr F32 FREE_PERCENTAGE_TRESHOLD = -0.1f; - constexpr U32 FREE_SYS_MEM_THRESHOLD = 100; // 100MB more than isSystemMemoryLow to avoid fluctuations. - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() + S32Megabytes(FREE_SYS_MEM_THRESHOLD)); - if (sDesiredDiscardBias > 1.f - && over_pct < FREE_PERCENTAGE_TRESHOLD - && free_sys_mem > MIN_FREE_MAIN_MEMORY - && !eviction_in_flight) - { - static LLCachedControl high_mem_discard_decrement(gSavedSettings, "RenderHighMemMinDiscardDecrement", .1f); - - F32 decrement = high_mem_discard_decrement - llmin(over_pct - FREE_PERCENTAGE_TRESHOLD, 0.f); - sDesiredDiscardBias -= decrement * gFrameIntervalSeconds; - } - } - - // set to max discard bias if the window has been backgrounded for a while - static F32 last_desired_discard_bias = 1.f; - static F32 last_texture_update_count_bias = 1.f; - static bool was_backgrounded = false; - static LLFrameTimer backgrounded_timer; - static LLCachedControl minimized_discard_time(gSavedSettings, "TextureDiscardMinimizedTime", 1.f); - static LLCachedControl backgrounded_discard_time(gSavedSettings, "TextureDiscardBackgroundedTime", 60.f); - - bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); - bool is_minimized = gViewerWindow && gViewerWindow->getWindow()->getMinimized() && in_background; - if (in_background) - { - F32 discard_time = is_minimized ? minimized_discard_time : backgrounded_discard_time; - if (discard_time > 0.f && backgrounded_timer.getElapsedTimeF32() > discard_time) - { - if (!was_backgrounded) - { - LL_INFOS() << "Viewer was " << (is_minimized ? "minimized" : "backgrounded") << " for " << discard_time - << "s, freeing up video memory." << LL_ENDL; - - last_desired_discard_bias = sDesiredDiscardBias; - was_backgrounded = true; - } - sDesiredDiscardBias = 5.f; - } - } - else - { - backgrounded_timer.reset(); - if (was_backgrounded) - { // if the viewer was backgrounded - LL_INFOS() << "Viewer is no longer backgrounded or minimized, resuming normal texture usage." << LL_ENDL; - was_backgrounded = false; - sDesiredDiscardBias = last_desired_discard_bias; - } - } - - // Background-window ramp: 0 -> 1 at rate per second while backgrounded, - // snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. - { - static LLCachedControl bg_factor_rate(gSavedSettings, "TextureBackgroundFactorRatePerSec", 0.011f); - if (in_background) - { - sBackgroundFactor += (F32)bg_factor_rate * gFrameIntervalSeconds; - sBackgroundFactor = llclampf(sBackgroundFactor); - } - else - { - sBackgroundFactor = 0.f; - } - } - - // Fetch-queue depth as a smoothed bias floor. First-order low-pass so the - // contribution decays naturally as the queue drains - the old one-way max - // floor would latch bias high in busy regions even after VRAM came back - // (the unrelated bias-decay path required VRAM-comfortable AND no eviction - // in flight, which often never coincided during sustained fetch floods). - static F32 s_fetch_bias = 0.f; - if (LLTextureFetch* fetcher = LLAppViewer::getTextureFetch()) - { - static LLCachedControl fetch_pressure_scale(gSavedSettings, "TextureFetchPressureScale", 1000.f); - static LLCachedControl fetch_bias_track_rate(gSavedSettings, "TextureFetchBiasTrackRate", 1.0f); - F32 scale = llmax((F32)fetch_pressure_scale, 1.f); - F32 target = llclamp((F32)fetcher->getNumRequests() / scale, 0.f, 3.f); - F32 dt = (F32)gFrameIntervalSeconds; - F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)fetch_bias_track_rate, 0.f)); - s_fetch_bias += (target - s_fetch_bias) * alpha; - } - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + s_fetch_bias); - - sDesiredDiscardBias = llclamp(sDesiredDiscardBias, 1.f, 4.f); - if (last_texture_update_count_bias < sDesiredDiscardBias) - { - // bias increased, reset texture update counter to - // let updates happen at an increased rate. - last_texture_update_count_bias = sDesiredDiscardBias; - sBiasTexturesUpdated = 0; - } - else if (last_texture_update_count_bias > sDesiredDiscardBias + 0.1f) - { - // bias decreased, 0.1f is there to filter out small fluctuations - // and not reset sBiasTexturesUpdated too often. - // Bias jumps to 1.5 at low memory, so getting stuck at 1.1 is not - // a problem. - last_texture_update_count_bias = sDesiredDiscardBias; - } - - // Quartile-pressure crossing resets the update counter so eviction - // candidates get re-evaluated when pressure escalates - parallel to - // the bias-rise reset above. The legacy bias term doesn't see fast - // VRAM-pressure changes (sDiscardBias ramps independently of it), so - // without this, a sudden VRAM pressure spike would wait for the - // round-trip through the whole mUUIDMap before re-evaluating. - static S32 last_pressure_quartile = 0; - S32 pressure_quartile = (S32)floorf(getMemoryPressureProgress() * 4.f); - if (pressure_quartile > last_pressure_quartile) - { - sBiasTexturesUpdated = 0; - } - last_pressure_quartile = pressure_quartile; } //static @@ -3248,155 +3035,128 @@ S8 LLViewerLODTexture::getType() const return LLViewerTexture::LOD_TEXTURE; } -// ---- processTextureStats discard pipeline helpers ---------------------- -// Each takes the working discard level and returns the updated value, -// reading per-texture member state directly. Pure functions of state (they -// never write members); the orchestrator in processTextureStats owns -// mDesiredDiscardLevel. See the header for the execution-order overview. - -// Canonical "1 texel per screen pixel" base discard. For UI-pinned textures -// (mKnownDrawWidth/Height) it uses the known render size; otherwise the -// on-screen pixel coverage with an oversample factor (sharper when we have -// headroom, plus the own-avatar boost) and the close-camera bubble clamp. -S32 LLViewerLODTexture::computeBaseDiscard(S32 dim_max_i) const +// Desired discard from the pixel:texel ratio - this is the entire streaming +// policy. For each channel bucket the texture is used in, the most-demanding +// (largest screen coverage) face sets that bucket's requirement at +// floor(log4(texels / (R_global * channelRatio[b] * coverage))); the sharpest +// requirement across buckets wins, since one GL image serves every channel it +// is used in. floor (not ceil) keeps content native up close - "1:1" is a +// target, not a hard cap that downrezzes anything slightly oversampled. A +// per-mip hysteresis dead-band against the current discard level prevents +// fetch/scaleDown thrash as an object slowly crosses a mip boundary. +S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) const { static const F64 log_4 = log(4.0); + // UI-pinned (icons / thumbnails): size against the known draw size, with no + // ratio or pressure - these want exact native-for-their-slot resolution. if (mKnownDrawWidth && mKnownDrawHeight) { - // UI-pinned target dimensions - pixel-area against the known render - // size, not the on-screen coverage. - S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; - draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); - return (S32)floor(log((F64)mTexelsPerImage / (F64)draw_texels) / log_4); + S32 draw_texels = llclamp(mKnownDrawWidth * mKnownDrawHeight, MIN_IMAGE_AREA, MAX_IMAGE_AREA); + S32 d = (draw_texels >= (S32)mTexelsPerImage) + ? 0 + : (S32)floor(log((F64)mTexelsPerImage / (F64)draw_texels) / log_4); + return llclamp(d, 0, dim_max_i); } - // Oversample: >1 = sharper than 1:1; <1 = allow under-sampling. Pressure - // shrinks it via pixelCapOversampleForPressure. Own-avatar boost doubles - // it (4x texels = exactly -1 discard) - a preference, not an exemption. - F32 oversample = pixelCapOversampleForPressure(); - if (mOnAgentAvatar) + // Avatar bakes ignore the global pressure ramp (a blurred bake reads as a + // cloud avatar); they always size against the configured max ratio. + static LLCachedControl ratio_max(gSavedSettings, "TexturePixelToTexelRatio", 1.0f); + const F32 r_global = avatar_bake ? llmax((F32)ratio_max, 0.01f) : sPixelToTexelRatio; + + static LLCachedControl ch_normal (gSavedSettings, "TextureChannelRatioNormal", 1.0f); + static LLCachedControl ch_basecolor(gSavedSettings, "TextureChannelRatioBaseColor", 1.0f); + static LLCachedControl ch_specular (gSavedSettings, "TextureChannelRatioSpecular", 0.5f); + static LLCachedControl ch_emissive (gSavedSettings, "TextureChannelRatioEmissive", 0.5f); + const F32 channel_ratio[4] = { (F32)ch_normal, (F32)ch_basecolor, (F32)ch_specular, (F32)ch_emissive }; + + // Continuous ideal discard = sharpest (smallest) requirement across the + // channels this texture is actually used in. + F32 ideal = (F32)dim_max_i; // default: coarsest, until a measurement arrives + bool measured = false; + for (S32 b = 0; b < 4; ++b) { - static LLCachedControl avatar_over_mult(gSavedSettings, "TextureAgentAvatarOversampleMultiplier", 2.0f); - oversample *= llmax((F32)avatar_over_mult, 1.f); + F32 coverage = mChannelCoverage[b]; + if (coverage <= 0.f) + { + continue; + } + measured = true; + F32 allowed_texels = coverage * r_global * llmax(channel_ratio[b], 0.01f); + F32 d; + if (allowed_texels <= 0.f) // ratio driven to 0 -> deepest mip + d = (F32)dim_max_i; + else if (allowed_texels >= (F32)mTexelsPerImage) + d = 0.f; + else + d = (F32)(log((F64)mTexelsPerImage / (F64)allowed_texels) / log_4); + ideal = llmin(ideal, d); } - - // Bubble clamp: any face inside the close-camera bubble - // (mMinDistanceFactor == 0) is treated as filling the screen, so the - // base saturates at 0 (full res) for bubble-resident content. - F32 effective_screen = mMaxOnScreenSize; - if (mMinDistanceFactor <= 0.f) + if (!measured) { - effective_screen = llmax(effective_screen, sWindowPixelArea); + return dim_max_i; // off-screen / never measured -> coarsest mip } - F32 visible_texels = effective_screen * oversample * oversample; - visible_texels = llclamp(visible_texels, (F32)MIN_IMAGE_AREA, (F32)mTexelsPerImage); + // Round toward sharper (floor): a texture stays at a mip level until its + // ideal is a full level past the boundary. This is what makes "1:1" mean + // "native up close" - ceil would downrez anything even slightly oversampled + // (a 2048 map can't hit its own resolution on a 1080p screen), which reads + // as everything being blurry. Pressure still evicts by lowering R_global. + ideal = llmax(ideal, 0.f); + const S32 target = (S32)floor(ideal); - S32 base_discard; - if ((F32)mTexelsPerImage <= visible_texels || mMaxOnScreenSize <= 0.f) + // Hysteresis: a texture at discard C is "happy" while floor(ideal) == C, + // i.e. ideal in [C, C+1). Only leave that band once ideal is past it by the + // margin, so coverage jitter at a boundary doesn't ping-pong fetch<->scaleDown. + static LLCachedControl uprez_margin(gSavedSettings, "TextureUpRezMargin", 0.2f); + const F32 margin = llclamp((F32)uprez_margin, 0.f, 0.9f); + const S32 current = getDiscardLevel(); + S32 desired; + if (current < 0) { - // Already at-or-below 1:1 with on-screen pixels; no discard needed. - // Also the fallback for never-measured textures (mMaxOnScreenSize==0) - // - keep full res until a measurement arrives. - base_discard = 0; + desired = target; // nothing loaded yet } - else + else if (ideal >= (F32)current + 1.f + margin) { - base_discard = (S32)floor(log((F64)mTexelsPerImage / (F64)visible_texels) / log_4); + desired = target; // clearly coarser -> evict } - return llclamp(base_discard, 0, dim_max_i); -} - -// Per-channel additive offset. Channel-priority order: -// 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. -// Defaults push specular/emissive +1 (one mip coarser) - users frequently -// put 2048 spec/emissive maps on tiny prims where the detail is invisible. -// Tier presets crank these further (e.g. Low pushes normals hard). -S32 LLViewerLODTexture::applyChannelOffset(S32 discard) const -{ - if (mPriorityChannel >= 0 && mPriorityChannel < 4) + else if (ideal <= (F32)current - margin) { - static LLCachedControl off_normal (gSavedSettings, "TextureChannelOffsetNormal", 0); - static LLCachedControl off_basecolor(gSavedSettings, "TextureChannelOffsetBaseColor", 0); - static LLCachedControl off_specular (gSavedSettings, "TextureChannelOffsetSpecular", 1); - static LLCachedControl off_emissive (gSavedSettings, "TextureChannelOffsetEmissive", 1); - const S32 offsets[4] = { - (S32)off_normal, - (S32)off_basecolor, - (S32)off_specular, - (S32)off_emissive, - }; - discard += offsets[mPriorityChannel]; + desired = target; // clearly finer -> uprez } - return discard; -} - -// VRAM pressure distance floor. Forces a minimum discard that ramps with -// distance; the ramp steepens as the watermark-driven sDiscardBias climbs, -// so the "force max discard" distance moves inward from draw distance toward -// the bubble. compression = 1 + bias*scale; the floor reaches dim_max at -// ~1/compression of draw distance. Bubble residents (mMinDistanceFactor==0) -// stay at floor 0 - protected. Subsumes the old last-ditch mechanism: at -// saturated bias every non-bubble texture is forced to its deepest mip. -// Avatar bakes exempt. -S32 LLViewerLODTexture::applyPressureFloor(S32 discard, F32 dim_max, bool avatar_bake) const -{ - if (!avatar_bake && sDiscardBias > 0.f) + else { - static LLCachedControl press_scale(gSavedSettings, "TexturePressureDiscardScale", 1.0f); - F32 ramp_compression = 1.f + sDiscardBias * llmax((F32)press_scale, 0.f); - F32 effective_dist = llmin(mMinDistanceFactor * ramp_compression, 1.f); - S32 pressure_floor = (S32)floorf(effective_dist * dim_max); - discard = llmax(discard, pressure_floor); + desired = current; // inside the dead-band -> hold } - return discard; -} - -// Staleness + background max-floors. Both are authored 0..1 elsewhere -// (updateImageDecodePriority / updateClass) and translated to discard space -// here. They only raise discard (worse quality), never reduce it. Background -// floor is capped at (dim_max - offset) so we keep some baseline quality -// while backgrounded; avatar bakes are exempt from the background floor. -S32 LLViewerLODTexture::applyStalenessBackgroundFloors(S32 discard, F32 dim_max, bool avatar_bake) const -{ - const S32 stale_floor = (S32)floorf(mStalenessFactor * dim_max); - discard = llmax(discard, stale_floor); + // Cooldown: don't snap an unseen texture straight to its deepest mip - step + // it up one level per TextureCooldownStepSeconds so briefly-occluded or + // backgrounded content isn't thrown away (and doesn't thrash the cache) when + // we look at it again. Driven by whichever is longer: time since last bind, + // or time backgrounded. It only raises discard and resets the moment the + // texture is bound again. One frame interval is subtracted so a + // continuously-visible texture (whose last bind is a frame old here, since + // this pass runs a frame ahead of its own render) reads as zero. Avatar + // bakes exempt. if (!avatar_bake) { - static LLCachedControl bg_offset(gSavedSettings, "TextureBackgroundDiscardOffset", 2); - F32 bg_norm = sBackgroundFactor; - if ((S32)bg_offset > 0 && dim_max > 0.f) + static LLCachedControl cooldown_step(gSavedSettings, "TextureCooldownStepSeconds", 1.f); + const F32 step = llmax((F32)cooldown_step, 0.01f); + F32 unbound = 0.f; + if (LLImageGL* gli = getGLTexture()) { - F32 cap = llmax(dim_max - (F32)(S32)bg_offset, 0.f) / dim_max; - bg_norm = llmin(bg_norm, cap); + const F32 ref = llmax(gli->mLastBindTime, gli->mGLCreateTime); + if (ref > 0.f) + { + unbound = llmax(0.f, LLImageGL::sLastFrameTime - ref - (F32)gFrameIntervalSeconds); + } } - const S32 bg_floor = (S32)floorf(bg_norm * dim_max); - discard = llmax(discard, bg_floor); + const F32 cooldown_seconds = llmax(unbound, sBackgroundSeconds); + const S32 cooldown_floor = llclamp((S32)floor(cooldown_seconds / step), 0, dim_max_i); + desired = llmax(desired, cooldown_floor); } - return discard; -} -// Caller-set min-discard cap (setMinDiscardLevel: terrain / avatar-self / -// thumbnails), relaxed under pressure. add = floor(progress * room * scale), -// where room is the gap to dim_max and progress is sDiscardBias normalized - -// mirrors the original cap_relax = (1 - 1/mult)*room shape. Caps of 0 -// (thumbnails) and avatar bakes are preserved. Returns the capped discard. -S32 LLViewerLODTexture::applyMinDesiredCap(S32 discard, S32 dim_max_i, bool avatar_bake) const -{ - S32 effective_min_cap = mMinDesiredDiscardLevel; - if (sDiscardBias > 0.f && - mMinDesiredDiscardLevel > 0 && mMinDesiredDiscardLevel < S8_MAX && - !avatar_bake) - { - static LLCachedControl cap_relax_scale(gSavedSettings, "TextureMinCapPressureRelaxScale", 1.0f); - F32 progress = getMemoryPressureProgress(); - F32 room = (F32)dim_max_i - (F32)mMinDesiredDiscardLevel; - S32 add = (S32)floorf(progress * room * llmax((F32)cap_relax_scale, 0.f)); - effective_min_cap = llmin(effective_min_cap + add, dim_max_i); - } - return llmin(effective_min_cap, discard); + return llclamp(desired, 0, dim_max_i); } // This is gauranteed to get called periodically for every texture @@ -3431,7 +3191,7 @@ void LLViewerLODTexture::processTextureStats() mDesiredDiscardLevel = 0; } // HUD/UI/preview and mDontDiscard textures bypass streaming - no - // face_distance signal applies, they need native resolution. + // coverage signal applies, they need native resolution. else if (mBoostLevel >= LLGLTexture::BOOST_HIGH || mDontDiscard || !mUseMipMaps) @@ -3447,34 +3207,16 @@ void LLViewerLODTexture::processTextureStats() } else { - // Pixel-area-primary discard pipeline. The canonical "1 texel per - // screen pixel" base means distance falls out for free (pixel - // coverage ~ 1/D^2 => +1 discard per doubling of distance). The - // per-stage math lives in the helpers (computeBaseDiscard etc.); - // this just orchestrates them. Every modifier after the base is - // monotone non-decreasing in discard. - // Per-texture max discard (smallest meaningful mip): floor(log2(max(w,h))). S32 dim_max_for_image_i = (mFullWidth > 0 && mFullHeight > 0) ? LLImageGL::dimDerivedMaxDiscard(mFullWidth, mFullHeight) : (S32)mCodecMaxDiscardLevel; - F32 dim_max_for_image = (F32)dim_max_for_image_i; - - S32 discard = computeBaseDiscard(dim_max_for_image_i); - // Channel / pressure / staleness+background floors apply only to the - // coverage-driven path; UI-pinned (mKnownDrawWidth/Height) textures - // take the base verbatim. - if (!(mKnownDrawWidth && mKnownDrawHeight)) - { - discard = applyChannelOffset(discard); - discard = applyPressureFloor(discard, dim_max_for_image, avatar_bake); - discard = applyStalenessBackgroundFloors(discard, dim_max_for_image, avatar_bake); - } + // The whole policy: pixel:texel ratio at the most-demanding face. + S32 discard = computeDesiredDiscard(dim_max_for_image_i, avatar_bake); - // Per-texture caps: min_discard forces 1 for sources over the - // resolution cap; effective_cap is the per-texture max (debug - // override or dim-derived). + // Per-texture caps: force >=1 for sources over the resolution cap; + // bound by the debug override or the dim-derived max. S32 min_discard = 0; if (mFullWidth > max_tex_res || mFullHeight > max_tex_res) min_discard = 1; @@ -3482,22 +3224,18 @@ void LLViewerLODTexture::processTextureStats() static LLCachedControl max_discard_override(gSavedSettings, "TextureMaxDiscardOverride", 0); const S32 effective_cap = (max_discard_override > 0) ? (S32)max_discard_override : dim_max_for_image_i; discard = llclamp(discard, min_discard, effective_cap); - mDesiredDiscardLevel = (S8)discard; - // Caller-set min-discard cap (terrain / avatar-self / thumbnails), - // relaxed under pressure. - mDesiredDiscardLevel = (S8)applyMinDesiredCap(mDesiredDiscardLevel, dim_max_for_image_i, avatar_bake); + // Caller-set min-discard ceiling (terrain / avatar-self / thumbnails): + // never coarser than the caller explicitly asked for. + discard = llmin(discard, (S32)mMinDesiredDiscardLevel); - // (There is no separate last-ditch floor - applyPressureFloor - // subsumes it. At saturated sDiscardBias the distance floor forces - // every non-bubble texture to its deepest mip, and in-bubble content - // stays protected, which is exactly the intended behavior.) + mDesiredDiscardLevel = (S8)discard; - // If the GPU already holds finer data than we now want, schedule a - // downscale. Avatar bakes exempt: shrinking mid-bake can leave the - // avatar stuck as a cloud until the next bake completes. + // If the GPU already holds finer data than we now want, evict it. + // Avatar bakes exempt: shrinking mid-bake can leave the avatar stuck + // as a cloud until the next bake completes. S32 current_discard = getDiscardLevel(); - if (!avatar_bake && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) + if (!avatar_bake && current_discard >= 0 && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { scaleDown(); } diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 51ef9f8206..6fc6ad72f9 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -205,24 +205,13 @@ protected: mutable S32 mMaxVirtualSizeResetInterval; LLFrameTimer mLastReferencedTimer; - // 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. -1 -> base color. - S8 mPriorityChannel = -1; - - // Bind-staleness floor, 0..1. Per-interval increment is 1/max_discard - // so any texture saturates after interval x max_discard seconds idle. - F32 mStalenessFactor = 0.f; - - // Closest face's face_distance / draw_distance, clamped 0..1. - // Defaults to 1 so never-measured textures resolve to deepest discard. - F32 mMinDistanceFactor = 1.f; - - // Largest per-face screen-space coverage in pixels. Raw - no bias or - // channel-priority contamination. - F32 mMaxOnScreenSize = 0.f; - - // Any face on the agent's avatar (rigged / animated). Drives the - // own-avatar quality boost in processTextureStats. - bool mOnAgentAvatar = false; + // Largest screen-space pixel coverage among the texture's faces, per + // priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive). 0 = the + // texture is not used in that channel (or hasn't been measured yet). + // Populated by LLViewerTextureList::updateImageDecodePriority; consumed by + // LLViewerLODTexture::computeDesiredDiscard. This is the only view-dependent + // streaming signal - distance, size, and channel role all collapse into it. + F32 mChannelCoverage[4] = { 0.f, 0.f, 0.f, 0.f }; ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; @@ -244,28 +233,21 @@ public: static S32 sRawCount; static S32 sAuxCount; static LLFrameTimer sEvaluationTimer; - static F32 sDesiredDiscardBias; - // Backgrounded-window discard floor, 0..1. Ramps while backgrounded, - // snaps to 0 in foreground. Avatar bakes exempt. - static F32 sBackgroundFactor; - - // Watermark-driven global discard bias, [0, TextureDiscardBiasMax]. - // The single VRAM-pressure controller: climbs while used VRAM is above - // the high watermark, relaxes below the low watermark, holds in the - // hysteresis band between. Replaces the old sMemoryPressureMultiplier + - // sLastDitchMinDiscard pair (and the predict-scan apparatus). Feeds the - // distance-weighted pressure floor in processTextureStats: close content - // is protected, distant content is evicted first, and as the bias climbs - // the "force max discard" distance moves inward from draw distance toward - // the bubble. Subsumes last-ditch - at max bias the distance floor forces - // everything outside the bubble to its deepest mip. - static F32 sDiscardBias; - - // 0..1 progress of sDiscardBias from baseline (0) to its configured cap - // (TextureDiscardBiasMax). Gates bubble shrink, pixel-area oversample - // collapse, the per-frame update count, and the min-cap relax. - static F32 getMemoryPressureProgress(); - static U32 sBiasTexturesUpdated; + + // The single VRAM-pressure knob: the global maximum pixel:texel ratio, + // expressed as texels per screen pixel (the "R" in 1:R). Starts at + // TexturePixelToTexelRatio (1.0 = one texel per pixel) and the watermark + // controller in updateClass() walks it down toward 0 (no floor) + // while used VRAM is above the high watermark, back up below the low + // watermark, holding in the band between. Lowering it raises every + // texture's desired discard, which drives scaleDown eviction. Consumed by + // LLViewerLODTexture::computeDesiredDiscard. + static F32 sPixelToTexelRatio; + + // Seconds the app has been backgrounded/minimized (0 in foreground). Drives + // the background half of the per-texture cooldown in computeDesiredDiscard. + static F32 sBackgroundSeconds; + static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; static U32 sMaxSmallImageSize ; @@ -334,27 +316,6 @@ public: || boost_level == BOOST_AVATAR_BAKED_SELF; } -public: - - struct Compare - { - // lhs < rhs - bool operator()(const LLPointer &lhs, const LLPointer &rhs) const - { - const LLViewerFetchedTexture* lhsp = (const LLViewerFetchedTexture*)lhs; - const LLViewerFetchedTexture* rhsp = (const LLViewerFetchedTexture*)rhs; - - // greater priority is "less" - const F32 lpriority = lhsp->mMaxVirtualSize; - const F32 rpriority = rhsp->mMaxVirtualSize; - if (lpriority > rpriority) // higher priority - return true; - if (lpriority < rpriority) - return false; - return lhsp < rhsp; - } - }; - public: /*virtual*/ S8 getType() const override; FTType getFTType() const; @@ -607,22 +568,15 @@ public: private: void init(bool firstinit) ; - // Streaming discard pipeline, factored out of processTextureStats so each - // stage is individually readable and testable. Execution order: - // base = computeBaseDiscard() // canonical texels/pixel (or UI-pinned) - // if not UI-pinned: - // base = applyChannelOffset(base) // per-channel additive bias - // base = applyPressureFloor(base) // distance-weighted VRAM floor - // base = applyStalenessBackgroundFloors(base) - // ... per-texture caps ... - // final = applyMinDesiredCap(final) // caller-set min, relaxed under pressure - // Each reads per-texture member state directly; avatar_bake and the - // dim-max values are computed once by the caller and threaded through. - S32 computeBaseDiscard(S32 dim_max_i) const; - S32 applyChannelOffset(S32 discard) const; - S32 applyPressureFloor(S32 discard, F32 dim_max, bool avatar_bake) const; - S32 applyStalenessBackgroundFloors(S32 discard, F32 dim_max, bool avatar_bake) const; - S32 applyMinDesiredCap(S32 discard, S32 dim_max_i, bool avatar_bake) const; + // The whole streaming pipeline: desired discard from the pixel:texel ratio. + // For each channel bucket the texture is used in, the most-demanding + // (largest coverage) face sets that bucket's requirement at + // floor(log4(texels / (R_global * channelRatio[b] * coverage))); the sharpest + // requirement across buckets wins (one GL image serves all its channels). + // A per-mip hysteresis dead-band against the current discard level prevents + // fetch/scaleDown thrash. avatar_bake textures use the configured max ratio + // instead of the pressure-driven sPixelToTexelRatio (anti cloud-bug). + S32 computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) const; }; // diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 780dda6ef7..16a3418bc7 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -71,7 +71,6 @@ void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; S32 LLViewerTextureList::sNumImages = 0; -F32 LLViewerTextureList::sCurrentBubbleMeters = 0.f; LLViewerTextureList gTextureList; @@ -917,100 +916,62 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag { llassert(!gCubeSnapshot); - constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; // perf gate for face-loop early exit - if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { static LLCachedControl texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.0095f); static LLCachedControl texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); - F32 max_vsize = 0.f; - bool on_screen = false; - - // Accumulators for the per-texture signals published below. - // Defaults map to "deepest discard wanted" until evidence updates them. - F32 min_distance_factor = 1.f; - F32 max_on_screen_size = 0.f; - bool on_agent_avatar = false; - F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 0.001f); - - // Close-camera bubble: faces inside `bubble` meters resolve to - // dist_factor = 0, so the distance ramp spans (bubble, draw_distance]. - static LLCachedControl close_bubble(gSavedSettings, "TextureCloseBubbleMeters", 5.f); - static LLCachedControl close_bubble_min(gSavedSettings, "TextureCloseBubbleMinMeters", 0.1f); - static LLCachedControl bubble_shrink_threshold(gSavedSettings, "TextureCloseBubbleShrinkThreshold", 0.8f); - static LLCachedControl bubble_track_rate(gSavedSettings, "TextureCloseBubbleTrackRate", 0.5f); - F32 bubble_full = llmax((F32)close_bubble, 0.f); - F32 bubble_min = llclamp((F32)close_bubble_min, 0.f, bubble_full); - // Advance the slow-track once per frame, not per texture: this - // function runs once per texture so a naive per-call lerp converges - // in a single frame. - static F32 s_tracked_bubble = -1.f; - static U32 s_tracked_bubble_frame = 0; - if (s_tracked_bubble < 0.f) s_tracked_bubble = bubble_full; - if (s_tracked_bubble_frame != LLFrameTimer::getFrameCount()) - { - s_tracked_bubble_frame = LLFrameTimer::getFrameCount(); - F32 progress = LLViewerTexture::getMemoryPressureProgress(); - F32 shrink_thresh = llclampf((F32)bubble_shrink_threshold); - F32 shrink_frac = (progress > shrink_thresh) - ? (progress - shrink_thresh) / llmax(1.f - shrink_thresh, 0.0001f) - : 0.f; - F32 target_bubble = bubble_full - (bubble_full - bubble_min) * shrink_frac; - F32 dt = (F32)gFrameIntervalSeconds; - F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)bubble_track_rate, 0.f)); - s_tracked_bubble += (target_bubble - s_tracked_bubble) * alpha; - s_tracked_bubble = llclamp(s_tracked_bubble, bubble_min, bubble_full); - sCurrentBubbleMeters = s_tracked_bubble; - } - F32 bubble = llclamp(s_tracked_bubble, 0.f, draw_distance - 0.001f); - F32 ramp_range = llmax(draw_distance - bubble, 0.001f); + // Largest screen-space pixel coverage per priority bucket + // (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive), plus the overall max. + // The per-bucket maxima drive desired discard with per-channel ratios; + // the overall max is the fetch priority (raw - no bias). A bucket with + // faces but zero coverage (all off-screen) publishes 0, which + // computeDesiredDiscard reads as "coarsest mip". + F32 channel_coverage[4] = { 0.f, 0.f, 0.f, 0.f }; + bool bucket_used[4] = { false, false, false, false }; + F32 max_coverage = 0.f; U32 face_count = 0; - U32 max_faces_to_check = 1024; + const U32 max_faces_to_check = 1024; - // Pick the least-aggressive channel across all uses, so a texture - // used as both diffuse and normal isn't penalized by its harshest - // role. -1 sentinel keeps emissive-only textures (W=3) from being - // clobbered by a smaller init value. - S32 priority_channel = -1; + // Cheap first pass: which buckets is this texture used in, and how many + // faces total. No per-face geometry work. for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { - if (imagep->getNumFaces(i) > 0) + U32 n = imagep->getNumFaces(i); + face_count += n; + if (n > 0) { - S32 mapped = sChannelToPriority[i]; - priority_channel = (priority_channel < 0) ? mapped : llmin(priority_channel, mapped); + S32 b = sChannelToPriority[i]; + if (b >= 0 && b < 4) bucket_used[b] = true; } } - if (priority_channel < 0) + + if (face_count > max_faces_to_check) { - priority_channel = 1; // no faces - default to diffuse + // Used in so many places that scanning the face list isn't worth it + // (and isn't time-sliced) - treat as full-screen so it loads sharp. + for (S32 b = 0; b < 4; ++b) + if (bucket_used[b]) channel_coverage[b] = (F32)MAX_IMAGE_AREA; + max_coverage = (F32)MAX_IMAGE_AREA; } - imagep->mPriorityChannel = (S8)priority_channel; - - // get adjusted bias based on image resolution - LLImageGL* img = imagep->getGLTexture(); - F32 max_discard = F32(img ? img->getMaxDiscardLevel() : MAX_DISCARD_LEVEL); - F32 bias = llclamp(max_discard - 2.f, 1.f, LLViewerTexture::sDesiredDiscardBias); - - // convert bias into a vsize scaler - bias = (F32) llroundf(powf(4, bias - 1.f)); - - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + else { - face_count += imagep->getNumFaces(i); - S32 faces_to_check = (face_count > max_faces_to_check) ? 0 : imagep->getNumFaces(i); - - for (S32 fi = 0; fi < faces_to_check; ++fi) + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { - LLFace* face = (*(imagep->getFaceList(i)))[fi]; - - if (face && face->getViewerObject()) + const S32 bucket = sChannelToPriority[i]; + const U32 num_faces = imagep->getNumFaces(i); + for (U32 fi = 0; fi < num_faces; ++fi) { + LLFace* face = (*(imagep->getFaceList(i)))[fi]; + if (!face || !face->getViewerObject()) + { + continue; + } + F32 radius; F32 cos_angle_to_view_dir; - if ((gFrameCount - face->mLastTextureUpdate) > 10) { // only call calcPixelArea at most once every 10 frames for a given face // this helps eliminate redundant calls to calcPixelArea for faces that have multiple textures @@ -1021,159 +982,48 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 vsize = face->getPixelArea(); - on_screen |= face->mInFrustum; - - F32 dist_above_bubble = llmax(face->mDistanceToCamera - bubble, 0.f); - F32 dist_factor = llclampf(dist_above_bubble / ramp_range); - min_distance_factor = llmin(min_distance_factor, dist_factor); - - if (face->mAvatar && face->mAvatar == gAgentAvatarp) - { - on_agent_avatar = true; - } - - // Scale desired texture resolution higher or lower depending on texture scale - // - // Minimum usage examples: a 1024x1024 texture with aplhabet (texture atlas), - // runing string shows one letter at a time. If texture has ten 100px symbols - // per side, minimal scale is (100/1024)^2 = 0.0095 - // - // Maximum usage examples: huge chunk of terrain repeats texture - // TODO: make this work with the GLTF texture transforms + // Scale coverage by texture repeat: a texture shown at a + // fraction of a face (atlas) needs only that fraction of + // texels; a tiled texture needs more. getMinScaleSq() is the + // cached min(|scaleS|,|scaleT|)^2, invalidated by setScale*. S32 te_offset = face->getTEOffset(); // offset is -1 if not inited LLViewerObject* objp = face->getViewerObject(); const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); - // getMinScaleSq() returns cached min(|scaleS|,|scaleT|)^2; - // invalidated by setScale*. Saves the abs/min/multiply per - // face per frame. Clamp against the user-tunable LLCachedControl - // values still happens here. F32 min_scale = te ? llclamp(te->getMinScaleSq(), texture_scale_min(), texture_scale_max()) : 1.f; vsize /= min_scale; - // Raw screen-space coverage - taken before the bias / - // camera-boost mutations below so the size signal is clean. - max_on_screen_size = llmax(max_on_screen_size, vsize); - - // apply bias to offscreen faces all the time, but only to onscreen faces when bias is large - // use mImportanceToCamera to make bias switch a bit more gradual - if (!face->mInFrustum || LLViewerTexture::sDesiredDiscardBias > 1.9f + face->mImportanceToCamera / 2.f) - { - vsize /= bias; - } - - max_vsize = llmax(max_vsize, vsize); - - // addTextureStats limits size to sMaxVirtualSize - if (max_vsize >= LLViewerFetchedTexture::sMaxVirtualSize - && (on_screen || LLViewerTexture::sDesiredDiscardBias <= BIAS_TRS_ON_SCREEN)) - { - break; - } + if (bucket >= 0 && bucket < 4) + channel_coverage[bucket] = llmax(channel_coverage[bucket], vsize); + max_coverage = llmax(max_coverage, vsize); } } - - if (max_vsize >= LLViewerFetchedTexture::sMaxVirtualSize - && (on_screen || LLViewerTexture::sDesiredDiscardBias <= BIAS_TRS_ON_SCREEN)) - { - break; - } } - bool used_face_fast_path = (face_count > max_faces_to_check); - if (used_face_fast_path) - { // this texture is used in so many places we should just boost it and not bother checking its vsize - // this is especially important because the above is not time sliced and can hit multiple ms for a single texture - max_vsize = MAX_IMAGE_AREA; - } - - imagep->addTextureStats(max_vsize); - - // Publish per-texture signals for processTextureStats. Closest face - // wins for distance (min); biggest face wins for size (max). Default - // distance=1, size=0 maps to "deepest discard wanted" - never- - // measured textures stay coarse until distance/size evidence arrives. - if (used_face_fast_path) + // Terrain detail textures register no faces (LLVOSurfacePatch + // addFace(NULL)); synthesize coverage from the nearest visible patch so + // they degrade with distance like everything else. + if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) { - // Fast path saw only a prefix of faces - force best-quality - // sentinels to match the MAX_IMAGE_AREA vsize boost above. - imagep->mMinDistanceFactor = 0.f; - imagep->mMaxOnScreenSize = (F32)MAX_IMAGE_AREA; - } - else if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) - { - // Terrain detail textures don't register faces with the texture - // (LLVOSurfacePatch addFace(NULL)). Drive distance from the LOD - // system; floor at a small nonzero value so pressure has - // something to bite into (pow(0, p) = 0). - static LLCachedControl terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); - static LLCachedControl terrain_coverage(gSavedSettings, "TextureTerrainCoverageFraction", 0.99f); F32 nearest = LLSurface::sNearestVisiblePatchDistance; - F32 nearest_above_bubble = (nearest < FLT_MAX) ? llmax(nearest - bubble, 0.f) : ramp_range; - F32 dist = llclampf(nearest_above_bubble / ramp_range); - imagep->mMinDistanceFactor = llmax(dist, llclampf((F32)terrain_distance_floor)); - imagep->mMaxOnScreenSize = LLViewerTexture::sWindowPixelArea * llclampf((F32)terrain_coverage); - } - else - { - imagep->mMinDistanceFactor = min_distance_factor; - imagep->mMaxOnScreenSize = max_on_screen_size; - } - imagep->mOnAgentAvatar = on_agent_avatar; - - // Bind-staleness. Avatar bakes exempt (cloud-bug protection). - // Per-interval increment is 1/max_discard so saturation time is - // interval * max_discard seconds regardless of texture size. - // Never-bound textures defer to distance/size or initial fetch - // could never start. - if (LLViewerFetchedTexture::isAgentAvatarBoost(imagep->getBoostLevel())) - { - imagep->mStalenessFactor = 0.f; + F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 1.f); + F32 near_frac = (nearest < FLT_MAX) ? llclampf(1.f - nearest / draw_distance) : 0.f; + // Floor so terrain never collapses to nothing at draw distance. + F32 cov = LLViewerTexture::sWindowPixelArea * llmax(near_frac, 0.05f); + channel_coverage[1] = cov; // terrain detail maps are diffuse / base color + max_coverage = cov; } - else if (LLImageGL* gli = imagep->getGLTexture()) + + imagep->addTextureStats(max_coverage); + + // Publish per-bucket coverage for LLViewerLODTexture::computeDesiredDiscard. + for (S32 b = 0; b < 4; ++b) { - static LLCachedControl bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); - static LLCachedControl staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); - F32 grace = llmax((F32)bind_decay_seconds, 0.f); - F32 interval = llmax((F32)staleness_interval, 0.0001f); - - // Clock starts at whichever is later: the last real bind or - // the GL-create time. The latter is the fallback for textures - // decoded into GL but never actually rendered - without it, - // mLastBindTime stays 0 forever and staleness can't evict. - F32 clock_time = llmax(gli->mLastBindTime, gli->mGLCreateTime); - bool has_clock = (clock_time > 0.f); - F32 time_since = has_clock ? (LLImageGL::sLastFrameTime - clock_time) : 0.f; - - if (!has_clock || time_since <= grace) - { - imagep->mStalenessFactor = 0.f; - } - else - { - S32 full_w = imagep->getFullWidth(); - S32 full_h = imagep->getFullHeight(); - S32 max_discard = (full_w > 0 && full_h > 0) - ? LLImageGL::dimDerivedMaxDiscard(full_w, full_h) - : (S32)gli->getMaxDiscardLevel(); - if (max_discard > 0) - { - F32 steps = (time_since - grace) / interval; - F32 step_size = 1.f / (F32)max_discard; - imagep->mStalenessFactor = llclampf(steps * step_size); - } - else - { - imagep->mStalenessFactor = 0.f; - } - } + imagep->mChannelCoverage[b] = channel_coverage[b]; } - } #if 0 - imagep->setDebugText(llformat("%d/%d - %d/%d -- %d/%d", - (S32)sqrtf(max_vsize), - (S32)sqrtf(imagep->mMaxVirtualSize), + imagep->setDebugText(llformat("%d/%d -- %d/%d", imagep->getDiscardLevel(), imagep->getDesiredDiscardLevel(), imagep->getWidth(), @@ -1412,28 +1262,6 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); - // Scale up the per-frame update window under VRAM pressure so eviction - // candidates get re-evaluated quickly. Use memory-pressure *progress* - // (0..1) rather than the raw multiplier so the cap can't blow up by 64x - // at peak pressure - the old code processed the entire mUUIDMap every - // frame at peak, inflating non-avatar frame time and tripping AutoFPS - // to walk RenderFarClip down. Legacy bias term is preserved (it's - // small-ranged 1..4) so behavior unchanged at moderate pressure. - static LLCachedControl update_cap(gSavedSettings, "TextureUpdateCountPressureMaxMultiplier", 6.f); - F32 cap_minus_1 = llmax((F32)update_cap - 1.f, 0.f); - F32 progress = LLViewerTexture::getMemoryPressureProgress(); - F32 bias_term = llmax(0.f, LLViewerTexture::sDesiredDiscardBias - 1.f); - F32 pressure_scale = 1.f + llmin(cap_minus_1, llmax(bias_term, progress * cap_minus_1)); - if (pressure_scale > 1.f - && LLViewerTexture::sBiasTexturesUpdated < (U32)mUUIDMap.size()) - { - update_count = (S32)(update_count * pressure_scale); - - // This isn't particularly precise and can overshoot, but it doesn't need - // to be, just making sure it did a full circle and doesn't get stuck updating - // at the scaled rate permanently. - LLViewerTexture::sBiasTexturesUpdated += update_count; - } update_count = llmin(update_count, (U32) mUUIDMap.size()); { // copy entries out of UUID map to avoid iterator invalidation from deletion inside updateImageDecodeProiroty or updateFetch below diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index dbed8b5c2f..931f2ed50e 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -244,10 +244,6 @@ private: bool mInitialized ; LLFrameTimer mForceDecodeTimer; -public: - // Current close-camera bubble in meters (frame-coherent, slow-tracked). - static F32 sCurrentBubbleMeters; - private: static S32 sNumImages; static void (*sUUIDCallback)(void**, const LLUUID &); -- cgit v1.3 From 6622a5694b5ccd1da77e1564c6475eaf81095f5b Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 7 Jul 2026 04:59:59 -0400 Subject: Get some more wins for overall downrezzing and minimizing thrashing. Also make sure that probes don't stamp - that creates thrashing when distant probes render. We're at a low enough resolution on those things that higher mips are likely to go unnoticed, and we bake them frequently enough that we can pick up lower mips when we actually need them. --- indra/llrender/llimagegl.cpp | 56 ++++++-- indra/llrender/llimagegl.h | 32 ++++- indra/llrender/llrender.cpp | 7 + indra/newview/app_settings/settings.xml | 50 ++++++- indra/newview/llfetchedgltfmaterial.cpp | 26 ++-- indra/newview/llviewertexture.cpp | 245 ++++++++++++++++++++++++++------ indra/newview/llviewertexture.h | 54 +++++-- indra/newview/llviewertexturelist.cpp | 3 + indra/newview/llviewerwindow.cpp | 8 +- indra/newview/pipeline.cpp | 6 + 10 files changed, 395 insertions(+), 92 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 6bcc34938c..95575c009b 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -166,6 +166,8 @@ U64 LLImageGL::getTextureBytesAllocated() //statics U32 LLImageGL::sUniqueCount = 0; +std::atomic LLImageGL::sOOMErrorCount(0); +thread_local bool LLImageGL::sStampBindFrame = true; U32 LLImageGL::sBindCount = 0; S32 LLImageGL::sCount = 0; @@ -777,6 +779,7 @@ void LLImageGL::setImage(const LLImageRaw* imageraw) bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 usename /* = 0 */) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLImageGLStampBypass no_stamp; // upload binds are not visibility const bool is_compressed = isCompressed(); @@ -1502,6 +1505,12 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt { free_cur_tex_image(); } + + // Drain stale GL errors so an OOM detected below belongs to this alloc. + // Otherwise a failed glTexImage2D is swallowed in release while + // alloc_tex_image still counts the bytes, inflating the used-VRAM figure. + while (glGetError() != GL_NO_ERROR) {} + const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) { @@ -1511,19 +1520,30 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt else { // break up calls to a manageable size for the GL command buffer - { - LL_PROFILE_ZONE_NAMED("glTexImage2D alloc"); - glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr); - } + LL_PROFILE_ZONE_NAMED("glTexImage2D alloc"); + glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr); + } - U8* src = (U8*)(pixels); - if (src) + if (glGetError() == GL_OUT_OF_MEMORY) + { + ++sOOMErrorCount; + LL_WARNS_ONCE("Texture") << "glTexImage2D failed with GL_OUT_OF_MEMORY (" + << width << "x" << height << " mip " << miplevel + << ") - not counting bytes" << LL_ENDL; + } + else + { + if (use_sub_image) { - LL_PROFILE_ZONE_NAMED("glTexImage2D copy"); - sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width); + U8* src = (U8*)(pixels); + if (src) + { + LL_PROFILE_ZONE_NAMED("glTexImage2D copy"); + sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width); + } } + alloc_tex_image(width, height, intformat, 1); } - alloc_tex_image(width, height, intformat, 1); } stop_glerror(); } @@ -1668,6 +1688,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LL_PROFILE_GPU_ZONE("createGLTexture"); checkActiveThread(); + LLImageGLStampBypass no_stamp; // creation binds are not visibility bool main_thread = on_main_thread(); @@ -2090,12 +2111,21 @@ S32 LLImageGL::dimDerivedMaxDiscard(S32 width, S32 height) void LLImageGL::stampBound() const { - // Skip the store on same-frame re-binds - bindFast is per-draw and - // would dirty this cache line per bind per texture otherwise. + // Both stamps skip same-frame re-binds (bindFast runs per draw). They dedupe + // separately, so a non-camera pass touching the time stamp first doesn't stop + // a real camera bind from setting the frame stamp later the same frame. if (mLastBindTime != sLastFrameTime) { mLastBindTime = sLastFrameTime; } + if (sStampBindFrame) + { + const U32 frame = LLFrameTimer::getFrameCount(); + if (mLastBindFrame != frame) + { + mLastBindFrame = frame; + } + } } S64 LLImageGL::getBytes(S32 discard_level) const @@ -2520,6 +2550,10 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below + // would otherwise stamp mLastBindFrame and keep the texture fetch-eligible. + LLImageGLStampBypass no_stamp; + if (mTarget != GL_TEXTURE_2D || mFormatInternal == -1 // not initialized ) diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 0869ae54fe..57ca79b3dd 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -39,6 +39,7 @@ #include "llrender.h" #include "threadpool.h" #include "workqueue.h" +#include #include #define LL_IMAGEGL_THREAD_CHECK 0 //set to 1 to enable thread debugging for ImageGL @@ -238,8 +239,17 @@ public: public: // Various GL/Rendering options S64Bytes mTextureMemory; - mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives the streaming cooldown - F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; cooldown fallback for never-bound textures + mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound (bind or bind-attempt) + mutable U32 mLastBindFrame = 0; // frame index (LLFrameTimer::getFrameCount) at last CAMERA-pass + // stampBound; 0 = never. Drives visibility GC + fetch gating. + F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created + + // When false, stampBound skips the mLastBindFrame stamp (mLastBindTime still + // updates). Set false around non-camera passes (probes, shadows, impostors) + // and administrative binds (upload, scaleDown - via LLImageGLStampBypass) so + // those binds don't count as camera visibility. Thread-local so a GL upload + // thread can't flip it on the render thread mid-frame. + static thread_local bool sStampBindFrame; private: U32 createPickMask(S32 pWidth, S32 pHeight); @@ -300,6 +310,11 @@ public: // Global memory statistics static U32 sBindCount; // Tracks number of texture binds for current frame static U32 sUniqueCount; // Tracks number of unique texture binds for current frame + // glTexImage2D GL_OUT_OF_MEMORY failures detected (bytes NOT counted for + // these). Written from whichever thread runs texture creation; read by + // the streaming 1Hz pressure log. Nonzero = the driver is refusing + // allocations and the VRAM budget is unreliable. + static std::atomic sOOMErrorCount; static bool sGlobalUseAnisotropic; static LLImageGL* sDefaultGLTexture ; static bool sAutomatedTest; @@ -355,6 +370,19 @@ public: }; +// RAII: suppress the mLastBindFrame stamp for the current scope. Use around +// administrative binds (upload, create, scaleDown) so they don't count as +// camera visibility - otherwise the GC's own scaleDown re-stamps what it just +// aged out and oscillates. Saves/restores, so it nests correctly. +class LLImageGLStampBypass +{ +public: + LLImageGLStampBypass() : mPrev(LLImageGL::sStampBindFrame) { LLImageGL::sStampBindFrame = false; } + ~LLImageGLStampBypass() { LLImageGL::sStampBindFrame = mPrev; } +private: + bool mPrev; +}; + class LLImageGLThread : public LLSimpleton, LL::ThreadPool { public: diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 5e845fbcce..f0a1c44507 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -245,6 +245,11 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) texture->setActive() ; texture->updateBindStatsForTester() ; } + // updateBindStats only stamps time; the GC and fetch gate use + // the frame stamp, so stamp it here too or bind()-drawn faces + // (bump/material/media) oscillate. Admin/non-camera binds are + // already suppressed via LLImageGLStampBypass / sStampBindFrame. + gl_tex->stampBound(); mHasMipMaps = gl_tex->mHasMipMaps; if (gl_tex->mTexOptionsDirty) { @@ -325,6 +330,8 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 glBindTexture(sGLTextureType[texture->getTarget()], mCurrTexture); stop_glerror(); texture->updateBindStats(); + // Frame-stamp fresh binds too - see bind(LLTexture*) above. + texture->stampBound(); mHasMipMaps = texture->mHasMipMaps; if (texture->mTexOptionsDirty) { diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 864b85034e..bdcbf3b7f2 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11896,7 +11896,7 @@ TexturePixelToTexelRatio Comment - Global maximum pixel:texel ratio, expressed as texels per screen pixel (the "R" in 1:R). 1.0 = one texel per pixel, the best quality the streamer will allocate. A texture is sized so its most-demanding on-screen face stays at or below this many texels per pixel; everything coarser falls out by distance. VRAM pressure walks the effective ratio down from here toward 0 (no floor). + Max texels per screen pixel the streamer will allocate (the "R" in a 1:R pixel:texel ratio). 1.0 = one texel per pixel. VRAM pressure walks the effective ratio down from here. Persist 1 Type @@ -11904,6 +11904,17 @@ Value 1.0 + TextureBackgroundMinRatio + + Comment + Lowest pixel:texel ratio the streamer decays to while the viewer is backgrounded, to free VRAM for other apps. Lower frees more but re-rezzes slower on return. Restored when focus comes back. + Persist + 1 + Type + F32 + Value + 0.001 + TexturePressureHighWater Comment @@ -11962,13 +11973,46 @@ TextureCooldownStepSeconds Comment - Seconds an unseen texture waits per mip level before stepping down. When a texture stops being bound (occluded / off-screen) or the window is backgrounded, its discard rises one level every this many seconds until it reaches the deepest mip, instead of snapping there immediately - so briefly-unseen content isn't thrown away and refetched (cache thrash) if it reappears. Resets the moment the texture is bound again. + While backgrounded, the pixel:texel ratio drops one mip toward TextureBackgroundMinRatio every this many seconds. Persist 1 Type F32 Value - 1.0 + 5.0 + + TextureGCStepFrames + + Comment + Foreground GC cooldown, in rendered frames. For every this many frames a texture goes without being drawn, its mip drops by TextureGCStepMips. Resets when the texture is drawn again. + Persist + 1 + Type + U32 + Value + 5 + + TextureGCStepMips + + Comment + Mip levels dropped each time a TextureGCStepFrames cooldown elapses. 1 is gentlest; higher sheds VRAM faster in coarser jumps. + Persist + 1 + Type + U32 + Value + 1 + + TextureFetchVisibilityFrames + + Comment + Only fetch a texture if it was drawn within this many rendered frames; out-of-view content isn't fetched. Minimum 1 (0 is clamped up). Boosted/UI textures, avatar bakes, and callback textures are exempt. + Persist + 1 + Type + U32 + Value + 1 TextureDecodeDisabled diff --git a/indra/newview/llfetchedgltfmaterial.cpp b/indra/newview/llfetchedgltfmaterial.cpp index 306067d2cf..d71f0c1bd4 100644 --- a/indra/newview/llfetchedgltfmaterial.cpp +++ b/indra/newview/llfetchedgltfmaterial.cpp @@ -75,11 +75,8 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex) if (media_tex) { - // The real basecolor/emissive stay registered for coverage while - // media hides them - stamp them so the streaming cooldown doesn't - // fight that coverage (coarsen -> refetch tug-of-war) the whole - // time media is playing. Blinn has no hidden-texture-under-media - // state, so without this the two systems diverge on media faces. + // Media hides these but they stay registered for coverage. Stamp them so + // the GC doesn't coarsen/refetch them while the media is playing. if (mBaseColorTexture.notNull()) { if (LLImageGL* gl_tex = mBaseColorTexture->getGLTexture()) { gl_tex->stampBound(); } @@ -114,15 +111,10 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex) if (!LLPipeline::sShadowRender) { - // Bind the normal map at whatever resolution is resident - matching - // how Blinn-Phong normal maps degrade (soft, never absent). The old - // "getDiscardLevel() <= 4" gate made PBR normals a cliff instead of - // a gradient: any material the streamer legitimately sized past - // discard 4 (distant / tiled) rendered with NO normal map while the - // equivalent Blinn content rendered a soft one, making PBR look - // categorically flatter. The flat-normal fallback remains only for - // the genuinely-not-yet-loaded window, where it is the correct - // normal-shaped default. + // Bind the normal map at whatever resolution is resident, like Blinn-Phong + // (soft, never absent). Only fall back to the flat normal when it's not + // loaded yet. (The old discard<=4 gate dropped distant/tiled PBR normals + // entirely, making PBR look flatter than equivalent Blinn content.) if (mNormalTexture.notNull() && mNormalTexture->hasGLTexture()) { shader->bindTexture(LLShaderMgr::BUMP_MAP, mNormalTexture); @@ -132,10 +124,8 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex) shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); if (mNormalTexture.notNull()) { - // In active use, just not loaded yet - stamp it so the - // streaming last-bound cooldown doesn't read "unbound" as - // "unseen" and pin it at the deepest mip before its first - // real bind. + // In use, just not loaded yet - stamp it so the GC doesn't treat + // it as unseen and pin it deep before its first real bind. if (LLImageGL* gl_tex = mNormalTexture->getGLTexture()) { gl_tex->stampBound(); diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 42cf91b43b..47d022c854 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -60,6 +60,7 @@ #include "llmediaentry.h" #include "llvovolume.h" #include "llviewermedia.h" +#include "lldrawable.h" #include "lltexturecache.h" #include "llviewerwindow.h" #include "llwindow.h" @@ -87,7 +88,12 @@ S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; F32 LLViewerTexture::sPixelToTexelRatio = 1.f; -F32 LLViewerTexture::sBackgroundSeconds = 0.f; +U32 LLViewerTexture::sGCSuspendedFrame = 0; +S64 LLViewerTexture::sPendingAllocBytes = 0; +S64 LLViewerTexture::sPendingFreeBytes = 0; +U32 LLViewerTexture::sUprezRequestCount = 0; +U32 LLViewerTexture::sDownscaleEnqueueCount = 0; +U32 LLViewerTexture::sCooldownFlooredCount = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size constexpr S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; @@ -109,6 +115,9 @@ LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTextur const F64 log_2 = log(2.0); +// GC evict->refetch cycle samples for the 1Hz TextureStream log (main thread). +static U32 sGCRefetchCount = 0; + //---------------------------------------------------------------------------------------------- //namespace: LLViewerTextureAccess //---------------------------------------------------------------------------------------------- @@ -537,33 +546,41 @@ void LLViewerTexture::updateClass() sFreeVRAMMegabytes = vram_target - vram_used; const S32Megabytes free_sys_mem = getFreeSystemMemory(); - // Single VRAM-pressure knob: the global maximum pixel:texel ratio (texels - // per screen pixel, the "R" in 1:R). It tightens (drops toward 0, no floor) - // while used VRAM is above the high watermark, - // relaxes back toward TexturePixelToTexelRatio below the low watermark, and - // holds steady in the hysteresis band between - the band is what prevents - // sawtooth. The ramp is deliberately slow so eviction (scaleDown draining - // mDownScaleQueue) frees bytes before the next step. Lowering the ratio - // raises every texture's desired discard, which is what actually evicts. + // VRAM pressure controller for the global pixel:texel ratio. Tightens above + // the high watermark, relaxes below the low one, holds in the band (the band + // stops sawtooth). Rates are slow so eviction frees bytes before the next step. { static LLCachedControl ratio_max(gSavedSettings, "TexturePixelToTexelRatio", 1.0f); + static LLCachedControl bg_min_ratio(gSavedSettings, "TextureBackgroundMinRatio", 0.001f); static LLCachedControl wm_high(gSavedSettings, "TexturePressureHighWater", 0.90f); static LLCachedControl wm_low(gSavedSettings, "TexturePressureLowWater", 0.70f); static LLCachedControl tighten_rate(gSavedSettings, "TexturePressureTightenRate", 0.30f); static LLCachedControl relax_rate(gSavedSettings, "TexturePressureRelaxRate", 0.10f); + static LLCachedControl cooldown_step(gSavedSettings, "TextureCooldownStepSeconds", 5.f); - // Unbounded downward: pressure drives the ratio all the way to 0 if it - // has to. There is no quality floor - at 0 every texture resolves to - // its deepest mip (computeDesiredDiscard treats zero allowed texels as - // dim_max), and desired discard is clamped to dim_max anyway, so it - // saturates on its own. Only the top is bounded, by the configured max. + // No lower bound: pressure can drive the ratio to 0 (deepest mip for + // everything). Only the top is capped, by the configured max. F32 r_max = llmax((F32)ratio_max, 0.f); F32 high_frac = llclamp((F32)wm_high, 0.1f, 1.f); F32 high = vram_budget * high_frac; F32 low = vram_budget * llclamp((F32)wm_low, 0.05f, high_frac); F32 dt = (F32)gFrameIntervalSeconds; - if (vram_used > high) + bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); + + if (in_background) + { + // Backgrounded: decay toward bg_min to free VRAM for other apps, one + // mip per cooldown_step seconds (multiplicative). Don't relax back up + // until we're focused again. Pressure can still push below bg_min. + F32 bg_min = llclamp((F32)bg_min_ratio, 0.f, r_max); + if (sPixelToTexelRatio > bg_min) + { + const F32 step = llmax((F32)cooldown_step, 0.01f); + sPixelToTexelRatio = llmax(sPixelToTexelRatio * powf(0.25f, dt / step), bg_min); + } + } + else if (vram_used > high) { sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * dt; } @@ -574,26 +591,44 @@ void LLViewerTexture::updateClass() // else: hold in the hysteresis band. sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max); - // Background cooldown clock: grows while backgrounded/minimized, resets in - // foreground. Feeds the per-texture cooldown in computeDesiredDiscard so - // backgrounding steps every texture up its mip chain over time instead of - // snapping straight to the deepest mip. - bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); - sBackgroundSeconds = in_background ? (sBackgroundSeconds + dt) : 0.f; + // Keep the GC-suspend frame current while backgrounded. This suppresses + // the foreground GC now, and gives it a grace window after we come back so + // visible content can re-stamp its bind frames before anything is collected. + if (in_background) + { + sGCSuspendedFrame = LLFrameTimer::getFrameCount(); + } - // 1 Hz pressure log. + // 1 Hz pressure log. `used` units are the doubled-bytes metric + // (bytes/524288, see above); pending ledgers are converted to match so + // eff(ective) = what used will read once in-flight work settles. static LLFrameTimer s_pressure_log_timer; if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) { s_pressure_log_timer.reset(); + constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; + F32 pend_alloc = (F32)sPendingAllocBytes * BYTES_TO_USED_UNITS; + F32 pend_free = (F32)sPendingFreeBytes * BYTES_TO_USED_UNITS; LL_INFOS("TextureStream") << "pressure" << " ratio=" << sPixelToTexelRatio << " used=" << vram_used + << " eff=" << vram_used + pend_alloc - pend_free + << " pend+=" << pend_alloc + << " pend-=" << pend_free << " budget=" << vram_budget << " high=" << high << " low=" << low << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() + << " uprez/s=" << sUprezRequestCount + << " dscale/s=" << sDownscaleEnqueueCount + << " cdfloor/s=" << sCooldownFlooredCount + << " gcref/s=" << sGCRefetchCount + << " gloom=" << LLImageGL::sOOMErrorCount.load() << LL_ENDL; + sUprezRequestCount = 0; + sDownscaleEnqueueCount = 0; + sCooldownFlooredCount = 0; + sGCRefetchCount = 0; } } @@ -1237,6 +1272,7 @@ FTType LLViewerFetchedTexture::getFTType() const void LLViewerFetchedTexture::cleanup() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + setPendingByteDelta(0); // never leak ledger entries on teardown for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { @@ -1436,15 +1472,20 @@ void LLViewerFetchedTexture::addToCreateTexture() //just update some variables, not to create a real GL texture. createGLTexture(mRawDiscardLevel, mRawImage, 0, false); mNeedsCreateTexture = false; + setPendingByteDelta(0); // no GL bytes will be committed destroyRawImage(); } else if(!force_update && getDiscardLevel() > -1 && getDiscardLevel() <= mRawDiscardLevel) { mNeedsCreateTexture = false; + setPendingByteDelta(0); // nothing to create; commitment over destroyRawImage(); } else { + // Dims are exact now (raw decoded) - refine the ledger entry to the + // discard level that will actually be created. + setPendingByteDelta(estimatedVRAMBytesAtDiscard(mRawDiscardLevel) - residentVRAMBytes()); scheduleCreateTexture(); } return; @@ -1569,6 +1610,61 @@ bool LLViewerFetchedTexture::preCreateTexture(S32 usename/*= 0*/) return res; } +S64 LLViewerFetchedTexture::estimatedVRAMBytesAtDiscard(S32 discard) const +{ + if (discard < 0) + { + return 0; + } + if (mFullWidth <= 0 || mFullHeight <= 0) + { + // Dims unknown (header not fetched yet): nominal placeholder, + // corrected as soon as the first decode reports real dimensions. + return 64 * 64 * 4; + } + S32 w = llmax(1, (S32)mFullWidth >> discard); + S32 h = llmax(1, (S32)mFullHeight >> discard); + S64 bytes = (S64)w * h * 4; // GL pads most formats to 4 components + bytes = bytes * 4 / 3; // mip chain + if (LLImageGL::sCompressTextures) + { + bytes /= 4; // rough DXT ratio + } + return bytes; +} + +S64 LLViewerFetchedTexture::residentVRAMBytes() const +{ + return mGLTexturep.notNull() ? (S64)mGLTexturep->mTextureMemory.value() : 0; +} + +void LLViewerFetchedTexture::setPendingByteDelta(S64 delta) +{ + if (delta == mPendingByteDelta) + { + return; + } + // retire the previous contribution + if (mPendingByteDelta > 0) + { + sPendingAllocBytes -= mPendingByteDelta; + } + else if (mPendingByteDelta < 0) + { + sPendingFreeBytes -= -mPendingByteDelta; + } + // apply the new one + if (delta > 0) + { + sPendingAllocBytes += delta; + } + else if (delta < 0) + { + sPendingFreeBytes += -delta; + } + mPendingByteDelta = delta; +} + bool LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/) { if (!mNeedsCreateTexture) @@ -1611,6 +1707,7 @@ void LLViewerFetchedTexture::postCreateTexture() } destroyRawImage(); // will save raw image if needed + setPendingByteDelta(0); // commitment realized - bytes now in LLImageGL accounting mNeedsCreateTexture = false; } @@ -2139,6 +2236,30 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); make_request = false; } + else + { + // Only fetch streamed world textures the renderer is actually drawing + // (mLastBindFrame is stamped per drawn frame). Out-of-view content gets + // no residency. Exempt: boosted/UI, avatar bakes, textures with loaded + // callbacks, and bake uploads. + static LLCachedControl vis_frames(gSavedSettings, "TextureFetchVisibilityFrames", 5); + const bool visibility_gated = mBoostLevel < LLGLTexture::BOOST_HIGH + && mUseMipMaps + && !mDontDiscard + && !isAgentAvatarBoost(mBoostLevel) + && !mForceToSaveRawImage + && mLoadedCallbackList.empty(); + if (visibility_gated && mGLTexturep.notNull()) + { + const U32 last = mGLTexturep->mLastBindFrame; + const U32 now = LLFrameTimer::getFrameCount(); + if (last == 0 || now - last > llmax((U32)vis_frames, 1u)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - not visible"); + make_request = false; + } + } + } if (make_request) { @@ -2194,6 +2315,19 @@ bool LLViewerFetchedTexture::updateFetch() // in some cases createRequest can modify discard, as an example // bake textures are always at discard 0 mRequestedDiscardLevel = llmin(desired_discard, fetch_request_response); + + // Open the committed-bytes ledger entry: bytes this request will make + // resident minus what's resident now. Settled at postCreateTexture (or + // on the cancel/failure paths). + setPendingByteDelta(estimatedVRAMBytesAtDiscard(mRequestedDiscardLevel) - residentVRAMBytes()); + ++sUprezRequestCount; + + if (mGCEvicted) + { + // GC evict->refetch cycle; counted as gcref/s, should be ~0 when settled. + mGCEvicted = false; + ++sGCRefetchCount; + } mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); } @@ -2242,6 +2376,12 @@ bool LLViewerFetchedTexture::updateFetch() LL_DEBUGS("Texture") << "exceeded idle time " << FETCH_IDLE_TIME << ", deleting request: " << getID() << LL_ENDL; LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); mHasFetcher = false; + if (!mNeedsCreateTexture && !mCreatePending) + { + // fetch retired without delivering anything still queued - + // settle the ledger (delivered data settles at postCreateTexture) + setPendingByteDelta(0); + } } } @@ -2271,6 +2411,10 @@ void LLViewerFetchedTexture::forceToDeleteRequest() mHasFetcher = false; mIsFetching = false; } + if (!mNeedsCreateTexture && !mCreatePending) + { + setPendingByteDelta(0); // request dead, nothing queued to create + } resetTextureStats(); @@ -2308,6 +2452,7 @@ void LLViewerFetchedTexture::setIsMissingAsset(bool is_missing) mFetchState = 0; mFetchPriority = 0; } + setPendingByteDelta(0); // nothing will be committed for a missing asset } else { @@ -3147,31 +3292,37 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c desired = current; // inside the dead-band -> hold } - // Cooldown: don't snap an unseen texture straight to its deepest mip - step - // it up one level per TextureCooldownStepSeconds so briefly-occluded or - // backgrounded content isn't thrown away (and doesn't thrash the cache) when - // we look at it again. Driven by whichever is longer: time since last bind, - // or time backgrounded. It only raises discard and resets the moment the - // texture is bound again. One frame interval is subtracted so a - // continuously-visible texture (whose last bind is a frame old here, since - // this pass runs a frame ahead of its own render) reads as zero. Avatar - // bakes exempt. + // Foreground visibility GC (avatar bakes exempt). Background degradation is + // handled by the ratio decay in updateClass, and the GC self-suppresses while + // backgrounded via the sGCSuspendedFrame check below, so the two don't fight. + // + // For every gc_cooldown frames a texture goes without a camera bind, drop its + // mip by gc_step, walking gradually toward the deepest mip instead of slamming. + // Content drawn within the last cooldown stays full-res, so a fast camera pan + // finds it only a step or two coarse on the way back. Resets when drawn again. if (!avatar_bake) { - static LLCachedControl cooldown_step(gSavedSettings, "TextureCooldownStepSeconds", 1.f); - const F32 step = llmax((F32)cooldown_step, 0.01f); - F32 unbound = 0.f; if (LLImageGL* gli = getGLTexture()) { - const F32 ref = llmax(gli->mLastBindTime, gli->mGLCreateTime); - if (ref > 0.f) + static LLCachedControl gc_cooldown_frames(gSavedSettings, "TextureGCStepFrames", 5); + static LLCachedControl gc_step_mips(gSavedSettings, "TextureGCStepMips", 1); + constexpr U32 GC_RESUME_GRACE_FRAMES = 10; + const U32 now = LLFrameTimer::getFrameCount(); + mGCFloored = false; + if (gli->mLastBindFrame > 0 // drawn at least once + && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background { - unbound = llmax(0.f, LLImageGL::sLastFrameTime - ref - (F32)gFrameIntervalSeconds); + const U32 cooldown = llmax((U32)gc_cooldown_frames, 1u); + const S32 periods = (S32)((now - gli->mLastBindFrame) / cooldown); + if (periods > 0) + { + const S32 step_mips = (S32)llmax((U32)gc_step_mips, 1u); + desired = llclamp(desired + periods * step_mips, desired, dim_max_i); + mGCFloored = true; + ++sCooldownFlooredCount; + } } } - const F32 cooldown_seconds = llmax(unbound, sBackgroundSeconds); - const S32 cooldown_floor = llclamp((S32)floor(cooldown_seconds / step), 0, dim_max_i); - desired = llmax(desired, cooldown_floor); } return llclamp(desired, 0, dim_max_i); @@ -3255,6 +3406,10 @@ void LLViewerLODTexture::processTextureStats() S32 current_discard = getDiscardLevel(); if (!avatar_bake && current_discard >= 0 && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { + if (mGCFloored) + { + mGCEvicted = true; // eviction attributable to the visibility GC + } scaleDown(); } @@ -3283,12 +3438,7 @@ bool LLViewerLODTexture::scaleDown() return false; } - // Hard structural blocks only. Per-texture policy (icons pinned to full - // res, etc.) lives in processTextureStats; if that policy is later - // relaxed (e.g. honor mKnownDrawWidth for icons rendered at 8x8 in a - // friend list) the scaleDown path stays open. - // BOOST_HIGH is the emergency-out for GLTF's "force full res" hack; - // the other two flags are structural. + // Structural blocks only; per-texture policy lives in processTextureStats. if (!mUseMipMaps || mDontDiscard || mBoostLevel >= LLGLTexture::BOOST_HIGH) { return false; @@ -3303,6 +3453,9 @@ bool LLViewerLODTexture::scaleDown() { mDownScalePending = true; gTextureList.mDownScaleQueue.push(this); + // Pending-free ledger entry: bytes decided-freed, returned when the queue drains. + setPendingByteDelta(estimatedVRAMBytesAtDiscard(mDesiredDiscardLevel) - residentVRAMBytes()); + ++sDownscaleEnqueueCount; } return true; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 28d896eff5..ce9a953de5 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -243,19 +243,27 @@ public: static S32 sAuxCount; static LLFrameTimer sEvaluationTimer; - // The single VRAM-pressure knob: the global maximum pixel:texel ratio, - // expressed as texels per screen pixel (the "R" in 1:R). Starts at - // TexturePixelToTexelRatio (1.0 = one texel per pixel) and the watermark - // controller in updateClass() walks it down toward 0 (no floor) - // while used VRAM is above the high watermark, back up below the low - // watermark, holding in the band between. Lowering it raises every - // texture's desired discard, which drives scaleDown eviction. Consumed by - // LLViewerLODTexture::computeDesiredDiscard. + // The global max pixel:texel ratio (texels per screen pixel, the "R" in 1:R). + // The watermark controller in updateClass walks it between TexturePixelToTexelRatio + // and 0 as VRAM pressure changes; lower means coarser desired discards. static F32 sPixelToTexelRatio; - // Seconds the app has been backgrounded/minimized (0 in foreground). Drives - // the background half of the per-texture cooldown in computeDesiredDiscard. - static F32 sBackgroundSeconds; + // Frame index the GC was last suspended (kept current while backgrounded). + // The foreground GC only runs once getFrameCount() is a grace window past + // this, so content can re-stamp after an alt-tab before anything is collected. + static U32 sGCSuspendedFrame; + + // Committed-but-not-yet-realized VRAM, in bytes (main thread only). + // effective_used = resident + sPendingAllocBytes - sPendingFreeBytes. + // Maintained by setPendingByteDelta; shown in the 1Hz pressure log. + static S64 sPendingAllocBytes; // in-flight toward allocation + static S64 sPendingFreeBytes; // queued for release, not yet returned + + // 1Hz churn counters (main thread; reset each pressure-log tick). High + // uprez+downscale with no memory pressure = per-texture oscillation. + static U32 sUprezRequestCount; // finer-mip fetch requests issued + static U32 sDownscaleEnqueueCount; // scaleDown enqueues + static U32 sCooldownFlooredCount; // desired raised by the cooldown floor static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; @@ -445,6 +453,26 @@ public: bool mCreatePending = false; // if true, this is in gTextureList.mCreateTextureList mutable bool mDownScalePending = false; // if true, this is in gTextureList.mDownScaleQueue + // GC-cycle diagnostics (main thread): mGCFloored = the visibility GC + // raised desired on the last computeDesiredDiscard; mGCEvicted = this + // texture was actually evicted because of it. A subsequent uprez fetch + // request while mGCEvicted is a full evict->refetch cycle - the churn + // signature - and gets sampled into the 1Hz TextureStream log. + mutable bool mGCFloored = false; + bool mGCEvicted = false; + + // --- committed-bytes ledger (main thread only) --- + // Estimated VRAM bytes this texture would occupy resident at `discard` + // (components ~4, x4/3 mip chain, /4 rough DXT when compression is on). + // Nominal small placeholder before dims are known. + S64 estimatedVRAMBytesAtDiscard(S32 discard) const; + // Actual bytes currently resident (LLImageGL accounting), 0 if none. + S64 residentVRAMBytes() const; + // Open/adjust/settle this texture's contribution to the global pending + // ledgers. delta > 0 = in-flight toward allocation; delta < 0 = queued + // free; 0 = settled. Replaces any previous contribution. + void setPendingByteDelta(S64 delta); + protected: S32 getCurrentDiscardLevelForFetching() ; void forceToRefetchTexture(S32 desired_discard = 0, F32 kept_time = 60.f); @@ -533,6 +561,10 @@ protected: LLFrameTimer mLastPacketTimer; // Time since last packet. LLFrameTimer mStopFetchingTimer; // Time since mDecodePriority == 0.f. + // This texture's open contribution to the pending-bytes ledgers + // (see setPendingByteDelta). 0 = no open commitment. Main thread only. + S64 mPendingByteDelta = 0; + bool mInImageList; // true if image is in list (in which case don't reset priority!) // This needs to be atomic, since it is written both in the main thread // and in the GL image worker thread... HB diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 70bca3854d..8fab391997 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -64,6 +64,7 @@ #include "llviewerwindow.h" #include "llsurface.h" #include "llvoavatarself.h" +#include "lldrawable.h" #include "llvovolume.h" #include "llviewertextureanim.h" #include "llprogressview.h" @@ -950,6 +951,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag bool bucket_used[4] = { false, false, false, false }; F32 max_coverage = 0.f; + U32 face_count = 0; const U32 max_faces_to_check = 1024; @@ -1387,6 +1389,7 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) img->scaleDown(image->getDesiredDiscardLevel()); } + image->setPendingByteDelta(0); // pending-free settled (or abandoned) image->mDownScalePending = false; mDownScaleQueue.pop(); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index dea96e2012..e2601e6465 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5597,7 +5597,13 @@ bool LLViewerWindow::cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubea // actually render the scene gCubeSnapshot = true; - display_cube_face(); + { + // Probe binds aren't visibility - otherwise every probe slice re-stamps + // behind-camera textures and cycles them evict->refetch. RAII so the + // nested shadow pass in display_cube_face doesn't re-enable stamping. + LLImageGLStampBypass stamp_bypass; + display_cube_face(); + } gCubeSnapshot = false; } diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 4ef88f5deb..f18ec727df 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -9461,6 +9461,9 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa LL_PROFILE_GPU_ZONE("renderShadow"); LLPipeline::sShadowRender = true; + // Shadow binds aren't visibility. RAII (not a hardcoded restore) so a shadow + // pass nested in a probe render doesn't re-enable stamping for the rest of it. + LLImageGLStampBypass stamp_bypass; // disable occlusion culling during shadow render U32 saved_occlusion = sUseOcclusion; @@ -10845,6 +10848,9 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool sShadowRender = true; sImpostorRender = true; + // Impostor binds aren't visibility. RAII so the nested shadow pass can't + // re-enable stamping mid-render. + LLImageGLStampBypass stamp_bypass; LLViewerCamera* viewer_camera = LLViewerCamera::getInstance(); -- cgit v1.3 From f117d33535097c3bb8d48f1f954a7a8df0449c44 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 7 Jul 2026 07:34:09 -0400 Subject: Kill the system memory stuff - it's not relevant to the new texture streaming. --- indra/llrender/llimagegl.cpp | 8 +- indra/llrender/llimagegl.h | 1 - indra/newview/app_settings/settings.xml | 11 --- indra/newview/llviewerdisplay.cpp | 5 - indra/newview/llviewermessage.cpp | 7 -- indra/newview/llviewertexture.cpp | 159 -------------------------------- indra/newview/llviewertexture.h | 22 ----- indra/newview/llvocache.cpp | 6 -- 8 files changed, 3 insertions(+), 216 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 95575c009b..c8a23d873e 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -493,7 +493,7 @@ bool LLImageGL::create(LLPointer& dest, const LLImageRaw* imageraw, b //---------------------------------------------------------------------------- LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mSaveData(0), mExternalTexture(false) +: mExternalTexture(false) { init(usemipmaps, allow_compression); setSize(0, 0, 0); @@ -502,7 +502,7 @@ LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true } LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mSaveData(0), mExternalTexture(false) +: mExternalTexture(false) { llassert( components <= 4 ); init(usemipmaps, allow_compression); @@ -512,7 +512,7 @@ LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = t } LLImageGL::LLImageGL(const LLImageRaw* imageraw, bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mSaveData(0), mExternalTexture(false) +: mExternalTexture(false) { init(usemipmaps, allow_compression); setSize(0, 0, 0); @@ -621,8 +621,6 @@ void LLImageGL::cleanup() destroyGLTexture(); } freePickMask(); - - mSaveData = NULL; // deletes data } //---------------------------------------------------------------------------- diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 57ca79b3dd..ca75b543c0 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -256,7 +256,6 @@ private: void freePickMask(); bool isCompressed(); - LLPointer mSaveData; // used for destroyGL/restoreGL LL::WorkQueue::weak_t mMainQueue; U8* mPickMask; //downsampled bitmap approximation of alpha channel. NULL if no alpha channel U16 mPickMaskWidth; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index bdcbf3b7f2..45a7dced5c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8002,17 +8002,6 @@ Value 1 - RenderMinFreeMainMemoryThreshold - - Comment - If available free physical memory is below this value textures get agresively scaled down - Persist - 0 - Type - U32 - Value - 512 - RenderLowMemMinDiscardIncrement Comment diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 4773a8a555..9f1b0d75f3 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -217,11 +217,6 @@ void display_update_camera() { final_far *= 0.5f; } - // When system memory is critically low or recovering, shrink draw distance. - else if (LLViewerTexture::getSystemMemoryBudgetFactor() > 1.f) - { - final_far = llmax(32.f, final_far / LLViewerTexture::getSystemMemoryBudgetFactor()); - } LLViewerCamera::getInstance()->setFar(final_far); LLVOAvatar::sRenderDistance = llclamp(final_far, 16.f, 256.f); gViewerWindow->setup3DRender(); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 612af029b9..09f17fec40 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3372,13 +3372,6 @@ 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::getSystemMemoryBudgetFactor() > 1.f) - { - // We are critcally low on memory or recovering, - // limit requested draw distance - memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / LLViewerTexture::getSystemMemoryBudgetFactor(), gAgentCamera.mDrawDistance / 2.f); - } - if (tp_state == LLAgent::TELEPORT_ARRIVING || LLStartUp::getStartupState() < STATE_MISC) { // Inform interest list, prioritize closer area. diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 47d022c854..7d07c95395 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -91,9 +91,6 @@ F32 LLViewerTexture::sPixelToTexelRatio = 1.f; U32 LLViewerTexture::sGCSuspendedFrame = 0; S64 LLViewerTexture::sPendingAllocBytes = 0; S64 LLViewerTexture::sPendingFreeBytes = 0; -U32 LLViewerTexture::sUprezRequestCount = 0; -U32 LLViewerTexture::sDownscaleEnqueueCount = 0; -U32 LLViewerTexture::sCooldownFlooredCount = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size constexpr S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; @@ -105,19 +102,14 @@ U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; F32 LLViewerTexture::sCurrentTime = 0.0f; -constexpr F32 MEMORY_CHECK_WAIT_TIME = 1.0f; constexpr F32 MIN_VRAM_BUDGET = 768.f; F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; F32 LLViewerTexture::sWindowPixelArea = 1.f; -F32 LLViewerTexture::sSysMemoryFactor = 1.f; LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; const F64 log_2 = log(2.0); -// GC evict->refetch cycle samples for the 1Hz TextureStream log (main thread). -static U32 sGCRefetchCount = 0; - //---------------------------------------------------------------------------------------------- //namespace: LLViewerTextureAccess //---------------------------------------------------------------------------------------------- @@ -489,13 +481,6 @@ void LLViewerTexture::initClass() LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture(); } -S32Megabytes get_render_free_main_memory_treshold() -{ - static LLCachedControl min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512); - const U32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory); - return MIN_FREE_MAIN_MEMORY; -} - //static void LLViewerTexture::updateClass() { @@ -544,7 +529,6 @@ void LLViewerTexture::updateClass() // 'bias' calculation to kick in. F32 vram_target = llmax(llmin(vram_budget - 512.f, vram_budget * 0.8f), MIN_VRAM_BUDGET); sFreeVRAMMegabytes = vram_target - vram_used; - const S32Megabytes free_sys_mem = getFreeSystemMemory(); // VRAM pressure controller for the global pixel:texel ratio. Tightens above // the high watermark, relaxes below the low one, holds in the band (the band @@ -598,135 +582,7 @@ void LLViewerTexture::updateClass() { sGCSuspendedFrame = LLFrameTimer::getFrameCount(); } - - // 1 Hz pressure log. `used` units are the doubled-bytes metric - // (bytes/524288, see above); pending ledgers are converted to match so - // eff(ective) = what used will read once in-flight work settles. - static LLFrameTimer s_pressure_log_timer; - if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) - { - s_pressure_log_timer.reset(); - constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; - F32 pend_alloc = (F32)sPendingAllocBytes * BYTES_TO_USED_UNITS; - F32 pend_free = (F32)sPendingFreeBytes * BYTES_TO_USED_UNITS; - LL_INFOS("TextureStream") << "pressure" - << " ratio=" << sPixelToTexelRatio - << " used=" << vram_used - << " eff=" << vram_used + pend_alloc - pend_free - << " pend+=" << pend_alloc - << " pend-=" << pend_free - << " budget=" << vram_budget - << " high=" << high - << " low=" << low - << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() - << " uprez/s=" << sUprezRequestCount - << " dscale/s=" << sDownscaleEnqueueCount - << " cdfloor/s=" << sCooldownFlooredCount - << " gcref/s=" << sGCRefetchCount - << " gloom=" << LLImageGL::sOOMErrorCount.load() - << LL_ENDL; - sUprezRequestCount = 0; - sDownscaleEnqueueCount = 0; - sCooldownFlooredCount = 0; - sGCRefetchCount = 0; - } - } - - // System-memory -> draw-distance factor. Separate from the VRAM ratio above: - // this is a last-resort response to running low on *system* RAM and only - // affects draw distance (via getSystemMemoryBudgetFactor, consumed by - // llviewerdisplay). Textures were mostly moved to VRAM, so this rarely fires. - bool is_sys_critically_low = isSystemMemoryCritical(); - static bool sys_was_low = false; - - // System memory factor - // 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_critically_low) - { - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); - // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory. - S32 sys_budget_debt = free_sys_mem - MIN_FREE_MAIN_MEMORY; - - // Leave some padding, otherwise we will crash out of memory before hitting factor 2. - const S32Megabytes PAD_BUFFER(32); - S32Megabytes budget_target = MIN_FREE_MAIN_MEMORY - PAD_BUFFER; - if (!sys_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 / (F32)budget_target; - 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 / (F32)budget_target, 0.f); - sSysMemoryFactor += increment * gFrameIntervalSeconds; - } - sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); - } - else - { - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); - // 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 - // treshold. - const S32Megabytes MEM_THRESHOLD = MIN_FREE_MAIN_MEMORY + S32Megabytes(150); - if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f) - { - // Ramp down factor over time. - constexpr F32 DECREMENT = 0.02f; - sSysMemoryFactor -= DECREMENT * gFrameIntervalSeconds; - sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); - } } - sys_was_low = is_sys_critically_low; -} - -//static -U32Megabytes LLViewerTexture::getFreeSystemMemory() -{ - static LLFrameTimer timer; - static U32Megabytes physical_res = U32Megabytes(U32_MAX); - - if (timer.getElapsedTimeF32() < MEMORY_CHECK_WAIT_TIME) //call this once per second. - { - return physical_res; - } - - timer.reset(); - - LLMemory::updateMemoryInfo(); - physical_res = LLMemory::getAvailableMemKB(); - return physical_res; -} - -//static -bool LLViewerTexture::isSystemMemoryLow() -{ - return getFreeSystemMemory() < get_render_free_main_memory_treshold(); -} - -//static -bool LLViewerTexture::isSystemMemoryCritical() -{ - return getFreeSystemMemory() < get_render_free_main_memory_treshold() / 2; -} - -// static -F32 LLViewerTexture::getSystemMemoryBudgetFactor() -{ - return sSysMemoryFactor; } //end of static functions @@ -2320,14 +2176,7 @@ bool LLViewerFetchedTexture::updateFetch() // resident minus what's resident now. Settled at postCreateTexture (or // on the cancel/failure paths). setPendingByteDelta(estimatedVRAMBytesAtDiscard(mRequestedDiscardLevel) - residentVRAMBytes()); - ++sUprezRequestCount; - if (mGCEvicted) - { - // GC evict->refetch cycle; counted as gcref/s, should be ~0 when settled. - mGCEvicted = false; - ++sGCRefetchCount; - } mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); } @@ -3308,7 +3157,6 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c static LLCachedControl gc_step_mips(gSavedSettings, "TextureGCStepMips", 1); constexpr U32 GC_RESUME_GRACE_FRAMES = 10; const U32 now = LLFrameTimer::getFrameCount(); - mGCFloored = false; if (gli->mLastBindFrame > 0 // drawn at least once && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background { @@ -3318,8 +3166,6 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c { const S32 step_mips = (S32)llmax((U32)gc_step_mips, 1u); desired = llclamp(desired + periods * step_mips, desired, dim_max_i); - mGCFloored = true; - ++sCooldownFlooredCount; } } } @@ -3406,10 +3252,6 @@ void LLViewerLODTexture::processTextureStats() S32 current_discard = getDiscardLevel(); if (!avatar_bake && current_discard >= 0 && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { - if (mGCFloored) - { - mGCEvicted = true; // eviction attributable to the visibility GC - } scaleDown(); } @@ -3455,7 +3297,6 @@ bool LLViewerLODTexture::scaleDown() gTextureList.mDownScaleQueue.push(this); // Pending-free ledger entry: bytes decided-freed, returned when the queue drains. setPendingByteDelta(estimatedVRAMBytesAtDiscard(mDesiredDiscardLevel) - residentVRAMBytes()); - ++sDownscaleEnqueueCount; } return true; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index ce9a953de5..145661fefe 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -114,11 +114,6 @@ protected: public: static void initClass(); static void updateClass(); - static bool isSystemMemoryLow(); - static bool isSystemMemoryCritical(); - - // Ranges from 1 (no RAM deficit) to 2 (RAM deficit) - static F32 getSystemMemoryBudgetFactor(); LLViewerTexture(bool usemipmaps = true); LLViewerTexture(const LLUUID& id, bool usemipmaps) ; @@ -198,8 +193,6 @@ private: friend class LLBumpImageList; friend class LLUIImageList; - static U32Megabytes getFreeSystemMemory(); - protected: friend class LLViewerTextureList; LLUUID mID; @@ -259,12 +252,6 @@ public: static S64 sPendingAllocBytes; // in-flight toward allocation static S64 sPendingFreeBytes; // queued for release, not yet returned - // 1Hz churn counters (main thread; reset each pressure-log tick). High - // uprez+downscale with no memory pressure = per-texture oscillation. - static U32 sUprezRequestCount; // finer-mip fetch requests issued - static U32 sDownscaleEnqueueCount; // scaleDown enqueues - static U32 sCooldownFlooredCount; // desired raised by the cooldown floor - static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; static U32 sMaxSmallImageSize ; @@ -273,7 +260,6 @@ public: // estimated free memory for textures, by bias calculation static F32 sFreeVRAMMegabytes; - static F32 sSysMemoryFactor; // Viewport pixel area, refreshed once per frame. Hoisted to keep the // per-texture hot path out of gViewerWindow. static F32 sWindowPixelArea; @@ -453,14 +439,6 @@ public: bool mCreatePending = false; // if true, this is in gTextureList.mCreateTextureList mutable bool mDownScalePending = false; // if true, this is in gTextureList.mDownScaleQueue - // GC-cycle diagnostics (main thread): mGCFloored = the visibility GC - // raised desired on the last computeDesiredDiscard; mGCEvicted = this - // texture was actually evicted because of it. A subsequent uprez fetch - // request while mGCEvicted is a full evict->refetch cycle - the churn - // signature - and gets sampled into the 1Hz TextureStream log. - mutable bool mGCFloored = false; - bool mGCEvicted = false; - // --- committed-bytes ledger (main thread only) --- // Estimated VRAM bytes this texture would occupy resident at `discard` // (components ~4, x4/3 mip chain, /4 rough DXT when compression is on). diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index 7618739b3c..f3efe3f3bb 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -488,12 +488,6 @@ void LLVOCacheEntry::updateDebugSettings() static const F32 MIN_RADIUS = 1.0f; F32 draw_radius = gAgentCamera.mDrawDistance; - if (LLViewerTexture::getSystemMemoryBudgetFactor() > 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); - } 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); -- cgit v1.3 From a937b237de3651e79cdb517f14381a5bdd4c844b Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Thu, 9 Jul 2026 08:09:24 -0400 Subject: Geenz/texture loading speed (#5985) * Add more controls for texture loading budgets. Should yield much faster loading within a given FPS target - should generally self regulate depending on your framerate. * Harden texture pipeline against stalls and OOM. Generally makes texture loading faster, at the expense of some budgeting (which we weren't doing a great job at anyways). --- indra/llrender/llgl.cpp | 6 + indra/llrender/llgl.h | 1 + indra/llrender/llimagegl.cpp | 14 +- indra/newview/app_settings/settings.xml | 37 ++- indra/newview/llface.cpp | 22 ++ indra/newview/llface.h | 18 +- indra/newview/llviewerdisplay.cpp | 31 ++- indra/newview/llviewertexture.cpp | 112 ++++++--- indra/newview/llviewertexture.h | 19 ++ indra/newview/llviewertexturelist.cpp | 426 +++++++++++++++++++------------- indra/newview/llviewertexturelist.h | 5 + 11 files changed, 482 insertions(+), 209 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 4584ed1d86..0e59c449db 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2370,6 +2370,12 @@ void clear_glerror() glGetError(); } +void drain_glerror() +{ + // bounded: a lost/reset context can return errors indefinitely + for (S32 i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} +} + /////////////////////////////////////////////////////////////// // // LLGLState diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index e1ab2a49e6..3f9a9de70a 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -159,6 +159,7 @@ void log_glerror(); void assert_glerror(); void clear_glerror(); +void drain_glerror(); // pops ALL pending GL error flags (bounded so a lost/reset context that returns errors forever cannot hang the caller); use before an attributable glGetError check. # define stop_glerror() assert_glerror() diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index c8a23d873e..b3cd8d2896 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1507,7 +1507,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt // Drain stale GL errors so an OOM detected below belongs to this alloc. // Otherwise a failed glTexImage2D is swallowed in release while // alloc_tex_image still counts the bytes, inflating the used-VRAM figure. - while (glGetError() != GL_NO_ERROR) {} + drain_glerror(); const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) @@ -1919,7 +1919,8 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre //----------------------------------------------------------------------------------------------- GLenum error ; - while((error = glGetError()) != GL_NO_ERROR) + S32 error_count = 0 ; + while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16) { LL_WARNS() << "GL Error happens before reading back texture. Error code: " << error << LL_ENDL ; } @@ -1979,7 +1980,8 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ; imageraw->deleteData() ; - while((error = glGetError()) != GL_NO_ERROR) + error_count = 0 ; + while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16) { LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ; } @@ -2548,8 +2550,10 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below - // would otherwise stamp mLastBindFrame and keep the texture fetch-eligible. + // Don't let eviction re-arm the GC: the glGenerateMipmap re-bind below would + // otherwise stamp mLastBindFrame, so the next computeDesiredDiscard treats the + // just-evicted texture as freshly drawn, un-floors it, and re-fetches - the + // evict/refetch oscillation. LLImageGLStampBypass no_stamp; if (mTarget != GL_TEXTURE_2D diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 45a7dced5c..1c2c2d0559 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11992,16 +11992,49 @@ Value 1 - TextureFetchVisibilityFrames + TextureLoadTargetFPS Comment - Only fetch a texture if it was drawn within this many rendered frames; out-of-view content isn't fetched. Minimum 1 (0 is clamped up). Boosted/UI textures, avatar bakes, and callback textures are exempt. + Frame rate the viewer is willing to drop to while loading textures. The texture pipeline's per-frame budget is the headroom between this and the actual frame cost, so fast machines load aggressively and slow ones hold their frame rate. + Persist + 1 + Type + F32 + Value + 30.0 + + TextureLoadBudgetMaxMS + + Comment + Hard cap, in milliseconds per frame, on the adaptive texture-pipeline budget. + Persist + 1 + Type + F32 + Value + 10.0 + + TextureFetchStepMips + + Comment + Fetch refinement step, in mip levels: a texture whose resident data is coarser than desired by more than this fetches in steps of this size instead of jumping straight to the final resolution, so it sharpens progressively instead of sitting blurry then popping. 0 = jump directly. Boosted/pinned textures always jump. Persist 1 Type U32 Value + 2 + + TextureFrustumAllowance + + Comment + Falloff width for out-of-frustum texture resolution, as a fraction of screen size. Content grazing the screen edge keeps full resolution; content this far past the edge reaches the deepest mip, lerped between. Keeps barely-out-of-view textures resident so panning back doesn't refetch them. + Persist 1 + Type + F32 + Value + 0.5 TextureDecodeDisabled diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index e34bea63ef..c96e1bd3b0 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -2297,6 +2297,8 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) // don't update every frame if (gFrameTimeSeconds - mLastPixelAreaUpdate < PIXEL_AREA_UPDATE_PERIOD) { + cos_angle_to_view_dir = mLastCosAngleToViewDir; + radius = mLastRadius; return true; } @@ -2368,6 +2370,7 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) // no rigged extents, zero out bounding box and skip update mRiggedExtents[0] = mRiggedExtents[1] = LLVector4a(0.f, 0.f, 0.f); + mInFrustum = false; return false; } @@ -2422,6 +2425,7 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) if(!camera->AABBInFrustum(center, size)) { mImportanceToCamera = 0.f ; + mInFrustum = false; return false ; } if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum @@ -2450,6 +2454,24 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; } + // On-screen test: does the face's projected disc overlap the screen disc? + // (Same construction as adjustPartialOverlapPixelArea.) Behind-camera faces + // get acos(cos) near pi and fall out; the generous screen radius errs toward + // "on screen" so fetch admission never starves edge content. mFrustumOverflow + // is how far past the boundary the disc sits, as a fraction of screen size - + // 0 on screen, 0.1 = 10% of a screen out - and feeds the frustum allowance + // falloff in computeDesiredDiscard. + { + F32 center_px = acosf(llclamp(cos_angle_to_view_dir, -1.f, 1.f)) * LLDrawable::sCurPixelAngle; + F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + F32 past_edge = center_px - radius - screen_radius; + mInFrustum = past_edge <= 5.f; + mFrustumOverflow = llmax(past_edge - 5.f, 0.f) / screen_radius; + } + + mLastCosAngleToViewDir = cos_angle_to_view_dir; + mLastRadius = radius; + return true ; } diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 71ba3d0f2f..0b0a17b8c8 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -269,10 +269,21 @@ public: // return mSkinInfo->mHash or 0 if mSkinInfo is null U64 getSkinHash(); - // true if face was recently in the main camera frustum according to LLViewerTextureList updates + // True if this face's projected bounding disc overlaps the screen - maintained + // by calcPixelArea() (sticky between its throttled updates). Drives per-texture + // fetch admission (LLViewerTextureList::updateImageDecodePriority -> mOnScreen). bool mInFrustum = false; + // How far past the screen boundary the projected disc sits, as a fraction of + // screen size (0 = on screen). Feeds the frustum-allowance falloff so barely + // out-of-view content keeps its resolution. Maintained with mInFrustum. + F32 mFrustumOverflow = 0.f; // value of gFrameCount the last time the face was touched by LLViewerTextureList::updateImageDecodePriority U32 mLastTextureUpdate = 0; + // Cached per-channel streaming coverage (repeat-adjusted screen pixels), + // refreshed at the mLastTextureUpdate cadence and shared by every texture + // on this face. 0 = degenerate / not yet measured. See + // update_face_stream_vsize in llviewertexturelist.cpp. + F32 mStreamVSize[LLRender::NUM_TEXTURE_CHANNELS] = {}; private: LLPointer mVertexBuffer; @@ -309,6 +320,11 @@ private: // gFrameTimeSeconds when mPixelArea was last updated F32 mLastPixelAreaUpdate = 0.f; + // Last cos-angle-to-view-dir and projected radius computed by calcPixelArea; + // reused by its throttled early-return so the overlap test gets real values. + F32 mLastCosAngleToViewDir = 1.f; + F32 mLastRadius = 0.f; + // virtual size of face in texture area (mPixelArea adjusted by texture repeats) // used to determine desired resolution of texture F32 mVSize; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 0d50ba6fe2..be83fd0279 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -140,6 +140,27 @@ void render_disconnected_background(); void getProfileStatsContext(boost::json::object& stats); std::string getProfileStatsFilename(); +// Adaptive texture-pipeline budget: spend the frame-time headroom between the +// frame we're rendering and TextureLoadTargetFPS, clamped [2ms, max]. Headroom +// is measured (smoothed frame interval minus the pipeline's own last spend), +// so fast machines get big budgets and machines already at target hold the +// floor. Only consumed while queues have work - drain loops exit when empty. +static F32 sTexturePipelineSpent = 0.f; +static F32 texture_pipeline_budget() +{ + static LLCachedControl target_fps(gSavedSettings, "TextureLoadTargetFPS", 60.f); + static LLCachedControl max_ms(gSavedSettings, "TextureLoadBudgetMaxMS", 10.f); + static F32 smoothed_other = 0.008f; + F32 other = llmax(gFrameIntervalSeconds.value() - sTexturePipelineSpent, 0.f); + // A single multi-second hitch must not crater the budget for the following + // frames, so cap the sample before it enters the EMA. + other = llmin(other, 0.1f); + smoothed_other = smoothed_other * 0.9f + other * 0.1f; + F32 target_interval = 1.f / llclamp((F32)target_fps, 15.f, 240.f); + F32 headroom = target_interval - smoothed_other; + return llclamp(headroom, 0.002f, llclamp((F32)max_ms, 2.f, 50.f) * 0.001f); +} + void display_startup() { if ( !gViewerWindow @@ -500,9 +521,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); - F32 max_image_decode_time = 0.050f * gFrameIntervalSeconds.value(); // 50 ms/second decode time - max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f); // min 2ms/frame, max 5ms/frame) + F32 max_image_decode_time = texture_pipeline_budget(); + LLTimer tex_timer; gTextureList.updateImages(max_image_decode_time); + sTexturePipelineSpent = tex_timer.getElapsedTimeF32(); } { @@ -862,9 +884,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); - F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time - max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame) + F32 max_image_decode_time = texture_pipeline_budget(); + LLTimer tex_timer; gTextureList.updateImages(max_image_decode_time); + sTexturePipelineSpent = tex_timer.getElapsedTimeF32(); } { diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 80daffdbf3..90facfa333 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -571,6 +571,20 @@ void LLViewerTexture::updateClass() sPixelToTexelRatio += llmax((F32)relax_rate, 0.f) * dt; } // else: hold in the hysteresis band. + + // Allocation failures outrank the byte estimate: if setManualImage hit + // GL_OUT_OF_MEMORY since last frame, the CPU-side vram_used estimate has + // diverged from reality, so step the ratio down now regardless of the band. + static U32 last_oom_count = 0; + U32 oom_count = LLImageGL::sOOMErrorCount.load(); + if (oom_count > last_oom_count) + { + U32 new_events = llmin(oom_count - last_oom_count, (U32)5); + sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * 1.0f * (F32)new_events; + last_oom_count = oom_count; + LL_WARNS_ONCE("Texture") << "GL out-of-memory during texture upload triggered a pixel:texel ratio backoff." << LL_ENDL; + } + sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max); // Keep the GC-suspend frame current while backgrounded. This suppresses @@ -1483,6 +1497,16 @@ void LLViewerFetchedTexture::postCreateTexture() setActive(); + // Start the visibility-GC clock at creation. A texture fetched but never + // drawn (occluded, or the camera moved on) would otherwise keep + // mLastBindFrame == 0 forever, and the GC skips never-bound textures - it + // would hold residency indefinitely. Anchoring here means it ages out on + // the normal GC cooldown unless a real draw stamps it first. + if (mGLTexturep.notNull() && mGLTexturep->mLastBindFrame == 0) + { + mGLTexturep->mLastBindFrame = LLFrameTimer::getFrameCount(); + } + // rebuild any volumes that are using this texture for sculpts in case their LoD has changed for (U32 i = 0; i < mNumVolumes[LLRender::SCULPT_TEX]; ++i) { @@ -1942,7 +1966,14 @@ bool LLViewerFetchedTexture::updateFetch() S32 current_discard = getCurrentDiscardLevelForFetching(); S32 desired_discard = getDesiredDiscardLevel(); - F32 decode_priority = mMaxVirtualSize; + + // Two-tier fetch priority, constantly drained by the fetch worker (it + // sorts HTTP dispatch by this value): any on-screen texture outranks every + // off-screen one. Within the visible tier, coverage orders by size on + // screen; within the off-screen tier the same geometric coverage + // (area/dist^2) orders by proximity. addTextureStats clamps + // mMaxVirtualSize to sMaxVirtualSize, so the band offset is strict. + F32 decode_priority = mMaxVirtualSize + (mOnScreen ? sMaxVirtualSize : 0.f); if (mIsFetching) { @@ -2003,6 +2034,29 @@ bool LLViewerFetchedTexture::updateFetch() // discards are served from the GL mip pyramid via scaleDown. desired_discard = llmin(desired_discard, (S32)mCodecMaxDiscardLevel); + // Progressive refinement: when resident data is much coarser than desired, + // fetch in steps of TextureFetchStepMips instead of jumping straight to the + // final discard. Each step is small, decodes fast, and shows on screen + // while the next chains behind it (the per-frame fast pump makes chaining + // nearly free). Without this, seen-before textures (dims known, so the + // coarse first-fetch fallback never applies) sat blurry for the whole + // full-file read+decode, then popped. Boosted/pinned content still jumps. + static LLCachedControl fetch_step(gSavedSettings, "TextureFetchStepMips", 2); + const S32 step = (S32)fetch_step; + if (step > 0 + && current_discard >= 0 + && desired_discard < current_discard - step + && mBoostLevel < LLGLTexture::BOOST_HIGH + && mUseMipMaps + && !mDontDiscard + && !isAgentAvatarBoost(mBoostLevel)) + { + // Re-clamp: current can sit past codec max after scaleDown (GL + // pyramid goes deeper than the codestream) - a stepped request + // above codec max reaches the decoder with an invalid discard. + desired_discard = llmin(current_discard - step, (S32)mCodecMaxDiscardLevel); + } + bool make_request = true; if (decode_priority <= 0) { @@ -2028,30 +2082,9 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); make_request = false; } - else - { - // Only fetch streamed world textures the renderer is actually drawing - // (mLastBindFrame is stamped per drawn frame). Out-of-view content gets - // no residency. Exempt: boosted/UI, avatar bakes, textures with loaded - // callbacks, and bake uploads. - static LLCachedControl vis_frames(gSavedSettings, "TextureFetchVisibilityFrames", 5); - const bool visibility_gated = mBoostLevel < LLGLTexture::BOOST_HIGH - && mUseMipMaps - && !mDontDiscard - && !isAgentAvatarBoost(mBoostLevel) - && !mForceToSaveRawImage - && mLoadedCallbackList.empty(); - if (visibility_gated && mGLTexturep.notNull()) - { - const U32 last = mGLTexturep->mLastBindFrame; - const U32 now = LLFrameTimer::getFrameCount(); - if (last == 0 || now - last > llmax((U32)vis_frames, 1u)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - not visible"); - make_request = false; - } - } - } + // No visibility gate here: off-screen content still fetches, just in the + // low-priority band (decode_priority above), so the worker services it only + // after visible work. Residency stays with the GC (computeDesiredDiscard). if (make_request) { @@ -3035,6 +3068,23 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c // (a 2048 map can't hit its own resolution on a 1080p screen), which reads // as everything being blurry. Pressure still evicts by lowering R_global. ideal = llmax(ideal, 0.f); + + // Frustum allowance: a falloff on how unloaded out-of-view content gets, + // by how far out it is. Grazing the edge keeps full resolution; at + // TextureFrustumAllowance (fraction of a screen) past the edge it reaches + // the deepest mip, lerped between. Keeps barely-out-of-view textures + // resident so a camera swing back doesn't refetch them. Applied to the + // continuous ideal so it shares the hysteresis dead-band below - applied + // after it, camera motion made desired flap a mip at a time and churned + // the fetch/scaleDown queues. + static LLCachedControl frustum_allowance(gSavedSettings, "TextureFrustumAllowance", 0.2f); + if (!avatar_bake && mFrustumOverflow > 0.f) + { + const F32 f = llclamp(mFrustumOverflow / llmax((F32)frustum_allowance, 0.01f), 0.f, 1.f); + ideal += f * ((F32)dim_max_i - ideal); + mLastOffScreenFrame = LLFrameTimer::getFrameCount(); + } + const S32 target = (S32)floor(ideal); // Hysteresis: a texture at discard C is "happy" while floor(ideal) == C, @@ -3069,7 +3119,12 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c // mip by gc_step, walking gradually toward the deepest mip instead of slamming. // Content drawn within the last cooldown stays full-res, so a fast camera pan // finds it only a step or two coarse on the way back. Resets when drawn again. - if (!avatar_bake) + // + // Out-of-frustum content is governed by the frustum allowance above instead + // (spatial falloff, not bind staleness) - without this exclusion the GC would + // walk barely-out-of-view content to the deepest mip within a second and the + // allowance would protect nothing. + if (!avatar_bake && mFrustumOverflow <= 0.f) { if (LLImageGL* gli = getGLTexture()) { @@ -3077,8 +3132,9 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c static LLCachedControl gc_step_mips(gSavedSettings, "TextureGCStepMips", 1); constexpr U32 GC_RESUME_GRACE_FRAMES = 10; const U32 now = LLFrameTimer::getFrameCount(); - if (gli->mLastBindFrame > 0 // drawn at least once - && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background + if (gli->mLastBindFrame > 0 // drawn, or anchored at creation (postCreateTexture) + && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES // not just back from background + && now - mLastOffScreenFrame > GC_RESUME_GRACE_FRAMES) // re-entering content gets one grace window to be drawn and re-stamp before staleness is judged { const U32 cooldown = llmax((U32)gc_cooldown_frames, 1u); const S32 periods = (S32)((now - gli->mLastBindFrame) / cooldown); diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index cc9fbe5e48..90993ed109 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -215,6 +215,25 @@ protected: F32 mChannelCoverage[4] = { 0.f, 0.f, 0.f, 0.f }; F32 mChannelCoverageMin[4] = { 0.f, 0.f, 0.f, 0.f }; + // Any face using this texture projects onto the screen (published alongside + // the coverage above). Selects the fetch-priority band in updateFetch. + // Defaults true so unmeasured textures (fresh objects, no-face users) are + // never starved. + bool mOnScreen = true; + + // How far out of frustum the texture's least-out-of-view use sits, as a + // fraction of screen size (0 = on screen). Drives the frustum-allowance + // falloff in computeDesiredDiscard. + F32 mFrustumOverflow = 0.f; + + // Last frame this texture was out of frustum (mFrustumOverflow > 0). The + // GC in computeDesiredDiscard gives re-entering content one grace window to + // be drawn and re-stamp mLastBindFrame before its staleness is judged. + mutable U32 mLastOffScreenFrame = 0; + + // Membership flag for LLViewerTextureList::mFastFetchList (dedup). + bool mInFastFetchList = false; + ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; LLFrameTimer mLastFaceListUpdateTimer ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 3e1481f8b4..ad05a0273b 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -378,6 +378,12 @@ void LLViewerTextureList::shutdown() } mFastCacheList.clear(); + for (auto& img : mFastFetchList) + { + img->mInFastFetchList = false; + } + mFastFetchList.clear(); + mUUIDMap.clear(); mImageList.clear(); @@ -871,6 +877,36 @@ void LLViewerTextureList::updateImages(F32 max_time) remaining_time -= updateImagesFetchTextures(remaining_time); remaining_time = llmax(remaining_time, min_time); + // Fast pump: advance every in-flight fetch each frame so results are + // collected and creates scheduled the frame they're ready, instead of + // one state transition per round-robin visit. Cheap - no face scans - + // and bounded by the fetch worker's own concurrency. + LLTimer fast_fetch_timer; + S32 min_count = 32; + for (size_t i = 0; i < mFastFetchList.size(); ) + { + LLViewerFetchedTexture* imagep = mFastFetchList[i]; + if (imagep->getNumRefs() > 1) + { + imagep->updateFetch(); + } + if (imagep->getNumRefs() <= 1 || (!imagep->isFetching() && !imagep->hasFetcher())) + { + imagep->mInFastFetchList = false; + mFastFetchList[i] = mFastFetchList.back(); + mFastFetchList.pop_back(); + } + else + { + ++i; + } + + if (fast_fetch_timer.getElapsedTimeF32() > remaining_time && --min_count <= 0) + { + break; + } + } + //handle results from decode threads updateImagesCreateTextures(remaining_time); @@ -915,6 +951,187 @@ void LLViewerTextureList::clearFetchingRequests() extern bool gCubeSnapshot; +// Refresh a face's cached per-channel streaming coverage (face->mStreamVSize). +// This is the most-demanding-point measurement plus each channel's own UV +// repeat source, computed ONCE per face per update cadence and shared by every +// texture registered on the face. Doing the material/transform pointer chases +// per texture visit instead made updateImageDecodePriority several times more +// expensive per face than develop's, and since the round-robin runs in a fixed +// per-frame time slice, that directly cut how many textures advance their +// load state each frame - the whole pipeline paced slower. +static void update_face_stream_vsize(LLFace* face) +{ + // Bounds on the per-face UV repeat-area divisor (mined from the old + // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost + // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips + // coarser) so pathological UV scales can't explode either direction. + constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f; + constexpr F32 MAX_REPEAT_AREA = 128.f; + + LLViewerObject* objp = face->getViewerObject(); + + // Most-demanding-point measurement: the spec is that the LOWEST pixel:texel + // ratio governs, so pixel density is evaluated at the face's NEAREST point + // and applied to the face's true world area. A whole-face average + // (bounding-disc pixel area) under-resolves perspective surfaces: on a + // floor, the tile at your feet covers far more screen than the average + // tile, and the GPU samples fine mips right there. + const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents; + LLVector4a diag; + diag.setSub(ext[1], ext[0]); + // World area of the face ~ product of the two largest AABB dims (max + // pairwise product; robust for flat faces). + F32 dx = diag[0], dy = diag[1], dz = diag[2]; + F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz)); + // Pixels per meter at the nearest point. Distance floored: nearer than + // this the screen clamp below governs anyway. + F32 dist = llmax(face->mDistanceToCamera, 0.5f); + F32 ppm = LLDrawable::sCurPixelAngle / dist; + F32 face_px = area_world * ppm * ppm; + if (face_px <= 0.f) + { + // Degenerate extents: the face hasn't been through a geometry build + // yet (or a rigged face has no rigged extents) - it isn't renderable, + // so it must not be measured. Zero marks "skip": an invented + // placeholder value would become the texture's least-demanding "use" + // and, under TextureDownrezCoverageBias, drag the whole texture to + // its deepest mip (and it poisoned BP and PBR asymmetrically, since + // the two register faces at different points in the geometry + // lifecycle). + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + face->mStreamVSize[ch] = 0.f; + } + return; + } + + S32 te_offset = face->getTEOffset(); // offset is -1 if not inited + const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); + + // Shared, channel-independent chases - hoisted out of the channel loop. + const LLGLTFMaterial* gltf_mat = te ? te->getGLTFRenderMaterial() : nullptr; + const LLMaterial* mat = te ? te->getMaterialParams().get() : nullptr; + + // Continuously-animated scale (llSetTextureAnim SCALE) bypasses both + // static sources via mTextureMatrix - the live animated values win. + bool anim_scale = false; + F32 anim_ss = 0.f, anim_st = 0.f; + if (te) + { + if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr) + { + LLViewerTextureAnim* anim = vvo->mTextureAnimp; + if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE) + && (anim->mFace < 0 || anim->mFace == te_offset)) + { + anim_scale = true; + anim_ss = anim->mScaleS; + anim_st = anim->mScaleT; + } + } + } + + // Mesh atlas sub-rect: a face whose intrinsic UVs span only part of + // [0,1]^2 shows that fraction of the image. Applies identically to all + // channels - the per-channel transforms stack on the raw face UVs. + F32 span = 1.f; + if (te) + { + if (LLVolume* vol = objp->getVolume()) + { + if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces()) + { + const LLVolumeFace& vf = vol->getVolumeFace(te_offset); + F32 s = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0]) + * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1])); + if (s > 0.f) + { + span = s; + } + } + } + } + + // Avatar bonus: worn attachments get a coverage multiplier - avatars are + // what people look at, and rigged extents make attachment coverage + // measurement unreliable anyway. Multiplicative, not a slam. + static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); + const F32 boost = objp->isAttachment() ? llmax((F32)avatar_boost, 1.f) : 1.f; + + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + // Effective UV repeat AREA: the tiling term of texels-drawn-per- + // screen-pixel. More tiling => each tile smaller on screen => coarser + // mips suffice (penalty). Repeats < 1 (atlas/crop) => whole-image + // residency for a sub-rect legitimately demands more than its screen + // coverage (boost). + F32 repeats = 1.f; + if (te) + { + // UV scale source: every channel reads the repeat values ITS + // renderer actually applies. diffuse -> TE scale; Blinn + // normal/spec -> LLMaterial per-map repeats; PBR channels -> KHR + // texture_transform scale. Fallback is the TE scale - never a + // silent hardcoded 1. + F32 scale_s = te->getScaleS(); + F32 scale_t = te->getScaleT(); + if (ch >= LLRender::BASECOLOR_MAP) + { + // LLRender channel -> LLGLTFMaterial::TextureInfo + static const S32 gltf_info[4] = { + LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3) + LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4) + LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5) + LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6) + }; + if (gltf_mat) + { + const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[ch - LLRender::BASECOLOR_MAP]].mScale; + scale_s = s.mV[0]; + scale_t = s.mV[1]; + } + } + else if (ch == LLRender::NORMAL_MAP || ch == LLRender::SPECULAR_MAP) + { + // Blinn-Phong normal/specular maps carry their own repeats in + // LLMaterial - the renderer builds their texture matrices + // from these, NOT from the TE's diffuse scale. + if (mat) + { + if (ch == LLRender::NORMAL_MAP) + { + mat->getNormalRepeat(scale_s, scale_t); + } + else + { + mat->getSpecularRepeat(scale_s, scale_t); + } + } + } + + if (anim_scale) + { + scale_s = anim_ss; + scale_t = anim_st; + } + + repeats = fabsf(scale_s * scale_t) * span; + } + + repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA); + + // Apply the two sides of the repeat term in the right order relative + // to the screen clamp: tiling (repeats > 1) divides the nearest-point + // footprint BEFORE the clamp (one tile can't draw more pixels than + // the screen); atlas/crop (repeats < 1) boosts AFTER it (whole-image + // residency for a crop legitimately demands more than its screen + // coverage). + F32 tiling = llmax(repeats, 1.f); + F32 crop = llmin(repeats, 1.f); + face->mStreamVSize[ch] = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop * boost; + } +} + void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep, bool flush_images) { llassert(!gCubeSnapshot); @@ -931,13 +1148,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { - // Bounds on the per-face UV repeat-area divisor (mined from the old - // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost - // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips - // coarser) so pathological UV scales can't explode either direction. - constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f; - constexpr F32 MAX_REPEAT_AREA = 128.f; - // Per priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive): // the HIGHEST per-face effective coverage (= the lowest texels-per-pixel // use, the most demanding variant - drives desired discard) and the @@ -950,6 +1160,9 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 channel_coverage_min[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; bool bucket_used[4] = { false, false, false, false }; F32 max_coverage = 0.f; + bool on_screen = false; // any face's projected disc overlaps the screen + bool any_face = false; + F32 min_overflow = FLT_MAX; // least out-of-frustum use across faces U32 face_count = 0; @@ -1000,175 +1213,29 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 radius; F32 cos_angle_to_view_dir; if ((gFrameCount - face->mLastTextureUpdate) > 10) - { // only call calcPixelArea at most once every 10 frames for a given face - // this helps eliminate redundant calls to calcPixelArea for faces that have multiple textures - // assigned to them, such as is the case with GLTF materials or Blinn-Phong materials - face->mInFrustum = face->calcPixelArea(cos_angle_to_view_dir, radius); + { // refresh the face's geometry + cached coverage at most once every + // 10 frames; every texture/channel sharing this face (GLTF and + // Blinn-Phong materials) reuses the cache instead of redoing the + // measurement. (calcPixelArea maintains face->mInFrustum itself.) + face->calcPixelArea(cos_angle_to_view_dir, radius); + update_face_stream_vsize(face); face->mLastTextureUpdate = gFrameCount; } - // Most-demanding-point measurement: the spec is that the - // LOWEST pixel:texel ratio governs, so pixel density is - // evaluated at the face's NEAREST point and applied to the - // face's true world area. The previous whole-face average - // (bounding-disc pixel area) under-resolved perspective - // surfaces: on a floor, the tile at your feet covers far - // more screen than the average tile, and the GPU samples - // fine mips right there - tiled (PBR-heavy) content went - // soft while untiled content looked fine. - const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents; - LLVector4a diag; - diag.setSub(ext[1], ext[0]); - // World area of the face ~ product of the two largest AABB - // dims (max pairwise product; robust for flat faces). - F32 dx = diag[0], dy = diag[1], dz = diag[2]; - F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz)); - // Pixels per meter at the nearest point. Distance floored: - // nearer than this the screen clamp below governs anyway. - F32 dist = llmax(face->mDistanceToCamera, 0.5f); - F32 ppm = LLDrawable::sCurPixelAngle / dist; - F32 face_px = area_world * ppm * ppm; - if (face_px <= 0.f) + // Cached measurement - see update_face_stream_vsize above. + // Zero = degenerate extents / not yet through a geometry + // build: not renderable, must not be measured (a + // placeholder value would poison the per-bucket MIN bound + // and drag the texture to its deepest mip). + F32 vsize = face->mStreamVSize[i]; + if (vsize <= 0.f) { - // Degenerate extents: the face hasn't been through a - // geometry build yet (or a rigged face has no rigged - // extents) - it isn't renderable, so it must not be - // measured. Skipping matters especially for the - // per-bucket MIN bound: any invented placeholder - // value (the old fallback hit LLFace::init's 16px - // default) becomes the texture's least-demanding - // "use" and, under TextureDownrezCoverageBias, drags - // the whole texture to its deepest mip - and it - // poisoned BP and PBR asymmetrically since the two - // systems register faces at different points in the - // geometry lifecycle. continue; } - // Effective UV repeat AREA across this face: the tiling - // term of texels-drawn-per-screen-pixel. More tiling => - // each tile is smaller on screen => coarser mips suffice - // (penalty). Repeats < 1 (atlas/crop) => only a sub-rect - // of the image is shown, but discard levels are whole- - // image, so the full image must be resident at 1/repeats - // times the crop's pixel count (boost). - S32 te_offset = face->getTEOffset(); // offset is -1 if not inited - LLViewerObject* objp = face->getViewerObject(); - const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); - - F32 repeats = 1.f; - if (te) - { - // UV scale source: every channel reads the repeat - // values ITS renderer actually applies, then flows - // through the identical pipeline below. Sources: - // diffuse -> TE scale - // Blinn normal/spec -> LLMaterial per-map repeats - // PBR channels -> KHR texture_transform scale - // Fallback for any missing material is the TE scale - - // never a silent hardcoded 1. - F32 scale_s = te->getScaleS(); - F32 scale_t = te->getScaleT(); - if (i >= LLRender::BASECOLOR_MAP) - { - // LLRender channel -> LLGLTFMaterial::TextureInfo - static const S32 gltf_info[4] = { - LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3) - LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4) - LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5) - LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6) - }; - if (const LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial()) - { - const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[i - LLRender::BASECOLOR_MAP]].mScale; - scale_s = s.mV[0]; - scale_t = s.mV[1]; - } - } - else if (i == LLRender::NORMAL_MAP || i == LLRender::SPECULAR_MAP) - { - // Blinn-Phong normal/specular maps carry their own - // repeats in LLMaterial - the renderer builds their - // texture matrices from these, NOT from the TE's - // diffuse scale. Reading the diffuse scale here made - // Blinn normals scale differently than PBR normals - // (whose per-channel transform IS read above). - if (const LLMaterial* mat = te->getMaterialParams().get()) - { - if (i == LLRender::NORMAL_MAP) - { - mat->getNormalRepeat(scale_s, scale_t); - } - else - { - mat->getSpecularRepeat(scale_s, scale_t); - } - } - } - - // Continuously-animated scale (llSetTextureAnim SCALE) - // bypasses both static sources via mTextureMatrix - - // the live animated values win on either path. - if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr) - { - LLViewerTextureAnim* anim = vvo->mTextureAnimp; - if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE) - && (anim->mFace < 0 || anim->mFace == te_offset)) - { - scale_s = anim->mScaleS; - scale_t = anim->mScaleT; - } - } - - repeats = fabsf(scale_s * scale_t); - - // Mesh atlas sub-rect: a face whose intrinsic UVs span - // only part of [0,1]^2 shows that fraction of the - // image. Applies identically to both paths - the - // transforms above stack on the raw face UVs. - if (LLVolume* vol = objp->getVolume()) - { - if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces()) - { - const LLVolumeFace& vf = vol->getVolumeFace(te_offset); - F32 span = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0]) - * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1])); - if (span > 0.f) - { - repeats *= span; - } - } - } - } - - repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA); - - // Apply the two sides of the repeat term in the right - // order relative to the screen clamp: - // - tiling (repeats > 1): the per-tile footprint at the - // nearest point, THEN clamped - one tile can't draw - // more pixels than the screen. (Clamping the whole - // face first and then dividing crushed near tiles.) - // - atlas/crop (repeats < 1): boost AFTER the clamp - - // whole-image residency for a crop legitimately - // demands more than its screen coverage. - F32 tiling = llmax(repeats, 1.f); - F32 crop = llmin(repeats, 1.f); - F32 vsize = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop; - - // Avatar bonus: worn attachments get a coverage - // multiplier - avatars are what people look at, and - // rigged extents make attachment coverage measurement - // unreliable anyway. Multiplicative, not a slam: a - // nearby avatar gains ~a mip of headroom while a distant - // one still downrezzes naturally with its coverage. - // (System-avatar bakes get the same bonus in the - // no-faces branch below.) - if (objp->isAttachment()) - { - static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); - vsize *= llmax((F32)avatar_boost, 1.f); - } + any_face = true; + on_screen = on_screen || face->mInFrustum; + min_overflow = llmin(min_overflow, face->mFrustumOverflow); if (bucket >= 0 && bucket < 4) { @@ -1242,6 +1309,15 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag imagep->mChannelCoverage[b] = channel_coverage[b]; imagep->mChannelCoverageMin[b] = (channel_coverage_min[b] == FLT_MAX) ? 0.f : channel_coverage_min[b]; } + + // Fetch admission signal: false only when faces were actually scanned and + // every one projects off screen. Textures with no scannable faces (bakes, + // spotlights, the >1024-face boost path, not-yet-built geometry) stay + // eligible - blocking them is what stalls load-in. + imagep->mOnScreen = on_screen || !any_face; + // Least out-of-frustum use governs the allowance falloff; unknown = 0 + // (no penalty), same reasoning as mOnScreen. + imagep->mFrustumOverflow = any_face ? min_overflow : 0.f; } #if 0 @@ -1510,6 +1586,18 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) { updateImageDecodePriority(imagep); imagep->updateFetch(); + + // Fast-pump membership: textures with an active fetch get + // updateFetch every frame (in updateImages) instead of waiting + // ~a sweep period per state transition - that wait, times the + // 2-4 transitions a load needs, was the measured throughput + // ceiling. Purely additive: the sweep still pumps everything, + // so fetches started by any other path can never strand. + if ((imagep->isFetching() || imagep->hasFetcher()) && !imagep->mInFastFetchList) + { + imagep->mInFastFetchList = true; + mFastFetchList.push_back(imagep); + } } if (timer.getElapsedTimeF32() > max_time) diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 931f2ed50e..7004238995 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -34,6 +34,7 @@ #include "llviewertexture.h" #include "llui.h" #include +#include #include #include "lluiimage.h" @@ -225,6 +226,10 @@ public: image_list_t mCallbackList; image_list_t mFastCacheList; + // In-flight fetches pumped every frame (additive to the round-robin + // sweep, which remains the universal pump). See updateImages. + std::vector > mFastFetchList; + bool mForceResetTextureStats; // to make "for (auto& imagep : gTextureList)" work -- cgit v1.3 From 91c11c8d87c963f2b17e45b525396bf8c8386c26 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 22 Jul 2026 00:38:20 -0400 Subject: Revert "Geenz/texture loading speed (#5985)" This reverts commit a937b237de3651e79cdb517f14381a5bdd4c844b. --- indra/llrender/llgl.cpp | 6 - indra/llrender/llgl.h | 1 - indra/llrender/llimagegl.cpp | 14 +- indra/newview/app_settings/settings.xml | 37 +-- indra/newview/llface.cpp | 22 -- indra/newview/llface.h | 18 +- indra/newview/llviewerdisplay.cpp | 31 +-- indra/newview/llviewertexture.cpp | 112 +++------ indra/newview/llviewertexture.h | 19 -- indra/newview/llviewertexturelist.cpp | 426 +++++++++++++------------------- indra/newview/llviewertexturelist.h | 5 - 11 files changed, 209 insertions(+), 482 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 0e59c449db..4584ed1d86 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2370,12 +2370,6 @@ void clear_glerror() glGetError(); } -void drain_glerror() -{ - // bounded: a lost/reset context can return errors indefinitely - for (S32 i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} -} - /////////////////////////////////////////////////////////////// // // LLGLState diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index 3f9a9de70a..e1ab2a49e6 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -159,7 +159,6 @@ void log_glerror(); void assert_glerror(); void clear_glerror(); -void drain_glerror(); // pops ALL pending GL error flags (bounded so a lost/reset context that returns errors forever cannot hang the caller); use before an attributable glGetError check. # define stop_glerror() assert_glerror() diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index b3cd8d2896..c8a23d873e 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1507,7 +1507,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt // Drain stale GL errors so an OOM detected below belongs to this alloc. // Otherwise a failed glTexImage2D is swallowed in release while // alloc_tex_image still counts the bytes, inflating the used-VRAM figure. - drain_glerror(); + while (glGetError() != GL_NO_ERROR) {} const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) @@ -1919,8 +1919,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre //----------------------------------------------------------------------------------------------- GLenum error ; - S32 error_count = 0 ; - while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16) + while((error = glGetError()) != GL_NO_ERROR) { LL_WARNS() << "GL Error happens before reading back texture. Error code: " << error << LL_ENDL ; } @@ -1980,8 +1979,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ; imageraw->deleteData() ; - error_count = 0 ; - while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16) + while((error = glGetError()) != GL_NO_ERROR) { LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ; } @@ -2550,10 +2548,8 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Don't let eviction re-arm the GC: the glGenerateMipmap re-bind below would - // otherwise stamp mLastBindFrame, so the next computeDesiredDiscard treats the - // just-evicted texture as freshly drawn, un-floors it, and re-fetches - the - // evict/refetch oscillation. + // Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below + // would otherwise stamp mLastBindFrame and keep the texture fetch-eligible. LLImageGLStampBypass no_stamp; if (mTarget != GL_TEXTURE_2D diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 1c2c2d0559..45a7dced5c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11992,49 +11992,16 @@ Value 1 - TextureLoadTargetFPS + TextureFetchVisibilityFrames Comment - Frame rate the viewer is willing to drop to while loading textures. The texture pipeline's per-frame budget is the headroom between this and the actual frame cost, so fast machines load aggressively and slow ones hold their frame rate. - Persist - 1 - Type - F32 - Value - 30.0 - - TextureLoadBudgetMaxMS - - Comment - Hard cap, in milliseconds per frame, on the adaptive texture-pipeline budget. - Persist - 1 - Type - F32 - Value - 10.0 - - TextureFetchStepMips - - Comment - Fetch refinement step, in mip levels: a texture whose resident data is coarser than desired by more than this fetches in steps of this size instead of jumping straight to the final resolution, so it sharpens progressively instead of sitting blurry then popping. 0 = jump directly. Boosted/pinned textures always jump. + Only fetch a texture if it was drawn within this many rendered frames; out-of-view content isn't fetched. Minimum 1 (0 is clamped up). Boosted/UI textures, avatar bakes, and callback textures are exempt. Persist 1 Type U32 Value - 2 - - TextureFrustumAllowance - - Comment - Falloff width for out-of-frustum texture resolution, as a fraction of screen size. Content grazing the screen edge keeps full resolution; content this far past the edge reaches the deepest mip, lerped between. Keeps barely-out-of-view textures resident so panning back doesn't refetch them. - Persist 1 - Type - F32 - Value - 0.5 TextureDecodeDisabled diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index c96e1bd3b0..e34bea63ef 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -2297,8 +2297,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) // don't update every frame if (gFrameTimeSeconds - mLastPixelAreaUpdate < PIXEL_AREA_UPDATE_PERIOD) { - cos_angle_to_view_dir = mLastCosAngleToViewDir; - radius = mLastRadius; return true; } @@ -2370,7 +2368,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) // no rigged extents, zero out bounding box and skip update mRiggedExtents[0] = mRiggedExtents[1] = LLVector4a(0.f, 0.f, 0.f); - mInFrustum = false; return false; } @@ -2425,7 +2422,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) if(!camera->AABBInFrustum(center, size)) { mImportanceToCamera = 0.f ; - mInFrustum = false; return false ; } if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum @@ -2454,24 +2450,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; } - // On-screen test: does the face's projected disc overlap the screen disc? - // (Same construction as adjustPartialOverlapPixelArea.) Behind-camera faces - // get acos(cos) near pi and fall out; the generous screen radius errs toward - // "on screen" so fetch admission never starves edge content. mFrustumOverflow - // is how far past the boundary the disc sits, as a fraction of screen size - - // 0 on screen, 0.1 = 10% of a screen out - and feeds the frustum allowance - // falloff in computeDesiredDiscard. - { - F32 center_px = acosf(llclamp(cos_angle_to_view_dir, -1.f, 1.f)) * LLDrawable::sCurPixelAngle; - F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); - F32 past_edge = center_px - radius - screen_radius; - mInFrustum = past_edge <= 5.f; - mFrustumOverflow = llmax(past_edge - 5.f, 0.f) / screen_radius; - } - - mLastCosAngleToViewDir = cos_angle_to_view_dir; - mLastRadius = radius; - return true ; } diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 0b0a17b8c8..71ba3d0f2f 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -269,21 +269,10 @@ public: // return mSkinInfo->mHash or 0 if mSkinInfo is null U64 getSkinHash(); - // True if this face's projected bounding disc overlaps the screen - maintained - // by calcPixelArea() (sticky between its throttled updates). Drives per-texture - // fetch admission (LLViewerTextureList::updateImageDecodePriority -> mOnScreen). + // true if face was recently in the main camera frustum according to LLViewerTextureList updates bool mInFrustum = false; - // How far past the screen boundary the projected disc sits, as a fraction of - // screen size (0 = on screen). Feeds the frustum-allowance falloff so barely - // out-of-view content keeps its resolution. Maintained with mInFrustum. - F32 mFrustumOverflow = 0.f; // value of gFrameCount the last time the face was touched by LLViewerTextureList::updateImageDecodePriority U32 mLastTextureUpdate = 0; - // Cached per-channel streaming coverage (repeat-adjusted screen pixels), - // refreshed at the mLastTextureUpdate cadence and shared by every texture - // on this face. 0 = degenerate / not yet measured. See - // update_face_stream_vsize in llviewertexturelist.cpp. - F32 mStreamVSize[LLRender::NUM_TEXTURE_CHANNELS] = {}; private: LLPointer mVertexBuffer; @@ -320,11 +309,6 @@ private: // gFrameTimeSeconds when mPixelArea was last updated F32 mLastPixelAreaUpdate = 0.f; - // Last cos-angle-to-view-dir and projected radius computed by calcPixelArea; - // reused by its throttled early-return so the overlap test gets real values. - F32 mLastCosAngleToViewDir = 1.f; - F32 mLastRadius = 0.f; - // virtual size of face in texture area (mPixelArea adjusted by texture repeats) // used to determine desired resolution of texture F32 mVSize; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index be83fd0279..0d50ba6fe2 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -140,27 +140,6 @@ void render_disconnected_background(); void getProfileStatsContext(boost::json::object& stats); std::string getProfileStatsFilename(); -// Adaptive texture-pipeline budget: spend the frame-time headroom between the -// frame we're rendering and TextureLoadTargetFPS, clamped [2ms, max]. Headroom -// is measured (smoothed frame interval minus the pipeline's own last spend), -// so fast machines get big budgets and machines already at target hold the -// floor. Only consumed while queues have work - drain loops exit when empty. -static F32 sTexturePipelineSpent = 0.f; -static F32 texture_pipeline_budget() -{ - static LLCachedControl target_fps(gSavedSettings, "TextureLoadTargetFPS", 60.f); - static LLCachedControl max_ms(gSavedSettings, "TextureLoadBudgetMaxMS", 10.f); - static F32 smoothed_other = 0.008f; - F32 other = llmax(gFrameIntervalSeconds.value() - sTexturePipelineSpent, 0.f); - // A single multi-second hitch must not crater the budget for the following - // frames, so cap the sample before it enters the EMA. - other = llmin(other, 0.1f); - smoothed_other = smoothed_other * 0.9f + other * 0.1f; - F32 target_interval = 1.f / llclamp((F32)target_fps, 15.f, 240.f); - F32 headroom = target_interval - smoothed_other; - return llclamp(headroom, 0.002f, llclamp((F32)max_ms, 2.f, 50.f) * 0.001f); -} - void display_startup() { if ( !gViewerWindow @@ -521,10 +500,9 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); - F32 max_image_decode_time = texture_pipeline_budget(); - LLTimer tex_timer; + F32 max_image_decode_time = 0.050f * gFrameIntervalSeconds.value(); // 50 ms/second decode time + max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f); // min 2ms/frame, max 5ms/frame) gTextureList.updateImages(max_image_decode_time); - sTexturePipelineSpent = tex_timer.getElapsedTimeF32(); } { @@ -884,10 +862,9 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); - F32 max_image_decode_time = texture_pipeline_budget(); - LLTimer tex_timer; + F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time + max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame) gTextureList.updateImages(max_image_decode_time); - sTexturePipelineSpent = tex_timer.getElapsedTimeF32(); } { diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 90facfa333..80daffdbf3 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -571,20 +571,6 @@ void LLViewerTexture::updateClass() sPixelToTexelRatio += llmax((F32)relax_rate, 0.f) * dt; } // else: hold in the hysteresis band. - - // Allocation failures outrank the byte estimate: if setManualImage hit - // GL_OUT_OF_MEMORY since last frame, the CPU-side vram_used estimate has - // diverged from reality, so step the ratio down now regardless of the band. - static U32 last_oom_count = 0; - U32 oom_count = LLImageGL::sOOMErrorCount.load(); - if (oom_count > last_oom_count) - { - U32 new_events = llmin(oom_count - last_oom_count, (U32)5); - sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * 1.0f * (F32)new_events; - last_oom_count = oom_count; - LL_WARNS_ONCE("Texture") << "GL out-of-memory during texture upload triggered a pixel:texel ratio backoff." << LL_ENDL; - } - sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max); // Keep the GC-suspend frame current while backgrounded. This suppresses @@ -1497,16 +1483,6 @@ void LLViewerFetchedTexture::postCreateTexture() setActive(); - // Start the visibility-GC clock at creation. A texture fetched but never - // drawn (occluded, or the camera moved on) would otherwise keep - // mLastBindFrame == 0 forever, and the GC skips never-bound textures - it - // would hold residency indefinitely. Anchoring here means it ages out on - // the normal GC cooldown unless a real draw stamps it first. - if (mGLTexturep.notNull() && mGLTexturep->mLastBindFrame == 0) - { - mGLTexturep->mLastBindFrame = LLFrameTimer::getFrameCount(); - } - // rebuild any volumes that are using this texture for sculpts in case their LoD has changed for (U32 i = 0; i < mNumVolumes[LLRender::SCULPT_TEX]; ++i) { @@ -1966,14 +1942,7 @@ bool LLViewerFetchedTexture::updateFetch() S32 current_discard = getCurrentDiscardLevelForFetching(); S32 desired_discard = getDesiredDiscardLevel(); - - // Two-tier fetch priority, constantly drained by the fetch worker (it - // sorts HTTP dispatch by this value): any on-screen texture outranks every - // off-screen one. Within the visible tier, coverage orders by size on - // screen; within the off-screen tier the same geometric coverage - // (area/dist^2) orders by proximity. addTextureStats clamps - // mMaxVirtualSize to sMaxVirtualSize, so the band offset is strict. - F32 decode_priority = mMaxVirtualSize + (mOnScreen ? sMaxVirtualSize : 0.f); + F32 decode_priority = mMaxVirtualSize; if (mIsFetching) { @@ -2034,29 +2003,6 @@ bool LLViewerFetchedTexture::updateFetch() // discards are served from the GL mip pyramid via scaleDown. desired_discard = llmin(desired_discard, (S32)mCodecMaxDiscardLevel); - // Progressive refinement: when resident data is much coarser than desired, - // fetch in steps of TextureFetchStepMips instead of jumping straight to the - // final discard. Each step is small, decodes fast, and shows on screen - // while the next chains behind it (the per-frame fast pump makes chaining - // nearly free). Without this, seen-before textures (dims known, so the - // coarse first-fetch fallback never applies) sat blurry for the whole - // full-file read+decode, then popped. Boosted/pinned content still jumps. - static LLCachedControl fetch_step(gSavedSettings, "TextureFetchStepMips", 2); - const S32 step = (S32)fetch_step; - if (step > 0 - && current_discard >= 0 - && desired_discard < current_discard - step - && mBoostLevel < LLGLTexture::BOOST_HIGH - && mUseMipMaps - && !mDontDiscard - && !isAgentAvatarBoost(mBoostLevel)) - { - // Re-clamp: current can sit past codec max after scaleDown (GL - // pyramid goes deeper than the codestream) - a stepped request - // above codec max reaches the decoder with an invalid discard. - desired_discard = llmin(current_discard - step, (S32)mCodecMaxDiscardLevel); - } - bool make_request = true; if (decode_priority <= 0) { @@ -2082,9 +2028,30 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); make_request = false; } - // No visibility gate here: off-screen content still fetches, just in the - // low-priority band (decode_priority above), so the worker services it only - // after visible work. Residency stays with the GC (computeDesiredDiscard). + else + { + // Only fetch streamed world textures the renderer is actually drawing + // (mLastBindFrame is stamped per drawn frame). Out-of-view content gets + // no residency. Exempt: boosted/UI, avatar bakes, textures with loaded + // callbacks, and bake uploads. + static LLCachedControl vis_frames(gSavedSettings, "TextureFetchVisibilityFrames", 5); + const bool visibility_gated = mBoostLevel < LLGLTexture::BOOST_HIGH + && mUseMipMaps + && !mDontDiscard + && !isAgentAvatarBoost(mBoostLevel) + && !mForceToSaveRawImage + && mLoadedCallbackList.empty(); + if (visibility_gated && mGLTexturep.notNull()) + { + const U32 last = mGLTexturep->mLastBindFrame; + const U32 now = LLFrameTimer::getFrameCount(); + if (last == 0 || now - last > llmax((U32)vis_frames, 1u)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - not visible"); + make_request = false; + } + } + } if (make_request) { @@ -3068,23 +3035,6 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c // (a 2048 map can't hit its own resolution on a 1080p screen), which reads // as everything being blurry. Pressure still evicts by lowering R_global. ideal = llmax(ideal, 0.f); - - // Frustum allowance: a falloff on how unloaded out-of-view content gets, - // by how far out it is. Grazing the edge keeps full resolution; at - // TextureFrustumAllowance (fraction of a screen) past the edge it reaches - // the deepest mip, lerped between. Keeps barely-out-of-view textures - // resident so a camera swing back doesn't refetch them. Applied to the - // continuous ideal so it shares the hysteresis dead-band below - applied - // after it, camera motion made desired flap a mip at a time and churned - // the fetch/scaleDown queues. - static LLCachedControl frustum_allowance(gSavedSettings, "TextureFrustumAllowance", 0.2f); - if (!avatar_bake && mFrustumOverflow > 0.f) - { - const F32 f = llclamp(mFrustumOverflow / llmax((F32)frustum_allowance, 0.01f), 0.f, 1.f); - ideal += f * ((F32)dim_max_i - ideal); - mLastOffScreenFrame = LLFrameTimer::getFrameCount(); - } - const S32 target = (S32)floor(ideal); // Hysteresis: a texture at discard C is "happy" while floor(ideal) == C, @@ -3119,12 +3069,7 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c // mip by gc_step, walking gradually toward the deepest mip instead of slamming. // Content drawn within the last cooldown stays full-res, so a fast camera pan // finds it only a step or two coarse on the way back. Resets when drawn again. - // - // Out-of-frustum content is governed by the frustum allowance above instead - // (spatial falloff, not bind staleness) - without this exclusion the GC would - // walk barely-out-of-view content to the deepest mip within a second and the - // allowance would protect nothing. - if (!avatar_bake && mFrustumOverflow <= 0.f) + if (!avatar_bake) { if (LLImageGL* gli = getGLTexture()) { @@ -3132,9 +3077,8 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c static LLCachedControl gc_step_mips(gSavedSettings, "TextureGCStepMips", 1); constexpr U32 GC_RESUME_GRACE_FRAMES = 10; const U32 now = LLFrameTimer::getFrameCount(); - if (gli->mLastBindFrame > 0 // drawn, or anchored at creation (postCreateTexture) - && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES // not just back from background - && now - mLastOffScreenFrame > GC_RESUME_GRACE_FRAMES) // re-entering content gets one grace window to be drawn and re-stamp before staleness is judged + if (gli->mLastBindFrame > 0 // drawn at least once + && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background { const U32 cooldown = llmax((U32)gc_cooldown_frames, 1u); const S32 periods = (S32)((now - gli->mLastBindFrame) / cooldown); diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 90993ed109..cc9fbe5e48 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -215,25 +215,6 @@ protected: F32 mChannelCoverage[4] = { 0.f, 0.f, 0.f, 0.f }; F32 mChannelCoverageMin[4] = { 0.f, 0.f, 0.f, 0.f }; - // Any face using this texture projects onto the screen (published alongside - // the coverage above). Selects the fetch-priority band in updateFetch. - // Defaults true so unmeasured textures (fresh objects, no-face users) are - // never starved. - bool mOnScreen = true; - - // How far out of frustum the texture's least-out-of-view use sits, as a - // fraction of screen size (0 = on screen). Drives the frustum-allowance - // falloff in computeDesiredDiscard. - F32 mFrustumOverflow = 0.f; - - // Last frame this texture was out of frustum (mFrustumOverflow > 0). The - // GC in computeDesiredDiscard gives re-entering content one grace window to - // be drawn and re-stamp mLastBindFrame before its staleness is judged. - mutable U32 mLastOffScreenFrame = 0; - - // Membership flag for LLViewerTextureList::mFastFetchList (dedup). - bool mInFastFetchList = false; - ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; LLFrameTimer mLastFaceListUpdateTimer ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index ad05a0273b..3e1481f8b4 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -378,12 +378,6 @@ void LLViewerTextureList::shutdown() } mFastCacheList.clear(); - for (auto& img : mFastFetchList) - { - img->mInFastFetchList = false; - } - mFastFetchList.clear(); - mUUIDMap.clear(); mImageList.clear(); @@ -877,36 +871,6 @@ void LLViewerTextureList::updateImages(F32 max_time) remaining_time -= updateImagesFetchTextures(remaining_time); remaining_time = llmax(remaining_time, min_time); - // Fast pump: advance every in-flight fetch each frame so results are - // collected and creates scheduled the frame they're ready, instead of - // one state transition per round-robin visit. Cheap - no face scans - - // and bounded by the fetch worker's own concurrency. - LLTimer fast_fetch_timer; - S32 min_count = 32; - for (size_t i = 0; i < mFastFetchList.size(); ) - { - LLViewerFetchedTexture* imagep = mFastFetchList[i]; - if (imagep->getNumRefs() > 1) - { - imagep->updateFetch(); - } - if (imagep->getNumRefs() <= 1 || (!imagep->isFetching() && !imagep->hasFetcher())) - { - imagep->mInFastFetchList = false; - mFastFetchList[i] = mFastFetchList.back(); - mFastFetchList.pop_back(); - } - else - { - ++i; - } - - if (fast_fetch_timer.getElapsedTimeF32() > remaining_time && --min_count <= 0) - { - break; - } - } - //handle results from decode threads updateImagesCreateTextures(remaining_time); @@ -951,187 +915,6 @@ void LLViewerTextureList::clearFetchingRequests() extern bool gCubeSnapshot; -// Refresh a face's cached per-channel streaming coverage (face->mStreamVSize). -// This is the most-demanding-point measurement plus each channel's own UV -// repeat source, computed ONCE per face per update cadence and shared by every -// texture registered on the face. Doing the material/transform pointer chases -// per texture visit instead made updateImageDecodePriority several times more -// expensive per face than develop's, and since the round-robin runs in a fixed -// per-frame time slice, that directly cut how many textures advance their -// load state each frame - the whole pipeline paced slower. -static void update_face_stream_vsize(LLFace* face) -{ - // Bounds on the per-face UV repeat-area divisor (mined from the old - // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost - // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips - // coarser) so pathological UV scales can't explode either direction. - constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f; - constexpr F32 MAX_REPEAT_AREA = 128.f; - - LLViewerObject* objp = face->getViewerObject(); - - // Most-demanding-point measurement: the spec is that the LOWEST pixel:texel - // ratio governs, so pixel density is evaluated at the face's NEAREST point - // and applied to the face's true world area. A whole-face average - // (bounding-disc pixel area) under-resolves perspective surfaces: on a - // floor, the tile at your feet covers far more screen than the average - // tile, and the GPU samples fine mips right there. - const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents; - LLVector4a diag; - diag.setSub(ext[1], ext[0]); - // World area of the face ~ product of the two largest AABB dims (max - // pairwise product; robust for flat faces). - F32 dx = diag[0], dy = diag[1], dz = diag[2]; - F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz)); - // Pixels per meter at the nearest point. Distance floored: nearer than - // this the screen clamp below governs anyway. - F32 dist = llmax(face->mDistanceToCamera, 0.5f); - F32 ppm = LLDrawable::sCurPixelAngle / dist; - F32 face_px = area_world * ppm * ppm; - if (face_px <= 0.f) - { - // Degenerate extents: the face hasn't been through a geometry build - // yet (or a rigged face has no rigged extents) - it isn't renderable, - // so it must not be measured. Zero marks "skip": an invented - // placeholder value would become the texture's least-demanding "use" - // and, under TextureDownrezCoverageBias, drag the whole texture to - // its deepest mip (and it poisoned BP and PBR asymmetrically, since - // the two register faces at different points in the geometry - // lifecycle). - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - face->mStreamVSize[ch] = 0.f; - } - return; - } - - S32 te_offset = face->getTEOffset(); // offset is -1 if not inited - const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); - - // Shared, channel-independent chases - hoisted out of the channel loop. - const LLGLTFMaterial* gltf_mat = te ? te->getGLTFRenderMaterial() : nullptr; - const LLMaterial* mat = te ? te->getMaterialParams().get() : nullptr; - - // Continuously-animated scale (llSetTextureAnim SCALE) bypasses both - // static sources via mTextureMatrix - the live animated values win. - bool anim_scale = false; - F32 anim_ss = 0.f, anim_st = 0.f; - if (te) - { - if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr) - { - LLViewerTextureAnim* anim = vvo->mTextureAnimp; - if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE) - && (anim->mFace < 0 || anim->mFace == te_offset)) - { - anim_scale = true; - anim_ss = anim->mScaleS; - anim_st = anim->mScaleT; - } - } - } - - // Mesh atlas sub-rect: a face whose intrinsic UVs span only part of - // [0,1]^2 shows that fraction of the image. Applies identically to all - // channels - the per-channel transforms stack on the raw face UVs. - F32 span = 1.f; - if (te) - { - if (LLVolume* vol = objp->getVolume()) - { - if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces()) - { - const LLVolumeFace& vf = vol->getVolumeFace(te_offset); - F32 s = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0]) - * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1])); - if (s > 0.f) - { - span = s; - } - } - } - } - - // Avatar bonus: worn attachments get a coverage multiplier - avatars are - // what people look at, and rigged extents make attachment coverage - // measurement unreliable anyway. Multiplicative, not a slam. - static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); - const F32 boost = objp->isAttachment() ? llmax((F32)avatar_boost, 1.f) : 1.f; - - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - // Effective UV repeat AREA: the tiling term of texels-drawn-per- - // screen-pixel. More tiling => each tile smaller on screen => coarser - // mips suffice (penalty). Repeats < 1 (atlas/crop) => whole-image - // residency for a sub-rect legitimately demands more than its screen - // coverage (boost). - F32 repeats = 1.f; - if (te) - { - // UV scale source: every channel reads the repeat values ITS - // renderer actually applies. diffuse -> TE scale; Blinn - // normal/spec -> LLMaterial per-map repeats; PBR channels -> KHR - // texture_transform scale. Fallback is the TE scale - never a - // silent hardcoded 1. - F32 scale_s = te->getScaleS(); - F32 scale_t = te->getScaleT(); - if (ch >= LLRender::BASECOLOR_MAP) - { - // LLRender channel -> LLGLTFMaterial::TextureInfo - static const S32 gltf_info[4] = { - LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3) - LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4) - LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5) - LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6) - }; - if (gltf_mat) - { - const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[ch - LLRender::BASECOLOR_MAP]].mScale; - scale_s = s.mV[0]; - scale_t = s.mV[1]; - } - } - else if (ch == LLRender::NORMAL_MAP || ch == LLRender::SPECULAR_MAP) - { - // Blinn-Phong normal/specular maps carry their own repeats in - // LLMaterial - the renderer builds their texture matrices - // from these, NOT from the TE's diffuse scale. - if (mat) - { - if (ch == LLRender::NORMAL_MAP) - { - mat->getNormalRepeat(scale_s, scale_t); - } - else - { - mat->getSpecularRepeat(scale_s, scale_t); - } - } - } - - if (anim_scale) - { - scale_s = anim_ss; - scale_t = anim_st; - } - - repeats = fabsf(scale_s * scale_t) * span; - } - - repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA); - - // Apply the two sides of the repeat term in the right order relative - // to the screen clamp: tiling (repeats > 1) divides the nearest-point - // footprint BEFORE the clamp (one tile can't draw more pixels than - // the screen); atlas/crop (repeats < 1) boosts AFTER it (whole-image - // residency for a crop legitimately demands more than its screen - // coverage). - F32 tiling = llmax(repeats, 1.f); - F32 crop = llmin(repeats, 1.f); - face->mStreamVSize[ch] = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop * boost; - } -} - void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep, bool flush_images) { llassert(!gCubeSnapshot); @@ -1148,6 +931,13 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { + // Bounds on the per-face UV repeat-area divisor (mined from the old + // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost + // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips + // coarser) so pathological UV scales can't explode either direction. + constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f; + constexpr F32 MAX_REPEAT_AREA = 128.f; + // Per priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive): // the HIGHEST per-face effective coverage (= the lowest texels-per-pixel // use, the most demanding variant - drives desired discard) and the @@ -1160,9 +950,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 channel_coverage_min[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; bool bucket_used[4] = { false, false, false, false }; F32 max_coverage = 0.f; - bool on_screen = false; // any face's projected disc overlaps the screen - bool any_face = false; - F32 min_overflow = FLT_MAX; // least out-of-frustum use across faces U32 face_count = 0; @@ -1213,29 +1000,175 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 radius; F32 cos_angle_to_view_dir; if ((gFrameCount - face->mLastTextureUpdate) > 10) - { // refresh the face's geometry + cached coverage at most once every - // 10 frames; every texture/channel sharing this face (GLTF and - // Blinn-Phong materials) reuses the cache instead of redoing the - // measurement. (calcPixelArea maintains face->mInFrustum itself.) - face->calcPixelArea(cos_angle_to_view_dir, radius); - update_face_stream_vsize(face); + { // only call calcPixelArea at most once every 10 frames for a given face + // this helps eliminate redundant calls to calcPixelArea for faces that have multiple textures + // assigned to them, such as is the case with GLTF materials or Blinn-Phong materials + face->mInFrustum = face->calcPixelArea(cos_angle_to_view_dir, radius); face->mLastTextureUpdate = gFrameCount; } - // Cached measurement - see update_face_stream_vsize above. - // Zero = degenerate extents / not yet through a geometry - // build: not renderable, must not be measured (a - // placeholder value would poison the per-bucket MIN bound - // and drag the texture to its deepest mip). - F32 vsize = face->mStreamVSize[i]; - if (vsize <= 0.f) + // Most-demanding-point measurement: the spec is that the + // LOWEST pixel:texel ratio governs, so pixel density is + // evaluated at the face's NEAREST point and applied to the + // face's true world area. The previous whole-face average + // (bounding-disc pixel area) under-resolved perspective + // surfaces: on a floor, the tile at your feet covers far + // more screen than the average tile, and the GPU samples + // fine mips right there - tiled (PBR-heavy) content went + // soft while untiled content looked fine. + const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents; + LLVector4a diag; + diag.setSub(ext[1], ext[0]); + // World area of the face ~ product of the two largest AABB + // dims (max pairwise product; robust for flat faces). + F32 dx = diag[0], dy = diag[1], dz = diag[2]; + F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz)); + // Pixels per meter at the nearest point. Distance floored: + // nearer than this the screen clamp below governs anyway. + F32 dist = llmax(face->mDistanceToCamera, 0.5f); + F32 ppm = LLDrawable::sCurPixelAngle / dist; + F32 face_px = area_world * ppm * ppm; + if (face_px <= 0.f) { + // Degenerate extents: the face hasn't been through a + // geometry build yet (or a rigged face has no rigged + // extents) - it isn't renderable, so it must not be + // measured. Skipping matters especially for the + // per-bucket MIN bound: any invented placeholder + // value (the old fallback hit LLFace::init's 16px + // default) becomes the texture's least-demanding + // "use" and, under TextureDownrezCoverageBias, drags + // the whole texture to its deepest mip - and it + // poisoned BP and PBR asymmetrically since the two + // systems register faces at different points in the + // geometry lifecycle. continue; } - any_face = true; - on_screen = on_screen || face->mInFrustum; - min_overflow = llmin(min_overflow, face->mFrustumOverflow); + // Effective UV repeat AREA across this face: the tiling + // term of texels-drawn-per-screen-pixel. More tiling => + // each tile is smaller on screen => coarser mips suffice + // (penalty). Repeats < 1 (atlas/crop) => only a sub-rect + // of the image is shown, but discard levels are whole- + // image, so the full image must be resident at 1/repeats + // times the crop's pixel count (boost). + S32 te_offset = face->getTEOffset(); // offset is -1 if not inited + LLViewerObject* objp = face->getViewerObject(); + const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); + + F32 repeats = 1.f; + if (te) + { + // UV scale source: every channel reads the repeat + // values ITS renderer actually applies, then flows + // through the identical pipeline below. Sources: + // diffuse -> TE scale + // Blinn normal/spec -> LLMaterial per-map repeats + // PBR channels -> KHR texture_transform scale + // Fallback for any missing material is the TE scale - + // never a silent hardcoded 1. + F32 scale_s = te->getScaleS(); + F32 scale_t = te->getScaleT(); + if (i >= LLRender::BASECOLOR_MAP) + { + // LLRender channel -> LLGLTFMaterial::TextureInfo + static const S32 gltf_info[4] = { + LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3) + LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4) + LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5) + LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6) + }; + if (const LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial()) + { + const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[i - LLRender::BASECOLOR_MAP]].mScale; + scale_s = s.mV[0]; + scale_t = s.mV[1]; + } + } + else if (i == LLRender::NORMAL_MAP || i == LLRender::SPECULAR_MAP) + { + // Blinn-Phong normal/specular maps carry their own + // repeats in LLMaterial - the renderer builds their + // texture matrices from these, NOT from the TE's + // diffuse scale. Reading the diffuse scale here made + // Blinn normals scale differently than PBR normals + // (whose per-channel transform IS read above). + if (const LLMaterial* mat = te->getMaterialParams().get()) + { + if (i == LLRender::NORMAL_MAP) + { + mat->getNormalRepeat(scale_s, scale_t); + } + else + { + mat->getSpecularRepeat(scale_s, scale_t); + } + } + } + + // Continuously-animated scale (llSetTextureAnim SCALE) + // bypasses both static sources via mTextureMatrix - + // the live animated values win on either path. + if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr) + { + LLViewerTextureAnim* anim = vvo->mTextureAnimp; + if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE) + && (anim->mFace < 0 || anim->mFace == te_offset)) + { + scale_s = anim->mScaleS; + scale_t = anim->mScaleT; + } + } + + repeats = fabsf(scale_s * scale_t); + + // Mesh atlas sub-rect: a face whose intrinsic UVs span + // only part of [0,1]^2 shows that fraction of the + // image. Applies identically to both paths - the + // transforms above stack on the raw face UVs. + if (LLVolume* vol = objp->getVolume()) + { + if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces()) + { + const LLVolumeFace& vf = vol->getVolumeFace(te_offset); + F32 span = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0]) + * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1])); + if (span > 0.f) + { + repeats *= span; + } + } + } + } + + repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA); + + // Apply the two sides of the repeat term in the right + // order relative to the screen clamp: + // - tiling (repeats > 1): the per-tile footprint at the + // nearest point, THEN clamped - one tile can't draw + // more pixels than the screen. (Clamping the whole + // face first and then dividing crushed near tiles.) + // - atlas/crop (repeats < 1): boost AFTER the clamp - + // whole-image residency for a crop legitimately + // demands more than its screen coverage. + F32 tiling = llmax(repeats, 1.f); + F32 crop = llmin(repeats, 1.f); + F32 vsize = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop; + + // Avatar bonus: worn attachments get a coverage + // multiplier - avatars are what people look at, and + // rigged extents make attachment coverage measurement + // unreliable anyway. Multiplicative, not a slam: a + // nearby avatar gains ~a mip of headroom while a distant + // one still downrezzes naturally with its coverage. + // (System-avatar bakes get the same bonus in the + // no-faces branch below.) + if (objp->isAttachment()) + { + static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); + vsize *= llmax((F32)avatar_boost, 1.f); + } if (bucket >= 0 && bucket < 4) { @@ -1309,15 +1242,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag imagep->mChannelCoverage[b] = channel_coverage[b]; imagep->mChannelCoverageMin[b] = (channel_coverage_min[b] == FLT_MAX) ? 0.f : channel_coverage_min[b]; } - - // Fetch admission signal: false only when faces were actually scanned and - // every one projects off screen. Textures with no scannable faces (bakes, - // spotlights, the >1024-face boost path, not-yet-built geometry) stay - // eligible - blocking them is what stalls load-in. - imagep->mOnScreen = on_screen || !any_face; - // Least out-of-frustum use governs the allowance falloff; unknown = 0 - // (no penalty), same reasoning as mOnScreen. - imagep->mFrustumOverflow = any_face ? min_overflow : 0.f; } #if 0 @@ -1586,18 +1510,6 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) { updateImageDecodePriority(imagep); imagep->updateFetch(); - - // Fast-pump membership: textures with an active fetch get - // updateFetch every frame (in updateImages) instead of waiting - // ~a sweep period per state transition - that wait, times the - // 2-4 transitions a load needs, was the measured throughput - // ceiling. Purely additive: the sweep still pumps everything, - // so fetches started by any other path can never strand. - if ((imagep->isFetching() || imagep->hasFetcher()) && !imagep->mInFastFetchList) - { - imagep->mInFastFetchList = true; - mFastFetchList.push_back(imagep); - } } if (timer.getElapsedTimeF32() > max_time) diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 7004238995..931f2ed50e 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -34,7 +34,6 @@ #include "llviewertexture.h" #include "llui.h" #include -#include #include #include "lluiimage.h" @@ -226,10 +225,6 @@ public: image_list_t mCallbackList; image_list_t mFastCacheList; - // In-flight fetches pumped every frame (additive to the round-robin - // sweep, which remains the universal pump). See updateImages. - std::vector > mFastFetchList; - bool mForceResetTextureStats; // to make "for (auto& imagep : gTextureList)" work -- cgit v1.3 From 6b9f0f0bf43250f984631032a3f3b41d245907a9 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 22 Jul 2026 00:38:54 -0400 Subject: Revert "Merge pull request #5982 from secondlife/geenz/texture-streaming-tweaks" This reverts commit 3d465f6c966f0a2fcfd5d3c9effb6ce20c2754c8, reversing changes made to f2c356fc7130ce5e38b994dcfa1e210609b458b9. --- indra/llprimitive/lltextureentry.cpp | 2 +- indra/llrender/llimagegl.cpp | 64 +-- indra/llrender/llimagegl.h | 33 +- indra/llrender/llrender.cpp | 7 - indra/newview/app_settings/settings.xml | 300 ++++++++++--- indra/newview/featuretable.txt | 1 + indra/newview/featuretable_linux.txt | 2 + indra/newview/featuretable_mac.txt | 1 + indra/newview/llfetchedgltfmaterial.cpp | 29 +- indra/newview/lltextureview.cpp | 13 +- indra/newview/llviewercontrol.cpp | 88 ++-- indra/newview/llviewermessage.cpp | 7 + indra/newview/llviewerobject.cpp | 18 +- indra/newview/llviewershadermgr.cpp | 6 + indra/newview/llviewertexture.cpp | 765 ++++++++++++++++++++++---------- indra/newview/llviewertexture.h | 102 +++-- indra/newview/llviewertexturelist.cpp | 495 +++++++++------------ indra/newview/llviewertexturelist.h | 4 + indra/newview/llviewerwindow.cpp | 8 +- indra/newview/llvovolume.cpp | 63 +-- indra/newview/pipeline.cpp | 6 - 21 files changed, 1160 insertions(+), 854 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llprimitive/lltextureentry.cpp b/indra/llprimitive/lltextureentry.cpp index 2b0f989701..ac482ffbf9 100644 --- a/indra/llprimitive/lltextureentry.cpp +++ b/indra/llprimitive/lltextureentry.cpp @@ -600,7 +600,7 @@ LLGLTFMaterial* LLTextureEntry::getGLTFRenderMaterial() const return mGLTFRenderMaterial; } - //llassert(getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); + llassert(getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); return getGLTFMaterial(); } diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index c8a23d873e..6bcc34938c 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -166,8 +166,6 @@ U64 LLImageGL::getTextureBytesAllocated() //statics U32 LLImageGL::sUniqueCount = 0; -std::atomic LLImageGL::sOOMErrorCount(0); -thread_local bool LLImageGL::sStampBindFrame = true; U32 LLImageGL::sBindCount = 0; S32 LLImageGL::sCount = 0; @@ -493,7 +491,7 @@ bool LLImageGL::create(LLPointer& dest, const LLImageRaw* imageraw, b //---------------------------------------------------------------------------- LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mExternalTexture(false) +: mSaveData(0), mExternalTexture(false) { init(usemipmaps, allow_compression); setSize(0, 0, 0); @@ -502,7 +500,7 @@ LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true } LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mExternalTexture(false) +: mSaveData(0), mExternalTexture(false) { llassert( components <= 4 ); init(usemipmaps, allow_compression); @@ -512,7 +510,7 @@ LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = t } LLImageGL::LLImageGL(const LLImageRaw* imageraw, bool usemipmaps/* = true*/, bool allow_compression/* = true*/) -: mExternalTexture(false) +: mSaveData(0), mExternalTexture(false) { init(usemipmaps, allow_compression); setSize(0, 0, 0); @@ -621,6 +619,8 @@ void LLImageGL::cleanup() destroyGLTexture(); } freePickMask(); + + mSaveData = NULL; // deletes data } //---------------------------------------------------------------------------- @@ -777,7 +777,6 @@ void LLImageGL::setImage(const LLImageRaw* imageraw) bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 usename /* = 0 */) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLImageGLStampBypass no_stamp; // upload binds are not visibility const bool is_compressed = isCompressed(); @@ -1503,12 +1502,6 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt { free_cur_tex_image(); } - - // Drain stale GL errors so an OOM detected below belongs to this alloc. - // Otherwise a failed glTexImage2D is swallowed in release while - // alloc_tex_image still counts the bytes, inflating the used-VRAM figure. - while (glGetError() != GL_NO_ERROR) {} - const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) { @@ -1518,30 +1511,19 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt else { // break up calls to a manageable size for the GL command buffer - LL_PROFILE_ZONE_NAMED("glTexImage2D alloc"); - glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr); - } + { + LL_PROFILE_ZONE_NAMED("glTexImage2D alloc"); + glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr); + } - if (glGetError() == GL_OUT_OF_MEMORY) - { - ++sOOMErrorCount; - LL_WARNS_ONCE("Texture") << "glTexImage2D failed with GL_OUT_OF_MEMORY (" - << width << "x" << height << " mip " << miplevel - << ") - not counting bytes" << LL_ENDL; - } - else - { - if (use_sub_image) + U8* src = (U8*)(pixels); + if (src) { - U8* src = (U8*)(pixels); - if (src) - { - LL_PROFILE_ZONE_NAMED("glTexImage2D copy"); - sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width); - } + LL_PROFILE_ZONE_NAMED("glTexImage2D copy"); + sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width); } - alloc_tex_image(width, height, intformat, 1); } + alloc_tex_image(width, height, intformat, 1); } stop_glerror(); } @@ -1686,7 +1668,6 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LL_PROFILE_GPU_ZONE("createGLTexture"); checkActiveThread(); - LLImageGLStampBypass no_stamp; // creation binds are not visibility bool main_thread = on_main_thread(); @@ -2109,21 +2090,12 @@ S32 LLImageGL::dimDerivedMaxDiscard(S32 width, S32 height) void LLImageGL::stampBound() const { - // Both stamps skip same-frame re-binds (bindFast runs per draw). They dedupe - // separately, so a non-camera pass touching the time stamp first doesn't stop - // a real camera bind from setting the frame stamp later the same frame. + // Skip the store on same-frame re-binds - bindFast is per-draw and + // would dirty this cache line per bind per texture otherwise. if (mLastBindTime != sLastFrameTime) { mLastBindTime = sLastFrameTime; } - if (sStampBindFrame) - { - const U32 frame = LLFrameTimer::getFrameCount(); - if (mLastBindFrame != frame) - { - mLastBindFrame = frame; - } - } } S64 LLImageGL::getBytes(S32 discard_level) const @@ -2548,10 +2520,6 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below - // would otherwise stamp mLastBindFrame and keep the texture fetch-eligible. - LLImageGLStampBypass no_stamp; - if (mTarget != GL_TEXTURE_2D || mFormatInternal == -1 // not initialized ) diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index ca75b543c0..0c85446b84 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -39,7 +39,6 @@ #include "llrender.h" #include "threadpool.h" #include "workqueue.h" -#include #include #define LL_IMAGEGL_THREAD_CHECK 0 //set to 1 to enable thread debugging for ImageGL @@ -239,23 +238,15 @@ public: public: // Various GL/Rendering options S64Bytes mTextureMemory; - mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound (bind or bind-attempt) - mutable U32 mLastBindFrame = 0; // frame index (LLFrameTimer::getFrameCount) at last CAMERA-pass - // stampBound; 0 = never. Drives visibility GC + fetch gating. - F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created - - // When false, stampBound skips the mLastBindFrame stamp (mLastBindTime still - // updates). Set false around non-camera passes (probes, shadows, impostors) - // and administrative binds (upload, scaleDown - via LLImageGLStampBypass) so - // those binds don't count as camera visibility. Thread-local so a GL upload - // thread can't flip it on the render thread mid-frame. - static thread_local bool sStampBindFrame; + mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives streaming staleness + F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; staleness fallback for never-bound textures private: U32 createPickMask(S32 pWidth, S32 pHeight); void freePickMask(); bool isCompressed(); + LLPointer mSaveData; // used for destroyGL/restoreGL LL::WorkQueue::weak_t mMainQueue; U8* mPickMask; //downsampled bitmap approximation of alpha channel. NULL if no alpha channel U16 mPickMaskWidth; @@ -309,11 +300,6 @@ public: // Global memory statistics static U32 sBindCount; // Tracks number of texture binds for current frame static U32 sUniqueCount; // Tracks number of unique texture binds for current frame - // glTexImage2D GL_OUT_OF_MEMORY failures detected (bytes NOT counted for - // these). Written from whichever thread runs texture creation; read by - // the streaming 1Hz pressure log. Nonzero = the driver is refusing - // allocations and the VRAM budget is unreliable. - static std::atomic sOOMErrorCount; static bool sGlobalUseAnisotropic; static LLImageGL* sDefaultGLTexture ; static bool sAutomatedTest; @@ -369,19 +355,6 @@ public: }; -// RAII: suppress the mLastBindFrame stamp for the current scope. Use around -// administrative binds (upload, create, scaleDown) so they don't count as -// camera visibility - otherwise the GC's own scaleDown re-stamps what it just -// aged out and oscillates. Saves/restores, so it nests correctly. -class LLImageGLStampBypass -{ -public: - LLImageGLStampBypass() : mPrev(LLImageGL::sStampBindFrame) { LLImageGL::sStampBindFrame = false; } - ~LLImageGLStampBypass() { LLImageGL::sStampBindFrame = mPrev; } -private: - bool mPrev; -}; - class LLImageGLThread : public LLSimpleton, LL::ThreadPool { public: diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index f0a1c44507..5e845fbcce 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -245,11 +245,6 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) texture->setActive() ; texture->updateBindStatsForTester() ; } - // updateBindStats only stamps time; the GC and fetch gate use - // the frame stamp, so stamp it here too or bind()-drawn faces - // (bump/material/media) oscillate. Admin/non-camera binds are - // already suppressed via LLImageGLStampBypass / sStampBindFrame. - gl_tex->stampBound(); mHasMipMaps = gl_tex->mHasMipMaps; if (gl_tex->mTexOptionsDirty) { @@ -330,8 +325,6 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 glBindTexture(sGLTextureType[texture->getTarget()], mCurrTexture); stop_glerror(); texture->updateBindStats(); - // Frame-stamp fresh binds too - see bind(LLTexture*) above. - texture->stampBound(); mHasMipMaps = texture->mHasMipMaps; if (texture->mTexOptionsDirty) { diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 45a7dced5c..81299a5b71 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8002,6 +8002,17 @@ Value 1 + RenderMinFreeMainMemoryThreshold + + Comment + If available free physical memory is below this value textures get agresively scaled down + Persist + 0 + Type + U32 + Value + 512 + RenderLowMemMinDiscardIncrement Comment @@ -8024,6 +8035,17 @@ Value 0.1 + RenderMaxTextureIndex + + Comment + Maximum texture index to use for indexed texture rendering. + Persist + 1 + Type + U32 + Value + 16 + RenderMaxTextureResolution Comment @@ -8038,7 +8060,7 @@ RenderTextureQuality Comment - Texture quality preset: 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, TexturePixelToTexelRatio, TexturePressureTightenRate, TexturePressureRelaxRate, and TextureChannelRatio* (Normal/BaseColor/Specular/Emissive). The pressure water marks (TexturePressureHighWater/LowWater) are constant across tiers. + Texture quality preset: 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, the four TextureChannel* exponents, and TextureDistanceDiscardPower. Persist 1 Type @@ -8057,6 +8079,17 @@ Value 0 + RenderDebugTextureBind + + Comment + Enable texture bind performance test. + Persist + 1 + Type + Boolean + Value + 0 + RenderDelayCreation Comment @@ -9373,6 +9406,17 @@ Value 64 + RenderReservedTextureIndices + + Comment + Count of texture indices to reserve for shadow and reflection maps when using indexed texture rendering. Probably only want to set from the login screen. + Persist + 1 + Type + S32 + Value + 14 + RenderResolutionDivisor Comment @@ -11827,10 +11871,10 @@ Value 20.0 - TextureChannelRatioNormal + TextureChannelNormal Comment - Per-channel pixel:texel ratio multiplier for normal maps (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 1.0 = full quality; lower = coarser. Driven by the RenderTextureQuality preset. + Per-channel discard exponent for normal maps. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type @@ -11838,21 +11882,21 @@ Value 1.0 - TextureChannelRatioBaseColor + TextureChannelBaseColor Comment - Per-channel pixel:texel ratio multiplier for base color / diffuse (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 1.0 = full quality. Driven by the RenderTextureQuality preset. + Per-channel discard exponent for base color / diffuse. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type F32 Value - 1.0 + 0.75 - TextureChannelRatioSpecular + TextureChannelSpecular Comment - Per-channel pixel:texel ratio multiplier for specular / metallic-roughness (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 0.5 = half resolution (specular detail is usually less perceptible than diffuse). Driven by the RenderTextureQuality preset. + Per-channel discard exponent for specular / metallic-roughness. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type @@ -11860,16 +11904,16 @@ Value 0.5 - TextureChannelRatioEmissive + TextureChannelEmissive Comment - Per-channel pixel:texel ratio multiplier for emissive (texels per pixel, applied on top of the global TexturePixelToTexelRatio). 0.5 = half resolution. Driven by the RenderTextureQuality preset. + Per-channel discard exponent for emissive. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type F32 Value - 0.5 + 0.75 TextureMaxDiscardOverride @@ -11882,87 +11926,165 @@ Value 0 - TexturePixelToTexelRatio + TextureMemoryHighWaterMark Comment - Max texels per screen pixel the streamer will allocate (the "R" in a 1:R pixel:texel ratio). 1.0 = one texel per pixel. VRAM pressure walks the effective ratio down from here. + Fraction of budget (0..1) at which the pressure controller bypasses smoothing and slams to cap. Last-ditch min-discard also creeps without waiting for mult_progress. Persist 1 Type F32 Value - 1.0 + 0.8 - TextureBackgroundMinRatio + TextureMemoryPressureBackoffStart Comment - Lowest pixel:texel ratio the streamer decays to while the viewer is backgrounded, to free VRAM for other apps. Lower frees more but re-rezzes slower on return. Restored when focus comes back. + Fraction of the VRAM target at which the pressure ramp starts (0..1). Lower = earlier headroom-building; 1.0 disables backoff (ramp only above target). Persist 1 Type F32 Value - 0.001 + 0.85 - TexturePressureHighWater + TextureMemoryPressureMaxMultiplier Comment - High water mark as a fraction of the VRAM budget. When used VRAM crosses this, the global pixel:texel ratio tightens (backs off detail) at TexturePressureTightenRate. Held at 0.90 - it's a physical "crossed the budget" threshold, not a tier preference. + Upper bound on the VRAM-pressure distance multiplier (>= 1). Mostly defensive -- at mult=64 the streaming ramp collapses to ~ramp_range/64, already extreme. Higher allows even more aggressive compression in tight-budget scenes. Persist 1 Type F32 Value - 0.90 + 64.0 - TexturePressureLowWater + TextureLastDitchEngageProgress Comment - Low water mark as a fraction of the VRAM budget. When used VRAM drops below this, the global pixel:texel ratio relaxes (restores detail) at TexturePressureRelaxRate. The band between low and high is the hysteresis zone where the ratio holds steady - wide enough to prevent sawtooth. + mult_progress (0..1) at which the last-ditch floor starts creeping up. The floor only advances when mult is at or above this fraction of its cap AND prediction is still over budget. Decays back toward 0 whenever prediction is under budget. Persist 1 Type F32 Value - 0.70 + 0.95 - TexturePressureTightenRate + TextureLastDitchRampRate Comment - Rate (ratio units/sec) at which the global pixel:texel ratio drops while used VRAM is above the high watermark. Deliberately slow so eviction (scaleDown draining) frees bytes before the next step - prevents over-shoot / thrash. + Rate (discard levels/sec) at which sLastDitchMinDiscard creeps up while engaged. 0.5 = takes ~2 sec to add one discard level. Mirrors sDesiredDiscardBias ramp shape. Persist 1 Type F32 Value - 0.30 + 0.5 - TexturePressureRelaxRate + TextureLastDitchDecayRate Comment - Rate (ratio units/sec) at which the global pixel:texel ratio climbs back toward TexturePixelToTexelRatio while used VRAM is below the low watermark. Typically slower than the tighten rate so detail returns gradually as headroom appears. + Rate (discard levels/sec) at which sLastDitchMinDiscard decays back to 0 when prediction is under budget. Persist 1 Type F32 Value - 0.10 + 0.5 - TextureUpRezMargin + TextureLastDitchMinDiscardMax Comment - Hysteresis dead-band (in mip levels) around a texture's current discard. A texture only fetches a finer mip when its ideal discard falls more than this below the current level, and only evicts when it rises more than this above - prevents fetch/scaleDown thrash at mip boundaries when an object slowly recedes. + Hard ceiling on sLastDitchMinDiscard. At 13 the floor can climb all the way to the deepest meaningful mip; lower values cap how aggressive the last-ditch escalation can get before we are simply out of discards. Persist 1 Type F32 Value - 0.2 + 13.0 - TextureCooldownStepSeconds + TextureMemoryPressurePredictionGain Comment - While backgrounded, the pixel:texel ratio drops one mip toward TextureBackgroundMinRatio every this many seconds. + Power exponent mapping predicted-over-budget ratio to target multiplier. target_mult = pred_over^gain. Higher gain saturates faster. + Persist + 1 + Type + F32 + Value + 10.0 + + TextureMemoryPressureSmoothingRate + + Comment + Lerp rate (1/sec) at which the pressure multiplier converges to its prediction-driven target. Higher = faster response, lower = smoother. Default 4 reaches ~63% in 0.25s. + Persist + 1 + Type + F32 + Value + 4.0 + + TextureTerrainDistanceFloor + + Comment + Minimum distance factor for BOOST_TERRAIN textures. Keeps combined > 0 so VRAM pressure can evict terrain. Lower = higher idle quality, less pressure response. + Persist + 1 + Type + F32 + Value + 0.01 + + TextureTerrainCoverageFraction + + Comment + Synthetic on-screen coverage fraction for BOOST_TERRAIN textures (no faces are registered). Higher = higher idle quality, less pressure response. + Persist + 1 + Type + F32 + Value + 0.99 + + TextureAgentAvatarBoost + + Comment + Quality boost (0..1) for textures on the agent's avatar (rigged mesh / animated objects). Lower = higher quality. Preference, not exemption - pressure can still evict. + Persist + 1 + Type + F32 + Value + 0.5 + + TextureBackgroundFactorRatePerSec + + Comment + Per-second ramp rate of the background-window discard floor (0..1). Snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. + Persist + 1 + Type + F32 + Value + 0.011 + + TextureBackgroundDiscardOffset + + Comment + Backgrounded textures will only discard up to (dim_max - offset). e.g. a 2048 texture (dim_max 11) with offset 2 caps the background floor at discard 9. 0 disables the cap (background can drive to max discard). + Persist + 1 + Type + S32 + Value + 2 + + + TextureCloseBubbleMeters + + Comment + Close-camera bubble (meters). Faces inside this distance get dist_factor = 0 (no discard contribution); the ramp to 1 spans (bubble, draw_distance]. Shrinks toward TextureCloseBubbleMinMeters as VRAM pressure ramps the multiplier toward its cap. Persist 1 Type @@ -11970,39 +12092,95 @@ Value 5.0 - TextureGCStepFrames + TextureCloseBubbleMinMeters Comment - Foreground GC cooldown, in rendered frames. For every this many frames a texture goes without being drawn, its mip drops by TextureGCStepMips. Resets when the texture is drawn again. + Floor (meters) for the close-camera bubble under maximum VRAM pressure. At sMemoryPressureMultiplier = TextureMemoryPressureMaxMultiplier the bubble collapses to this value, allowing eviction of even close textures when nothing else fits. Persist 1 Type - U32 + F32 Value - 5 + 3.0 - TextureGCStepMips + TextureCloseBubbleShrinkThreshold Comment - Mip levels dropped each time a TextureGCStepFrames cooldown elapses. 1 is gentlest; higher sheds VRAM faster in coarser jumps. + Bubble stays at full size until mult_progress exceeds this fraction (0..1) of its range to the cap. Above that, bubble lerps from full to TextureCloseBubbleMinMeters. Keeps the bubble out of the normal feedback loop. Persist 1 Type - U32 + F32 Value + 0.8 + + TextureCloseBubbleTrackRate + + Comment + Rate (1/sec) at which the actual bubble tracks its target. Lower = smoother, slower to react. Damps short-term multiplier swings so close textures don't yo-yo. + Persist 1 + Type + F32 + Value + 0.5 - TextureFetchVisibilityFrames + TextureDistanceDiscardPower Comment - Only fetch a texture if it was drawn within this many rendered frames; out-of-view content isn't fetched. Minimum 1 (0 is clamped up). Boosted/UI textures, avatar bakes, and callback textures are exempt. + Exponent on the distance factor (face_distance / draw_distance). 1.0 = linear; lower = textures hit max discard sooner with distance. Default 0.5 = sqrt. Persist 1 Type - U32 + F32 + Value + 0.5 + + TextureSizeDiscardPower + + Comment + Exponent on the on-screen size factor (1 - coverage). 1.0 = linear; lower = small-on-screen textures attenuate sooner. + Persist + 1 + Type + F32 + Value + 1.0 + + TextureStalenessIntervalSeconds + + Comment + Seconds per staleness step. Per-interval increment is 1/max_discard so any texture saturates after interval * max_discard seconds idle. + Persist + 1 + Type + F32 + Value + 5.0 + + TextureBindDecaySeconds + + Comment + Grace seconds after a bind during which the staleness factor stays at 0. Prevents intermittently-bound textures from ramping. 0 disables the grace period. + Persist + 1 + Type + F32 Value + 5.0 + + TextureFetchPressureScale + + Comment + Pending-fetch divisor for the bias floor: bias floor = 1 + clamp(pending / scale, 0, 3). Lower = bias rises sooner under load floods. + Persist 1 + Type + F32 + Value + 1000.0 + TextureDecodeDisabled Comment @@ -12036,6 +12214,28 @@ Value 0 + TextureDiscardBackgroundedTime + + Comment + Specify how long to wait before discarding texture data after viewer is backgrounded. (zero or negative to disable) + Persist + 1 + Type + F32 + Value + 60.0 + + TextureDiscardMinimizedTime + + Comment + Specify how long to wait before discarding texture data after viewer is minimized. (zero or negative to disable) + Persist + 1 + Type + F32 + Value + 1.0 + TextureFetchConcurrency Comment @@ -12124,27 +12324,27 @@ Value - TextureAvatarBoost + TextureScaleMinAreaFactor Comment - Coverage multiplier for avatar textures: worn attachments (rigged extents make their measurement unreliable) and baked system-avatar textures. 4.0 = one mip finer than measured. A bonus, not a pin - nearby avatars gain headroom while distant avatars still downrez with their measured coverage. 1.0 disables. + Limits how texture scale affects area calculation. Persist 1 Type F32 Value - 4.0 + 0.0095 - TextureDownrezCoverageBias + TextureScaleMaxAreaFactor Comment - Which end of a texture's texels-per-pixel spread sizes it. Each texture tracks the screen coverage of its most demanding use (lowest texels per pixel) and least demanding use (highest texels per pixel, most oversampled). 0 = size to the most demanding use (best quality); 1 = size to the least demanding use (frees the most memory). Interpolation is geometric (log-space), so the resulting discard level moves linearly with this value - 0.5 sits halfway between the two ends in mip levels. Default 0.25 is the best universal balance. + Limits how texture scale affects area calculation. Persist 1 Type F32 Value - 0.25 + 25.0 ThreadPoolSizes diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index 72e0f43c0e..f05f77c222 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -68,6 +68,7 @@ RenderShadowDetail 1 2 RenderUseStreamVBO 1 1 RenderFSAAType 1 2 RenderFSAASamples 1 3 +RenderMaxTextureIndex 1 16 RenderGLContextCoreProfile 1 1 RenderGLMultiThreadedTextures 1 0 RenderGLMultiThreadedMedia 1 1 diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index a39c64510f..d8d4f08429 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -66,6 +66,7 @@ RenderDeferredSSAO 1 1 RenderUseAdvancedAtmospherics 1 0 RenderShadowDetail 1 2 RenderFSAASamples 1 16 +RenderMaxTextureIndex 1 16 RenderMirrors 1 1 // @@ -490,6 +491,7 @@ RenderVBOEnable 1 0 list OpenGLPre30 RenderDeferred 0 0 +RenderMaxTextureIndex 1 1 list Intel RenderAnisotropic 1 0 diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index ebfe45d9ea..b11fa28c48 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -65,6 +65,7 @@ RenderShadowDetail 1 2 RenderUseStreamVBO 1 1 RenderFSAAType 1 2 RenderFSAASamples 1 3 +RenderMaxTextureIndex 1 16 RenderGLContextCoreProfile 1 1 RenderGLMultiThreadedTextures 1 1 RenderGLMultiThreadedMedia 1 1 diff --git a/indra/newview/llfetchedgltfmaterial.cpp b/indra/newview/llfetchedgltfmaterial.cpp index d71f0c1bd4..a05f725673 100644 --- a/indra/newview/llfetchedgltfmaterial.cpp +++ b/indra/newview/llfetchedgltfmaterial.cpp @@ -73,20 +73,6 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex) LLViewerTexture* baseColorTex = media_tex ? media_tex : mBaseColorTexture; LLViewerTexture* emissiveTex = media_tex ? media_tex : mEmissiveTexture; - if (media_tex) - { - // Media hides these but they stay registered for coverage. Stamp them so - // the GC doesn't coarsen/refetch them while the media is playing. - if (mBaseColorTexture.notNull()) - { - if (LLImageGL* gl_tex = mBaseColorTexture->getGLTexture()) { gl_tex->stampBound(); } - } - if (mEmissiveTexture.notNull()) - { - if (LLImageGL* gl_tex = mEmissiveTexture->getGLTexture()) { gl_tex->stampBound(); } - } - } - if (!LLPipeline::sShadowRender || (mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK)) { if (mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) @@ -111,26 +97,13 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex) if (!LLPipeline::sShadowRender) { - // Bind the normal map at whatever resolution is resident, like Blinn-Phong - // (soft, never absent). Only fall back to the flat normal when it's not - // loaded yet. (The old discard<=4 gate dropped distant/tiled PBR normals - // entirely, making PBR look flatter than equivalent Blinn content.) - if (mNormalTexture.notNull() && mNormalTexture->hasGLTexture()) + if (mNormalTexture.notNull() && mNormalTexture->getDiscardLevel() <= 4) { shader->bindTexture(LLShaderMgr::BUMP_MAP, mNormalTexture); } else { shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); - if (mNormalTexture.notNull()) - { - // In use, just not loaded yet - stamp it so the GC doesn't treat - // it as unseen and pin it deep before its first real bind. - if (LLImageGL* gl_tex = mNormalTexture->getGLTexture()) - { - gl_tex->stampBound(); - } - } } if (mMetallicRoughnessTexture.notNull()) diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 10b57f47da..470a133fa7 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -476,7 +476,7 @@ private: void LLGLTexMemBar::draw() { - F32 pixel_to_texel_ratio = LLViewerTexture::sPixelToTexelRatio; + F32 discard_bias = LLViewerTexture::sDesiredDiscardBias; F32 cache_usage = (F32)LLAppViewer::getTextureCache()->getUsage().valueInUnits(); F32 cache_max_usage = (F32)LLAppViewer::getTextureCache()->getMaxUsage().valueInUnits(); S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); @@ -560,22 +560,25 @@ void LLGLTexMemBar::draw() gGL.color4f(0.f, 0.f, 0.f, 0.25f); gl_rect_2d(-10, getRect().getHeight() + line_height*2 + 1, getRect().getWidth()+2, getRect().getHeight()+2); - text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Probe#: %d Probe Mem: %d MB Px:Texel 1:%.2f Cache: %.1f/%.1f MB", + text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Probe#: %d Probe Mem: %d MB Bias: %.2f Cache: %.1f/%.1f MB", (S32)LLViewerTexture::sFreeVRAMMegabytes, LLMemory::getAvailableMemKB()/1024, LLRenderTarget::sBytesAllocated/(1024*1024), gPipeline.mReflectionMapManager.probeCount(), gPipeline.mReflectionMapManager.probeMemory(), - pixel_to_texel_ratio, + discard_bias, cache_usage, cache_max_usage); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*8, text_color, LLFontGL::LEFT, LLFontGL::TOP); - text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB)", + text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB) Bubble: %.1fm PressMult: %.1fx LDMin: %.1f", image_count, raw_image_count, raw_image_bytes_MB, saved_raw_image_count, saved_raw_image_bytes_MB, - aux_raw_image_count, aux_raw_image_bytes_MB); + aux_raw_image_count, aux_raw_image_bytes_MB, + LLViewerTextureList::sCurrentBubbleMeters, + LLViewerTexture::sMemoryPressureMultiplier, + LLViewerTexture::sLastDitchMinDiscard); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height * 7, text_color, LLFontGL::LEFT, LLFontGL::TOP); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index dd2357ae8f..a4a7f1fd20 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -112,60 +112,45 @@ static bool handleRenderAvatarMouselookChanged(const LLSD& newvalue) return true; } -// Per-tier texture quality preset. Data-driven so adding a setting is -// "add a column", and the tier values are visible side-by-side. Index is -// RenderTextureQuality: 0=Low, 1=Medium, 2=High, 3=Ultra. -namespace -{ - struct TexturePreset - { - const char* name; - U32 max_resolution; - F32 pixel_to_texel_ratio; // TexturePixelToTexelRatio (R_max, texels per pixel) - F32 pressure_tighten_rate; // TexturePressureTightenRate (ratio units/sec) - F32 pressure_relax_rate; // TexturePressureRelaxRate (ratio units/sec) - F32 channel_ratio_normal; // TextureChannelRatioNormal - F32 channel_ratio_basecolor; // TextureChannelRatioBaseColor - F32 channel_ratio_specular; // TextureChannelRatioSpecular - F32 channel_ratio_emissive; // TextureChannelRatioEmissive - }; - - // Tier values: Low (2-4GB), Medium (4-8GB), High (8-16GB), Ultra (16+GB). - // Quality ladder, expressed in pixel:texel (texels per pixel): - // - pixel_to_texel_ratio is the baseline quality: how many texels per - // screen pixel the tier allocates when VRAM is comfortable (1.0 = 1:1). - // Under pressure the runtime drives the global ratio below this with no - // floor (down to 0 = deepest mips), so there is no per-tier minimum. - // - the channel ratios coarsen specular/emissive/normal relative to base - // color (each is a multiplier on the global ratio). - // The pressure water marks (TexturePressureHighWater/LowWater) are NOT tiered - they're a - // physical "crossed the budget" threshold (0.90 / 0.70), constant across - // tiers. Lower tiers start blurrier (lower R_max) and tighten faster. - // max_res Rmax tight relax N BC S E - static constexpr TexturePreset TEXTURE_PRESETS[4] = { - /* 0 Low */ { "Low", 1024, 0.10f, 0.50f, 0.05f, 0.50f, 1.00f, 0.25f, 0.25f }, - /* 1 Medium */ { "Medium", 2048, 0.40f, 0.35f, 0.08f, 1.00f, 1.00f, 0.50f, 0.50f }, - /* 2 High */ { "High", 2048, 0.80f, 0.25f, 0.10f, 1.00f, 1.00f, 0.50f, 1.00f }, - /* 3 Ultra */ { "Ultra", 2048, 1.00f, 0.15f, 0.12f, 1.00f, 1.00f, 1.00f, 1.00f }, - }; -} - static bool handleRenderTextureQualityChanged(const LLSD& newvalue) { + // 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, + // the four TextureChannel* exponents (Normal/BaseColor/Spec/Emissive), + // and TextureDistanceDiscardPower. U32 quality = (U32)newvalue.asInteger(); - if (quality > 3) quality = 3; - const TexturePreset& p = TEXTURE_PRESETS[quality]; - - gSavedSettings.setU32("RenderMaxTextureResolution", p.max_resolution); - gSavedSettings.setF32("TexturePixelToTexelRatio", p.pixel_to_texel_ratio); - gSavedSettings.setF32("TexturePressureTightenRate", p.pressure_tighten_rate); - gSavedSettings.setF32("TexturePressureRelaxRate", p.pressure_relax_rate); - gSavedSettings.setF32("TextureChannelRatioNormal", p.channel_ratio_normal); - gSavedSettings.setF32("TextureChannelRatioBaseColor", p.channel_ratio_basecolor); - gSavedSettings.setF32("TextureChannelRatioSpecular", p.channel_ratio_specular); - gSavedSettings.setF32("TextureChannelRatioEmissive", p.channel_ratio_emissive); - - LL_INFOS("TextureStream") << "Applied texture quality preset: " << p.name << LL_ENDL; + U32 max_res = 2048; + F32 ch_normal = 1.0f; + F32 ch_basecolor = 0.75f; + F32 ch_specular = 0.5f; + F32 ch_emissive = 0.75f; + F32 distance_power = 0.5f; + switch (quality) + { + case 0: // Low + max_res = 1024; + ch_normal = 0.5f; ch_basecolor = 0.75f; ch_specular = 0.1f; ch_emissive = 0.5f; + distance_power = 0.15f; + break; + case 1: // Medium + ch_normal = 0.75f; ch_basecolor = 0.75f; ch_specular = 0.3f; ch_emissive = 0.75f; + distance_power = 0.25f; + break; + case 2: // High + // channel defaults above + distance_power = 0.35f; + break; + case 3: // Ultra + default: + ch_normal = 1.f; ch_basecolor = 1.f; ch_specular = 1.f; ch_emissive = 1.f; + distance_power = 0.5f; + break; + } + gSavedSettings.setU32("RenderMaxTextureResolution", max_res); + gSavedSettings.setF32("TextureChannelNormal", ch_normal); + gSavedSettings.setF32("TextureChannelBaseColor", ch_basecolor); + gSavedSettings.setF32("TextureChannelSpecular", ch_specular); + gSavedSettings.setF32("TextureChannelEmissive", ch_emissive); + gSavedSettings.setF32("TextureDistanceDiscardPower", distance_power); return true; } @@ -884,6 +869,7 @@ void settings_setup_listeners() setting_setup_signal_listener(gSavedSettings, "OctreeMaxNodeCapacity", handleRepartition); setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition); setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged); setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized); setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged); setting_setup_signal_listener(gSavedSettings, "RenderFSAAType", handleReleaseGLBufferChanged); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 8863dfb501..7e671ef9a2 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3380,6 +3380,13 @@ void send_agent_update(bool force_send, bool send_reliable) memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / mem_factor, gAgentCamera.mDrawDistance / 2.f); } + if (LLViewerTexture::getSystemMemoryBudgetFactor() > 1.f) + { + // We are critcally low on memory or recovering, + // limit requested draw distance + memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / LLViewerTexture::getSystemMemoryBudgetFactor(), gAgentCamera.mDrawDistance / 2.f); + } + if (tp_state == LLAgent::TELEPORT_ARRIVING || LLStartUp::getStartupState() < STATE_MISC) { // Inform interest list, prioritize closer area. diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 4e4282a8ca..7c26cb3c9f 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -5229,20 +5229,7 @@ void LLViewerObject::setTE(const U8 te, const LLTextureEntry& texture_entry) const LLUUID& image_id = getTE(te)->getID(); LLViewerTexture* bakedTexture = getBakedTextureForMagicId(image_id); - if (bakedTexture) - { - mTEImages[te] = bakedTexture; - } - else - { - LLViewerFetchedTexture* img = LLViewerTextureManager::getFetchedTexture(image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - // Same creation seed the PBR path applies in updateTEMaterialTextures' - // fetch_texture - without it a Blinn texture has decode_priority 0 and - // cannot even fetch headers until its first coverage measurement, - // while the equivalent PBR texture starts fetching immediately. - img->addTextureStats(64.f * 64.f, true); - mTEImages[te] = img; - } + mTEImages[te] = bakedTexture ? bakedTexture : LLViewerTextureManager::getFetchedTexture(image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); updateAvatarMeshVisibility(image_id, old_image_id); @@ -5253,14 +5240,11 @@ void LLViewerObject::updateTEMaterialTextures(U8 te) { if (getTE(te)->getMaterialParams().notNull()) { - // Same creation seed as the PBR fetch_texture below - see setTE. const LLUUID& norm_id = getTE(te)->getMaterialParams()->getNormalID(); mTENormalMaps[te] = LLViewerTextureManager::getFetchedTexture(norm_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - mTENormalMaps[te]->addTextureStats(64.f * 64.f, true); const LLUUID& spec_id = getTE(te)->getMaterialParams()->getSpecularID(); mTESpecularMaps[te] = LLViewerTextureManager::getFetchedTexture(spec_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - mTESpecularMaps[te]->addTextureStats(64.f * 64.f, true); } LLFetchedGLTFMaterial* mat = (LLFetchedGLTFMaterial*) getTE(te)->getGLTFRenderMaterial(); diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 255317ce80..27865f7598 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -561,7 +561,13 @@ void LLViewerShaderMgr::setShaders() LLAppViewer::instance()->isSecondInstance()); } + static LLCachedControl max_texture_index(gSavedSettings, "RenderMaxTextureIndex", 16); + + // when using indexed texture rendering, leave some texture units available for shadow and reflection maps + static LLCachedControl reserved_texture_units(gSavedSettings, "RenderReservedTextureIndices", 14); + LLGLSLShader::sIndexedTextureChannels = 4; + //llclamp(max_texture_index, 1, gGLManager.mNumTextureImageUnits-reserved_texture_units); reentrance = true; diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 80daffdbf3..ac8bb1d0c5 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -60,7 +60,6 @@ #include "llmediaentry.h" #include "llvovolume.h" #include "llviewermedia.h" -#include "lldrawable.h" #include "lltexturecache.h" #include "llviewerwindow.h" #include "llwindow.h" @@ -87,8 +86,19 @@ S32 LLViewerTexture::sImageCount = 0; S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; -F32 LLViewerTexture::sPixelToTexelRatio = 1.f; -U32 LLViewerTexture::sGCSuspendedFrame = 0; +F32 LLViewerTexture::sDesiredDiscardBias = 0.f; +F32 LLViewerTexture::sBackgroundFactor = 0.f; +F32 LLViewerTexture::sMemoryPressureMultiplier = 1.f; +F32 LLViewerTexture::sLastDitchMinDiscard = 0.f; + +//static +F32 LLViewerTexture::getMemoryPressureProgress() +{ + static LLCachedControl max_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); + F32 cap = llmax((F32)max_mult, 1.0001f); + return llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); +} +U32 LLViewerTexture::sBiasTexturesUpdated = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size constexpr S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; @@ -100,9 +110,11 @@ U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; F32 LLViewerTexture::sCurrentTime = 0.0f; +constexpr F32 MEMORY_CHECK_WAIT_TIME = 1.0f; constexpr F32 MIN_VRAM_BUDGET = 768.f; F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; F32 LLViewerTexture::sWindowPixelArea = 1.f; +F32 LLViewerTexture::sSysMemoryFactor = 1.f; LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; @@ -479,6 +491,13 @@ void LLViewerTexture::initClass() LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture(); } +S32Megabytes get_render_free_main_memory_treshold() +{ + static LLCachedControl min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512); + const U32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory); + return MIN_FREE_MAIN_MEMORY; +} + //static void LLViewerTexture::updateClass() { @@ -527,60 +546,375 @@ void LLViewerTexture::updateClass() // 'bias' calculation to kick in. F32 vram_target = llmax(llmin(vram_budget - 512.f, vram_budget * 0.8f), MIN_VRAM_BUDGET); sFreeVRAMMegabytes = vram_target - vram_used; + const S32Megabytes free_sys_mem = getFreeSystemMemory(); + + F32 over_pct = (vram_used - vram_target) / vram_target; + + // Predicted-VRAM pressure controller. Eviction is fast, refetch is slow, + // so feedback on instantaneous `used` sawtooths; feeding `used + + // in_flight_delta` lets mult converge to equilibrium instead of cycling. + { + static LLCachedControl backoff_start(gSavedSettings, "TextureMemoryPressureBackoffStart", 0.85f); + static LLCachedControl max_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); + static LLCachedControl prediction_gain(gSavedSettings, "TextureMemoryPressurePredictionGain", 10.f); + static LLCachedControl smoothing_rate(gSavedSettings, "TextureMemoryPressureSmoothingRate", 4.f); + + F32 backoff_target = vram_target * llclamp((F32)backoff_start, 0.05f, 1.f); + F32 cap = llmax((F32)max_mult, 1.0001f); + F32 dt = (F32)gFrameIntervalSeconds; + + // Skip the full-list iteration when there is no pressure to react to: + // mult already at baseline, last-ditch at zero, and used well clear of + // the backoff target. Worst case the controller picks up the spike one + // frame later, from `used` alone. + bool need_predict = sMemoryPressureMultiplier > 1.001f + || sLastDitchMinDiscard > 0.f + || vram_used > backoff_target * 0.5f; + + S64 pending_bytes_increase = 0; + S64 pending_bytes_decrease = 0; + if (need_predict) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vt - in-flight predict"); + for (auto& imagep : gTextureList) + { + if (imagep.isNull()) continue; + // Cheap inline checks first so the virtual getDiscardLevel() + // call only fires when there is a real chance of contribution. + S32 desired = imagep->getDesiredDiscardLevel(); + if (desired < 0) continue; + S32 fw = imagep->getFullWidth(); + S32 fh = imagep->getFullHeight(); + if (fw <= 0 || fh <= 0) continue; + S32 current = imagep->getDiscardLevel(); + if (current < 0 || desired == current) continue; + + S32 wd = llmax(1, fw >> desired); + S32 hd = llmax(1, fh >> desired); + S32 wc = llmax(1, fw >> current); + S32 hc = llmax(1, fh >> current); + // bpp=4, mip pyramid overhead 4/3 + S64 size_d = (S64)wd * hd * 4 * 4 / 3; + S64 size_c = (S64)wc * hc * 4 * 4 / 3; + + if (desired < current) + pending_bytes_increase += (size_d - size_c); + else + pending_bytes_decrease += (size_c - size_d); + } + } - // VRAM pressure controller for the global pixel:texel ratio. Tightens above - // the high watermark, relaxes below the low one, holds in the band (the band - // stops sawtooth). Rates are slow so eviction frees bytes before the next step. - { - static LLCachedControl ratio_max(gSavedSettings, "TexturePixelToTexelRatio", 1.0f); - static LLCachedControl bg_min_ratio(gSavedSettings, "TextureBackgroundMinRatio", 0.001f); - static LLCachedControl wm_high(gSavedSettings, "TexturePressureHighWater", 0.90f); - static LLCachedControl wm_low(gSavedSettings, "TexturePressureLowWater", 0.70f); - static LLCachedControl tighten_rate(gSavedSettings, "TexturePressureTightenRate", 0.30f); - static LLCachedControl relax_rate(gSavedSettings, "TexturePressureRelaxRate", 0.10f); - static LLCachedControl cooldown_step(gSavedSettings, "TextureCooldownStepSeconds", 5.f); + // 1024 * 512 = 524288: matches the unit reduction at line 513. + constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; + F32 predicted_used = vram_used + + (F32)pending_bytes_increase * BYTES_TO_USED_UNITS + - (F32)pending_bytes_decrease * BYTES_TO_USED_UNITS; + F32 predicted_over = predicted_used / llmax(backoff_target, 1.f); + + // High water mark: when used crosses budget * high_water, skip the + // smoothed convergence and slam the controller into hard-cap state. + // Recovers the historical 90% behavior - immediate aggressive + // response instead of waiting for the lerp to chase the target. + static LLCachedControl high_water(gSavedSettings, "TextureMemoryHighWaterMark", 0.8f); + bool above_high_water = vram_used >= vram_budget * llclamp((F32)high_water, 0.5f, 1.f); + + F32 target_mult = llclamp(powf(llmax(predicted_over, 1.f), llmax((F32)prediction_gain, 0.0001f)), 1.f, cap); + if (above_high_water) + { + target_mult = cap; + sMemoryPressureMultiplier = cap; + } + else + { + // ~63% convergence in 1/smoothing_rate seconds (default 0.25s). + F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)smoothing_rate, 0.f)); + sMemoryPressureMultiplier += (target_mult - sMemoryPressureMultiplier) * alpha; + } + sMemoryPressureMultiplier = llclamp(sMemoryPressureMultiplier, 1.f, cap); - // No lower bound: pressure can drive the ratio to 0 (deepest mip for - // everything). Only the top is capped, by the configured max. - F32 r_max = llmax((F32)ratio_max, 0.f); - F32 high_frac = llclamp((F32)wm_high, 0.1f, 1.f); - F32 high = vram_budget * high_frac; - F32 low = vram_budget * llclamp((F32)wm_low, 0.05f, high_frac); - F32 dt = (F32)gFrameIntervalSeconds; + F32 progress = getMemoryPressureProgress(); - bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); + { + static LLCachedControl ld_engage(gSavedSettings, "TextureLastDitchEngageProgress", 0.95f); + static LLCachedControl ld_ramp(gSavedSettings, "TextureLastDitchRampRate", 0.5f); + static LLCachedControl ld_decay(gSavedSettings, "TextureLastDitchDecayRate", 0.5f); + static LLCachedControl ld_max(gSavedSettings, "TextureLastDitchMinDiscardMax", 13.f); + // Above the high water mark, last-ditch creeps regardless of + // mult_progress: by definition we are out of normal headroom. + bool engage = above_high_water || progress >= llclampf((F32)ld_engage); + if (engage && predicted_over > 1.f) + { + sLastDitchMinDiscard += llmax((F32)ld_ramp, 0.f) * dt; + } + else if (!above_high_water && predicted_over < 1.f) + { + sLastDitchMinDiscard -= llmax((F32)ld_decay, 0.f) * dt; + } + sLastDitchMinDiscard = llclamp(sLastDitchMinDiscard, 0.f, llmax((F32)ld_max, 0.f)); + } - if (in_background) + // 1 Hz pressure log. + static LLFrameTimer s_pressure_log_timer; + if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) + { + s_pressure_log_timer.reset(); + F32 over = vram_used / llmax(backoff_target, 1.f); + LL_INFOS("TextureStream") << "pressure" + << " mult=" << sMemoryPressureMultiplier + << " target_mult=" << target_mult + << " progress=" << progress + << " used=" << vram_used + << " predicted=" << predicted_used + << " target=" << vram_target + << " over=" << over + << " pred_over=" << predicted_over + << " in+=" << (S32)(pending_bytes_increase / 1024 / 1024) + << "MB in-=" << (S32)(pending_bytes_decrease / 1024 / 1024) + << "MB bias=" << sDesiredDiscardBias + << " ldmin=" << sLastDitchMinDiscard + << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() + << LL_ENDL; + } + } + + bool is_sys_low = isSystemMemoryLow(); + bool is_sys_critically_low = isSystemMemoryCritical(); + bool is_low = is_sys_low || over_pct > 0.f; + + static bool was_low = false; + static bool sys_was_low = false; + + // System memory factor + // 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_critically_low) + { + const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); + // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory. + S32 sys_budget_debt = free_sys_mem - MIN_FREE_MAIN_MEMORY; + + // Leave some padding, otherwise we will crash out of memory before hitting factor 2. + const S32Megabytes PAD_BUFFER(32); + S32Megabytes budget_target = MIN_FREE_MAIN_MEMORY - PAD_BUFFER; + if (!sys_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 / (F32)budget_target; + sSysMemoryFactor = llmax(sSysMemoryFactor, new_factor); + } + else { - // Backgrounded: decay toward bg_min to free VRAM for other apps, one - // mip per cooldown_step seconds (multiplicative). Don't relax back up - // until we're focused again. Pressure can still push below bg_min. - F32 bg_min = llclamp((F32)bg_min_ratio, 0.f, r_max); - if (sPixelToTexelRatio > bg_min) + // 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 / (F32)budget_target, 0.f); + sSysMemoryFactor += increment * gFrameIntervalSeconds; + } + sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); + } + else + { + const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); + // 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 + // treshold. + const S32Megabytes MEM_THRESHOLD = MIN_FREE_MAIN_MEMORY + S32Megabytes(150); + if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f) + { + // Ramp down factor over time. + constexpr F32 DECREMENT = 0.02f; + sSysMemoryFactor -= DECREMENT * gFrameIntervalSeconds; + sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); + } + } + sys_was_low = is_sys_critically_low; + + // VRAM memory bias + if (is_low && !was_low) + { + if (is_sys_low) + { + // Not having system memory is more serious, so discard harder + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * getSystemMemoryBudgetFactor()); + } + else + { + // Slam to 1.5 bias the moment we hit low memory (discards off screen textures immediately) + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f); + } + + if (is_sys_low || over_pct > 2.f) + { // if we're low on system memory, emergency purge off screen textures to avoid a death spiral + LL_WARNS() << "Low system memory detected, emergency downrezzing off screen textures" << LL_ENDL; + for (auto& image : gTextureList) { - const F32 step = llmax((F32)cooldown_step, 0.01f); - sPixelToTexelRatio = llmax(sPixelToTexelRatio * powf(0.25f, dt / step), bg_min); + gTextureList.updateImageDecodePriority(image, false /*will modify gTextureList otherwise!*/); } } - else if (vram_used > high) + } + + was_low = is_low; + + if (is_low) + { + // ramp up discard bias over time to free memory + if (sEvaluationTimer.getElapsedTimeF32() > MEMORY_CHECK_WAIT_TIME) + { + static LLCachedControl low_mem_min_discard_increment(gSavedSettings, "RenderLowMemMinDiscardIncrement", .1f); + + F32 increment = low_mem_min_discard_increment + llmax(over_pct, 0.f); + sDesiredDiscardBias += increment * gFrameIntervalSeconds; + } + } + else + { + // don't execute above until the slam to 1.5 has a chance to take effect + sEvaluationTimer.reset(); + + // Don't decay bias while downscale is still draining - those bytes + // are about to free and the loop would oscillate. + bool eviction_in_flight = !gTextureList.mDownScaleQueue.empty(); + + // lower discard bias over time when at least 10% of budget is free + constexpr F32 FREE_PERCENTAGE_TRESHOLD = -0.1f; + constexpr U32 FREE_SYS_MEM_THRESHOLD = 100; // 100MB more than isSystemMemoryLow to avoid fluctuations. + const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() + S32Megabytes(FREE_SYS_MEM_THRESHOLD)); + if (sDesiredDiscardBias > 1.f + && over_pct < FREE_PERCENTAGE_TRESHOLD + && free_sys_mem > MIN_FREE_MAIN_MEMORY + && !eviction_in_flight) { - sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * dt; + static LLCachedControl high_mem_discard_decrement(gSavedSettings, "RenderHighMemMinDiscardDecrement", .1f); + + F32 decrement = high_mem_discard_decrement - llmin(over_pct - FREE_PERCENTAGE_TRESHOLD, 0.f); + sDesiredDiscardBias -= decrement * gFrameIntervalSeconds; } - else if (vram_used < low) + } + + // set to max discard bias if the window has been backgrounded for a while + static F32 last_desired_discard_bias = 1.f; + static F32 last_texture_update_count_bias = 1.f; + static bool was_backgrounded = false; + static LLFrameTimer backgrounded_timer; + static LLCachedControl minimized_discard_time(gSavedSettings, "TextureDiscardMinimizedTime", 1.f); + static LLCachedControl backgrounded_discard_time(gSavedSettings, "TextureDiscardBackgroundedTime", 60.f); + + bool in_background = (gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus(); + bool is_minimized = gViewerWindow && gViewerWindow->getWindow()->getMinimized() && in_background; + if (in_background) + { + F32 discard_time = is_minimized ? minimized_discard_time : backgrounded_discard_time; + if (discard_time > 0.f && backgrounded_timer.getElapsedTimeF32() > discard_time) { - sPixelToTexelRatio += llmax((F32)relax_rate, 0.f) * dt; + if (!was_backgrounded) + { + LL_INFOS() << "Viewer was " << (is_minimized ? "minimized" : "backgrounded") << " for " << discard_time + << "s, freeing up video memory." << LL_ENDL; + + last_desired_discard_bias = sDesiredDiscardBias; + was_backgrounded = true; + } + sDesiredDiscardBias = 5.f; + } + } + else + { + backgrounded_timer.reset(); + if (was_backgrounded) + { // if the viewer was backgrounded + LL_INFOS() << "Viewer is no longer backgrounded or minimized, resuming normal texture usage." << LL_ENDL; + was_backgrounded = false; + sDesiredDiscardBias = last_desired_discard_bias; } - // else: hold in the hysteresis band. - sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max); + } - // Keep the GC-suspend frame current while backgrounded. This suppresses - // the foreground GC now, and gives it a grace window after we come back so - // visible content can re-stamp its bind frames before anything is collected. + // Background-window ramp: 0 -> 1 at rate per second while backgrounded, + // snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. + { + static LLCachedControl bg_factor_rate(gSavedSettings, "TextureBackgroundFactorRatePerSec", 0.011f); if (in_background) { - sGCSuspendedFrame = LLFrameTimer::getFrameCount(); + sBackgroundFactor += (F32)bg_factor_rate * gFrameIntervalSeconds; + sBackgroundFactor = llclampf(sBackgroundFactor); + } + else + { + sBackgroundFactor = 0.f; } } + + // Fetch-queue depth as a one-way bias floor (decay path still drops + // bias when the queue drains). Pushes bias up before VRAM overflows + // during teleport/scene-change floods. + if (LLTextureFetch* fetcher = LLAppViewer::getTextureFetch()) + { + S32 pending = fetcher->getNumRequests(); + static LLCachedControl fetch_pressure_scale(gSavedSettings, "TextureFetchPressureScale", 1000.f); + F32 scale = llmax((F32)fetch_pressure_scale, 1.f); + F32 fetch_pressure = llclamp((F32)pending / scale, 0.f, 3.f); + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + fetch_pressure); + } + + sDesiredDiscardBias = llclamp(sDesiredDiscardBias, 1.f, 4.f); + if (last_texture_update_count_bias < sDesiredDiscardBias) + { + // bias increased, reset texture update counter to + // let updates happen at an increased rate. + last_texture_update_count_bias = sDesiredDiscardBias; + sBiasTexturesUpdated = 0; + } + else if (last_texture_update_count_bias > sDesiredDiscardBias + 0.1f) + { + // bias decreased, 0.1f is there to filter out small fluctuations + // and not reset sBiasTexturesUpdated too often. + // Bias jumps to 1.5 at low memory, so getting stuck at 1.1 is not + // a problem. + last_texture_update_count_bias = sDesiredDiscardBias; + } +} + +//static +U32Megabytes LLViewerTexture::getFreeSystemMemory() +{ + static LLFrameTimer timer; + static U32Megabytes physical_res = U32Megabytes(U32_MAX); + + if (timer.getElapsedTimeF32() < MEMORY_CHECK_WAIT_TIME) //call this once per second. + { + return physical_res; + } + + timer.reset(); + + LLMemory::updateMemoryInfo(); + physical_res = LLMemory::getAvailableMemKB(); + return physical_res; +} + +//static +bool LLViewerTexture::isSystemMemoryLow() +{ + return getFreeSystemMemory() < get_render_free_main_memory_treshold(); +} + +//static +bool LLViewerTexture::isSystemMemoryCritical() +{ + return getFreeSystemMemory() < get_render_free_main_memory_treshold() / 2; +} + +// static +F32 LLViewerTexture::getSystemMemoryBudgetFactor() +{ + return sSysMemoryFactor; } //end of static functions @@ -2028,30 +2362,6 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); make_request = false; } - else - { - // Only fetch streamed world textures the renderer is actually drawing - // (mLastBindFrame is stamped per drawn frame). Out-of-view content gets - // no residency. Exempt: boosted/UI, avatar bakes, textures with loaded - // callbacks, and bake uploads. - static LLCachedControl vis_frames(gSavedSettings, "TextureFetchVisibilityFrames", 5); - const bool visibility_gated = mBoostLevel < LLGLTexture::BOOST_HIGH - && mUseMipMaps - && !mDontDiscard - && !isAgentAvatarBoost(mBoostLevel) - && !mForceToSaveRawImage - && mLoadedCallbackList.empty(); - if (visibility_gated && mGLTexturep.notNull()) - { - const U32 last = mGLTexturep->mLastBindFrame; - const U32 now = LLFrameTimer::getFrameCount(); - if (last == 0 || now - last > llmax((U32)vis_frames, 1u)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - not visible"); - make_request = false; - } - } - } if (make_request) { @@ -2107,7 +2417,6 @@ bool LLViewerFetchedTexture::updateFetch() // in some cases createRequest can modify discard, as an example // bake textures are always at discard 0 mRequestedDiscardLevel = llmin(desired_discard, fetch_request_response); - mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); } @@ -2949,151 +3258,6 @@ S8 LLViewerLODTexture::getType() const return LLViewerTexture::LOD_TEXTURE; } -// Desired discard from the pixel:texel ratio - this is the entire streaming -// policy. For each channel bucket the texture is used in, the most-demanding -// (largest screen coverage) face sets that bucket's requirement at -// floor(log4(texels / (R_global * channelRatio[b] * coverage))); the sharpest -// requirement across buckets wins, since one GL image serves every channel it -// is used in. floor (not ceil) keeps content native up close - "1:1" is a -// target, not a hard cap that downrezzes anything slightly oversampled. A -// per-mip hysteresis dead-band against the current discard level prevents -// fetch/scaleDown thrash as an object slowly crosses a mip boundary. -S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) const -{ - static const F64 log_4 = log(4.0); - - // UI-pinned (icons / thumbnails): size against the known draw size, with no - // ratio or pressure - these want exact native-for-their-slot resolution. - if (mKnownDrawWidth && mKnownDrawHeight) - { - S32 draw_texels = llclamp(mKnownDrawWidth * mKnownDrawHeight, MIN_IMAGE_AREA, MAX_IMAGE_AREA); - S32 d = (draw_texels >= (S32)mTexelsPerImage) - ? 0 - : (S32)floor(log((F64)mTexelsPerImage / (F64)draw_texels) / log_4); - return llclamp(d, 0, dim_max_i); - } - - // Avatar bakes ignore the global pressure ramp (a blurred bake reads as a - // cloud avatar); they always size against the configured max ratio. - static LLCachedControl ratio_max(gSavedSettings, "TexturePixelToTexelRatio", 1.0f); - const F32 r_global = avatar_bake ? llmax((F32)ratio_max, 0.01f) : sPixelToTexelRatio; - - static LLCachedControl ch_normal (gSavedSettings, "TextureChannelRatioNormal", 1.0f); - static LLCachedControl ch_basecolor(gSavedSettings, "TextureChannelRatioBaseColor", 1.0f); - static LLCachedControl ch_specular (gSavedSettings, "TextureChannelRatioSpecular", 0.5f); - static LLCachedControl ch_emissive (gSavedSettings, "TextureChannelRatioEmissive", 0.5f); - const F32 channel_ratio[4] = { (F32)ch_normal, (F32)ch_basecolor, (F32)ch_specular, (F32)ch_emissive }; - - // Downrez bias: 0 sizes each bucket to its most demanding use (the lowest - // texels-per-pixel variant - the quality bound); 1 sizes to its least - // demanding use (the most oversampled variant - frees the most memory). - // Values between lerp across the texture's measured coverage spread. - static LLCachedControl downrez_bias(gSavedSettings, "TextureDownrezCoverageBias", 0.25f); - const F32 cov_bias = llclampf((F32)downrez_bias); - - // Continuous ideal discard = sharpest (smallest) requirement across the - // channels this texture is actually used in. - F32 ideal = (F32)dim_max_i; // default: coarsest, until a measurement arrives - bool measured = false; - for (S32 b = 0; b < 4; ++b) - { - F32 coverage = mChannelCoverage[b]; - if (coverage <= 0.f) - { - continue; - } - if (cov_bias > 0.f && mChannelCoverageMin[b] > 0.f && mChannelCoverageMin[b] < coverage) - { - // Geometric (log-space) lerp between the coverage bounds. The - // consumer below is log4(coverage), and min/max are routinely - // orders of magnitude apart - a linear pixel-area lerp barely - // moves the resulting discard until bias approaches 1, then - // plunges (reads as binary). Interpolating the RATIO instead - // moves the discard linearly with bias: 0.5 = halfway between - // the two ends in mip levels. - coverage *= powf(mChannelCoverageMin[b] / coverage, cov_bias); - } - measured = true; - F32 allowed_texels = coverage * r_global * llmax(channel_ratio[b], 0.01f); - F32 d; - if (allowed_texels <= 0.f) // ratio driven to 0 -> deepest mip - d = (F32)dim_max_i; - else if (allowed_texels >= (F32)mTexelsPerImage) - d = 0.f; - else - d = (F32)(log((F64)mTexelsPerImage / (F64)allowed_texels) / log_4); - ideal = llmin(ideal, d); - } - if (!measured) - { - return dim_max_i; // off-screen / never measured -> coarsest mip - } - - // Round toward sharper (floor): a texture stays at a mip level until its - // ideal is a full level past the boundary. This is what makes "1:1" mean - // "native up close" - ceil would downrez anything even slightly oversampled - // (a 2048 map can't hit its own resolution on a 1080p screen), which reads - // as everything being blurry. Pressure still evicts by lowering R_global. - ideal = llmax(ideal, 0.f); - const S32 target = (S32)floor(ideal); - - // Hysteresis: a texture at discard C is "happy" while floor(ideal) == C, - // i.e. ideal in [C, C+1). Only leave that band once ideal is past it by the - // margin, so coverage jitter at a boundary doesn't ping-pong fetch<->scaleDown. - static LLCachedControl uprez_margin(gSavedSettings, "TextureUpRezMargin", 0.2f); - const F32 margin = llclamp((F32)uprez_margin, 0.f, 0.9f); - const S32 current = getDiscardLevel(); - S32 desired; - if (current < 0) - { - desired = target; // nothing loaded yet - } - else if (ideal >= (F32)current + 1.f + margin) - { - desired = target; // clearly coarser -> evict - } - else if (ideal <= (F32)current - margin) - { - desired = target; // clearly finer -> uprez - } - else - { - desired = current; // inside the dead-band -> hold - } - - // Foreground visibility GC (avatar bakes exempt). Background degradation is - // handled by the ratio decay in updateClass, and the GC self-suppresses while - // backgrounded via the sGCSuspendedFrame check below, so the two don't fight. - // - // For every gc_cooldown frames a texture goes without a camera bind, drop its - // mip by gc_step, walking gradually toward the deepest mip instead of slamming. - // Content drawn within the last cooldown stays full-res, so a fast camera pan - // finds it only a step or two coarse on the way back. Resets when drawn again. - if (!avatar_bake) - { - if (LLImageGL* gli = getGLTexture()) - { - static LLCachedControl gc_cooldown_frames(gSavedSettings, "TextureGCStepFrames", 5); - static LLCachedControl gc_step_mips(gSavedSettings, "TextureGCStepMips", 1); - constexpr U32 GC_RESUME_GRACE_FRAMES = 10; - const U32 now = LLFrameTimer::getFrameCount(); - if (gli->mLastBindFrame > 0 // drawn at least once - && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background - { - const U32 cooldown = llmax((U32)gc_cooldown_frames, 1u); - const S32 periods = (S32)((now - gli->mLastBindFrame) / cooldown); - if (periods > 0) - { - const S32 step_mips = (S32)llmax((U32)gc_step_mips, 1u); - desired = llclamp(desired + periods * step_mips, desired, dim_max_i); - } - } - } - } - - return llclamp(desired, 0, dim_max_i); -} - // This is gauranteed to get called periodically for every texture //virtual void LLViewerLODTexture::processTextureStats() @@ -3101,13 +3265,7 @@ void LLViewerLODTexture::processTextureStats() LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; updateVirtualSize(); - // Hoisted once: avatar bake textures are exempt from several pressure - // mechanisms below (anti-cloud-bug protection). Reads of `avatar_bake` - // replace inline isAgentAvatarBoost(mBoostLevel) calls; each site keeps - // its own intent comment explaining *why* the exemption applies. See - // also LLViewerFetchedTexture::isAgentAvatarBoost() in the header for - // the canonical list of exemption sites. - const bool avatar_bake = isAgentAvatarBoost(mBoostLevel); + bool did_downscale = false; static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); @@ -3126,7 +3284,7 @@ void LLViewerLODTexture::processTextureStats() mDesiredDiscardLevel = 0; } // HUD/UI/preview and mDontDiscard textures bypass streaming - no - // coverage signal applies, they need native resolution. + // face_distance signal applies, they need native resolution. else if (mBoostLevel >= LLGLTexture::BOOST_HIGH || mDontDiscard || !mUseMipMaps) @@ -3142,37 +3300,165 @@ void LLViewerLODTexture::processTextureStats() } else { - // Per-texture max discard (smallest meaningful mip): floor(log2(max(w,h))). + F32 discard_level = 0.f; + + // floor(log2(max(w, h))) - both the multiplier on the normalized + // factor and the cap clamp at the bottom of this function. S32 dim_max_for_image_i = (mFullWidth > 0 && mFullHeight > 0) ? LLImageGL::dimDerivedMaxDiscard(mFullWidth, mFullHeight) : (S32)mCodecMaxDiscardLevel; + F32 dim_max_for_image = (F32)dim_max_for_image_i; + + if (mKnownDrawWidth && mKnownDrawHeight) + { + // UI-pinned target dimensions - use pixel-area math. + static const F64 log_4 = log(4.0); + S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; + draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); + discard_level = (F32)(log(mTexelsPerImage / draw_texels) / log_4); + } + else + { + // Two 0..1 signals composed multiplicatively: + // discard = distance_factor * size_factor * max_discard + // distance_factor: face_dist / draw_dist, shaped by + // TextureDistanceDiscardPower (default 0.5 = sqrt). + // size_factor: 1 - (mMaxOnScreenSize / window_pixels), shaped + // by TextureSizeDiscardPower. + // Either factor near 0 keeps the result fine - both have to + // be high for the texture to go deep. + static LLCachedControl distance_power(gSavedSettings, "TextureDistanceDiscardPower", 0.5f); + F32 power = llmax((F32)distance_power, 0.0001f); + F32 distance_factor = (power == 1.f) ? mMinDistanceFactor : powf(mMinDistanceFactor, power); + + static LLCachedControl size_power(gSavedSettings, "TextureSizeDiscardPower", 1.f); + F32 sz_power = llmax((F32)size_power, 0.0001f); + F32 coverage = llclampf(mMaxOnScreenSize / sWindowPixelArea); + F32 inv_cov = 1.f - coverage; + F32 size_factor = (sz_power == 1.f) ? inv_cov : powf(inv_cov, sz_power); + + F32 combined = distance_factor * size_factor; + + // VRAM pressure: multiply the combined signal and clamp to 0..1. + // Compresses the effective draw range and picks up close-coverage + // textures (small combined) too. Applied before the channel + // exponent so subsequent transforms see a normalized 0..1 value. + // Avatar bakes exempt. + if (!isAgentAvatarBoost(mBoostLevel) && sMemoryPressureMultiplier > 1.f) + { + combined = llmin(combined * sMemoryPressureMultiplier, 1.f); + } + + // Per-channel exponent. 1.0 = baseline; <1.0 pushes combined + // toward 1 (max attenuation) faster. Edges are preserved: + // pow(0, p) = 0, pow(1, p) = 1. + // mPriorityChannel order: 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. + S32 priority_channel = (mPriorityChannel >= 0 && mPriorityChannel < 4) ? (S32)mPriorityChannel : 1; + static LLCachedControl channel_normal (gSavedSettings, "TextureChannelNormal", 1.0f); + static LLCachedControl channel_basecolor(gSavedSettings, "TextureChannelBaseColor", 0.75f); + static LLCachedControl channel_specular (gSavedSettings, "TextureChannelSpecular", 0.5f); + static LLCachedControl channel_emissive (gSavedSettings, "TextureChannelEmissive", 0.75f); + const F32 channels[4] = { + (F32)channel_normal, + (F32)channel_basecolor, + (F32)channel_specular, + (F32)channel_emissive, + }; + F32 channel_power = llmax(channels[priority_channel], 0.0001f); + if (channel_power != 1.f) + { + combined = powf(combined, channel_power); + } + + // Own-avatar boost: shave combined for rigged/animated faces + // on gAgentAvatarp. Preference, not exemption - applied + // before the staleness/background/pressure floors so heavy + // pressure can still evict. + if (mOnAgentAvatar) + { + static LLCachedControl agent_avatar_boost(gSavedSettings, "TextureAgentAvatarBoost", 0.5f); + combined *= llclampf((F32)agent_avatar_boost); + } + + // Staleness / background floors. Avatar bakes exempt from + // background to avoid the universal-cloud bug when re-foregrounding. + combined = llmax(combined, mStalenessFactor); + if (!isAgentAvatarBoost(mBoostLevel)) + { + // Background floor capped at (dim_max - offset) so we can + // keep some baseline quality while backgrounded. + static LLCachedControl bg_offset(gSavedSettings, "TextureBackgroundDiscardOffset", 2); + F32 bg = sBackgroundFactor; + if ((S32)bg_offset > 0 && dim_max_for_image > 0.f) + { + F32 cap = llmax(dim_max_for_image - (F32)(S32)bg_offset, 0.f) / dim_max_for_image; + bg = llmin(bg, cap); + } + combined = llmax(combined, bg); + } + + discard_level = combined * dim_max_for_image; + } - // The whole policy: pixel:texel ratio at the most-demanding face. - S32 discard = computeDesiredDiscard(dim_max_for_image_i, avatar_bake); + discard_level = floorf(discard_level); - // Per-texture caps: force >=1 for sources over the resolution cap; - // bound by the debug override or the dim-derived max. - S32 min_discard = 0; + F32 min_discard = 0.f; if (mFullWidth > max_tex_res || mFullHeight > max_tex_res) - min_discard = 1; + min_discard = 1.f; + // dim_max_for_image_i is the per-texture cap. TextureMaxDiscardOverride + // raises it (debug). Codec_max applies only to fetches, not here. static LLCachedControl max_discard_override(gSavedSettings, "TextureMaxDiscardOverride", 0); - const S32 effective_cap = (max_discard_override > 0) ? (S32)max_discard_override : dim_max_for_image_i; - discard = llclamp(discard, min_discard, effective_cap); + S32 effective_cap = (max_discard_override > 0) ? (S32)max_discard_override : dim_max_for_image_i; + discard_level = llclamp(discard_level, min_discard, (F32)effective_cap); + + mDesiredDiscardLevel = llmin(effective_cap, (S32)discard_level); + + // Apply the setMinDiscardLevel cap, relaxed under VRAM pressure + // (cap_relax = 1 - 1/mult: 0 at mult=1, ~0.5 at mult=2, ~0.9 at + // mult=10). Caps of 0 (thumbnails) and avatar bakes are preserved. + S32 effective_min_cap = mMinDesiredDiscardLevel; + if (sMemoryPressureMultiplier > 1.f && + mMinDesiredDiscardLevel > 0 && mMinDesiredDiscardLevel < S8_MAX && + !isAgentAvatarBoost(mBoostLevel)) + { + F32 cap_relax = 1.f - 1.f / sMemoryPressureMultiplier; + F32 room = (F32)dim_max_for_image_i - (F32)mMinDesiredDiscardLevel; + effective_min_cap += (S32)(cap_relax * room); + effective_min_cap = llmin(effective_min_cap, dim_max_for_image_i); + } + mDesiredDiscardLevel = llmin((S8)effective_min_cap, mDesiredDiscardLevel); - // Caller-set min-discard ceiling (terrain / avatar-self / thumbnails): - // never coarser than the caller explicitly asked for. - discard = llmin(discard, (S32)mMinDesiredDiscardLevel); + // Halve the floor for bubble-resident textures (mMinDistanceFactor == 0 + // = at least one face inside the bubble) so the close-vs-far gradient + // is preserved at every pressure level. + if (!isAgentAvatarBoost(mBoostLevel)) + { + S32 forced = (S32)floorf(sLastDitchMinDiscard); + if (mMinDistanceFactor <= 0.f) forced /= 2; + forced = llclamp(forced, 0, dim_max_for_image_i); + if (forced > mDesiredDiscardLevel) + { + mDesiredDiscardLevel = (S8)forced; + } + } - mDesiredDiscardLevel = (S8)discard; - // If the GPU already holds finer data than we now want, evict it. - // Avatar bakes exempt: shrinking mid-bake can leave the avatar stuck - // as a cloud until the next bake completes. + // + // At this point we've calculated the quality level that we want, + // if possible. Now we check to see if we have it, and take the + // proper action if we don't. + // + S32 current_discard = getDiscardLevel(); - if (!avatar_bake && current_discard >= 0 && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) + // Avatar bakes exempt: shrinking mid-bake can leave the avatar + // stuck as a cloud until the next bake completes. + if (!isAgentAvatarBoost(mBoostLevel)) { - scaleDown(); + if (current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) + { // should scale down + scaleDown(); + } } mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); @@ -3200,7 +3486,12 @@ bool LLViewerLODTexture::scaleDown() return false; } - // Structural blocks only; per-texture policy lives in processTextureStats. + // Hard structural blocks only. Per-texture policy (icons pinned to full + // res, etc.) lives in processTextureStats; if that policy is later + // relaxed (e.g. honor mKnownDrawWidth for icons rendered at 8x8 in a + // friend list) the scaleDown path stays open. + // BOOST_HIGH is the emergency-out for GLTF's "force full res" hack; + // the other two flags are structural. if (!mUseMipMaps || mDontDiscard || mBoostLevel >= LLGLTexture::BOOST_HIGH) { return false; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index cc9fbe5e48..8c5c48bafd 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -114,6 +114,11 @@ protected: public: static void initClass(); static void updateClass(); + static bool isSystemMemoryLow(); + static bool isSystemMemoryCritical(); + + // Ranges from 1 (no RAM deficit) to 2 (RAM deficit) + static F32 getSystemMemoryBudgetFactor(); LLViewerTexture(bool usemipmaps = true); LLViewerTexture(const LLUUID& id, bool usemipmaps) ; @@ -145,11 +150,6 @@ public: virtual F32 getMaxVirtualSize() ; - // Read-only debug access to the per-bucket coverage bounds (see - // mChannelCoverage) - used by the RENDER_DEBUG_TEXTURE_PRIORITY overlay. - F32 getChannelCoverage(S32 bucket) const { return (bucket >= 0 && bucket < 4) ? mChannelCoverage[bucket] : 0.f; } - F32 getChannelCoverageMin(S32 bucket) const { return (bucket >= 0 && bucket < 4) ? mChannelCoverageMin[bucket] : 0.f; } - LLFrameTimer* getLastReferencedTimer() { return &mLastReferencedTimer; } S32 getFullWidth() const { return mFullWidth; } @@ -193,6 +193,8 @@ private: friend class LLBumpImageList; friend class LLUIImageList; + static U32Megabytes getFreeSystemMemory(); + protected: friend class LLViewerTextureList; LLUUID mID; @@ -203,17 +205,24 @@ protected: mutable S32 mMaxVirtualSizeResetInterval; LLFrameTimer mLastReferencedTimer; - // Screen-space pixel coverage bounds among the texture's faces, per - // priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive). 0 = the - // texture is not used in that channel (or hasn't been measured yet). - // Populated by LLViewerTextureList::updateImageDecodePriority; consumed by - // LLViewerLODTexture::computeDesiredDiscard. This is the only view-dependent - // streaming signal - distance, size, tiling, and channel role all collapse - // into it. Max = the most demanding use (lowest texels-per-pixel variant, - // the quality bound); Min = the least demanding positive use (highest - // texels-per-pixel, most oversampled - the downrez-bias end). - F32 mChannelCoverage[4] = { 0.f, 0.f, 0.f, 0.f }; - F32 mChannelCoverageMin[4] = { 0.f, 0.f, 0.f, 0.f }; + // 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. -1 -> base color. + S8 mPriorityChannel = -1; + + // Bind-staleness floor, 0..1. Per-interval increment is 1/max_discard + // so any texture saturates after interval x max_discard seconds idle. + F32 mStalenessFactor = 0.f; + + // Closest face's face_distance / draw_distance, clamped 0..1. + // Defaults to 1 so never-measured textures resolve to deepest discard. + F32 mMinDistanceFactor = 1.f; + + // Largest per-face screen-space coverage in pixels. Raw - no bias or + // channel-priority contamination. + F32 mMaxOnScreenSize = 0.f; + + // Any face on the agent's avatar (rigged / animated). Drives the + // own-avatar quality boost in processTextureStats. + bool mOnAgentAvatar = false; ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; @@ -235,17 +244,24 @@ public: static S32 sRawCount; static S32 sAuxCount; static LLFrameTimer sEvaluationTimer; - - // The global max pixel:texel ratio (texels per screen pixel, the "R" in 1:R). - // The watermark controller in updateClass walks it between TexturePixelToTexelRatio - // and 0 as VRAM pressure changes; lower means coarser desired discards. - static F32 sPixelToTexelRatio; - - // Frame index the GC was last suspended (kept current while backgrounded). - // The foreground GC only runs once getFrameCount() is a grace window past - // this, so content can re-stamp after an alt-tab before anything is collected. - static U32 sGCSuspendedFrame; - + static F32 sDesiredDiscardBias; + // Backgrounded-window discard floor, 0..1. Ramps while backgrounded, + // snaps to 0 in foreground. Avatar bakes exempt. + static F32 sBackgroundFactor; + + // VRAM-pressure distance multiplier, >= 1. Compresses the distance + // signal: dist_factor = clamp(mMinDistanceFactor * mult, 0, 1). + // Grows geometrically while over budget; decays back to 1 when fitting. + static F32 sMemoryPressureMultiplier; + // Last-ditch global discard floor. Mirrors sDesiredDiscardBias once the + // multiplier is exhausted. + static F32 sLastDitchMinDiscard; + + // 0..1 progress of the pressure multiplier from baseline (1) to its + // configured cap (TextureMemoryPressureMaxMultiplier). Used to gate + // bubble shrink and last-ditch engagement. + static F32 getMemoryPressureProgress(); + static U32 sBiasTexturesUpdated; static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; static U32 sMaxSmallImageSize ; @@ -254,6 +270,7 @@ public: // estimated free memory for textures, by bias calculation static F32 sFreeVRAMMegabytes; + static F32 sSysMemoryFactor; // Viewport pixel area, refreshed once per frame. Hoisted to keep the // per-texture hot path out of gViewerWindow. static F32 sWindowPixelArea; @@ -313,6 +330,27 @@ public: || boost_level == BOOST_AVATAR_BAKED_SELF; } +public: + + struct Compare + { + // lhs < rhs + bool operator()(const LLPointer &lhs, const LLPointer &rhs) const + { + const LLViewerFetchedTexture* lhsp = (const LLViewerFetchedTexture*)lhs; + const LLViewerFetchedTexture* rhsp = (const LLViewerFetchedTexture*)rhs; + + // greater priority is "less" + const F32 lpriority = lhsp->mMaxVirtualSize; + const F32 rpriority = rhsp->mMaxVirtualSize; + if (lpriority > rpriority) // higher priority + return true; + if (lpriority < rpriority) + return false; + return lhsp < rhsp; + } + }; + public: /*virtual*/ S8 getType() const override; FTType getFTType() const; @@ -564,16 +602,6 @@ public: private: void init(bool firstinit) ; - - // The whole streaming pipeline: desired discard from the pixel:texel ratio. - // For each channel bucket the texture is used in, the most-demanding - // (largest coverage) face sets that bucket's requirement at - // floor(log4(texels / (R_global * channelRatio[b] * coverage))); the sharpest - // requirement across buckets wins (one GL image serves all its channels). - // A per-mip hysteresis dead-band against the current discard level prevents - // fetch/scaleDown thrash. avatar_bake textures use the configured max ratio - // instead of the pressure-driven sPixelToTexelRatio (anti cloud-bug). - S32 computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) const; }; // diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 3e1481f8b4..4d09eff74e 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -64,9 +64,6 @@ #include "llviewerwindow.h" #include "llsurface.h" #include "llvoavatarself.h" -#include "lldrawable.h" -#include "llvovolume.h" -#include "llviewertextureanim.h" #include "llprogressview.h" //////////////////////////////////////////////////////////////////////////// @@ -74,6 +71,7 @@ void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; S32 LLViewerTextureList::sNumImages = 0; +F32 LLViewerTextureList::sCurrentBubbleMeters = 0.f; LLViewerTextureList gTextureList; @@ -919,86 +917,100 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag { llassert(!gCubeSnapshot); - // Refresh spotlight priorities first: light projector textures register as - // LIGHT_TEX volumes (no faces), and both their fetch priority - // (addTextureStats inside updateSpotLightPriority) and their coverage - // (folded into the block below) derive from mSpotLightPriority. - for (S32 vi = 0; vi < imagep->getNumVolumes(LLRender::LIGHT_TEX); ++vi) - { - LLVOVolume* volume = (*imagep->getVolumeList(LLRender::LIGHT_TEX))[vi]; - volume->updateSpotLightPriority(); - } + constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; // perf gate for face-loop early exit if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { - // Bounds on the per-face UV repeat-area divisor (mined from the old - // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost - // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips - // coarser) so pathological UV scales can't explode either direction. - constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f; - constexpr F32 MAX_REPEAT_AREA = 128.f; - - // Per priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive): - // the HIGHEST per-face effective coverage (= the lowest texels-per-pixel - // use, the most demanding variant - drives desired discard) and the - // LOWEST positive coverage (= the highest texels-per-pixel use, the most - // oversampled variant - available for downrez-bias policy). The overall - // max is the fetch priority (raw - no bias). A bucket with faces but - // zero coverage (all off-screen) publishes 0, which - // computeDesiredDiscard reads as "coarsest mip". - F32 channel_coverage[4] = { 0.f, 0.f, 0.f, 0.f }; - F32 channel_coverage_min[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; - bool bucket_used[4] = { false, false, false, false }; - F32 max_coverage = 0.f; - + static LLCachedControl texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.0095f); + static LLCachedControl texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); + + F32 max_vsize = 0.f; + bool on_screen = false; + + // Accumulators for the per-texture signals published below. + // Defaults map to "deepest discard wanted" until evidence updates them. + F32 min_distance_factor = 1.f; + F32 max_on_screen_size = 0.f; + bool on_agent_avatar = false; + F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 0.001f); + + // Close-camera bubble: faces inside `bubble` meters resolve to + // dist_factor = 0, so the distance ramp spans (bubble, draw_distance]. + static LLCachedControl close_bubble(gSavedSettings, "TextureCloseBubbleMeters", 5.f); + static LLCachedControl close_bubble_min(gSavedSettings, "TextureCloseBubbleMinMeters", 0.1f); + static LLCachedControl bubble_shrink_threshold(gSavedSettings, "TextureCloseBubbleShrinkThreshold", 0.8f); + static LLCachedControl bubble_track_rate(gSavedSettings, "TextureCloseBubbleTrackRate", 0.5f); + F32 bubble_full = llmax((F32)close_bubble, 0.f); + F32 bubble_min = llclamp((F32)close_bubble_min, 0.f, bubble_full); + // Advance the slow-track once per frame, not per texture: this + // function runs once per texture so a naive per-call lerp converges + // in a single frame. + static F32 s_tracked_bubble = -1.f; + static U32 s_tracked_bubble_frame = 0; + if (s_tracked_bubble < 0.f) s_tracked_bubble = bubble_full; + if (s_tracked_bubble_frame != LLFrameTimer::getFrameCount()) + { + s_tracked_bubble_frame = LLFrameTimer::getFrameCount(); + F32 progress = LLViewerTexture::getMemoryPressureProgress(); + F32 shrink_thresh = llclampf((F32)bubble_shrink_threshold); + F32 shrink_frac = (progress > shrink_thresh) + ? (progress - shrink_thresh) / llmax(1.f - shrink_thresh, 0.0001f) + : 0.f; + F32 target_bubble = bubble_full - (bubble_full - bubble_min) * shrink_frac; + F32 dt = (F32)gFrameIntervalSeconds; + F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)bubble_track_rate, 0.f)); + s_tracked_bubble += (target_bubble - s_tracked_bubble) * alpha; + s_tracked_bubble = llclamp(s_tracked_bubble, bubble_min, bubble_full); + sCurrentBubbleMeters = s_tracked_bubble; + } + F32 bubble = llclamp(s_tracked_bubble, 0.f, draw_distance - 0.001f); + F32 ramp_range = llmax(draw_distance - bubble, 0.001f); U32 face_count = 0; - const U32 max_faces_to_check = 1024; + U32 max_faces_to_check = 1024; - // Cheap first pass: which buckets is this texture used in, and how many - // faces total. No per-face geometry work. + // Pick the least-aggressive channel across all uses, so a texture + // used as both diffuse and normal isn't penalized by its harshest + // role. -1 sentinel keeps emissive-only textures (W=3) from being + // clobbered by a smaller init value. + S32 priority_channel = -1; for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { - U32 n = imagep->getNumFaces(i); - face_count += n; - if (n > 0) + if (imagep->getNumFaces(i) > 0) { - S32 b = sChannelToPriority[i]; - if (b >= 0 && b < 4) bucket_used[b] = true; + S32 mapped = sChannelToPriority[i]; + priority_channel = (priority_channel < 0) ? mapped : llmin(priority_channel, mapped); } } - - if (face_count > max_faces_to_check) + if (priority_channel < 0) { - // Used in so many places that scanning the face list isn't worth it - // (and isn't time-sliced) - treat as full-screen so it loads sharp. - for (S32 b = 0; b < 4; ++b) - { - if (bucket_used[b]) - { - channel_coverage[b] = (F32)MAX_IMAGE_AREA; - channel_coverage_min[b] = (F32)MAX_IMAGE_AREA; - } - } - max_coverage = (F32)MAX_IMAGE_AREA; + priority_channel = 1; // no faces - default to diffuse } - else + imagep->mPriorityChannel = (S8)priority_channel; + + // get adjusted bias based on image resolution + LLImageGL* img = imagep->getGLTexture(); + F32 max_discard = F32(img ? img->getMaxDiscardLevel() : MAX_DISCARD_LEVEL); + F32 bias = llclamp(max_discard - 2.f, 1.f, LLViewerTexture::sDesiredDiscardBias); + + // convert bias into a vsize scaler + bias = (F32) llroundf(powf(4, bias - 1.f)); + + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + face_count += imagep->getNumFaces(i); + S32 faces_to_check = (face_count > max_faces_to_check) ? 0 : imagep->getNumFaces(i); + + for (S32 fi = 0; fi < faces_to_check; ++fi) { - const S32 bucket = sChannelToPriority[i]; - const U32 num_faces = imagep->getNumFaces(i); - for (U32 fi = 0; fi < num_faces; ++fi) - { - LLFace* face = (*(imagep->getFaceList(i)))[fi]; - if (!face || !face->getViewerObject()) - { - continue; - } + LLFace* face = (*(imagep->getFaceList(i)))[fi]; + if (face && face->getViewerObject()) + { F32 radius; F32 cos_angle_to_view_dir; + if ((gFrameCount - face->mLastTextureUpdate) > 10) { // only call calcPixelArea at most once every 10 frames for a given face // this helps eliminate redundant calls to calcPixelArea for faces that have multiple textures @@ -1007,251 +1019,171 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag face->mLastTextureUpdate = gFrameCount; } - // Most-demanding-point measurement: the spec is that the - // LOWEST pixel:texel ratio governs, so pixel density is - // evaluated at the face's NEAREST point and applied to the - // face's true world area. The previous whole-face average - // (bounding-disc pixel area) under-resolved perspective - // surfaces: on a floor, the tile at your feet covers far - // more screen than the average tile, and the GPU samples - // fine mips right there - tiled (PBR-heavy) content went - // soft while untiled content looked fine. - const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents; - LLVector4a diag; - diag.setSub(ext[1], ext[0]); - // World area of the face ~ product of the two largest AABB - // dims (max pairwise product; robust for flat faces). - F32 dx = diag[0], dy = diag[1], dz = diag[2]; - F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz)); - // Pixels per meter at the nearest point. Distance floored: - // nearer than this the screen clamp below governs anyway. - F32 dist = llmax(face->mDistanceToCamera, 0.5f); - F32 ppm = LLDrawable::sCurPixelAngle / dist; - F32 face_px = area_world * ppm * ppm; - if (face_px <= 0.f) + F32 vsize = face->getPixelArea(); + + on_screen |= face->mInFrustum; + + F32 dist_above_bubble = llmax(face->mDistanceToCamera - bubble, 0.f); + F32 dist_factor = llclampf(dist_above_bubble / ramp_range); + min_distance_factor = llmin(min_distance_factor, dist_factor); + + if (face->mAvatar && face->mAvatar == gAgentAvatarp) { - // Degenerate extents: the face hasn't been through a - // geometry build yet (or a rigged face has no rigged - // extents) - it isn't renderable, so it must not be - // measured. Skipping matters especially for the - // per-bucket MIN bound: any invented placeholder - // value (the old fallback hit LLFace::init's 16px - // default) becomes the texture's least-demanding - // "use" and, under TextureDownrezCoverageBias, drags - // the whole texture to its deepest mip - and it - // poisoned BP and PBR asymmetrically since the two - // systems register faces at different points in the - // geometry lifecycle. - continue; + on_agent_avatar = true; } - // Effective UV repeat AREA across this face: the tiling - // term of texels-drawn-per-screen-pixel. More tiling => - // each tile is smaller on screen => coarser mips suffice - // (penalty). Repeats < 1 (atlas/crop) => only a sub-rect - // of the image is shown, but discard levels are whole- - // image, so the full image must be resident at 1/repeats - // times the crop's pixel count (boost). + // Scale desired texture resolution higher or lower depending on texture scale + // + // Minimum usage examples: a 1024x1024 texture with aplhabet (texture atlas), + // runing string shows one letter at a time. If texture has ten 100px symbols + // per side, minimal scale is (100/1024)^2 = 0.0095 + // + // Maximum usage examples: huge chunk of terrain repeats texture + // TODO: make this work with the GLTF texture transforms S32 te_offset = face->getTEOffset(); // offset is -1 if not inited LLViewerObject* objp = face->getViewerObject(); const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); + F32 min_scale = te ? llmin(fabsf(te->getScaleS()), fabsf(te->getScaleT())) : 1.f; + min_scale = llclamp(min_scale * min_scale, texture_scale_min(), texture_scale_max()); + vsize /= min_scale; - F32 repeats = 1.f; - if (te) - { - // UV scale source: every channel reads the repeat - // values ITS renderer actually applies, then flows - // through the identical pipeline below. Sources: - // diffuse -> TE scale - // Blinn normal/spec -> LLMaterial per-map repeats - // PBR channels -> KHR texture_transform scale - // Fallback for any missing material is the TE scale - - // never a silent hardcoded 1. - F32 scale_s = te->getScaleS(); - F32 scale_t = te->getScaleT(); - if (i >= LLRender::BASECOLOR_MAP) - { - // LLRender channel -> LLGLTFMaterial::TextureInfo - static const S32 gltf_info[4] = { - LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3) - LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4) - LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5) - LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6) - }; - if (const LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial()) - { - const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[i - LLRender::BASECOLOR_MAP]].mScale; - scale_s = s.mV[0]; - scale_t = s.mV[1]; - } - } - else if (i == LLRender::NORMAL_MAP || i == LLRender::SPECULAR_MAP) - { - // Blinn-Phong normal/specular maps carry their own - // repeats in LLMaterial - the renderer builds their - // texture matrices from these, NOT from the TE's - // diffuse scale. Reading the diffuse scale here made - // Blinn normals scale differently than PBR normals - // (whose per-channel transform IS read above). - if (const LLMaterial* mat = te->getMaterialParams().get()) - { - if (i == LLRender::NORMAL_MAP) - { - mat->getNormalRepeat(scale_s, scale_t); - } - else - { - mat->getSpecularRepeat(scale_s, scale_t); - } - } - } - - // Continuously-animated scale (llSetTextureAnim SCALE) - // bypasses both static sources via mTextureMatrix - - // the live animated values win on either path. - if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr) - { - LLViewerTextureAnim* anim = vvo->mTextureAnimp; - if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE) - && (anim->mFace < 0 || anim->mFace == te_offset)) - { - scale_s = anim->mScaleS; - scale_t = anim->mScaleT; - } - } - - repeats = fabsf(scale_s * scale_t); - - // Mesh atlas sub-rect: a face whose intrinsic UVs span - // only part of [0,1]^2 shows that fraction of the - // image. Applies identically to both paths - the - // transforms above stack on the raw face UVs. - if (LLVolume* vol = objp->getVolume()) - { - if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces()) - { - const LLVolumeFace& vf = vol->getVolumeFace(te_offset); - F32 span = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0]) - * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1])); - if (span > 0.f) - { - repeats *= span; - } - } - } - } + // Raw screen-space coverage - taken before the bias / + // camera-boost mutations below so the size signal is clean. + max_on_screen_size = llmax(max_on_screen_size, vsize); - repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA); - - // Apply the two sides of the repeat term in the right - // order relative to the screen clamp: - // - tiling (repeats > 1): the per-tile footprint at the - // nearest point, THEN clamped - one tile can't draw - // more pixels than the screen. (Clamping the whole - // face first and then dividing crushed near tiles.) - // - atlas/crop (repeats < 1): boost AFTER the clamp - - // whole-image residency for a crop legitimately - // demands more than its screen coverage. - F32 tiling = llmax(repeats, 1.f); - F32 crop = llmin(repeats, 1.f); - F32 vsize = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop; - - // Avatar bonus: worn attachments get a coverage - // multiplier - avatars are what people look at, and - // rigged extents make attachment coverage measurement - // unreliable anyway. Multiplicative, not a slam: a - // nearby avatar gains ~a mip of headroom while a distant - // one still downrezzes naturally with its coverage. - // (System-avatar bakes get the same bonus in the - // no-faces branch below.) - if (objp->isAttachment()) + // apply bias to offscreen faces all the time, but only to onscreen faces when bias is large + // use mImportanceToCamera to make bias switch a bit more gradual + if (!face->mInFrustum || LLViewerTexture::sDesiredDiscardBias > 1.9f + face->mImportanceToCamera / 2.f) { - static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); - vsize *= llmax((F32)avatar_boost, 1.f); + vsize /= bias; } - if (bucket >= 0 && bucket < 4) + max_vsize = llmax(max_vsize, vsize); + + // addTextureStats limits size to sMaxVirtualSize + if (max_vsize >= LLViewerFetchedTexture::sMaxVirtualSize + && (on_screen || LLViewerTexture::sDesiredDiscardBias <= BIAS_TRS_ON_SCREEN)) { - channel_coverage[bucket] = llmax(channel_coverage[bucket], vsize); - if (vsize > 0.f) - { - channel_coverage_min[bucket] = llmin(channel_coverage_min[bucket], vsize); - } + break; } - max_coverage = llmax(max_coverage, vsize); } } + + if (max_vsize >= LLViewerFetchedTexture::sMaxVirtualSize + && (on_screen || LLViewerTexture::sDesiredDiscardBias <= BIAS_TRS_ON_SCREEN)) + { + break; + } + } + + bool used_face_fast_path = (face_count > max_faces_to_check); + if (used_face_fast_path) + { // this texture is used in so many places we should just boost it and not bother checking its vsize + // this is especially important because the above is not time sliced and can hit multiple ms for a single texture + max_vsize = MAX_IMAGE_AREA; } - // Terrain detail textures register no faces (LLVOSurfacePatch - // addFace(NULL)); synthesize coverage from the nearest visible patch so - // they degrade with distance like everything else. - if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) + imagep->addTextureStats(max_vsize); + + // Publish per-texture signals for processTextureStats. Closest face + // wins for distance (min); biggest face wins for size (max). Default + // distance=1, size=0 maps to "deepest discard wanted" - never- + // measured textures stay coarse until distance/size evidence arrives. + if (used_face_fast_path) + { + // Fast path saw only a prefix of faces - force best-quality + // sentinels to match the MAX_IMAGE_AREA vsize boost above. + imagep->mMinDistanceFactor = 0.f; + imagep->mMaxOnScreenSize = (F32)MAX_IMAGE_AREA; + } + else if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) { + // Terrain detail textures don't register faces with the texture + // (LLVOSurfacePatch addFace(NULL)). Drive distance from the LOD + // system; floor at a small nonzero value so pressure has + // something to bite into (pow(0, p) = 0). + static LLCachedControl terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); + static LLCachedControl terrain_coverage(gSavedSettings, "TextureTerrainCoverageFraction", 0.99f); F32 nearest = LLSurface::sNearestVisiblePatchDistance; - F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 1.f); - F32 near_frac = (nearest < FLT_MAX) ? llclampf(1.f - nearest / draw_distance) : 0.f; - // Floor so terrain never collapses to nothing at draw distance. - F32 cov = LLViewerTexture::sWindowPixelArea * llmax(near_frac, 0.05f); - channel_coverage[1] = cov; // terrain detail maps are diffuse / base color - channel_coverage_min[1] = cov; - max_coverage = cov; + F32 nearest_above_bubble = (nearest < FLT_MAX) ? llmax(nearest - bubble, 0.f) : ramp_range; + F32 dist = llclampf(nearest_above_bubble / ramp_range); + imagep->mMinDistanceFactor = llmax(dist, llclampf((F32)terrain_distance_floor)); + imagep->mMaxOnScreenSize = LLViewerTexture::sWindowPixelArea * llclampf((F32)terrain_coverage); + } + else + { + imagep->mMinDistanceFactor = min_distance_factor; + imagep->mMaxOnScreenSize = max_on_screen_size; } - // Baked avatar textures render on system-avatar body meshes whose - // joint meshes never register faces with the texture list - // (LLAvatarJointMesh::setTexture just stores the pointer). - // LLVOAvatar::updateTextures feeds their on-screen pixel area through - // addTextureStats every frame - use that as BaseColor coverage, with - // the same avatar bonus attachments get above. - else if (face_count == 0 - && (imagep->getFTType() == FTT_SERVER_BAKE || imagep->getFTType() == FTT_HOST_BAKE)) + imagep->mOnAgentAvatar = on_agent_avatar; + + // Bind-staleness. Avatar bakes exempt (cloud-bug protection). + // Per-interval increment is 1/max_discard so saturation time is + // interval * max_discard seconds regardless of texture size. + // Never-bound textures defer to distance/size or initial fetch + // could never start. + if (LLViewerFetchedTexture::isAgentAvatarBoost(imagep->getBoostLevel())) { - static LLCachedControl avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f); - F32 cov = llmin(imagep->getMaxVirtualSize(), LLViewerTexture::sWindowPixelArea) - * llmax((F32)avatar_boost, 1.f); - if (cov > 0.f) - { - channel_coverage[1] = cov; - channel_coverage_min[1] = cov; - max_coverage = cov; - } + imagep->mStalenessFactor = 0.f; } - - // Light projector textures register as LIGHT_TEX volumes, not faces. - // mSpotLightPriority (refreshed above) is already a screen-pixel-area - // estimate of the lit radius - fold it in as BaseColor coverage so - // projectors stream view-dependently like everything else. - for (S32 vi = 0; vi < imagep->getNumVolumes(LLRender::LIGHT_TEX); ++vi) + else if (LLImageGL* gli = imagep->getGLTexture()) { - LLVOVolume* volume = (*imagep->getVolumeList(LLRender::LIGHT_TEX))[vi]; - F32 cov = llmin(volume->getSpotLightPriority(), LLViewerTexture::sWindowPixelArea); - if (cov > 0.f) + static LLCachedControl bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); + static LLCachedControl staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); + F32 grace = llmax((F32)bind_decay_seconds, 0.f); + F32 interval = llmax((F32)staleness_interval, 0.0001f); + + // Clock starts at whichever is later: the last real bind or + // the GL-create time. The latter is the fallback for textures + // decoded into GL but never actually rendered - without it, + // mLastBindTime stays 0 forever and staleness can't evict. + F32 clock_time = llmax(gli->mLastBindTime, gli->mGLCreateTime); + bool has_clock = (clock_time > 0.f); + F32 time_since = has_clock ? (LLImageGL::sLastFrameTime - clock_time) : 0.f; + + if (!has_clock || time_since <= grace) + { + imagep->mStalenessFactor = 0.f; + } + else { - channel_coverage[1] = llmax(channel_coverage[1], cov); - channel_coverage_min[1] = llmin(channel_coverage_min[1], cov); - max_coverage = llmax(max_coverage, cov); + S32 full_w = imagep->getFullWidth(); + S32 full_h = imagep->getFullHeight(); + S32 max_discard = (full_w > 0 && full_h > 0) + ? LLImageGL::dimDerivedMaxDiscard(full_w, full_h) + : (S32)gli->getMaxDiscardLevel(); + if (max_discard > 0) + { + F32 steps = (time_since - grace) / interval; + F32 step_size = 1.f / (F32)max_discard; + imagep->mStalenessFactor = llclampf(steps * step_size); + } + else + { + imagep->mStalenessFactor = 0.f; + } } } - imagep->addTextureStats(max_coverage); - - // Publish per-bucket coverage bounds for - // LLViewerLODTexture::computeDesiredDiscard. - for (S32 b = 0; b < 4; ++b) - { - imagep->mChannelCoverage[b] = channel_coverage[b]; - imagep->mChannelCoverageMin[b] = (channel_coverage_min[b] == FLT_MAX) ? 0.f : channel_coverage_min[b]; - } } #if 0 - imagep->setDebugText(llformat("%d/%d -- %d/%d", + imagep->setDebugText(llformat("%d/%d - %d/%d -- %d/%d", + (S32)sqrtf(max_vsize), + (S32)sqrtf(imagep->mMaxVirtualSize), imagep->getDiscardLevel(), imagep->getDesiredDiscardLevel(), imagep->getWidth(), imagep->getFullWidth())); #endif + // make sure to addTextureStats for any spotlights that are using this texture + for (S32 vi = 0; vi < imagep->getNumVolumes(LLRender::LIGHT_TEX); ++vi) + { + LLVOVolume* volume = (*imagep->getVolumeList(LLRender::LIGHT_TEX))[vi]; + volume->updateSpotLightPriority(); + } + F32 max_inactive_time = 20.f; // inactive time before deleting saved raw image S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, and 1 for "entries" in updateImagesFetchTextures @@ -1477,6 +1409,21 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); + // Scale up the per-frame update window under VRAM pressure so eviction + // candidates get re-evaluated quickly. Both the legacy bias and the + // new pressure multiplier widen the window. + F32 pressure_scale = llmax(LLViewerTexture::sDesiredDiscardBias, + LLViewerTexture::sMemoryPressureMultiplier); + if (pressure_scale > 1.f + && LLViewerTexture::sBiasTexturesUpdated < (U32)mUUIDMap.size()) + { + update_count = (S32)(update_count * pressure_scale); + + // This isn't particularly precise and can overshoot, but it doesn't need + // to be, just making sure it did a full circle and doesn't get stuck updating + // at bias = 4 with 4 times the rate permanently. + LLViewerTexture::sBiasTexturesUpdated += update_count; + } update_count = llmin(update_count, (U32) mUUIDMap.size()); { // copy entries out of UUID map to avoid iterator invalidation from deletion inside updateImageDecodeProiroty or updateFetch below diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 931f2ed50e..dbed8b5c2f 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -244,6 +244,10 @@ private: bool mInitialized ; LLFrameTimer mForceDecodeTimer; +public: + // Current close-camera bubble in meters (frame-coherent, slow-tracked). + static F32 sCurrentBubbleMeters; + private: static S32 sNumImages; static void (*sUUIDCallback)(void**, const LLUUID &); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index e2601e6465..dea96e2012 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5597,13 +5597,7 @@ bool LLViewerWindow::cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubea // actually render the scene gCubeSnapshot = true; - { - // Probe binds aren't visibility - otherwise every probe slice re-stamps - // behind-camera textures and cycles them evict->refetch. RAII so the - // nested shadow pass in display_cube_face doesn't re-enable stamping. - LLImageGLStampBypass stamp_bypass; - display_cube_face(); - } + display_cube_face(); gCubeSnapshot = false; } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index d57456aa5a..3b41ccb6fc 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -878,15 +878,11 @@ void LLVOVolume::updateTextureVirtualSize(bool forced) LLViewerFetchedTexture* img = LLViewerTextureManager::staticCastToFetchedTexture(imagep) ; if(img) { - // cur:desired:width then per-bucket coverage bounds - // (N/BC/S/E, sqrt so values read as pixel dimensions, - // max~min) - the exact inputs computeDesiredDiscard sees. - debug_text << img->getDiscardLevel() << ":" << img->getDesiredDiscardLevel() << ":" << img->getWidth() - << " N" << (S32)sqrtf(img->getChannelCoverage(0)) << "~" << (S32)sqrtf(img->getChannelCoverageMin(0)) - << " BC" << (S32)sqrtf(img->getChannelCoverage(1)) << "~" << (S32)sqrtf(img->getChannelCoverageMin(1)) - << " S" << (S32)sqrtf(img->getChannelCoverage(2)) << "~" << (S32)sqrtf(img->getChannelCoverageMin(2)) - << " E" << (S32)sqrtf(img->getChannelCoverage(3)) << "~" << (S32)sqrtf(img->getChannelCoverageMin(3)) - << "\n"; + debug_text << img->getDiscardLevel() << ":" << img->getDesiredDiscardLevel() << ":" << img->getWidth() << ":" << (S32) sqrtf(vsize) << ":" << (S32) sqrtf(img->getMaxVirtualSize()) << "\n"; + /*F32 pri = img->getDecodePriority(); + pri = llmax(pri, 0.0f); + if (pri < min_vsize) min_vsize = pri; + if (pri > max_vsize) max_vsize = pri;*/ } } else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) @@ -932,7 +928,7 @@ void LLVOVolume::updateTextureVirtualSize(bool forced) { LLLightImageParams* params = getLightImageParams(); LLUUID id = params->getLightTexture(); - mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); if (mLightTexture.notNull()) { F32 rad = getLightRadius(); @@ -1801,41 +1797,6 @@ void LLVOVolume::regenFaces() facep->setNormalMap(getTENormalMap(i)); facep->setSpecularMap(getTESpecularMap(i)); } - - // Register PBR channel textures HERE, at geometry build, exactly when - // the Blinn textures above register - mirrored from rebuildGeom - // (which still re-runs this when the material resolves later). - // Without this, PBR textures had zero registered faces until the - // spatial group's budget-throttled rebuildGeom ran, so the streaming - // math read "not measured -> deepest mip" for PBR content while - // identical Blinn content on the same geometry measured immediately. - { - const LLTextureEntry* te = facep->getTextureEntry(); - LLFetchedGLTFMaterial* gltf_mat = te ? (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial() : nullptr; - if (gltf_mat) - { - if (!facep->hasMedia()) - { - facep->setTexture(LLRender::DIFFUSE_MAP, nullptr); - } - facep->setTexture(LLRender::NORMAL_MAP, nullptr); - facep->setTexture(LLRender::SPECULAR_MAP, nullptr); - facep->setTexture(LLRender::BASECOLOR_MAP, gltf_mat->mBaseColorTexture); - facep->setTexture(LLRender::GLTF_NORMAL_MAP, gltf_mat->mNormalTexture); - facep->setTexture(LLRender::METALLIC_ROUGHNESS_MAP, gltf_mat->mMetallicRoughnessTexture); - facep->setTexture(LLRender::EMISSIVE_MAP, gltf_mat->mEmissiveTexture); - } - else - { - // No (or no longer a) PBR material: clear any stale GLTF - // channel registrations so a removed material's textures - // stop accruing phantom coverage from this face. - facep->setTexture(LLRender::BASECOLOR_MAP, nullptr); - facep->setTexture(LLRender::GLTF_NORMAL_MAP, nullptr); - facep->setTexture(LLRender::METALLIC_ROUGHNESS_MAP, nullptr); - facep->setTexture(LLRender::EMISSIVE_MAP, nullptr); - } - } facep->setViewerObject(this); // If the face had media on it, this will have broken the link between the LLViewerMediaTexture and the face. @@ -3406,7 +3367,7 @@ LLViewerTexture* LLVOVolume::getLightTexture() { if (mLightTexture.isNull() || id != mLightTexture->getID()) { - mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); } } else @@ -5890,16 +5851,6 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) facep->setTexture(LLRender::METALLIC_ROUGHNESS_MAP, gltf_mat->mMetallicRoughnessTexture); facep->setTexture(LLRender::EMISSIVE_MAP, gltf_mat->mEmissiveTexture); } - else - { - // Face is not (or no longer) PBR: clear any stale GLTF - // channel registrations, or a removed material's textures - // keep accruing phantom coverage from this face forever. - facep->setTexture(LLRender::BASECOLOR_MAP, nullptr); - facep->setTexture(LLRender::GLTF_NORMAL_MAP, nullptr); - facep->setTexture(LLRender::METALLIC_ROUGHNESS_MAP, nullptr); - facep->setTexture(LLRender::EMISSIVE_MAP, nullptr); - } //ALWAYS null out vertex buffer on rebuild -- if the face lands in a render // batch, it will recover its vertex buffer reference from the spatial group diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index f18ec727df..4ef88f5deb 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -9461,9 +9461,6 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa LL_PROFILE_GPU_ZONE("renderShadow"); LLPipeline::sShadowRender = true; - // Shadow binds aren't visibility. RAII (not a hardcoded restore) so a shadow - // pass nested in a probe render doesn't re-enable stamping for the rest of it. - LLImageGLStampBypass stamp_bypass; // disable occlusion culling during shadow render U32 saved_occlusion = sUseOcclusion; @@ -10848,9 +10845,6 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool sShadowRender = true; sImpostorRender = true; - // Impostor binds aren't visibility. RAII so the nested shadow pass can't - // re-enable stamping mid-render. - LLImageGLStampBypass stamp_bypass; LLViewerCamera* viewer_camera = LLViewerCamera::getInstance(); -- cgit v1.3 From 55e94847b4270d7f71016fe651800ef9e43b4bc5 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 22 Jul 2026 00:40:35 -0400 Subject: Revert "Merge pull request #5829 from secondlife/geenz/texture-quality" This reverts commit 6b4e3f3288ec1e9917cecd862a7a52945e5b4db2, reversing changes made to 99ab6316b4ae9058f22d9f57d21e795ca45797fd. --- indra/llimage/llimagej2c.cpp | 34 +- indra/llimage/llimagej2c.h | 5 - indra/llimagej2coj/llimagej2coj.cpp | 29 -- indra/llimagej2coj/llimagej2coj.h | 19 +- indra/llkdu/llimagej2ckdu.cpp | 30 -- indra/llkdu/llimagej2ckdu.h | 2 - indra/llkdu/tests/llimagej2ckdu_test.cpp | 2 - indra/llrender/llimagegl.cpp | 96 +--- indra/llrender/llimagegl.h | 17 +- indra/llrender/llrender.cpp | 29 +- indra/newview/app_settings/settings.xml | 326 +------------ indra/newview/featuretable.txt | 31 +- indra/newview/featuretable_mac.txt | 31 +- indra/newview/lldrawpoolwater.cpp | 8 +- indra/newview/llface.cpp | 30 +- indra/newview/llface.h | 7 +- indra/newview/llfeaturemanager.cpp | 24 +- indra/newview/llsurface.cpp | 14 +- indra/newview/llsurface.h | 6 - indra/newview/lltexturefetch.cpp | 12 +- indra/newview/lltexturefetch.h | 3 +- indra/newview/lltextureview.cpp | 8 +- indra/newview/llviewercontrol.cpp | 43 -- indra/newview/llviewertexture.cpp | 536 ++++----------------- indra/newview/llviewertexture.h | 62 +-- indra/newview/llviewertexturelist.cpp | 240 +++------ indra/newview/llviewertexturelist.h | 9 - indra/newview/llvlcomposition.cpp | 18 +- .../en/floater_preferences_graphics_advanced.xml | 32 +- 29 files changed, 291 insertions(+), 1412 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 8099a18045..5a941dc958 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -268,11 +268,35 @@ S32 LLImageJ2C::calcHeaderSizeJ2C() //static S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) { - // Dispatch to the linked impl so OpenJPEG (block-aligned, needs - // over-allocation) and KDU (packet-aligned, lean) each return what - // their decoder actually needs. - static std::unique_ptr s_estimator(fallbackCreateLLImageJ2CImpl()); - return s_estimator->estimateDataSize(w, h, comp, discard_level, rate); + // Note: This provides an estimation for the first to last quality layer of a given discard level + // This is however an efficient approximation, as the true discard level boundary would be + // in general too big for fast fetching. + // For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study + + // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). + constexpr S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support + constexpr S32 max_components = 4; // assumed the file has four components; three color and alpha + // Use MAX_IMAGE_SIZE_DEFAULT (currently 2048) if either dimension is unknown (zero) + S32 width = (w > 0) ? w : 2048; + S32 height = (h > 0) ? h : 2048; + S32 max_dimension = llmax(width, height); // Find largest dimension + S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64) + S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); // Find number of powers of two between extents and block size to a minimum of 4 + block_area *= llmax(max_layers, 1); // Adjust initial block area by max number of layers + S32 totalbytes = (S32) (MIN_LAYER_SIZE * max_components * precision); // Start estimation with a minimum reasonable size + S32 block_layers = 0; + while (block_layers <= max_layers) // Walk the layers + { + if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer. + totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate + block_layers++; // Move to next layer + block_area *= 4; // Increase block area by power of four + } + + totalbytes /= 8; // to bytes + totalbytes += calcHeaderSizeJ2C(); // header + + return totalbytes; } S32 LLImageJ2C::calcHeaderSize() diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index 81b24cc0b3..19744a7f87 100644 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h @@ -106,11 +106,6 @@ class LLImageJ2CImpl { public: virtual ~LLImageJ2CImpl(); - - // Estimate the byte size of a J2C codestream sufficient to decode the - // given discard level. KDU uses a packet-by-packet impl; OpenJPEG - // overrides with a more conservative block-aligned estimate. - virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const = 0; protected: // Find out the image size and number of channels. // Return value: diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 488c07e1c9..7cfadb889d 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -919,32 +919,3 @@ bool LLImageJ2COJ::getMetadata(LLImageJ2C &base) base.setSize(width, height, components); return true; } - - -// OpenJPEG-tuned byte estimator. Conservative pyramid walk that accounts for -// OJ's whole-code-block decode behavior (even with strict mode off). Larger -// images get a per-resolution multiplier so the byte range lands inside the -// last needed code-block boundary. -S32 LLImageJ2COJ::estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const -{ - constexpr S32 precision = 8; - constexpr S32 max_components = 4; - S32 width = (w > 0) ? w : 2048; - S32 height = (h > 0) ? h : 2048; - S32 max_dimension = llmax(width, height); - S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; - S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); - block_area *= llmax(max_layers, 1); - S32 totalbytes = (S32)(MIN_LAYER_SIZE * max_components * precision); - S32 block_layers = 0; - while (block_layers <= max_layers) - { - if (block_layers <= (5 - discard_level)) - totalbytes += (S32)(block_area * max_components * precision * rate); - block_layers++; - block_area *= 4; - } - totalbytes /= 8; - totalbytes += LLImageJ2C::calcHeaderSizeJ2C(); - return totalbytes; -} diff --git a/indra/llimagej2coj/llimagej2coj.h b/indra/llimagej2coj/llimagej2coj.h index 0cc8d5c34c..da49597302 100644 --- a/indra/llimagej2coj/llimagej2coj.h +++ b/indra/llimagej2coj/llimagej2coj.h @@ -35,20 +35,15 @@ class LLImageJ2COJ : public LLImageJ2CImpl { public: LLImageJ2COJ(); - virtual ~LLImageJ2COJ() override; + virtual ~LLImageJ2COJ(); protected: - virtual bool getMetadata(LLImageJ2C &base) override; - virtual bool decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) override; + virtual bool getMetadata(LLImageJ2C &base); + virtual bool decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count); virtual bool encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0, - bool reversible = false) override; - virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL) override; - virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0) override; - virtual std::string getEngineInfo() const override; -public: - // OpenJPEG decodes whole code-blocks even with strict mode off, so the - // lean packet-walk under-allocates and clips quality. Keep the older - // conservative pyramid-with-multiplier estimate here. - virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const override; + bool reversible = false); + virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL); + virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0); + virtual std::string getEngineInfo() const; }; #endif diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 74aaffbd65..cf45f93168 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -1551,33 +1551,3 @@ void kdc_flow_control::process_components() } } } - -// Layer-factored byte estimator. Walks the resolution pyramid to count -// layers, weights by layer_factor, then picks between a sqrt-based "new" -// estimate and a raw-dimensions "old" estimate per TextureNewByteRange. -// Reference: https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study -S32 LLImageJ2CKDU::estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const -{ - S32 width = (w > 0) ? w : 2048; - S32 height = (h > 0) ? h : 2048; - S32 nb_layers = 1; - S32 surface = width * height; - S32 s = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; - while (surface > s) - { - nb_layers++; - s *= 4; - } - F32 layer_factor = 3.0f * (7 - llclamp(nb_layers, 1, 6)); - - width >>= discard_level; - height >>= discard_level; - width = llmax(width, 1); - height = llmax(height, 1); - - S32 new_bytes = (S32)(sqrtf((F32)(width * height)) * (F32)comp * rate * 1000.f / layer_factor); - S32 old_bytes = (S32)((F32)(width * height * comp) * rate); - S32 bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes)) ? new_bytes : old_bytes; - bytes = llmax(bytes, LLImageJ2C::calcHeaderSizeJ2C()); - return bytes; -} diff --git a/indra/llkdu/llimagej2ckdu.h b/indra/llkdu/llimagej2ckdu.h index 6079585948..c9aa0c5250 100644 --- a/indra/llkdu/llimagej2ckdu.h +++ b/indra/llkdu/llimagej2ckdu.h @@ -67,8 +67,6 @@ protected: virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL); virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0); virtual std::string getEngineInfo() const; -public: - virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const; private: bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level = -1, int* region = NULL); diff --git a/indra/llkdu/tests/llimagej2ckdu_test.cpp b/indra/llkdu/tests/llimagej2ckdu_test.cpp index 6be2d3a078..bc52a15c4a 100644 --- a/indra/llkdu/tests/llimagej2ckdu_test.cpp +++ b/indra/llkdu/tests/llimagej2ckdu_test.cpp @@ -83,8 +83,6 @@ void LLImageBase::setSize(S32 , S32 , S32 ) { } bool LLImageBase::isBufferInvalid() const { return false; } LLImageJ2CImpl::~LLImageJ2CImpl() { } -bool LLImage::sUseNewByteRange = false; -S32 LLImageJ2C::calcHeaderSizeJ2C() { return 0; } LLImageFormatted::LLImageFormatted(S8 ) { } LLImageFormatted::~LLImageFormatted() { } diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 6bcc34938c..4a3d32c7ff 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -66,8 +66,8 @@ static LLMutex sTexMemMutex; static std::unordered_map sTextureAllocs; static U64 sTextureBytes = 0; -// Per-mip upload paths call this once per level; only free_tex_image -// removes a texture's accounting entirely. +// track a texture alloc on the currently bound texture. +// asserts that no currently tracked alloc exists void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count) { U32 texUnit = gGL.getCurrentTexUnitIndex(); @@ -80,43 +80,12 @@ void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32 sTexMemMutex.lock(); - auto iter = sTextureAllocs.find(texName); - if (iter != sTextureAllocs.end()) - { - iter->second += size; - } - else - { - sTextureAllocs[texName] = size; - } - sTextureBytes += size; - - sTexMemMutex.unlock(); -} + // it is a precondition that no existing allocation exists for this texture + llassert(sTextureAllocs.find(texName) == sTextureAllocs.end()); -// Add mip 1..N bytes to existing accounting. Use after glGenerateMipmap. -void LLImageGLMemory::account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat) -{ - U64 extra = 0; - U32 w = base_width; - U32 h = base_height; - while (w > 1 || h > 1) - { - w = w > 1 ? w >> 1 : 1; - h = h > 1 ? h >> 1 : 1; - extra += LLImageGL::dataFormatBytes(intformat, w, h); - } - - U32 texUnit = gGL.getCurrentTexUnitIndex(); - U32 texName = gGL.getTexUnit(texUnit)->getCurrTexture(); + sTextureAllocs[texName] = size; + sTextureBytes += size; - sTexMemMutex.lock(); - auto iter = sTextureAllocs.find(texName); - if (iter != sTextureAllocs.end()) - { - iter->second += extra; - sTextureBytes += extra; - } sTexMemMutex.unlock(); } @@ -715,10 +684,7 @@ void LLImageGL::dump() //---------------------------------------------------------------------------- void LLImageGL::forceUpdateBindStats(void) const { - // Intentionally a no-op: mLastBindTime is written only by real bind - // paths so the staleness signal reflects actual GPU use. Callers that - // still invoke this (avatar "keep alive" sites, deleted-texture - // fallback) no longer falsely refresh staleness. + mLastBindTime = sLastFrameTime; } bool LLImageGL::updateBindStats() const @@ -872,7 +838,7 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 mMipLevels = wpo2(llmax(w, h)); //use legacy mipmap generation mode (note: making this condional can cause rendering issues) - // - but making it not conditional triggers deprecation warnings when core profile is enabled + // -- but making it not conditional triggers deprecation warnings when core profile is enabled // (some rendering issues while core profile is enabled are acceptable at this point in time) if (!LLRender::sGLCoreProfile) { @@ -898,7 +864,6 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 { LL_PROFILE_GPU_ZONE("generate mip map"); glGenerateMipmap(mTarget); - account_extra_mip_bytes(w, h, mFormatInternal); } stop_glerror(); } @@ -1496,12 +1461,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt LL_PROFILE_ZONE_NUM(width); LL_PROFILE_ZONE_NUM(height); - // Release prior accounting only on the base mip; per-mip iteration - // accumulates the rest via the additive alloc_tex_image. - if (miplevel == 0) - { - free_cur_tex_image(); - } + free_cur_tex_image(); const bool use_sub_image = should_stagger_image_set(compress); if (!use_sub_image) { @@ -1653,6 +1613,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S { destroyGLTexture(); mCurrentDiscardLevel = discard_level; + mLastBindTime = sLastFrameTime; mGLTextureCreated = false; return true ; } @@ -1768,7 +1729,9 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_ mTextureMemory = (S64Bytes)getMipBytes(mCurrentDiscardLevel); - mGLCreateTime = sLastFrameTime; + + // mark this as bound at this point, so we don't throw it out immediately + mLastBindTime = sLastFrameTime; checkActiveThread(); return true; @@ -1895,7 +1858,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre LLGLint is_compressed = 0; if (compressed_ok) { - glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); + glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); } //----------------------------------------------------------------------------------------------- @@ -2076,28 +2039,6 @@ S32 LLImageGL::getWidth(S32 discard_level) const return width; } -// static -S32 LLImageGL::dimDerivedMaxDiscard(S32 width, S32 height) -{ - if (width <= 0 || height <= 0) - { - return 0; - } - // max(w,h) - min() caps short on rectangular textures - // (1024x512 reaches 1x1 at discard 10, not 9). - return (S32)floorf(log2f((F32)llmax(width, height))); -} - -void LLImageGL::stampBound() const -{ - // Skip the store on same-frame re-binds - bindFast is per-draw and - // would dirty this cache line per bind per texture otherwise. - if (mLastBindTime != sLastFrameTime) - { - mLastBindTime = sLastFrameTime; - } -} - S64 LLImageGL::getBytes(S32 discard_level) const { if (discard_level < 0) @@ -2527,12 +2468,7 @@ bool LLImageGL::scaleDown(S32 desired_discard) return false; } - // GL pyramid reaches 1x1 regardless of codec levels; - // mMaxDiscardLevel is hardcapped at MAX_DISCARD_LEVEL. - S32 dim_max_discard = (mWidth > 0 && mHeight > 0) - ? dimDerivedMaxDiscard(mWidth, mHeight) - : (S32)mMaxDiscardLevel; - desired_discard = llmin(desired_discard, dim_max_discard); + desired_discard = llmin(desired_discard, mMaxDiscardLevel); if (desired_discard <= mCurrentDiscardLevel) { @@ -2565,7 +2501,6 @@ bool LLImageGL::scaleDown(S32 desired_discard) LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap"); gGL.getTexUnit(0)->bind(this); glGenerateMipmap(mTarget); - account_extra_mip_bytes(desired_width, desired_height, mFormatInternal); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } } @@ -2611,7 +2546,6 @@ bool LLImageGL::scaleDown(S32 desired_discard) { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap"); glGenerateMipmap(mTarget); - account_extra_mip_bytes(desired_width, desired_height, mFormatInternal); } gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 0c85446b84..6b4492c09e 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -51,11 +51,6 @@ class LLWindow; namespace LLImageGLMemory { void alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count); - - // Add mip 1..N bytes to existing accounting. Call after glGenerateMipmap - // when only the base mip was accounted; without this the bytes counter - // undercounts mipmap-generated textures by ~25%. - void account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat); void free_tex_image(U32 texName); void free_tex_images(U32 count, const U32* texNames); void free_cur_tex_image(); @@ -156,15 +151,6 @@ public: S32 getDiscardLevel() const { return mCurrentDiscardLevel; } S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; } - // floor(log2(max(w, h))) - deepest GL pyramid level (down to 1x1). - // Returns 0 for non-positive inputs. - static S32 dimDerivedMaxDiscard(S32 width, S32 height); - - // Record the wall-clock bind time - every bind path that touches a - // streaming-managed texture must call this, or the staleness signal - // sees the texture as never-bound and ramps it toward eviction. - void stampBound() const; - // override the current discard level // should only be used for local textures where you know exactly what you're doing void setDiscardLevel(S32 level) { mCurrentDiscardLevel = level; } @@ -238,8 +224,7 @@ public: public: // Various GL/Rendering options S64Bytes mTextureMemory; - mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound; drives streaming staleness - F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created; staleness fallback for never-bound textures + mutable F32 mLastBindTime; // last time this was bound, by discard level private: U32 createPickMask(S32 pWidth, S32 pHeight); diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 5e845fbcce..57be8570af 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -197,10 +197,6 @@ void LLTexUnit::bindFast(LLTexture* texture) glActiveTexture(GL_TEXTURE0 + mIndex); gGL.mCurrTextureUnitIndex = mIndex; mCurrTexture = gl_tex->getTexName(); - mCurrTexType = gl_tex->getTarget(); - // bindFast bypasses updateBindStats(); stamp directly so the staleness - // signal sees per-frame use of batched textures. - gl_tex->stampBound(); if (!mCurrTexture) { LL_PROFILE_ZONE_NAMED("MISSING TEXTURE"); @@ -253,17 +249,11 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) setTextureFilteringOption(gl_tex->mFilterOption); } } - else - { - // Already current - still being used, keep it fresh. - gl_tex->stampBound(); - } } else { //if deleted, will re-generate it immediately texture->forceImmediateUpdate() ; - gl_tex->stampBound(); gl_tex->forceUpdateBindStats() ; return texture->bindDefaultImage(mIndex); @@ -335,11 +325,6 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 stop_glerror(); } } - else - { - // Already current - still being used, keep it fresh. - texture->stampBound(); - } stop_glerror(); @@ -1733,16 +1718,7 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count) LLVertexBuffer * vb = new LLVertexBuffer(attribute_mask); vb->allocateBuffer(count, 0); - // 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->setBuffer(); vb->setPositionData(mVerticesp.get()); @@ -1757,9 +1733,6 @@ 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/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 81299a5b71..1ac4583dd3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8049,24 +8049,13 @@ RenderMaxTextureResolution Comment - Maximum texture resolution to download for non-boosted textures. Driven by RenderTextureQuality. + Maximum texture resolution to download for non-boosted textures. Persist 1 Type U32 Value 2048 - - RenderTextureQuality - - Comment - Texture quality preset: 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, the four TextureChannel* exponents, and TextureDistanceDiscardPower. - Persist - 1 - Type - U32 - Value - 2 RenderDownScaleMethod @@ -9265,7 +9254,7 @@ RenderReflectionDetail Comment - DEPRECATED - use RenderTransparentWater and RenderReflectionProbeDetail - Detail of reflection render pass. + DEPRECATED -- use RenderTransparentWater and RenderReflectionProbeDetail -- Detail of reflection render pass. Persist 1 Type @@ -11871,316 +11860,33 @@ Value 20.0 - TextureChannelNormal + TextureChannelPriority Comment - Per-channel discard exponent for normal maps. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. + Per-channel texture streaming aggressiveness. X=normals, Y=diffuse, Z=specular/metallic, W=emissive. 1.0=baseline, higher=more aggressive downrez. Persist 1 Type - F32 + Vector4 Value - 1.0 - - TextureChannelBaseColor - - Comment - Per-channel discard exponent for base color / diffuse. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - F32 - Value - 0.75 - - TextureChannelSpecular - - Comment - Per-channel discard exponent for specular / metallic-roughness. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureChannelEmissive - - Comment - Per-channel discard exponent for emissive. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. - Persist - 1 - Type - F32 - Value - 0.75 + + 5 + 7.5 + 20 + 7.5 + - TextureMaxDiscardOverride + TextureCameraBoost Comment - When non-zero, overrides the per-texture codec-derived max discard cap. 0 = use codec-reported levels. Higher lets the streaming math push past the codec ceiling; scaleDown handles the GL side. + Amount to boost resolution of textures that are important to the camera. Persist - 1 - Type - S32 - Value 0 - - TextureMemoryHighWaterMark - - Comment - Fraction of budget (0..1) at which the pressure controller bypasses smoothing and slams to cap. Last-ditch min-discard also creeps without waiting for mult_progress. - Persist - 1 - Type - F32 - Value - 0.8 - - TextureMemoryPressureBackoffStart - - Comment - Fraction of the VRAM target at which the pressure ramp starts (0..1). Lower = earlier headroom-building; 1.0 disables backoff (ramp only above target). - Persist - 1 - Type - F32 - Value - 0.85 - - TextureMemoryPressureMaxMultiplier - - Comment - Upper bound on the VRAM-pressure distance multiplier (>= 1). Mostly defensive -- at mult=64 the streaming ramp collapses to ~ramp_range/64, already extreme. Higher allows even more aggressive compression in tight-budget scenes. - Persist - 1 - Type - F32 - Value - 64.0 - - TextureLastDitchEngageProgress - - Comment - mult_progress (0..1) at which the last-ditch floor starts creeping up. The floor only advances when mult is at or above this fraction of its cap AND prediction is still over budget. Decays back toward 0 whenever prediction is under budget. - Persist - 1 - Type - F32 - Value - 0.95 - - TextureLastDitchRampRate - - Comment - Rate (discard levels/sec) at which sLastDitchMinDiscard creeps up while engaged. 0.5 = takes ~2 sec to add one discard level. Mirrors sDesiredDiscardBias ramp shape. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureLastDitchDecayRate - - Comment - Rate (discard levels/sec) at which sLastDitchMinDiscard decays back to 0 when prediction is under budget. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureLastDitchMinDiscardMax - - Comment - Hard ceiling on sLastDitchMinDiscard. At 13 the floor can climb all the way to the deepest meaningful mip; lower values cap how aggressive the last-ditch escalation can get before we are simply out of discards. - Persist - 1 - Type - F32 - Value - 13.0 - - TextureMemoryPressurePredictionGain - - Comment - Power exponent mapping predicted-over-budget ratio to target multiplier. target_mult = pred_over^gain. Higher gain saturates faster. - Persist - 1 - Type - F32 - Value - 10.0 - - TextureMemoryPressureSmoothingRate - - Comment - Lerp rate (1/sec) at which the pressure multiplier converges to its prediction-driven target. Higher = faster response, lower = smoother. Default 4 reaches ~63% in 0.25s. - Persist - 1 - Type - F32 - Value - 4.0 - - TextureTerrainDistanceFloor - - Comment - Minimum distance factor for BOOST_TERRAIN textures. Keeps combined > 0 so VRAM pressure can evict terrain. Lower = higher idle quality, less pressure response. - Persist - 1 - Type - F32 - Value - 0.01 - - TextureTerrainCoverageFraction - - Comment - Synthetic on-screen coverage fraction for BOOST_TERRAIN textures (no faces are registered). Higher = higher idle quality, less pressure response. - Persist - 1 - Type - F32 - Value - 0.99 - - TextureAgentAvatarBoost - - Comment - Quality boost (0..1) for textures on the agent's avatar (rigged mesh / animated objects). Lower = higher quality. Preference, not exemption - pressure can still evict. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureBackgroundFactorRatePerSec - - Comment - Per-second ramp rate of the background-window discard floor (0..1). Snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. - Persist - 1 - Type - F32 - Value - 0.011 - - TextureBackgroundDiscardOffset - - Comment - Backgrounded textures will only discard up to (dim_max - offset). e.g. a 2048 texture (dim_max 11) with offset 2 caps the background floor at discard 9. 0 disables the cap (background can drive to max discard). - Persist - 1 - Type - S32 - Value - 2 - - - TextureCloseBubbleMeters - - Comment - Close-camera bubble (meters). Faces inside this distance get dist_factor = 0 (no discard contribution); the ramp to 1 spans (bubble, draw_distance]. Shrinks toward TextureCloseBubbleMinMeters as VRAM pressure ramps the multiplier toward its cap. - Persist - 1 - Type - F32 - Value - 5.0 - - TextureCloseBubbleMinMeters - - Comment - Floor (meters) for the close-camera bubble under maximum VRAM pressure. At sMemoryPressureMultiplier = TextureMemoryPressureMaxMultiplier the bubble collapses to this value, allowing eviction of even close textures when nothing else fits. - Persist - 1 - Type - F32 - Value - 3.0 - - TextureCloseBubbleShrinkThreshold - - Comment - Bubble stays at full size until mult_progress exceeds this fraction (0..1) of its range to the cap. Above that, bubble lerps from full to TextureCloseBubbleMinMeters. Keeps the bubble out of the normal feedback loop. - Persist - 1 - Type - F32 - Value - 0.8 - - TextureCloseBubbleTrackRate - - Comment - Rate (1/sec) at which the actual bubble tracks its target. Lower = smoother, slower to react. Damps short-term multiplier swings so close textures don't yo-yo. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureDistanceDiscardPower - - Comment - Exponent on the distance factor (face_distance / draw_distance). 1.0 = linear; lower = textures hit max discard sooner with distance. Default 0.5 = sqrt. - Persist - 1 - Type - F32 - Value - 0.5 - - TextureSizeDiscardPower - - Comment - Exponent on the on-screen size factor (1 - coverage). 1.0 = linear; lower = small-on-screen textures attenuate sooner. - Persist - 1 Type F32 Value - 1.0 - - TextureStalenessIntervalSeconds - - Comment - Seconds per staleness step. Per-interval increment is 1/max_discard so any texture saturates after interval * max_discard seconds idle. - Persist - 1 - Type - F32 - Value - 5.0 - - TextureBindDecaySeconds - - Comment - Grace seconds after a bind during which the staleness factor stays at 0. Prevents intermittently-bound textures from ramping. 0 disables the grace period. - Persist - 1 - Type - F32 - Value - 5.0 - - TextureFetchPressureScale - - Comment - Pending-fetch divisor for the bias floor: bias floor = 1 + clamp(pending / scale, 0, 3). Lower = bias rises sooner under load floods. - Persist - 1 - Type - F32 - Value - 1000.0 + 8.0 - TextureDecodeDisabled Comment @@ -16544,7 +16250,7 @@ EmulateCoreCount Comment - For debugging - number of cores to restrict the main process to, or 0 for no limit. Requires restart. + For debugging -- number of cores to restrict the main process to, or 0 for no limit. Requires restart. Persist 1 Type @@ -16698,7 +16404,7 @@ MultiModeDoubleClickFolder Comment - Sets the action for Double-click on folder in multi-folder view (0 - expands and collapses folder, 1 - opens a new window, 2 - stays in current floater but switches to SFV) + Sets the action for Double-click on folder in multi-folder view (0 - expands and collapses folder, 1 - opens a new window, 2 – stays in current floater but switches to SFV) Persist 1 Type diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index f05f77c222..1090dd8ffb 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -1,4 +1,4 @@ -version 76 +version 74 // The version number above should be incremented IF AND ONLY IF some // change has been made that is sufficiently important to justify // resetting the graphics preferences of all users to the recommended @@ -85,7 +85,7 @@ RenderExposure 1 4 RenderTonemapType 1 1 RenderTonemapMix 1 1 RenderDisableVintageMode 1 1 -RenderTextureQuality 1 3 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -128,6 +128,7 @@ RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 +RenderMaxTextureResolution 1 512 RenderReflectionProbeCount 1 1 // @@ -170,6 +171,7 @@ RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 +RenderMaxTextureResolution 1 1024 RenderReflectionProbeCount 1 32 // @@ -211,6 +213,7 @@ RenderCASSharpness 1 0 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 64 // @@ -252,6 +255,7 @@ RenderCASSharpness 1 0 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 64 // @@ -293,6 +297,7 @@ RenderCASSharpness 1 0.4 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 128 // @@ -334,6 +339,7 @@ RenderCASSharpness 1 0.4 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -375,6 +381,7 @@ RenderCASSharpness 1 0.4 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -392,24 +399,6 @@ RenderDisableVintageMode 1 0 list VRAMGT512 RenderCompressTextures 1 0 -// -// VRAM 4GB to 8GB -// -list VRAMLT8GB -RenderTextureQuality 1 2 - -// -// VRAM 2GB to 4GB -// -list VRAMLT4GB -RenderTextureQuality 1 1 - -// -// VRAM 2GB and under -// -list VRAMLT2GB -RenderTextureQuality 1 0 - // // "Default" setups for safe, low, medium, high // @@ -426,7 +415,7 @@ RenderShadowDetail 0 0 RenderReflectionProbeDetail 0 -1 RenderMirrors 0 0 RenderDisableVintageMode 1 0 -RenderTextureQuality 1 2 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 0 0 list Intel diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index b11fa28c48..c3e2dd0c41 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -1,4 +1,4 @@ -version 75 +version 73 // The version number above should be incremented IF AND ONLY IF some // change has been made that is sufficiently important to justify // resetting the graphics preferences of all users to the recommended @@ -85,7 +85,7 @@ RenderTonemapType 1 1 RenderTonemapMix 1 1 RenderDisableVintageMode 1 1 RenderDownScaleMethod 1 0 -RenderTextureQuality 1 3 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -128,6 +128,7 @@ RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 +RenderMaxTextureResolution 1 512 RenderReflectionProbeCount 1 1 // @@ -170,6 +171,7 @@ RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 +RenderMaxTextureResolution 1 1024 RenderReflectionProbeCount 1 32 // @@ -211,6 +213,7 @@ RenderCASSharpness 1 0 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 64 // @@ -252,6 +255,7 @@ RenderCASSharpness 1 0 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 64 // @@ -293,6 +297,7 @@ RenderCASSharpness 1 0 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 128 // @@ -334,6 +339,7 @@ RenderCASSharpness 1 0.4 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -375,6 +381,7 @@ RenderCASSharpness 1 0.4 RenderExposure 1 1 RenderTonemapType 1 1 RenderTonemapMix 1 0.7 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 1 256 // @@ -393,24 +400,6 @@ RenderDisableVintageMode 1 0 list VRAMGT512 RenderCompressTextures 1 0 -// -// VRAM 4GB to 8GB -// -list VRAMLT8GB -RenderTextureQuality 1 2 - -// -// VRAM 2GB to 4GB -// -list VRAMLT4GB -RenderTextureQuality 1 1 - -// -// VRAM 2GB and under -// -list VRAMLT2GB -RenderTextureQuality 1 0 - // // "Default" setups for safe, low, medium, high // @@ -425,7 +414,7 @@ RenderDeferredSSAO 0 0 RenderShadowDetail 0 0 RenderMirrors 0 0 RenderDisableVintageMode 1 0 -RenderTextureQuality 1 2 +RenderMaxTextureResolution 1 2048 RenderReflectionProbeCount 0 0 list TexUnit8orLess diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp index fbecaa1583..cdf3244389 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))) { - tex_a->setFilteringOption(filter_mode); shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a); + tex_a->setFilteringOption(filter_mode); blend_factor = 0; // only one tex provided, no blending } else if (tex_b && !tex_a) { - tex_b->setFilteringOption(filter_mode); shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b); + tex_b->setFilteringOption(filter_mode); blend_factor = 0; // only one tex provided, no blending } else if (tex_b != tex_a) { - tex_a->setFilteringOption(filter_mode); - tex_b->setFilteringOption(filter_mode); 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(LLShaderMgr::WATER_EXCLUSIONTEX, &gPipeline.mWaterExclusionMask); diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index e34bea63ef..018d4c4bba 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -1577,7 +1577,7 @@ bool LLFace::getGeometryVolume(const LLVolume& volume, skin = mSkinInfo; } - //TODO - cache this (check profile marker above)? + //TODO -- cache this (check profile marker above)? glm::mat4 m = glm::make_mat4((F32*)skin->mBindShapeMatrix.getF32ptr()); m = glm::transpose(glm::inverse(m)); mat_normal.loadu(glm::value_ptr(m)); @@ -2265,18 +2265,7 @@ F32 LLFace::getTextureVirtualSize() face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); } - // Diffuse source area as the dim-aware hint for adjustPixelArea. - S32 source_area = 0; - if (mTexture[LLRender::DIFFUSE_MAP].notNull()) - { - S32 sw = mTexture[LLRender::DIFFUSE_MAP]->getFullWidth(); - S32 sh = mTexture[LLRender::DIFFUSE_MAP]->getFullHeight(); - if (sw > 0 && sh > 0) - { - source_area = sw * sh; - } - } - face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area, source_area); + face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area); if(face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. { if(mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->isLargeImage()) @@ -2400,7 +2389,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) F32 dist = lookAt.getLength3().getF32(); dist = llmax(dist-size.getLength3().getF32(), 0.001f); - mDistanceToCamera = dist; lookAt.normalize3fast() ; @@ -2525,16 +2513,8 @@ F32 LLFace::calcImportanceToCamera(F32 cos_angle_to_view_dir, F32 dist) } //static -F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area, S32 source_area) +F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area) { - // Dim-aware floor: source_area/256 lowers the "large but unimportant" - // clamp proportionally so smaller sources can be pushed past discard 4. - F32 large_floor = (F32)LLViewerTexture::sMinLargeImageSize; - if (source_area > 0) - { - large_floor = llmin(large_floor, (F32)source_area / 256.f); - } - if(pixel_area > LLViewerTexture::sMaxSmallImageSize) { if(importance < LEAST_IMPORTANCE) //if the face is not important, do not load hi-res. @@ -2542,11 +2522,11 @@ F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area, S32 source_area) static const F32 MAX_LEAST_IMPORTANCE_IMAGE_SIZE = 128.0f * 128.0f ; pixel_area = llmin(pixel_area * 0.5f, MAX_LEAST_IMPORTANCE_IMAGE_SIZE) ; } - else if(pixel_area > large_floor) //if is large image, shrink face_area by considering the partial overlapping. + else if(pixel_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. { if(importance < LEAST_IMPORTANCE_FOR_LARGE_IMAGE)//if the face is not important, do not load hi-res. { - pixel_area = large_floor ; + pixel_area = (F32)LLViewerTexture::sMinLargeImageSize ; } } } diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 71ba3d0f2f..6e9d23c3a2 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -242,9 +242,7 @@ private: bool calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) ; public: static F32 calcImportanceToCamera(F32 to_view_dir, F32 dist); - // source_area > 0 lowers the "large but unimportant" floor for - // moderate sources; 0 keeps the legacy sMinLargeImageSize floor. - static F32 adjustPixelArea(F32 importance, F32 pixel_area, S32 source_area = 0) ; + static F32 adjustPixelArea(F32 importance, F32 pixel_area) ; public: @@ -253,9 +251,6 @@ public: LLVector2 mTexExtents[2]; F32 mDistance; - // Camera-to-face distance, cached by calcPixelArea; read by the - // streaming math's distance signal. - F32 mDistanceToCamera = 0.f; F32 mLastUpdateTime; F32 mLastSkinTime; F32 mLastMoveTime; diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index aab2865ef7..c8692224f1 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -752,29 +752,9 @@ void LLFeatureManager::applyBaseMasks() { maskFeatures("VRAMGT512"); } - - // Texture quality is driven by detected VRAM. Feature masks take the MIN - // of applied values, so cascading lower tiers downgrade RenderTextureQuality: - // <= 2GB -> Low(0), <= 4GB -> Medium(1), < 8GB -> High(2), >= 8GB -> Ultra(3). - // When VRAM cannot be detected (mVRAM == 0, common on Linux) fall back to Medium. - if (gGLManager.mVRAM == 0) + if (gGLManager.mVRAM < 2048) { - maskFeatures("VRAMLT4GB"); - } - else - { - if (gGLManager.mVRAM < 8192) - { - maskFeatures("VRAMLT8GB"); - } - if (gGLManager.mVRAM <= 4096) - { - maskFeatures("VRAMLT4GB"); - } - if (gGLManager.mVRAM <= 2048) - { - maskFeatures("VRAMLT2GB"); - } + maskFeatures("VRAMLT2GB"); } if (gGLManager.mGLVersion < 3.99f) { diff --git a/indra/newview/llsurface.cpp b/indra/newview/llsurface.cpp index fe77d585c8..64359b6cbe 100644 --- a/indra/newview/llsurface.cpp +++ b/indra/newview/llsurface.cpp @@ -57,8 +57,6 @@ namespace LLColor4U MAX_WATER_COLOR(0, 48, 96, 240); S32 LLSurface::sTextureSize = 256; -F32 LLSurface::sNearestVisiblePatchDistance = FLT_MAX; -U32 LLSurface::sNearestVisiblePatchFrame = 0; // ---------------- LLSurface:: Public Members --------------- @@ -124,7 +122,7 @@ LLSurface::~LLSurface() else if (poolp->mReferences.empty()) { gPipeline.removePool(poolp); - // Don't enable this until we blitz the draw pool for it as well. - djs + // Don't enable this until we blitz the draw pool for it as well. -- djs mSTexturep = nullptr; } else @@ -585,13 +583,6 @@ void LLSurface::updatePatchVisibilities(LLAgent &agent) LLSurfacePatch *patchp; - // Reset the cross-region accumulator at the start of each frame. - if (sNearestVisiblePatchFrame != gFrameCount) - { - sNearestVisiblePatchDistance = FLT_MAX; - sNearestVisiblePatchFrame = gFrameCount; - } - mVisiblePatchCount = 0; for (S32 i=0; iupdateCameraDistanceRegion(pos_region); - sNearestVisiblePatchDistance = llmin(sNearestVisiblePatchDistance, patchp->getDistance()); } } } @@ -971,7 +961,7 @@ std::ostream& operator<<(std::ostream &s, const LLSurface &S) void LLSurface::createPatchData() { // Assumes mGridsPerEdge, mGridsPerPatchEdge, and mPatchesPerEdge have been properly set - // TODO - check for create() called when surface is not empty + // TODO -- check for create() called when surface is not empty S32 i, j; LLSurfacePatch *patchp; diff --git a/indra/newview/llsurface.h b/indra/newview/llsurface.h index 4bdb90b102..a599019ca5 100644 --- a/indra/newview/llsurface.h +++ b/indra/newview/llsurface.h @@ -163,12 +163,6 @@ public: F32 mDetailTextureScale; // Number of times to repeat detail texture across this surface - // Closest visible patch distance across all surfaces this frame - // (meters), or FLT_MAX if none visible. Drives BOOST_TERRAIN - // streaming - terrain has no faces registered with its texture. - static F32 sNearestVisiblePatchDistance; - static U32 sNearestVisiblePatchFrame; - private: void createSTexture(); void initTextures(); diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 574c200eb4..51ade60827 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -542,7 +542,6 @@ private: S32 mRequestedDiscard; S32 mLoadedDiscard; S32 mDecodedDiscard; - S32 mCodecLevels = 0; LLFrameTimer mRequestedDeltaTimer; LLFrameTimer mFetchDeltaTimer; LLTimer mCacheReadTimer; @@ -1844,10 +1843,6 @@ bool LLTextureFetchWorker::doWork(S32 param) else { llassert_always(mRawImage.notNull()); - if (mFormattedImage.notNull()) - { - mCodecLevels = (S32)mFormattedImage->getLevels(); - } LL_DEBUGS(LOG_TXT) << mID << ": Decoded. Discard: " << mDecodedDiscard << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; setState(WRITE_TO_CACHE); @@ -2779,12 +2774,10 @@ LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id) // Threads: T* bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, S32& worker_state, LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status, - S32& codec_levels) + LLCore::HttpStatus& last_http_get_status) { LL_PROFILE_ZONE_SCOPED; bool res = false; - codec_levels = 0; LLTextureFetchWorker* worker = getWorker(id); if (worker) { @@ -2816,9 +2809,6 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, S3 discard_level = worker->mDecodedDiscard; raw = worker->mRawImage; aux = worker->mAuxImage; - // Cached on the worker so the value survives mFormattedImage - // clears (cache-retry, decode-abort, write-to-cache complete). - codec_levels = worker->mCodecLevels; decode_time = worker->mDecodeTime; fetch_time = worker->mFetchTime; diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index d75e16ab7c..851d6c11a0 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -106,8 +106,7 @@ public: // keep in mind that if fetcher isn't done, it still might need original raw image bool getRequestFinished(const LLUUID& id, S32& discard_level, S32& worker_state, LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status, - S32& codec_levels); + LLCore::HttpStatus& last_http_get_status); // Threads: T* bool updateRequestPriority(const LLUUID& id, F32 priority); diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 470a133fa7..cbe2dac52a 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -572,13 +572,9 @@ void LLGLTexMemBar::draw() LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*8, text_color, LLFontGL::LEFT, LLFontGL::TOP); - text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB) Bubble: %.1fm PressMult: %.1fx LDMin: %.1f", - image_count, raw_image_count, raw_image_bytes_MB, + text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB)", image_count, raw_image_count, raw_image_bytes_MB, saved_raw_image_count, saved_raw_image_bytes_MB, - aux_raw_image_count, aux_raw_image_bytes_MB, - LLViewerTextureList::sCurrentBubbleMeters, - LLViewerTexture::sMemoryPressureMultiplier, - LLViewerTexture::sLastDitchMinDiscard); + aux_raw_image_count, aux_raw_image_bytes_MB); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height * 7, text_color, LLFontGL::LEFT, LLFontGL::TOP); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index a4a7f1fd20..0c93b24751 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -112,48 +112,6 @@ static bool handleRenderAvatarMouselookChanged(const LLSD& newvalue) return true; } -static bool handleRenderTextureQualityChanged(const LLSD& newvalue) -{ - // 0=Low, 1=Medium, 2=High, 3=Ultra. Drives RenderMaxTextureResolution, - // the four TextureChannel* exponents (Normal/BaseColor/Spec/Emissive), - // and TextureDistanceDiscardPower. - U32 quality = (U32)newvalue.asInteger(); - U32 max_res = 2048; - F32 ch_normal = 1.0f; - F32 ch_basecolor = 0.75f; - F32 ch_specular = 0.5f; - F32 ch_emissive = 0.75f; - F32 distance_power = 0.5f; - switch (quality) - { - case 0: // Low - max_res = 1024; - ch_normal = 0.5f; ch_basecolor = 0.75f; ch_specular = 0.1f; ch_emissive = 0.5f; - distance_power = 0.15f; - break; - case 1: // Medium - ch_normal = 0.75f; ch_basecolor = 0.75f; ch_specular = 0.3f; ch_emissive = 0.75f; - distance_power = 0.25f; - break; - case 2: // High - // channel defaults above - distance_power = 0.35f; - break; - case 3: // Ultra - default: - ch_normal = 1.f; ch_basecolor = 1.f; ch_specular = 1.f; ch_emissive = 1.f; - distance_power = 0.5f; - break; - } - gSavedSettings.setU32("RenderMaxTextureResolution", max_res); - gSavedSettings.setF32("TextureChannelNormal", ch_normal); - gSavedSettings.setF32("TextureChannelBaseColor", ch_basecolor); - gSavedSettings.setF32("TextureChannelSpecular", ch_specular); - gSavedSettings.setF32("TextureChannelEmissive", ch_emissive); - gSavedSettings.setF32("TextureDistanceDiscardPower", distance_power); - return true; -} - static bool handleRenderFarClipChanged(const LLSD& newvalue) { if (LLStartUp::getStartupState() >= STATE_STARTED) @@ -857,7 +815,6 @@ void settings_setup_listeners() { LL_PROFILE_ZONE_SCOPED; setting_setup_signal_listener(gSavedSettings, "FirstPersonAvatarVisible", handleRenderAvatarMouselookChanged); - setting_setup_signal_listener(gSavedSettings, "RenderTextureQuality", handleRenderTextureQualityChanged); setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged); setting_setup_signal_listener(gSavedSettings, "RenderTerrainScale", handleTerrainScaleChanged); setting_setup_signal_listener(gSavedSettings, "RenderTerrainPBRScale", handlePBRTerrainScaleChanged); diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index ac8bb1d0c5..0f23596c9a 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -87,17 +87,6 @@ S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLFrameTimer LLViewerTexture::sEvaluationTimer; F32 LLViewerTexture::sDesiredDiscardBias = 0.f; -F32 LLViewerTexture::sBackgroundFactor = 0.f; -F32 LLViewerTexture::sMemoryPressureMultiplier = 1.f; -F32 LLViewerTexture::sLastDitchMinDiscard = 0.f; - -//static -F32 LLViewerTexture::getMemoryPressureProgress() -{ - static LLCachedControl max_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); - F32 cap = llmax((F32)max_mult, 1.0001f); - return llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); -} U32 LLViewerTexture::sBiasTexturesUpdated = 0; S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size @@ -108,13 +97,12 @@ constexpr S32 DEFAULT_ICON_DIMENSIONS = 32; constexpr S32 DEFAULT_THUMBNAIL_DIMENSIONS = 256; U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; +bool LLViewerTexture::sFreezeImageUpdates = false; F32 LLViewerTexture::sCurrentTime = 0.0f; constexpr F32 MEMORY_CHECK_WAIT_TIME = 1.0f; constexpr F32 MIN_VRAM_BUDGET = 768.f; F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; -F32 LLViewerTexture::sWindowPixelArea = 1.f; -F32 LLViewerTexture::sSysMemoryFactor = 1.f; LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; @@ -132,7 +120,7 @@ LLLoadedCallbackEntry::LLLoadedCallbackEntry(loaded_callback_func cb, LLViewerFetchedTexture* target, bool pause) : mCallback(cb), - mLastUsedDiscard(S32_MAX), + mLastUsedDiscard(MAX_DISCARD_LEVEL+1), mDesiredDiscard(discard_level), mNeedsImageRaw(need_imageraw), mUserData(userdata), @@ -491,26 +479,12 @@ void LLViewerTexture::initClass() LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture(); } -S32Megabytes get_render_free_main_memory_treshold() -{ - static LLCachedControl min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512); - const U32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory); - return MIN_FREE_MAIN_MEMORY; -} - //static void LLViewerTexture::updateClass() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; sCurrentTime = gFrameTimeSeconds; - if (gViewerWindow) - { - F32 w = (F32)gViewerWindow->getWindowWidthRaw(); - F32 h = (F32)gViewerWindow->getWindowHeightRaw(); - sWindowPixelArea = llmax(w * h, 1.f); - } - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); if (tester) { @@ -531,213 +505,29 @@ void LLViewerTexture::updateClass() // get an estimate of how much video memory we're using // NOTE: our metrics miss about half the vram we use, so this biases high but turns out to typically be within 5% of the real number - F32 vram_used = (F32)ll_round(texture_bytes_alloc + vertex_bytes_alloc); + F32 used = (F32)ll_round(texture_bytes_alloc + vertex_bytes_alloc); // For debugging purposes, it's useful to be able to set the VRAM budget manually. // But when manual control is not enabled, use the VRAM divisor. // While we're at it, assume we have 1024 to play with at minimum when the divisor is in use. Works more elegantly with the logic below this. // -Geenz 2025-03-21 - F32 vram_budget = max_vram_budget == 0 ? llmax(1024, (F32)gGLManager.mVRAM / tex_vram_divisor) : (F32)max_vram_budget; + F32 budget = max_vram_budget == 0 ? llmax(1024, (F32)gGLManager.mVRAM / tex_vram_divisor) : (F32)max_vram_budget; // Try to leave at least half a GB for everyone else and for bias, // but keep at least 768MB for ourselves // Viewer can 'overshoot' target when scene changes, if viewer goes over budget it // can negatively impact performance, so leave 20% of a breathing room for // 'bias' calculation to kick in. - F32 vram_target = llmax(llmin(vram_budget - 512.f, vram_budget * 0.8f), MIN_VRAM_BUDGET); - sFreeVRAMMegabytes = vram_target - vram_used; - const S32Megabytes free_sys_mem = getFreeSystemMemory(); - - F32 over_pct = (vram_used - vram_target) / vram_target; - - // Predicted-VRAM pressure controller. Eviction is fast, refetch is slow, - // so feedback on instantaneous `used` sawtooths; feeding `used + - // in_flight_delta` lets mult converge to equilibrium instead of cycling. - { - static LLCachedControl backoff_start(gSavedSettings, "TextureMemoryPressureBackoffStart", 0.85f); - static LLCachedControl max_mult(gSavedSettings, "TextureMemoryPressureMaxMultiplier", 64.f); - static LLCachedControl prediction_gain(gSavedSettings, "TextureMemoryPressurePredictionGain", 10.f); - static LLCachedControl smoothing_rate(gSavedSettings, "TextureMemoryPressureSmoothingRate", 4.f); - - F32 backoff_target = vram_target * llclamp((F32)backoff_start, 0.05f, 1.f); - F32 cap = llmax((F32)max_mult, 1.0001f); - F32 dt = (F32)gFrameIntervalSeconds; - - // Skip the full-list iteration when there is no pressure to react to: - // mult already at baseline, last-ditch at zero, and used well clear of - // the backoff target. Worst case the controller picks up the spike one - // frame later, from `used` alone. - bool need_predict = sMemoryPressureMultiplier > 1.001f - || sLastDitchMinDiscard > 0.f - || vram_used > backoff_target * 0.5f; - - S64 pending_bytes_increase = 0; - S64 pending_bytes_decrease = 0; - if (need_predict) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vt - in-flight predict"); - for (auto& imagep : gTextureList) - { - if (imagep.isNull()) continue; - // Cheap inline checks first so the virtual getDiscardLevel() - // call only fires when there is a real chance of contribution. - S32 desired = imagep->getDesiredDiscardLevel(); - if (desired < 0) continue; - S32 fw = imagep->getFullWidth(); - S32 fh = imagep->getFullHeight(); - if (fw <= 0 || fh <= 0) continue; - S32 current = imagep->getDiscardLevel(); - if (current < 0 || desired == current) continue; - - S32 wd = llmax(1, fw >> desired); - S32 hd = llmax(1, fh >> desired); - S32 wc = llmax(1, fw >> current); - S32 hc = llmax(1, fh >> current); - // bpp=4, mip pyramid overhead 4/3 - S64 size_d = (S64)wd * hd * 4 * 4 / 3; - S64 size_c = (S64)wc * hc * 4 * 4 / 3; - - if (desired < current) - pending_bytes_increase += (size_d - size_c); - else - pending_bytes_decrease += (size_c - size_d); - } - } - - // 1024 * 512 = 524288: matches the unit reduction at line 513. - constexpr F32 BYTES_TO_USED_UNITS = 1.f / 524288.f; - F32 predicted_used = vram_used - + (F32)pending_bytes_increase * BYTES_TO_USED_UNITS - - (F32)pending_bytes_decrease * BYTES_TO_USED_UNITS; - F32 predicted_over = predicted_used / llmax(backoff_target, 1.f); - - // High water mark: when used crosses budget * high_water, skip the - // smoothed convergence and slam the controller into hard-cap state. - // Recovers the historical 90% behavior - immediate aggressive - // response instead of waiting for the lerp to chase the target. - static LLCachedControl high_water(gSavedSettings, "TextureMemoryHighWaterMark", 0.8f); - bool above_high_water = vram_used >= vram_budget * llclamp((F32)high_water, 0.5f, 1.f); - - F32 target_mult = llclamp(powf(llmax(predicted_over, 1.f), llmax((F32)prediction_gain, 0.0001f)), 1.f, cap); - if (above_high_water) - { - target_mult = cap; - sMemoryPressureMultiplier = cap; - } - else - { - // ~63% convergence in 1/smoothing_rate seconds (default 0.25s). - F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)smoothing_rate, 0.f)); - sMemoryPressureMultiplier += (target_mult - sMemoryPressureMultiplier) * alpha; - } - sMemoryPressureMultiplier = llclamp(sMemoryPressureMultiplier, 1.f, cap); + F32 target = llmax(llmin(budget - 512.f, budget * 0.8f), MIN_VRAM_BUDGET); + sFreeVRAMMegabytes = target - used; - F32 progress = getMemoryPressureProgress(); - - { - static LLCachedControl ld_engage(gSavedSettings, "TextureLastDitchEngageProgress", 0.95f); - static LLCachedControl ld_ramp(gSavedSettings, "TextureLastDitchRampRate", 0.5f); - static LLCachedControl ld_decay(gSavedSettings, "TextureLastDitchDecayRate", 0.5f); - static LLCachedControl ld_max(gSavedSettings, "TextureLastDitchMinDiscardMax", 13.f); - // Above the high water mark, last-ditch creeps regardless of - // mult_progress: by definition we are out of normal headroom. - bool engage = above_high_water || progress >= llclampf((F32)ld_engage); - if (engage && predicted_over > 1.f) - { - sLastDitchMinDiscard += llmax((F32)ld_ramp, 0.f) * dt; - } - else if (!above_high_water && predicted_over < 1.f) - { - sLastDitchMinDiscard -= llmax((F32)ld_decay, 0.f) * dt; - } - sLastDitchMinDiscard = llclamp(sLastDitchMinDiscard, 0.f, llmax((F32)ld_max, 0.f)); - } - - // 1 Hz pressure log. - static LLFrameTimer s_pressure_log_timer; - if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) - { - s_pressure_log_timer.reset(); - F32 over = vram_used / llmax(backoff_target, 1.f); - LL_INFOS("TextureStream") << "pressure" - << " mult=" << sMemoryPressureMultiplier - << " target_mult=" << target_mult - << " progress=" << progress - << " used=" << vram_used - << " predicted=" << predicted_used - << " target=" << vram_target - << " over=" << over - << " pred_over=" << predicted_over - << " in+=" << (S32)(pending_bytes_increase / 1024 / 1024) - << "MB in-=" << (S32)(pending_bytes_decrease / 1024 / 1024) - << "MB bias=" << sDesiredDiscardBias - << " ldmin=" << sLastDitchMinDiscard - << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() - << LL_ENDL; - } - } + F32 over_pct = (used - target) / target; bool is_sys_low = isSystemMemoryLow(); - bool is_sys_critically_low = isSystemMemoryCritical(); bool is_low = is_sys_low || over_pct > 0.f; static bool was_low = false; - static bool sys_was_low = false; - - // System memory factor - // 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_critically_low) - { - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); - // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory. - S32 sys_budget_debt = free_sys_mem - MIN_FREE_MAIN_MEMORY; - - // Leave some padding, otherwise we will crash out of memory before hitting factor 2. - const S32Megabytes PAD_BUFFER(32); - S32Megabytes budget_target = MIN_FREE_MAIN_MEMORY - PAD_BUFFER; - if (!sys_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 / (F32)budget_target; - 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 / (F32)budget_target, 0.f); - sSysMemoryFactor += increment * gFrameIntervalSeconds; - } - sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); - } - else - { - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); - // 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 - // treshold. - const S32Megabytes MEM_THRESHOLD = MIN_FREE_MAIN_MEMORY + S32Megabytes(150); - if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f) - { - // Ramp down factor over time. - constexpr F32 DECREMENT = 0.02f; - sSysMemoryFactor -= DECREMENT * gFrameIntervalSeconds; - sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); - } - } - sys_was_low = is_sys_critically_low; - // VRAM memory bias if (is_low && !was_low) { if (is_sys_low) @@ -779,18 +569,14 @@ void LLViewerTexture::updateClass() // don't execute above until the slam to 1.5 has a chance to take effect sEvaluationTimer.reset(); - // Don't decay bias while downscale is still draining - those bytes - // are about to free and the loop would oscillate. - bool eviction_in_flight = !gTextureList.mDownScaleQueue.empty(); - // lower discard bias over time when at least 10% of budget is free constexpr F32 FREE_PERCENTAGE_TRESHOLD = -0.1f; - constexpr U32 FREE_SYS_MEM_THRESHOLD = 100; // 100MB more than isSystemMemoryLow to avoid fluctuations. - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() + S32Megabytes(FREE_SYS_MEM_THRESHOLD)); + constexpr U32 FREE_SYS_MEM_TRESHOLD = 100; + static LLCachedControl min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512); + const S32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory() + FREE_SYS_MEM_TRESHOLD); if (sDesiredDiscardBias > 1.f && over_pct < FREE_PERCENTAGE_TRESHOLD - && free_sys_mem > MIN_FREE_MAIN_MEMORY - && !eviction_in_flight) + && getFreeSystemMemory() > MIN_FREE_MAIN_MEMORY) { static LLCachedControl high_mem_discard_decrement(gSavedSettings, "RenderHighMemMinDiscardDecrement", .1f); @@ -836,33 +622,6 @@ void LLViewerTexture::updateClass() } } - // Background-window ramp: 0 -> 1 at rate per second while backgrounded, - // snaps to 0 in foreground. Default 0.011 ~ 90s to saturate. - { - static LLCachedControl bg_factor_rate(gSavedSettings, "TextureBackgroundFactorRatePerSec", 0.011f); - if (in_background) - { - sBackgroundFactor += (F32)bg_factor_rate * gFrameIntervalSeconds; - sBackgroundFactor = llclampf(sBackgroundFactor); - } - else - { - sBackgroundFactor = 0.f; - } - } - - // Fetch-queue depth as a one-way bias floor (decay path still drops - // bias when the queue drains). Pushes bias up before VRAM overflows - // during teleport/scene-change floods. - if (LLTextureFetch* fetcher = LLAppViewer::getTextureFetch()) - { - S32 pending = fetcher->getNumRequests(); - static LLCachedControl fetch_pressure_scale(gSavedSettings, "TextureFetchPressureScale", 1000.f); - F32 scale = llmax((F32)fetch_pressure_scale, 1.f); - F32 fetch_pressure = llclamp((F32)pending / scale, 0.f, 3.f); - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + fetch_pressure); - } - sDesiredDiscardBias = llclamp(sDesiredDiscardBias, 1.f, 4.f); if (last_texture_update_count_bias < sDesiredDiscardBias) { @@ -879,6 +638,8 @@ void LLViewerTexture::updateClass() // a problem. last_texture_update_count_bias = sDesiredDiscardBias; } + + LLViewerTexture::sFreezeImageUpdates = false; } //static @@ -899,6 +660,13 @@ U32Megabytes LLViewerTexture::getFreeSystemMemory() return physical_res; } +S32Megabytes get_render_free_main_memory_treshold() +{ + static LLCachedControl min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512); + const U32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory); + return MIN_FREE_MAIN_MEMORY; +} + //static bool LLViewerTexture::isSystemMemoryLow() { @@ -911,10 +679,18 @@ bool LLViewerTexture::isSystemMemoryCritical() return getFreeSystemMemory() < get_render_free_main_memory_treshold() / 2; } -// static F32 LLViewerTexture::getSystemMemoryBudgetFactor() { - return sSysMemoryFactor; + 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 @@ -1380,10 +1156,8 @@ void LLViewerFetchedTexture::init(bool firstinit) mRequestedDownloadPriority = 0.f; mFullyLoaded = false; mCanUseHTTP = true; - mDesiredDiscardLevel = S8_MAX; - // S8_MAX = no cap. setMinDiscardLevel takes min(current, new), so - // explicit caps from terrain / avatar self / thumbnails still apply. - mMinDesiredDiscardLevel = S8_MAX; + mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; + mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; mDecodingAux = false; @@ -2203,10 +1977,21 @@ bool LLViewerFetchedTexture::processFetchResults(S32& desired_discard, S32 curre setIsMissingAsset(); desired_discard = -1; } - // Transient failure (decoder OOM, network blip): don't latch - // mMinDiscardLevel - that would block all future fetches via - // the make_request gate. Permanent failures are caught above - // (getDiscardLevel()<0 -> setIsMissingAsset). + else + { + //LL_WARNS() << mID << ": Setting min discard to " << current_discard << LL_ENDL; + if (current_discard >= 0) + { + mMinDiscardLevel = current_discard; + //desired_discard = current_discard; + } + else + { + S32 dis_level = getDiscardLevel(); + mMinDiscardLevel = dis_level; + //desired_discard = dis_level; + } + } destroyRawImage(); } else if (mRawImage.notNull()) @@ -2287,18 +2072,8 @@ bool LLViewerFetchedTexture::updateFetch() if (mRawImage.notNull()) sRawCount--; if (mAuxRawImage.notNull()) sAuxCount--; // keep in mind that fetcher still might need raw image, don't modify original - S32 codec_levels = 0; bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mFetchState, mRawImage, mAuxRawImage, - mLastHttpGetStatus, codec_levels); - if (codec_levels > 0) - { - mCodecMaxDiscardLevel = (S8)llmin(codec_levels, (S32)S8_MAX); - if (codec_levels > 5) - { - LL_DEBUGS("TextureStream") << "Texture " << mID << " codec-reported max discard " - << codec_levels << " (above the historical hardcoded cap of 5)" << LL_ENDL; - } - } + mLastHttpGetStatus); if (mRawImage.notNull()) sRawCount++; if (mAuxRawImage.notNull()) { @@ -2333,9 +2108,7 @@ bool LLViewerFetchedTexture::updateFetch() } } - // Clamp the fetch request to what the codestream encodes; deeper - // discards are served from the GL mip pyramid via scaleDown. - desired_discard = llmin(desired_discard, (S32)mCodecMaxDiscardLevel); + desired_discard = llmin(desired_discard, getMaxDiscardLevel()); bool make_request = true; if (decode_priority <= 0) @@ -2343,13 +2116,9 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - priority <= 0"); make_request = false; } - else if (mDesiredDiscardLevel > (S32)mCodecMaxDiscardLevel && - current_discard >= 0) + else if (mDesiredDiscardLevel > getMaxDiscardLevel()) { - // Desired is past codec_max. Only scaleDown can satisfy it. - // Applies even when current is also past codec_max (post-scaleDown); - // re-fetching at codec_max then scaleDown-ing again is pure thrash. - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > codec max"); + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > max"); make_request = false; } else if (mNeedsCreateTexture || mIsMissingAsset) @@ -2801,21 +2570,20 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() S32 gl_discard = getDiscardLevel(); - // S32_MAX is the "no data" sentinel; real discards can now exceed - // MAX_DISCARD_LEVEL via dimDerivedMaxDiscard. + // If we don't have a legit GL image, set it to be lower than the worst discard level if (gl_discard == -1) { - gl_discard = S32_MAX; + gl_discard = MAX_DISCARD_LEVEL + 1; } // // Determine the quality levels of textures that we can provide to callbacks // and whether we need to do decompression/readback to get it // - S32 current_raw_discard = S32_MAX; // We can always do a readback to get a raw discard + S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard S32 best_raw_discard = gl_discard; // Current GL quality level - S32 current_aux_discard = S32_MAX; - S32 best_aux_discard = S32_MAX; + S32 current_aux_discard = MAX_DISCARD_LEVEL + 1; + S32 best_aux_discard = MAX_DISCARD_LEVEL + 1; LLImageRaw *current_raw_image = nullptr; if (mIsRawImageValid) @@ -2915,7 +2683,7 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() // // Run raw/auxiliary data callbacks // - if (run_raw_callbacks && current_raw_image != nullptr && current_raw_discard != S32_MAX) + if (run_raw_callbacks && current_raw_image != nullptr && (current_raw_discard <= getMaxDiscardLevel())) { // Do callbacks which require raw image data. //LL_INFOS() << "doLoadedCallbacks raw for " << getID() << LL_ENDL; @@ -2955,7 +2723,7 @@ bool LLViewerFetchedTexture::doLoadedCallbacks() // // Run GL callbacks // - if (run_gl_callbacks && gl_discard != S32_MAX) + if (run_gl_callbacks && (gl_discard <= getMaxDiscardLevel())) { //LL_INFOS() << "doLoadedCallbacks GL for " << getID() << LL_ENDL; @@ -3258,6 +3026,11 @@ S8 LLViewerLODTexture::getType() const return LLViewerTexture::LOD_TEXTURE; } +bool LLViewerLODTexture::isUpdateFrozen() +{ + return LLViewerTexture::sFreezeImageUpdates; +} + // This is gauranteed to get called periodically for every texture //virtual void LLViewerLODTexture::processTextureStats() @@ -3283,15 +3056,18 @@ void LLViewerLODTexture::processTextureStats() { mDesiredDiscardLevel = 0; } - // HUD/UI/preview and mDontDiscard textures bypass streaming - no - // face_distance signal applies, they need native resolution. - else if (mBoostLevel >= LLGLTexture::BOOST_HIGH - || mDontDiscard - || !mUseMipMaps) + // Generate the request priority and render priority + else if (mDontDiscard || !mUseMipMaps) { mDesiredDiscardLevel = 0; if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) - mDesiredDiscardLevel = 1; // 4096^2 source can't be loaded full res + mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 2048 and max size ever is 4096 + } + else if (mBoostLevel < LLGLTexture::BOOST_HIGH && mMaxVirtualSize <= 10.f) + { + // If the image has not been significantly visible in a while, we don't want it + mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1)); + mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); } else if (!mFullWidth || !mFullHeight) { @@ -3300,104 +3076,28 @@ void LLViewerLODTexture::processTextureStats() } else { - F32 discard_level = 0.f; + //static const F64 log_2 = log(2.0); + static const F64 log_4 = log(4.0); - // floor(log2(max(w, h))) - both the multiplier on the normalized - // factor and the cap clamp at the bottom of this function. - S32 dim_max_for_image_i = (mFullWidth > 0 && mFullHeight > 0) - ? LLImageGL::dimDerivedMaxDiscard(mFullWidth, mFullHeight) - : (S32)mCodecMaxDiscardLevel; - F32 dim_max_for_image = (F32)dim_max_for_image_i; + F32 discard_level = 0.f; + // If we know the output width and height, we can force the discard + // level to the correct value, and thus not decode more texture + // data than we need to. if (mKnownDrawWidth && mKnownDrawHeight) { - // UI-pinned target dimensions - use pixel-area math. - static const F64 log_4 = log(4.0); S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); + + // Use log_4 because we're in square-pixel space, so an image + // with twice the width and twice the height will have mTexelsPerImage + // 4 * draw_size discard_level = (F32)(log(mTexelsPerImage / draw_texels) / log_4); } else { - // Two 0..1 signals composed multiplicatively: - // discard = distance_factor * size_factor * max_discard - // distance_factor: face_dist / draw_dist, shaped by - // TextureDistanceDiscardPower (default 0.5 = sqrt). - // size_factor: 1 - (mMaxOnScreenSize / window_pixels), shaped - // by TextureSizeDiscardPower. - // Either factor near 0 keeps the result fine - both have to - // be high for the texture to go deep. - static LLCachedControl distance_power(gSavedSettings, "TextureDistanceDiscardPower", 0.5f); - F32 power = llmax((F32)distance_power, 0.0001f); - F32 distance_factor = (power == 1.f) ? mMinDistanceFactor : powf(mMinDistanceFactor, power); - - static LLCachedControl size_power(gSavedSettings, "TextureSizeDiscardPower", 1.f); - F32 sz_power = llmax((F32)size_power, 0.0001f); - F32 coverage = llclampf(mMaxOnScreenSize / sWindowPixelArea); - F32 inv_cov = 1.f - coverage; - F32 size_factor = (sz_power == 1.f) ? inv_cov : powf(inv_cov, sz_power); - - F32 combined = distance_factor * size_factor; - - // VRAM pressure: multiply the combined signal and clamp to 0..1. - // Compresses the effective draw range and picks up close-coverage - // textures (small combined) too. Applied before the channel - // exponent so subsequent transforms see a normalized 0..1 value. - // Avatar bakes exempt. - if (!isAgentAvatarBoost(mBoostLevel) && sMemoryPressureMultiplier > 1.f) - { - combined = llmin(combined * sMemoryPressureMultiplier, 1.f); - } - - // Per-channel exponent. 1.0 = baseline; <1.0 pushes combined - // toward 1 (max attenuation) faster. Edges are preserved: - // pow(0, p) = 0, pow(1, p) = 1. - // mPriorityChannel order: 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. - S32 priority_channel = (mPriorityChannel >= 0 && mPriorityChannel < 4) ? (S32)mPriorityChannel : 1; - static LLCachedControl channel_normal (gSavedSettings, "TextureChannelNormal", 1.0f); - static LLCachedControl channel_basecolor(gSavedSettings, "TextureChannelBaseColor", 0.75f); - static LLCachedControl channel_specular (gSavedSettings, "TextureChannelSpecular", 0.5f); - static LLCachedControl channel_emissive (gSavedSettings, "TextureChannelEmissive", 0.75f); - const F32 channels[4] = { - (F32)channel_normal, - (F32)channel_basecolor, - (F32)channel_specular, - (F32)channel_emissive, - }; - F32 channel_power = llmax(channels[priority_channel], 0.0001f); - if (channel_power != 1.f) - { - combined = powf(combined, channel_power); - } - - // Own-avatar boost: shave combined for rigged/animated faces - // on gAgentAvatarp. Preference, not exemption - applied - // before the staleness/background/pressure floors so heavy - // pressure can still evict. - if (mOnAgentAvatar) - { - static LLCachedControl agent_avatar_boost(gSavedSettings, "TextureAgentAvatarBoost", 0.5f); - combined *= llclampf((F32)agent_avatar_boost); - } - - // Staleness / background floors. Avatar bakes exempt from - // background to avoid the universal-cloud bug when re-foregrounding. - combined = llmax(combined, mStalenessFactor); - if (!isAgentAvatarBoost(mBoostLevel)) - { - // Background floor capped at (dim_max - offset) so we can - // keep some baseline quality while backgrounded. - static LLCachedControl bg_offset(gSavedSettings, "TextureBackgroundDiscardOffset", 2); - F32 bg = sBackgroundFactor; - if ((S32)bg_offset > 0 && dim_max_for_image > 0.f) - { - F32 cap = llmax(dim_max_for_image - (F32)(S32)bg_offset, 0.f) / dim_max_for_image; - bg = llmin(bg, cap); - } - combined = llmax(combined, bg); - } - - discard_level = combined * dim_max_for_image; + // Calculate the required scale factor of the image using pixels per texel + discard_level = (F32)(log(mTexelsPerImage / mMaxVirtualSize) / log_4); } discard_level = floorf(discard_level); @@ -3406,43 +3106,12 @@ void LLViewerLODTexture::processTextureStats() if (mFullWidth > max_tex_res || mFullHeight > max_tex_res) min_discard = 1.f; - // dim_max_for_image_i is the per-texture cap. TextureMaxDiscardOverride - // raises it (debug). Codec_max applies only to fetches, not here. - static LLCachedControl max_discard_override(gSavedSettings, "TextureMaxDiscardOverride", 0); - S32 effective_cap = (max_discard_override > 0) ? (S32)max_discard_override : dim_max_for_image_i; - discard_level = llclamp(discard_level, min_discard, (F32)effective_cap); - - mDesiredDiscardLevel = llmin(effective_cap, (S32)discard_level); - - // Apply the setMinDiscardLevel cap, relaxed under VRAM pressure - // (cap_relax = 1 - 1/mult: 0 at mult=1, ~0.5 at mult=2, ~0.9 at - // mult=10). Caps of 0 (thumbnails) and avatar bakes are preserved. - S32 effective_min_cap = mMinDesiredDiscardLevel; - if (sMemoryPressureMultiplier > 1.f && - mMinDesiredDiscardLevel > 0 && mMinDesiredDiscardLevel < S8_MAX && - !isAgentAvatarBoost(mBoostLevel)) - { - F32 cap_relax = 1.f - 1.f / sMemoryPressureMultiplier; - F32 room = (F32)dim_max_for_image_i - (F32)mMinDesiredDiscardLevel; - effective_min_cap += (S32)(cap_relax * room); - effective_min_cap = llmin(effective_min_cap, dim_max_for_image_i); - } - mDesiredDiscardLevel = llmin((S8)effective_min_cap, mDesiredDiscardLevel); - - // Halve the floor for bubble-resident textures (mMinDistanceFactor == 0 - // = at least one face inside the bubble) so the close-vs-far gradient - // is preserved at every pressure level. - if (!isAgentAvatarBoost(mBoostLevel)) - { - S32 forced = (S32)floorf(sLastDitchMinDiscard); - if (mMinDistanceFactor <= 0.f) forced /= 2; - forced = llclamp(forced, 0, dim_max_for_image_i); - if (forced > mDesiredDiscardLevel) - { - mDesiredDiscardLevel = (S8)forced; - } - } + discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL); + // Can't go higher than the max discard level + mDesiredDiscardLevel = llmin(getMaxDiscardLevel() + 1, (S32)discard_level); + // Clamp to min desired discard + mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel); // // At this point we've calculated the quality level that we want, @@ -3451,9 +3120,7 @@ void LLViewerLODTexture::processTextureStats() // S32 current_discard = getDiscardLevel(); - // Avatar bakes exempt: shrinking mid-bake can leave the avatar - // stuck as a cloud until the next bake completes. - if (!isAgentAvatarBoost(mBoostLevel)) + if (mBoostLevel < LLGLTexture::BOOST_AVATAR_BAKED) { if (current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { // should scale down @@ -3461,6 +3128,13 @@ void LLViewerLODTexture::processTextureStats() } } + if (isUpdateFrozen() // we are out of memory and nearing max allowed bias + && mBoostLevel < LLGLTexture::BOOST_SCULPTED + && mDesiredDiscardLevel < current_discard) + { + // stop requesting more + mDesiredDiscardLevel = current_discard; + } mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); } @@ -3486,22 +3160,6 @@ bool LLViewerLODTexture::scaleDown() return false; } - // Hard structural blocks only. Per-texture policy (icons pinned to full - // res, etc.) lives in processTextureStats; if that policy is later - // relaxed (e.g. honor mKnownDrawWidth for icons rendered at 8x8 in a - // friend list) the scaleDown path stays open. - // BOOST_HIGH is the emergency-out for GLTF's "force full res" hack; - // the other two flags are structural. - if (!mUseMipMaps || mDontDiscard || mBoostLevel >= LLGLTexture::BOOST_HIGH) - { - return false; - } - // Avatar bakes are exempt from mid-bake eviction (cloud avatar risk). - if (isAgentAvatarBoost(mBoostLevel)) - { - return false; - } - if (!mDownScalePending) { mDownScalePending = true; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 8c5c48bafd..2937651995 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -116,8 +116,6 @@ public: static void updateClass(); static bool isSystemMemoryLow(); static bool isSystemMemoryCritical(); - - // Ranges from 1 (no RAM deficit) to 2 (RAM deficit) static F32 getSystemMemoryBudgetFactor(); LLViewerTexture(bool usemipmaps = true); @@ -205,25 +203,6 @@ protected: mutable S32 mMaxVirtualSizeResetInterval; LLFrameTimer mLastReferencedTimer; - // 0=Normal, 1=BaseColor, 2=Specular, 3=Emissive. -1 -> base color. - S8 mPriorityChannel = -1; - - // Bind-staleness floor, 0..1. Per-interval increment is 1/max_discard - // so any texture saturates after interval x max_discard seconds idle. - F32 mStalenessFactor = 0.f; - - // Closest face's face_distance / draw_distance, clamped 0..1. - // Defaults to 1 so never-measured textures resolve to deepest discard. - F32 mMinDistanceFactor = 1.f; - - // Largest per-face screen-space coverage in pixels. Raw - no bias or - // channel-priority contamination. - F32 mMaxOnScreenSize = 0.f; - - // Any face on the agent's avatar (rigged / animated). Drives the - // own-avatar quality boost in processTextureStats. - bool mOnAgentAvatar = false; - ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; LLFrameTimer mLastFaceListUpdateTimer ; @@ -245,36 +224,16 @@ public: static S32 sAuxCount; static LLFrameTimer sEvaluationTimer; static F32 sDesiredDiscardBias; - // Backgrounded-window discard floor, 0..1. Ramps while backgrounded, - // snaps to 0 in foreground. Avatar bakes exempt. - static F32 sBackgroundFactor; - - // VRAM-pressure distance multiplier, >= 1. Compresses the distance - // signal: dist_factor = clamp(mMinDistanceFactor * mult, 0, 1). - // Grows geometrically while over budget; decays back to 1 when fitting. - static F32 sMemoryPressureMultiplier; - // Last-ditch global discard floor. Mirrors sDesiredDiscardBias once the - // multiplier is exhausted. - static F32 sLastDitchMinDiscard; - - // 0..1 progress of the pressure multiplier from baseline (1) to its - // configured cap (TextureMemoryPressureMaxMultiplier). Used to gate - // bubble shrink and last-ditch engagement. - static F32 getMemoryPressureProgress(); static U32 sBiasTexturesUpdated; static S32 sMaxSculptRez ; static U32 sMinLargeImageSize ; static U32 sMaxSmallImageSize ; + static bool sFreezeImageUpdates; static F32 sCurrentTime ; // estimated free memory for textures, by bias calculation static F32 sFreeVRAMMegabytes; - static F32 sSysMemoryFactor; - // Viewport pixel area, refreshed once per frame. Hoisted to keep the - // per-texture hot path out of gViewerWindow. - static F32 sWindowPixelArea; - enum EDebugTexels { DEBUG_TEXELS_OFF, @@ -320,16 +279,6 @@ public: LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps); LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); - // Avatar bake/skin textures - exempt from non-visibility-driven discard - // (staleness, background, pressure) to avoid the universal-cloud bug. - static bool isAgentAvatarBoost(S32 boost_level) - { - return boost_level == BOOST_AVATAR - || boost_level == BOOST_AVATAR_BAKED - || boost_level == BOOST_AVATAR_SELF - || boost_level == BOOST_AVATAR_BAKED_SELF; - } - public: struct Compare @@ -390,7 +339,7 @@ public: void updateVirtualSize() ; - S32 getDesiredDiscardLevel() const { return mDesiredDiscardLevel; } + S32 getDesiredDiscardLevel() { return mDesiredDiscardLevel; } void setMinDiscardLevel(S32 discard) { mMinDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel,(S8)discard); } void setBoostLevel(S32 level) override; @@ -512,12 +461,6 @@ protected: S8 mDesiredDiscardLevel; // The discard level we'd LIKE to have - if we have it and there's space S8 mMinDesiredDiscardLevel; // The minimum discard level we'd like to have - // Fetch-side discard cap from the J2C codestream's DWT level count - // (populated by the fetcher). Distinct from LLImageGL::mMaxDiscardLevel - - // scaleDown can still trim the GL pyramid past this. - static constexpr S8 sFallbackCodecMaxDiscardLevel = 5; // MIN_DECOMPOSITION_LEVELS - S8 mCodecMaxDiscardLevel = sFallbackCodecMaxDiscardLevel; - bool mNeedsAux; // We need to decode the auxiliary channels bool mHasAux; // We have aux channels bool mDecodingAux; // Are we decoding high components @@ -597,6 +540,7 @@ public: S8 getType() const override; // Process image stats to determine priority/quality requirements. void processTextureStats() override; + bool isUpdateFrozen() ; bool scaleDown() override; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 4d09eff74e..7dd32074cf 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -47,7 +47,6 @@ #include "message.h" #include "lldrawpoolbump.h" // to init bumpmap images -#include "llagentcamera.h" #include "lltexturecache.h" #include "lltexturefetch.h" #include "llviewercontrol.h" @@ -62,8 +61,6 @@ #include "lltracerecording.h" #include "llviewerdisplay.h" #include "llviewerwindow.h" -#include "llsurface.h" -#include "llvoavatarself.h" #include "llprogressview.h" //////////////////////////////////////////////////////////////////////////// @@ -71,7 +68,6 @@ void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; S32 LLViewerTextureList::sNumImages = 0; -F32 LLViewerTextureList::sCurrentBubbleMeters = 0.f; LLViewerTextureList gTextureList; @@ -97,20 +93,6 @@ LLTextureKey::LLTextureKey(LLUUID id, ETexListType tex_type) /////////////////////////////////////////////////////////////////////////////// -// eTexIndex -> TextureChannel* index (0=Normal, 1=BaseColor, 2=Specular, -// 3=Emissive). Single source of truth - route all channel-priority lookups -// through this table. -const S32 LLViewerTextureList::sChannelToPriority[LLRender::NUM_TEXTURE_CHANNELS] = -{ - 1, // DIFFUSE_MAP (0) -> Y (diffuse) - 0, // NORMAL_MAP / ALT_DIFFUSE (1) -> X (normals) - 2, // SPECULAR_MAP (2) -> Z (specular/metallic) - 1, // BASECOLOR_MAP (3) -> Y (diffuse) - 2, // METALLIC_ROUGHNESS_MAP (4) -> Z (specular/metallic) - 0, // GLTF_NORMAL_MAP (5) -> X (normals) - 3, // EMISSIVE_MAP (6) -> W (emissive) -}; - LLViewerTextureList::LLViewerTextureList() : mForceResetTextureStats(false), mInitialized(false) @@ -917,7 +899,8 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag { llassert(!gCubeSnapshot); - constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; // perf gate for face-loop early exit + constexpr F32 BIAS_TRS_OUT_OF_SCREEN = 1.5f; + constexpr F32 BIAS_TRS_ON_SCREEN = 1.f; if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { @@ -927,67 +910,9 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag F32 max_vsize = 0.f; bool on_screen = false; - // Accumulators for the per-texture signals published below. - // Defaults map to "deepest discard wanted" until evidence updates them. - F32 min_distance_factor = 1.f; - F32 max_on_screen_size = 0.f; - bool on_agent_avatar = false; - F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 0.001f); - - // Close-camera bubble: faces inside `bubble` meters resolve to - // dist_factor = 0, so the distance ramp spans (bubble, draw_distance]. - static LLCachedControl close_bubble(gSavedSettings, "TextureCloseBubbleMeters", 5.f); - static LLCachedControl close_bubble_min(gSavedSettings, "TextureCloseBubbleMinMeters", 0.1f); - static LLCachedControl bubble_shrink_threshold(gSavedSettings, "TextureCloseBubbleShrinkThreshold", 0.8f); - static LLCachedControl bubble_track_rate(gSavedSettings, "TextureCloseBubbleTrackRate", 0.5f); - F32 bubble_full = llmax((F32)close_bubble, 0.f); - F32 bubble_min = llclamp((F32)close_bubble_min, 0.f, bubble_full); - // Advance the slow-track once per frame, not per texture: this - // function runs once per texture so a naive per-call lerp converges - // in a single frame. - static F32 s_tracked_bubble = -1.f; - static U32 s_tracked_bubble_frame = 0; - if (s_tracked_bubble < 0.f) s_tracked_bubble = bubble_full; - if (s_tracked_bubble_frame != LLFrameTimer::getFrameCount()) - { - s_tracked_bubble_frame = LLFrameTimer::getFrameCount(); - F32 progress = LLViewerTexture::getMemoryPressureProgress(); - F32 shrink_thresh = llclampf((F32)bubble_shrink_threshold); - F32 shrink_frac = (progress > shrink_thresh) - ? (progress - shrink_thresh) / llmax(1.f - shrink_thresh, 0.0001f) - : 0.f; - F32 target_bubble = bubble_full - (bubble_full - bubble_min) * shrink_frac; - F32 dt = (F32)gFrameIntervalSeconds; - F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)bubble_track_rate, 0.f)); - s_tracked_bubble += (target_bubble - s_tracked_bubble) * alpha; - s_tracked_bubble = llclamp(s_tracked_bubble, bubble_min, bubble_full); - sCurrentBubbleMeters = s_tracked_bubble; - } - F32 bubble = llclamp(s_tracked_bubble, 0.f, draw_distance - 0.001f); - F32 ramp_range = llmax(draw_distance - bubble, 0.001f); - U32 face_count = 0; U32 max_faces_to_check = 1024; - // Pick the least-aggressive channel across all uses, so a texture - // used as both diffuse and normal isn't penalized by its harshest - // role. -1 sentinel keeps emissive-only textures (W=3) from being - // clobbered by a smaller init value. - S32 priority_channel = -1; - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - if (imagep->getNumFaces(i) > 0) - { - S32 mapped = sChannelToPriority[i]; - priority_channel = (priority_channel < 0) ? mapped : llmin(priority_channel, mapped); - } - } - if (priority_channel < 0) - { - priority_channel = 1; // no faces - default to diffuse - } - imagep->mPriorityChannel = (S8)priority_channel; - // get adjusted bias based on image resolution LLImageGL* img = imagep->getGLTexture(); F32 max_discard = F32(img ? img->getMaxDiscardLevel() : MAX_DISCARD_LEVEL); @@ -1023,15 +948,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag on_screen |= face->mInFrustum; - F32 dist_above_bubble = llmax(face->mDistanceToCamera - bubble, 0.f); - F32 dist_factor = llclampf(dist_above_bubble / ramp_range); - min_distance_factor = llmin(min_distance_factor, dist_factor); - - if (face->mAvatar && face->mAvatar == gAgentAvatarp) - { - on_agent_avatar = true; - } - // Scale desired texture resolution higher or lower depending on texture scale // // Minimum usage examples: a 1024x1024 texture with aplhabet (texture atlas), @@ -1047,10 +963,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag min_scale = llclamp(min_scale * min_scale, texture_scale_min(), texture_scale_max()); vsize /= min_scale; - // Raw screen-space coverage - taken before the bias / - // camera-boost mutations below so the size signal is clean. - max_on_screen_size = llmax(max_on_screen_size, vsize); - // apply bias to offscreen faces all the time, but only to onscreen faces when bias is large // use mImportanceToCamera to make bias switch a bit more gradual if (!face->mInFrustum || LLViewerTexture::sDesiredDiscardBias > 1.9f + face->mImportanceToCamera / 2.f) @@ -1058,6 +970,13 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag vsize /= bias; } + // boost resolution of textures that are important to the camera + if (face->mInFrustum) + { + static LLCachedControl texture_camera_boost(gSavedSettings, "TextureCameraBoost", 8.f); + vsize *= llmax(face->mImportanceToCamera*texture_camera_boost, 1.f); + } + max_vsize = llmax(max_vsize, vsize); // addTextureStats limits size to sMaxVirtualSize @@ -1076,95 +995,57 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag } } - bool used_face_fast_path = (face_count > max_faces_to_check); - if (used_face_fast_path) + if (face_count > max_faces_to_check) { // this texture is used in so many places we should just boost it and not bother checking its vsize // this is especially important because the above is not time sliced and can hit multiple ms for a single texture max_vsize = MAX_IMAGE_AREA; } - imagep->addTextureStats(max_vsize); + if (imagep->getType() == LLViewerTexture::LOD_TEXTURE && imagep->getBoostLevel() == LLViewerTexture::BOOST_NONE) + { // conditionally reset max virtual size for unboosted LOD_TEXTURES + // this is an alternative to decaying mMaxVirtualSize over time + // that keeps textures from continously downrezzing and uprezzing in the background - // Publish per-texture signals for processTextureStats. Closest face - // wins for distance (min); biggest face wins for size (max). Default - // distance=1, size=0 maps to "deepest discard wanted" - never- - // measured textures stay coarse until distance/size evidence arrives. - if (used_face_fast_path) - { - // Fast path saw only a prefix of faces - force best-quality - // sentinels to match the MAX_IMAGE_AREA vsize boost above. - imagep->mMinDistanceFactor = 0.f; - imagep->mMaxOnScreenSize = (F32)MAX_IMAGE_AREA; - } - else if (face_count == 0 && imagep->getBoostLevel() == LLGLTexture::BOOST_TERRAIN) - { - // Terrain detail textures don't register faces with the texture - // (LLVOSurfacePatch addFace(NULL)). Drive distance from the LOD - // system; floor at a small nonzero value so pressure has - // something to bite into (pow(0, p) = 0). - static LLCachedControl terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); - static LLCachedControl terrain_coverage(gSavedSettings, "TextureTerrainCoverageFraction", 0.99f); - F32 nearest = LLSurface::sNearestVisiblePatchDistance; - F32 nearest_above_bubble = (nearest < FLT_MAX) ? llmax(nearest - bubble, 0.f) : ramp_range; - F32 dist = llclampf(nearest_above_bubble / ramp_range); - imagep->mMinDistanceFactor = llmax(dist, llclampf((F32)terrain_distance_floor)); - imagep->mMaxOnScreenSize = LLViewerTexture::sWindowPixelArea * llclampf((F32)terrain_coverage); - } - else - { - imagep->mMinDistanceFactor = min_distance_factor; - imagep->mMaxOnScreenSize = max_on_screen_size; - } - imagep->mOnAgentAvatar = on_agent_avatar; - - // Bind-staleness. Avatar bakes exempt (cloud-bug protection). - // Per-interval increment is 1/max_discard so saturation time is - // interval * max_discard seconds regardless of texture size. - // Never-bound textures defer to distance/size or initial fetch - // could never start. - if (LLViewerFetchedTexture::isAgentAvatarBoost(imagep->getBoostLevel())) - { - imagep->mStalenessFactor = 0.f; - } - else if (LLImageGL* gli = imagep->getGLTexture()) - { - static LLCachedControl bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); - static LLCachedControl staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); - F32 grace = llmax((F32)bind_decay_seconds, 0.f); - F32 interval = llmax((F32)staleness_interval, 0.0001f); - - // Clock starts at whichever is later: the last real bind or - // the GL-create time. The latter is the fallback for textures - // decoded into GL but never actually rendered - without it, - // mLastBindTime stays 0 forever and staleness can't evict. - F32 clock_time = llmax(gli->mLastBindTime, gli->mGLCreateTime); - bool has_clock = (clock_time > 0.f); - F32 time_since = has_clock ? (LLImageGL::sLastFrameTime - clock_time) : 0.f; - - if (!has_clock || time_since <= grace) + if (LLViewerTexture::sDesiredDiscardBias > BIAS_TRS_OUT_OF_SCREEN || + (!on_screen && LLViewerTexture::sDesiredDiscardBias > BIAS_TRS_ON_SCREEN)) { - imagep->mStalenessFactor = 0.f; + imagep->mMaxVirtualSize = 0.f; } - else + } + + 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) { - S32 full_w = imagep->getFullWidth(); - S32 full_h = imagep->getFullHeight(); - S32 max_discard = (full_w > 0 && full_h > 0) - ? LLImageGL::dimDerivedMaxDiscard(full_w, full_h) - : (S32)gli->getMaxDiscardLevel(); - if (max_discard > 0) + if (imagep->getNumFaces(i) > 0) { - F32 steps = (time_since - grace) / interval; - F32 step_size = 1.f / (F32)max_discard; - imagep->mStalenessFactor = llclampf(steps * step_size); - } - else - { - imagep->mStalenessFactor = 0.f; + priority_channel = llmin(priority_channel, render_to_priority[i]); } } - } + static LLCachedControl 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 @@ -1273,7 +1154,8 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) imagep->postCreateTexture(); imagep->mCreatePending = false; - if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel()) + if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel() && + (imagep->getDesiredDiscardLevel() <= MAX_DISCARD_LEVEL)) { // NOTE: this may happen if the desired discard reduces while a decode is in progress and does not // necessarily indicate a problem, but if log occurrences excede that of dsiplay_stats: FPS, @@ -1298,16 +1180,13 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) gCopyProgram.bind(); gPipeline.mScreenTriangleVB->setBuffer(); - // give time to downscaling first - if mDownScaleQueue is not empty, we're running out of memory and need + // give time to downscaling first -- if mDownScaleQueue is not empty, we're running out of memory and need // to free up memory by discarding off screen textures quickly - // Drain rate scales with both pending creates and the downscale - // queue itself. Without the queue term, a backlog of evictions - // could only drain 5/frame regardless of size, and the system - // can't actually free VRAM fast enough under pressure. - S32 min_count = (S32)mCreateTextureList.size() / 20 - + (S32)mDownScaleQueue.size() / 5 - + 5; + // do at least 5 and make sure we don't get too far behind even if it violates + // the time limit. If we don't downscale quickly the viewer will hit swap and may + // freeze. + S32 min_count = (S32)mCreateTextureList.size() / 20 + 5; create_timer.reset(); while (!mDownScaleQueue.empty()) @@ -1409,15 +1288,12 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); - // Scale up the per-frame update window under VRAM pressure so eviction - // candidates get re-evaluated quickly. Both the legacy bias and the - // new pressure multiplier widen the window. - F32 pressure_scale = llmax(LLViewerTexture::sDesiredDiscardBias, - LLViewerTexture::sMemoryPressureMultiplier); - if (pressure_scale > 1.f + if (LLViewerTexture::sDesiredDiscardBias > 1.f && LLViewerTexture::sBiasTexturesUpdated < (U32)mUUIDMap.size()) { - update_count = (S32)(update_count * pressure_scale); + // We are over memory target. Bias affects discard rates, so update + // existing textures agresively to free memory faster. + update_count = (S32)(update_count * LLViewerTexture::sDesiredDiscardBias); // This isn't particularly precise and can overshoot, but it doesn't need // to be, just making sure it did a full circle and doesn't get stuck updating diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index dbed8b5c2f..7c7112f4cf 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -30,7 +30,6 @@ #include "lluuid.h" //#include "message.h" #include "llgl.h" -#include "llrender.h" #include "llviewertexture.h" #include "llui.h" #include @@ -93,10 +92,6 @@ class LLViewerTextureList friend class LLLocalBitmap; public: - // eTexIndex -> TextureChannel* index (0=Normal, 1=BaseColor, - // 2=Specular, 3=Emissive). Single source of truth. - static const S32 sChannelToPriority[LLRender::NUM_TEXTURE_CHANNELS]; - static bool createUploadFile(LLPointer raw_image, const std::string& out_filename, const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, @@ -244,10 +239,6 @@ private: bool mInitialized ; LLFrameTimer mForceDecodeTimer; -public: - // Current close-camera bubble in meters (frame-coherent, slow-tracked). - static F32 sCurrentBubbleMeters; - private: static S32 sNumImages; static void (*sUUIDCallback)(void**, const LLUUID &); diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp index eb0261b5e5..3441e25c6a 100644 --- a/indra/newview/llvlcomposition.cpp +++ b/indra/newview/llvlcomposition.cpp @@ -173,10 +173,7 @@ LLPointer fetch_terrain_texture(const LLUUID& id) return nullptr; } - // LOD_TEXTURE so streaming math runs (the base-class processTextureStats - // pins mDesiredDiscardLevel at 0). - LLPointer tex = LLViewerTextureManager::getFetchedTexture( - id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + LLPointer tex = LLViewerTextureManager::getFetchedTexture(id); return tex; } @@ -346,9 +343,18 @@ bool LLTerrainMaterials::makeTextureReady(LLPointer& tex { if (boost) { - // Quality is driven by the streaming math via synthetic signals - // for BOOST_TERRAIN textures in updateImageDecodePriority. boost_minimap_texture(tex, BASE_SIZE*BASE_SIZE); + + S32 width = tex->getFullWidth(); + S32 height = tex->getFullHeight(); + S32 min_dim = llmin(width, height); + S32 ddiscard = 0; + while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) + { + ddiscard++; + min_dim /= 2; + } + tex->setMinDiscardLevel(ddiscard); } return false; } diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml index 3043b17589..99dca7b395 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml @@ -127,35 +127,31 @@ top_delta="16" left="30" width="160" - name="TextureQualityLabel" + name="MaxTextureResolutionLabel" text_readonly_color="LabelDisabledColor"> - Texture quality: + Maximum LOD resolution: - + label="512" + name="512" + value="512"/> + label="1024" + name="1024" + value="1024"/> + label="2048" + name="2048" + value="2048"/> Date: Fri, 22 May 2026 13:12:58 -0400 Subject: A few OpenGL state fixes provided by Rye from the Alchemy Viewer. --- indra/llrender/llimagegl.cpp | 2 +- indra/llrender/llrender.cpp | 15 ++++++++++++++- indra/newview/lldrawpoolwater.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) (limited to 'indra/llrender') diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 4a3d32c7ff..5292ef7f6f 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1858,7 +1858,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 57be8570af..65525c1449 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -197,6 +197,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"); @@ -1718,7 +1719,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()); @@ -1733,6 +1743,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/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); -- cgit v1.3