From 0a1690418f1ce6b66635bafe3f8dfecda69c9f8c Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 27 May 2026 20:37:50 -0400 Subject: Some additional texture streaming tweaks - more intelligently discard rather than assuming 1x1 is where we need to be at 1 --- indra/newview/llviewercontrol.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra/newview/llviewercontrol.cpp') diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index a4a7f1fd20..bb868b950b 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -124,25 +124,30 @@ static bool handleRenderTextureQualityChanged(const LLSD& newvalue) F32 ch_specular = 0.5f; F32 ch_emissive = 0.75f; F32 distance_power = 0.5f; + F32 screen_oversample = 1.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; + screen_oversample = 0.75f; break; case 1: // Medium ch_normal = 0.75f; ch_basecolor = 0.75f; ch_specular = 0.3f; ch_emissive = 0.75f; distance_power = 0.25f; + screen_oversample = 1.0f; break; case 2: // High // channel defaults above distance_power = 0.35f; + screen_oversample = 1.5f; 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; + screen_oversample = 2.0f; break; } gSavedSettings.setU32("RenderMaxTextureResolution", max_res); @@ -151,6 +156,7 @@ static bool handleRenderTextureQualityChanged(const LLSD& newvalue) gSavedSettings.setF32("TextureChannelSpecular", ch_specular); gSavedSettings.setF32("TextureChannelEmissive", ch_emissive); gSavedSettings.setF32("TextureDistanceDiscardPower", distance_power); + gSavedSettings.setF32("TextureScreenSizeOversample", screen_oversample); return true; } -- cgit v1.3 From c299c8038e73e541cafe3c8df135752cb48b903c Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 29 May 2026 05:29:27 -0400 Subject: Add watermark driven discards. Does a much better job of staying within the memory budget. --- indra/llprimitive/lltextureentry.cpp | 15 +- indra/llprimitive/lltextureentry.h | 14 + indra/newview/app_settings/settings.xml | 120 +++---- indra/newview/lltextureview.cpp | 6 +- indra/newview/llviewercontrol.cpp | 113 ++++--- indra/newview/llviewertexture.cpp | 550 +++++++++++++++----------------- indra/newview/llviewertexture.h | 43 ++- indra/newview/llviewertexturelist.cpp | 24 +- 8 files changed, 465 insertions(+), 420 deletions(-) (limited to 'indra/newview/llviewercontrol.cpp') diff --git a/indra/llprimitive/lltextureentry.cpp b/indra/llprimitive/lltextureentry.cpp index ac482ffbf9..5296e86a69 100644 --- a/indra/llprimitive/lltextureentry.cpp +++ b/indra/llprimitive/lltextureentry.cpp @@ -356,6 +356,7 @@ S32 LLTextureEntry::setScale(F32 s, F32 t) { mScaleS = s; mScaleT = t; + mMinScaleSq = -1.f; // invalidate cache for getMinScaleSq() retval = TEM_CHANGE_TEXTURE; } @@ -368,6 +369,7 @@ S32 LLTextureEntry::setScaleS(F32 s) if (mScaleS != s) { mScaleS = s; + mMinScaleSq = -1.f; // invalidate cache for getMinScaleSq() retval = TEM_CHANGE_TEXTURE; } return retval; @@ -379,11 +381,22 @@ S32 LLTextureEntry::setScaleT(F32 t) if (mScaleT != t) { mScaleT = t; + mMinScaleSq = -1.f; // invalidate cache for getMinScaleSq() retval = TEM_CHANGE_TEXTURE; } return retval; } +F32 LLTextureEntry::getMinScaleSq() const +{ + if (mMinScaleSq < 0.f) + { + F32 m = llmin(fabsf(mScaleS), fabsf(mScaleT)); + mMinScaleSq = m * m; + } + return mMinScaleSq; +} + S32 LLTextureEntry::setColor(const LLColor4 &color) { if (mColor != color) @@ -600,7 +613,7 @@ LLGLTFMaterial* LLTextureEntry::getGLTFRenderMaterial() const return mGLTFRenderMaterial; } - llassert(getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); + //llassert(getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); return getGLTFMaterial(); } diff --git a/indra/llprimitive/lltextureentry.h b/indra/llprimitive/lltextureentry.h index 78c61b4d65..765a5f5fdc 100644 --- a/indra/llprimitive/lltextureentry.h +++ b/indra/llprimitive/lltextureentry.h @@ -143,6 +143,13 @@ public: F32 getScaleS() const { return mScaleS; } F32 getScaleT() const { return mScaleT; } + // Cached min(|mScaleS|, |mScaleT|)^2, lazily computed and invalidated + // in setScale/setScaleS/setScaleT. Used by the texture streaming face + // loop (updateImageDecodePriority) to avoid the per-face per-frame + // sqrt/abs/min/multiply chain. Returns the raw (unclamped) value; + // callers still apply TextureScaleMin/MaxAreaFactor clamps. + F32 getMinScaleSq() const; + void getOffset(F32 *s, F32 *t) const { *s = mOffsetS; *t = mOffsetT; } F32 getOffsetS() const { return mOffsetS; } F32 getOffsetT() const { return mOffsetT; } @@ -218,6 +225,13 @@ public: F32 mOffsetT; // S, T offset F32 mRotation; // anti-clockwise rotation in rad about the bottom left corner +private: + // Cache for getMinScaleSq(). -1.f sentinel = stale. Invalidated by + // setScale/setScaleS/setScaleT. Mutable so getMinScaleSq() can fill it + // on first read without breaking const correctness for read-only callers. + mutable F32 mMinScaleSq = -1.f; +public: + static const LLTextureEntry null; // LLSD key defines diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index e4f5f0b6d4..6e25d2e835 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, the four TextureChannel* exponents, TextureDistanceDiscardPower, and TextureScreenSizeOversample. + 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. Persist 1 Type @@ -11871,49 +11871,49 @@ Value 20.0 - TextureChannelNormal + TextureChannelOffsetNormal Comment - Per-channel discard exponent for normal maps. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. + 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 - F32 + S32 Value - 1.0 + 0 - TextureChannelBaseColor + TextureChannelOffsetBaseColor Comment - Per-channel discard exponent for base color / diffuse. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. + 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 - F32 + S32 Value - 0.75 + 0 - TextureChannelSpecular + TextureChannelOffsetSpecular Comment - Per-channel discard exponent for specular / metallic-roughness. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. + 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 - F32 + S32 Value - 0.5 + 1 - TextureChannelEmissive + TextureChannelOffsetEmissive Comment - Per-channel discard exponent for emissive. 1.0 = baseline; lower = more aggressive. Driven by the RenderTextureQuality preset. + Additive discard offset for emissive (integer mip levels). Higher = more aggressive. Driven by the RenderTextureQuality preset. Persist 1 Type - F32 + S32 Value - 0.75 + 1 TextureMaxDiscardOverride @@ -11926,104 +11926,104 @@ Value 0 - TextureMemoryHighWaterMark + TextureWatermarkHigh 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. + 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.8 + 0.90 - TextureMemoryPressureBackoffStart + TextureWatermarkLow 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). + 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.85 + 0.70 - TextureMemoryPressureMaxMultiplier + TextureDiscardBiasMax 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. + 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 - 64.0 + 12.0 - TextureLastDitchEngageProgress + TextureDiscardBiasRampRate 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. + 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 - 0.95 + 2.0 - TextureLastDitchRampRate + TextureDiscardBiasDecayRate 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. + 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 - 0.5 + 1.0 - TextureLastDitchDecayRate + TexturePressureDiscardScale Comment - Rate (discard levels/sec) at which sLastDitchMinDiscard decays back to 0 when prediction is under budget. + 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 - 0.5 + 1.0 - TextureLastDitchMinDiscardMax + TextureMinCapPressureRelaxScale 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. + 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. Persist 1 Type F32 Value - 13.0 + 1.0 - TextureMemoryPressurePredictionGain + TextureUpdateCountPressureMaxMultiplier Comment - Power exponent mapping predicted-over-budget ratio to target multiplier. target_mult = pred_over^gain. Higher gain saturates faster. + 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 - 10.0 + 6.0 - TextureMemoryPressureSmoothingRate + TextureFetchBiasTrackRate 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. + 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. Persist 1 Type F32 Value - 4.0 + 1.0 TextureTerrainDistanceFloor @@ -12047,16 +12047,16 @@ Value 0.99 - TextureAgentAvatarBoost + TextureAgentAvatarOversampleMultiplier 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. + 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 - 0.5 + 2.0 TextureBackgroundFactorRatePerSec @@ -12095,7 +12095,7 @@ 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. + 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 @@ -12125,28 +12125,6 @@ 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 - TextureScreenSizeOversample Comment @@ -12161,7 +12139,7 @@ TextureScreenSizeOversampleUnderPressure Comment - Floor that TextureScreenSizeOversample collapses toward as the pressure multiplier walks up. Pinned to this value above TextureMemoryHighWaterMark. Lower = allow distant textures to drop below their on-screen contribution under pressure. + 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. Persist 1 Type diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 4534db958f..1cc26896b1 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -572,13 +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) Bubble: %.1fm PressMult: %.1fx LDMin: %.1f", + text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB) Bubble: %.1fm DiscardBias: %.2f Progress: %.2f", 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); + LLViewerTexture::sDiscardBias, + LLViewerTexture::getMemoryPressureProgress()); 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 bb868b950b..19c28b12f1 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -112,51 +112,80 @@ 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 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; + }; + + // Tier values: Low (2-4GB), Medium (4-8GB), High (8-16GB), Ultra (16+GB). + // Pressure-aggression ladder: Low burns hot, Ultra barely sweats. + // + // 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 + 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 }, + }; +} + 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; - F32 screen_oversample = 1.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; - screen_oversample = 0.75f; - break; - case 1: // Medium - ch_normal = 0.75f; ch_basecolor = 0.75f; ch_specular = 0.3f; ch_emissive = 0.75f; - distance_power = 0.25f; - screen_oversample = 1.0f; - break; - case 2: // High - // channel defaults above - distance_power = 0.35f; - screen_oversample = 1.5f; - 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; - screen_oversample = 2.0f; - 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); - gSavedSettings.setF32("TextureScreenSizeOversample", screen_oversample); + 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); + + 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 fa29ffdf2d..c2cdd54910 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -88,15 +88,14 @@ 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; +F32 LLViewerTexture::sDiscardBias = 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)); + 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. @@ -556,128 +555,56 @@ void LLViewerTexture::updateClass() F32 over_pct = (used - target) / 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 = 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; - - 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 = 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 = used >= 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) + // 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. + { + 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 high_frac = llclamp((F32)wm_high, 0.1f, 1.f); + F32 low_frac = llclamp((F32)wm_low, 0.05f, high_frac); + F32 high = budget * high_frac; + F32 low = budget * low_frac; + F32 cap = llmax((F32)bias_max, 0.f); + F32 dt = (F32)gFrameIntervalSeconds; + + if (used > high) { - target_mult = cap; - sMemoryPressureMultiplier = cap; + sDiscardBias += llmax((F32)bias_ramp, 0.f) * dt; } - else + else if (used < low) { - // ~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 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)); + sDiscardBias -= llmax((F32)bias_decay, 0.f) * dt; } + // In the hysteresis band [low, high]: hold steady. + sDiscardBias = llclamp(sDiscardBias, 0.f, cap); // 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 = used / llmax(backoff_target, 1.f); LL_INFOS("TextureStream") << "pressure" - << " mult=" << sMemoryPressureMultiplier - << " target_mult=" << target_mult - << " progress=" << progress + << " discard_bias=" << sDiscardBias + << " progress=" << getMemoryPressureProgress() << " 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 + << " budget=" << budget + << " high=" << high + << " low=" << low + << " over_pct=" << over_pct + << " legacy_bias=" << sDesiredDiscardBias << " dsq=" << (S32)gTextureList.mDownScaleQueue.size() << LL_ENDL; } @@ -802,17 +729,23 @@ void LLViewerTexture::updateClass() } } - // 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. + // 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()) { - S32 pending = fetcher->getNumRequests(); 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 fetch_pressure = llclamp((F32)pending / scale, 0.f, 3.f); - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + fetch_pressure); + 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) @@ -830,6 +763,20 @@ void LLViewerTexture::updateClass() // 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 @@ -3224,6 +3171,157 @@ 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 +{ + static const F64 log_4 = log(4.0); + + 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); + } + + // 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) + { + static LLCachedControl avatar_over_mult(gSavedSettings, "TextureAgentAvatarOversampleMultiplier", 2.0f); + oversample *= llmax((F32)avatar_over_mult, 1.f); + } + + // 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) + { + effective_screen = llmax(effective_screen, sWindowPixelArea); + } + + F32 visible_texels = effective_screen * oversample * oversample; + visible_texels = llclamp(visible_texels, (F32)MIN_IMAGE_AREA, (F32)mTexelsPerImage); + + S32 base_discard; + if ((F32)mTexelsPerImage <= visible_texels || mMaxOnScreenSize <= 0.f) + { + // 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; + } + else + { + base_discard = (S32)floor(log((F64)mTexelsPerImage / (F64)visible_texels) / log_4); + } + 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) + { + 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]; + } + 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) + { + 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); + } + 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); + + if (!avatar_bake) + { + static LLCachedControl bg_offset(gSavedSettings, "TextureBackgroundDiscardOffset", 2); + F32 bg_norm = sBackgroundFactor; + if ((S32)bg_offset > 0 && dim_max > 0.f) + { + F32 cap = llmax(dim_max - (F32)(S32)bg_offset, 0.f) / dim_max; + bg_norm = llmin(bg_norm, cap); + } + const S32 bg_floor = (S32)floorf(bg_norm * dim_max); + discard = llmax(discard, bg_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); +} + // This is gauranteed to get called periodically for every texture //virtual void LLViewerLODTexture::processTextureStats() @@ -3231,7 +3329,13 @@ void LLViewerLODTexture::processTextureStats() LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; updateVirtualSize(); - bool did_downscale = false; + // 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); static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); @@ -3266,183 +3370,59 @@ void LLViewerLODTexture::processTextureStats() } else { - F32 discard_level = 0.f; + // 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. - // floor(log2(max(w, h))) - both the multiplier on the normalized - // factor and the cap clamp at the bottom of this function. + // 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; - 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); - } + S32 discard = computeBaseDiscard(dim_max_for_image_i); - // 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; + // 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); } - discard_level = floorf(discard_level); - - F32 min_discard = 0.f; + // 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). + S32 min_discard = 0; if (mFullWidth > max_tex_res || mFullHeight > max_tex_res) - min_discard = 1.f; + min_discard = 1; - // 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); - - // Pixel-area discard cap. Don't let mDesiredDiscardLevel push the - // GL resolution below the texture's largest on-screen contribution - // x an oversample factor (sharper when we have memory headroom, - // looser under pressure). Avatar bakes exempt. - if (!isAgentAvatarBoost(mBoostLevel) && mMaxOnScreenSize > 0.f && mTexelsPerImage > 0) - { - static const F64 log_4 = log(4.0); - F32 oversample = pixelCapOversampleForPressure(); - F32 visible_texels = mMaxOnScreenSize * oversample * oversample; - visible_texels = llclamp(visible_texels, (F32)MIN_IMAGE_AREA, (F32)mTexelsPerImage); - if ((F32)mTexelsPerImage > visible_texels) - { - S32 d_pixel_cap = (S32)floor(log((F32)mTexelsPerImage / visible_texels) / log_4); - d_pixel_cap = llclamp(d_pixel_cap, 0, dim_max_for_image_i); - mDesiredDiscardLevel = llmin((S32)mDesiredDiscardLevel, d_pixel_cap); - } - } - - // 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; - } - } - - - // - // 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. - // - + 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); + + // (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.) + + // 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. 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 (!avatar_bake && current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) { - if (current_discard < mDesiredDiscardLevel && !mForceToSaveRawImage) - { // should scale down - scaleDown(); - } + scaleDown(); } mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S32)mLoadedCallbackDesiredDiscardLevel); diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 991bb638a1..390a6caa58 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -247,17 +247,21 @@ public: // 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. + // 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; static S32 sMaxSculptRez ; @@ -599,6 +603,23 @@ 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; }; // diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 4d09eff74e..780dda6ef7 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1043,8 +1043,11 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag 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()); + // 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 / @@ -1410,10 +1413,17 @@ 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); + // 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()) { @@ -1421,7 +1431,7 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) // 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. + // at the scaled rate permanently. LLViewerTexture::sBiasTexturesUpdated += update_count; } update_count = llmin(update_count, (U32) mUUIDMap.size()); -- 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/newview/llviewercontrol.cpp') 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 3e756b57f42e61f6aad3991c7bfa0a581c894244 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 6 Jul 2026 19:59:40 -0400 Subject: Remove some old unused settings, and rename mis-labeled ones to the current nomenclature --- indra/newview/app_settings/settings.xml | 43 ++++----------------------------- indra/newview/featuretable.txt | 1 - indra/newview/featuretable_linux.txt | 2 -- indra/newview/featuretable_mac.txt | 1 - indra/newview/llviewercontrol.cpp | 3 +-- indra/newview/llviewershadermgr.cpp | 6 ----- indra/newview/llviewertexture.cpp | 4 +-- 7 files changed, 8 insertions(+), 52 deletions(-) (limited to 'indra/newview/llviewercontrol.cpp') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c1a2ca2745..864b85034e 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8035,17 +8035,6 @@ Value 0.1 - RenderMaxTextureIndex - - Comment - Maximum texture index to use for indexed texture rendering. - Persist - 1 - Type - U32 - Value - 16 - RenderMaxTextureResolution Comment @@ -8060,7 +8049,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). 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). The pressure water marks (TexturePressureHighWater/LowWater) are constant across tiers. Persist 1 Type @@ -8079,17 +8068,6 @@ Value 0 - RenderDebugTextureBind - - Comment - Enable texture bind performance test. - Persist - 1 - Type - Boolean - Value - 0 - RenderDelayCreation Comment @@ -9406,17 +9384,6 @@ 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 @@ -11937,10 +11904,10 @@ Value 1.0 - TextureWatermarkHigh + TexturePressureHighWater Comment - 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. + 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. Persist 1 Type @@ -11948,10 +11915,10 @@ Value 0.90 - TextureWatermarkLow + TexturePressureLowWater Comment - 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. + 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. Persist 1 Type diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index f05f77c222..72e0f43c0e 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -68,7 +68,6 @@ 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 d8d4f08429..a39c64510f 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -66,7 +66,6 @@ RenderDeferredSSAO 1 1 RenderUseAdvancedAtmospherics 1 0 RenderShadowDetail 1 2 RenderFSAASamples 1 16 -RenderMaxTextureIndex 1 16 RenderMirrors 1 1 // @@ -491,7 +490,6 @@ 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 b11fa28c48..ebfe45d9ea 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -65,7 +65,6 @@ 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/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index e2af54539a..dd2357ae8f 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -138,7 +138,7 @@ namespace // 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 + // 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 @@ -884,7 +884,6 @@ 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/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 27865f7598..255317ce80 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -561,13 +561,7 @@ 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 baf5874507..42cf91b43b 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -547,8 +547,8 @@ void LLViewerTexture::updateClass() // 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 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); -- cgit v1.3