diff options
| -rw-r--r-- | indra/llrender/llimagegl.cpp | 84 | ||||
| -rw-r--r-- | indra/llrender/llimagegl.h | 16 | ||||
| -rw-r--r-- | indra/llrender/llrender.cpp | 14 | ||||
| -rw-r--r-- | indra/newview/app_settings/settings.xml | 144 | ||||
| -rw-r--r-- | indra/newview/llface.cpp | 30 | ||||
| -rw-r--r-- | indra/newview/llface.h | 7 | ||||
| -rw-r--r-- | indra/newview/llsurface.cpp | 14 | ||||
| -rw-r--r-- | indra/newview/llsurface.h | 6 | ||||
| -rw-r--r-- | indra/newview/lltexturefetch.cpp | 12 | ||||
| -rw-r--r-- | indra/newview/lltexturefetch.h | 3 | ||||
| -rw-r--r-- | indra/newview/llviewercontrol.cpp | 21 | ||||
| -rw-r--r-- | indra/newview/llviewertexture.cpp | 276 | ||||
| -rw-r--r-- | indra/newview/llviewertexture.h | 50 | ||||
| -rw-r--r-- | indra/newview/llviewertexturelist.cpp | 174 | ||||
| -rw-r--r-- | indra/newview/llviewertexturelist.h | 5 | ||||
| -rw-r--r-- | indra/newview/llvlcomposition.cpp | 18 |
16 files changed, 701 insertions, 173 deletions
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<U32, U64> 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 @@ <key>RenderReflectionDetail</key> <map> <key>Comment</key> - <string>DEPRECATED -- use RenderTransparentWater and RenderReflectionProbeDetail -- Detail of reflection render pass.</string> + <string>DEPRECATED - use RenderTransparentWater and RenderReflectionProbeDetail - Detail of reflection render pass.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -11819,30 +11819,152 @@ <key>TextureChannelPriority</key> <map> <key>Comment</key> - <string>Per-channel texture streaming aggressiveness. X=normals, Y=diffuse, Z=specular/metallic, W=emissive. 1.0=baseline, higher=more aggressive downrez.</string> + <string>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.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> <string>Vector4</string> <key>Value</key> <array> - <real>5</real> - <real>7.5</real> - <real>20</real> - <real>7.5</real> + <real>1.0</real> + <real>0.75</real> + <real>0.5</real> + <real>0.75</real> </array> </map> - <key>TextureCameraBoost</key> + <key>TextureMaxDiscardOverride</key> <map> <key>Comment</key> - <string>Amount to boost resolution of textures that are important to the camera.</string> + <string>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.</string> <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> <integer>0</integer> + </map> + <key>TextureMemoryPressureRampRate</key> + <map> + <key>Comment</key> + <string>Feedback rate (per second) for the VRAM-pressure factor (0..1). Higher = faster convergence on the budget; lower = gentler.</string> + <key>Persist</key> + <integer>1</integer> <key>Type</key> <string>F32</string> <key>Value</key> - <real>8.0</real> + <real>3.0</real> + </map> + <key>TextureMemoryPressureBackoffStart</key> + <map> + <key>Comment</key> + <string>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).</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.85</real> + </map> + <key>TextureTerrainDistanceFloor</key> + <map> + <key>Comment</key> + <string>Minimum distance factor for BOOST_TERRAIN textures. Keeps combined > 0 so VRAM pressure can evict terrain. Lower = higher idle quality, less pressure response.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.01</real> + </map> + <key>TextureTerrainCoverageFraction</key> + <map> + <key>Comment</key> + <string>Synthetic on-screen coverage fraction for BOOST_TERRAIN textures (no faces are registered). Higher = higher idle quality, less pressure response.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.99</real> + </map> + <key>TextureAgentAvatarBoost</key> + <map> + <key>Comment</key> + <string>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.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.5</real> + </map> + <key>TextureBackgroundFactorRatePerSec</key> + <map> + <key>Comment</key> + <string>Per-second ramp rate of the background-window discard floor (0..1). Snaps to 0 in foreground. Default 0.011 ~ 90s to saturate.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.011</real> + </map> + <key>TextureDistanceDiscardPower</key> + <map> + <key>Comment</key> + <string>Exponent on the distance factor (face_distance / draw_distance). 1.0 = linear; lower = textures hit max discard sooner with distance. Default 0.5 = sqrt.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.5</real> </map> + <key>TextureSizeDiscardPower</key> + <map> + <key>Comment</key> + <string>Exponent on the on-screen size factor (1 - coverage). 1.0 = linear; lower = small-on-screen textures attenuate sooner.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>1.0</real> + </map> + <key>TextureStalenessIntervalSeconds</key> + <map> + <key>Comment</key> + <string>Seconds per staleness step. Per-interval increment is 1/max_discard so any texture saturates after interval * max_discard seconds idle.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>5.0</real> + </map> + <key>TextureBindDecaySeconds</key> + <map> + <key>Comment</key> + <string>Grace seconds after a bind during which the staleness factor stays at 0. Prevents intermittently-bound textures from ramping. 0 disables the grace period.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>5.0</real> + </map> + <key>TextureFetchPressureScale</key> + <map> + <key>Comment</key> + <string>Pending-fetch divisor for the bias floor: bias floor = 1 + clamp(pending / scale, 0, 3). Lower = bias rises sooner under load floods.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>1000.0</real> + </map> + <key>TextureDecodeDisabled</key> <map> <key>Comment</key> @@ -16383,7 +16505,7 @@ <key>EmulateCoreCount</key> <map> <key>Comment</key> - <string>For debugging -- number of cores to restrict the main process to, or 0 for no limit. Requires restart.</string> + <string>For debugging - number of cores to restrict the main process to, or 0 for no limit. Requires restart.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -16537,7 +16659,7 @@ <key>MultiModeDoubleClickFolder</key> <map> <key>Comment</key> - <string>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)</string> + <string>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)</string> <key>Persist</key> <integer>1</integer> <key>Type</key> 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; i<mNumberOfPatches; i++) { @@ -593,6 +602,7 @@ void LLSurface::updatePatchVisibilities(LLAgent &agent) { mVisiblePatchCount++; patchp->updateCameraDistanceRegion(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<LLImageRaw>& raw, LLPointer<LLImageRaw>& 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<LLImageRaw>& raw, LLPointer<LLImageRaw>& 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<F32> 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<F32> 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<F32> 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<F32> 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<F32> 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<F32> 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<F32> 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<LLVector4> 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<F32> 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<S32> 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<F32> 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<F32> terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); + static LLCachedControl<F32> 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; - 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 + // 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 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) - }; + static LLCachedControl<F32> bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); + static LLCachedControl<F32> staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); + F32 grace = llmax((F32)bind_decay_seconds, 0.f); + F32 interval = llmax((F32)staleness_interval, 0.0001f); - S32 priority_channel = 1; // default to diffuse - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + bool ever_bound = (gli->mLastBindTime > 0.f); + F32 time_since_bind = ever_bound ? (LLImageGL::sLastFrameTime - gli->mLastBindTime) : 0.f; + + 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<LLVector4> channel_priority(gSavedSettings, "TextureChannelPriority", - LLVector4(10.0f, 20.0f, 40.0f, 20.0f)); - F32 factor = llmax(channel_priority().mV[priority_channel], 0.1f); - if (factor != 1.0f) + 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 <list> @@ -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<LLImageRaw> 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<LLViewerFetchedTexture> fetch_terrain_texture(const LLUUID& id) return nullptr; } - LLPointer<LLViewerFetchedTexture> tex = LLViewerTextureManager::getFetchedTexture(id); + // LOD_TEXTURE so streaming math runs (the base-class processTextureStats + // pins mDesiredDiscardLevel at 0). + LLPointer<LLViewerFetchedTexture> tex = LLViewerTextureManager::getFetchedTexture( + id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); return tex; } @@ -343,18 +346,9 @@ bool LLTerrainMaterials::makeTextureReady(LLPointer<LLViewerFetchedTexture>& 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; } |
