summaryrefslogtreecommitdiff
path: root/indra/newview/llviewertexture.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llviewertexture.cpp')
-rw-r--r--indra/newview/llviewertexture.cpp112
1 files changed, 84 insertions, 28 deletions
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index d50b9ea0a9..243fd60f7c 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<U32> 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<U32> 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<F32> 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<U32> 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);