From c4bc64c7515c19bfb1c4d619a8e5d235954ba8a4 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 19 May 2026 13:32:58 -0400 Subject: Rework VRAM controller and bubble interaction — gate iteration on pressure, halve last-ditch floor inside bubble. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- indra/newview/app_settings/settings.xml | 11 ----- indra/newview/llviewertexture.cpp | 79 +++++++++++++++++---------------- indra/newview/llviewertexture.h | 10 +++-- indra/newview/llviewertexturelist.cpp | 31 +++++-------- 4 files changed, 59 insertions(+), 72 deletions(-) (limited to 'indra') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0c4df7a805..d33082b449 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11871,17 +11871,6 @@ Value 0 - TextureMemoryPressureRampRate - - Comment - 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 - 1.0 - TextureMemoryPressureBackoffStart Comment diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index e55e4016ec..e84481bade 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -90,6 +90,14 @@ 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 @@ -533,12 +541,9 @@ void LLViewerTexture::updateClass() F32 over_pct = (used - target) / 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. + // 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); @@ -546,24 +551,34 @@ void LLViewerTexture::updateClass() static LLCachedControl smoothing_rate(gSavedSettings, "TextureMemoryPressureSmoothingRate", 4.f); F32 backoff_target = 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 + || used > backoff_target * 0.5f; - // 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; + 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 desired = imagep->getDesiredDiscardLevel(); S32 current = imagep->getDiscardLevel(); - if (desired < 0 || current < 0 || desired == current) continue; + if (current < 0 || desired == current) continue; S32 wd = llmax(1, fw >> desired); S32 hd = llmax(1, fh >> desired); @@ -580,36 +595,26 @@ void LLViewerTexture::updateClass() } } - // Match the unit conversion used to produce `used` (texture_bytes_alloc - // divided by 1024/512). 1024*512 = 524288. + // 1024 * 512 = 524288: matches the unit reduction at line 513. 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; + // ~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); - // 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. + 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); - F32 progress = llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); bool mult_saturated = progress >= llclampf((F32)ld_engage); if (mult_saturated && predicted_over > 1.f) { @@ -622,18 +627,16 @@ void LLViewerTexture::updateClass() 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. + // 1 Hz pressure log. static LLFrameTimer s_pressure_log_timer; if (s_pressure_log_timer.getElapsedTimeF32() > 1.f) { s_pressure_log_timer.reset(); - F32 mult_progress = llclampf((sMemoryPressureMultiplier - 1.f) / (cap - 1.f)); + F32 over = used / llmax(backoff_target, 1.f); LL_INFOS("TextureStream") << "pressure" << " mult=" << sMemoryPressureMultiplier << " target_mult=" << target_mult - << " progress=" << mult_progress + << " progress=" << progress << " used=" << used << " predicted=" << predicted_used << " target=" << target @@ -3360,14 +3363,13 @@ void LLViewerLODTexture::processTextureStats() } 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. + // 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) { @@ -3422,11 +3424,10 @@ bool LLViewerLODTexture::scaleDown() // 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) { - // 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). diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index d40d3bc5ee..991bb638a1 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -251,10 +251,14 @@ public: // 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. + // 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 ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index ee21d6a4dd..4d09eff74e 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -934,44 +934,37 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag bool on_agent_avatar = false; F32 draw_distance = llmax(gAgentCamera.mDrawDistance, 0.001f); - // Close-camera bubble: distances under bubble_meters resolve to - // dist_factor = 0 (no discard contribution). The ramp from 0 -> 1 - // spans (bubble, draw_distance] rather than (0, draw_distance]. + // 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 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. + // 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); - sCurrentBubbleMeters = bubble; U32 face_count = 0; U32 max_faces_to_check = 1024; -- cgit v1.3