diff options
| author | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2026-07-06 02:34:15 -0400 |
|---|---|---|
| committer | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2026-07-06 02:34:15 -0400 |
| commit | b55a7038511a6ed3d97e17b13c9d83bc2017ba61 (patch) | |
| tree | 1ef43d267a055f07b177b53d5a4fe23a00381822 /indra/newview/llviewertexturelist.cpp | |
| parent | cd62a49879374c84b66e1ee963e161c3e6109977 (diff) | |
Move over to a pixels to texels ratio to drive texture resolution.
Diffstat (limited to 'indra/newview/llviewertexturelist.cpp')
| -rw-r--r-- | indra/newview/llviewertexturelist.cpp | 290 |
1 files changed, 59 insertions, 231 deletions
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<F32> texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.0095f); static LLCachedControl<F32> 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<F32> close_bubble(gSavedSettings, "TextureCloseBubbleMeters", 5.f); - static LLCachedControl<F32> close_bubble_min(gSavedSettings, "TextureCloseBubbleMinMeters", 0.1f); - static LLCachedControl<F32> bubble_shrink_threshold(gSavedSettings, "TextureCloseBubbleShrinkThreshold", 0.8f); - static LLCachedControl<F32> 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) - { - // 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 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) { - // Terrain detail textures don't register faces with the texture - // (LLVOSurfacePatch addFace(NULL)). Drive distance from the LOD - // system; floor at a small nonzero value so pressure has - // something to bite into (pow(0, p) = 0). - static LLCachedControl<F32> terrain_distance_floor(gSavedSettings, "TextureTerrainDistanceFloor", 0.01f); - static LLCachedControl<F32> terrain_coverage(gSavedSettings, "TextureTerrainCoverageFraction", 0.99f); F32 nearest = LLSurface::sNearestVisiblePatchDistance; - F32 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); + 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 - { - imagep->mMinDistanceFactor = min_distance_factor; - imagep->mMaxOnScreenSize = max_on_screen_size; - } - imagep->mOnAgentAvatar = on_agent_avatar; - - // Bind-staleness. Avatar bakes exempt (cloud-bug protection). - // Per-interval increment is 1/max_discard so saturation time is - // interval * max_discard seconds regardless of texture size. - // Never-bound textures defer to distance/size or initial fetch - // could never start. - if (LLViewerFetchedTexture::isAgentAvatarBoost(imagep->getBoostLevel())) - { - imagep->mStalenessFactor = 0.f; - } - else if (LLImageGL* gli = imagep->getGLTexture()) - { - static LLCachedControl<F32> bind_decay_seconds(gSavedSettings, "TextureBindDecaySeconds", 5.f); - static LLCachedControl<F32> staleness_interval(gSavedSettings, "TextureStalenessIntervalSeconds", 5.f); - F32 grace = llmax((F32)bind_decay_seconds, 0.f); - F32 interval = llmax((F32)staleness_interval, 0.0001f); - // 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; + imagep->addTextureStats(max_coverage); - 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; - } - } + // Publish per-bucket coverage for LLViewerLODTexture::computeDesiredDiscard. + for (S32 b = 0; b < 4; ++b) + { + 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<F32> 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 |
