summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llrender/llgl.cpp6
-rw-r--r--indra/llrender/llgl.h1
-rw-r--r--indra/llrender/llimagegl.cpp14
-rw-r--r--indra/newview/app_settings/settings.xml37
-rw-r--r--indra/newview/llface.cpp22
-rw-r--r--indra/newview/llface.h18
-rw-r--r--indra/newview/llviewerdisplay.cpp31
-rw-r--r--indra/newview/llviewertexture.cpp112
-rw-r--r--indra/newview/llviewertexture.h19
-rw-r--r--indra/newview/llviewertexturelist.cpp426
-rw-r--r--indra/newview/llviewertexturelist.h5
11 files changed, 209 insertions, 482 deletions
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 0e59c449db..4584ed1d86 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -2370,12 +2370,6 @@ void clear_glerror()
glGetError();
}
-void drain_glerror()
-{
- // bounded: a lost/reset context can return errors indefinitely
- for (S32 i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {}
-}
-
///////////////////////////////////////////////////////////////
//
// LLGLState
diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h
index 3f9a9de70a..e1ab2a49e6 100644
--- a/indra/llrender/llgl.h
+++ b/indra/llrender/llgl.h
@@ -159,7 +159,6 @@ void log_glerror();
void assert_glerror();
void clear_glerror();
-void drain_glerror(); // pops ALL pending GL error flags (bounded so a lost/reset context that returns errors forever cannot hang the caller); use before an attributable glGetError check.
# define stop_glerror() assert_glerror()
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index b3cd8d2896..c8a23d873e 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -1507,7 +1507,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
// Drain stale GL errors so an OOM detected below belongs to this alloc.
// Otherwise a failed glTexImage2D is swallowed in release while
// alloc_tex_image still counts the bytes, inflating the used-VRAM figure.
- drain_glerror();
+ while (glGetError() != GL_NO_ERROR) {}
const bool use_sub_image = should_stagger_image_set(compress);
if (!use_sub_image)
@@ -1919,8 +1919,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
//-----------------------------------------------------------------------------------------------
GLenum error ;
- S32 error_count = 0 ;
- while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16)
+ while((error = glGetError()) != GL_NO_ERROR)
{
LL_WARNS() << "GL Error happens before reading back texture. Error code: " << error << LL_ENDL ;
}
@@ -1980,8 +1979,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ;
imageraw->deleteData() ;
- error_count = 0 ;
- while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16)
+ while((error = glGetError()) != GL_NO_ERROR)
{
LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ;
}
@@ -2550,10 +2548,8 @@ bool LLImageGL::scaleDown(S32 desired_discard)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
- // Don't let eviction re-arm the GC: the glGenerateMipmap re-bind below would
- // otherwise stamp mLastBindFrame, so the next computeDesiredDiscard treats the
- // just-evicted texture as freshly drawn, un-floors it, and re-fetches - the
- // evict/refetch oscillation.
+ // Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below
+ // would otherwise stamp mLastBindFrame and keep the texture fetch-eligible.
LLImageGLStampBypass no_stamp;
if (mTarget != GL_TEXTURE_2D
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1c2c2d0559..45a7dced5c 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -11992,49 +11992,16 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>TextureLoadTargetFPS</key>
+ <key>TextureFetchVisibilityFrames</key>
<map>
<key>Comment</key>
- <string>Frame rate the viewer is willing to drop to while loading textures. The texture pipeline's per-frame budget is the headroom between this and the actual frame cost, so fast machines load aggressively and slow ones hold their frame rate.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>30.0</real>
- </map>
- <key>TextureLoadBudgetMaxMS</key>
- <map>
- <key>Comment</key>
- <string>Hard cap, in milliseconds per frame, on the adaptive texture-pipeline budget.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>10.0</real>
- </map>
- <key>TextureFetchStepMips</key>
- <map>
- <key>Comment</key>
- <string>Fetch refinement step, in mip levels: a texture whose resident data is coarser than desired by more than this fetches in steps of this size instead of jumping straight to the final resolution, so it sharpens progressively instead of sitting blurry then popping. 0 = jump directly. Boosted/pinned textures always jump.</string>
+ <string>Only fetch a texture if it was drawn within this many rendered frames; out-of-view content isn't fetched. Minimum 1 (0 is clamped up). Boosted/UI textures, avatar bakes, and callback textures are exempt.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>2</integer>
- </map>
- <key>TextureFrustumAllowance</key>
- <map>
- <key>Comment</key>
- <string>Falloff width for out-of-frustum texture resolution, as a fraction of screen size. Content grazing the screen edge keeps full resolution; content this far past the edge reaches the deepest mip, lerped between. Keeps barely-out-of-view textures resident so panning back doesn't refetch them.</string>
- <key>Persist</key>
<integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>0.5</real>
</map>
<key>TextureDecodeDisabled</key>
<map>
diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp
index c96e1bd3b0..e34bea63ef 100644
--- a/indra/newview/llface.cpp
+++ b/indra/newview/llface.cpp
@@ -2297,8 +2297,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
// don't update every frame
if (gFrameTimeSeconds - mLastPixelAreaUpdate < PIXEL_AREA_UPDATE_PERIOD)
{
- cos_angle_to_view_dir = mLastCosAngleToViewDir;
- radius = mLastRadius;
return true;
}
@@ -2370,7 +2368,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
// no rigged extents, zero out bounding box and skip update
mRiggedExtents[0] = mRiggedExtents[1] = LLVector4a(0.f, 0.f, 0.f);
- mInFrustum = false;
return false;
}
@@ -2425,7 +2422,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
if(!camera->AABBInFrustum(center, size))
{
mImportanceToCamera = 0.f ;
- mInFrustum = false;
return false ;
}
if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum
@@ -2454,24 +2450,6 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ;
}
- // On-screen test: does the face's projected disc overlap the screen disc?
- // (Same construction as adjustPartialOverlapPixelArea.) Behind-camera faces
- // get acos(cos) near pi and fall out; the generous screen radius errs toward
- // "on screen" so fetch admission never starves edge content. mFrustumOverflow
- // is how far past the boundary the disc sits, as a fraction of screen size -
- // 0 on screen, 0.1 = 10% of a screen out - and feeds the frustum allowance
- // falloff in computeDesiredDiscard.
- {
- F32 center_px = acosf(llclamp(cos_angle_to_view_dir, -1.f, 1.f)) * LLDrawable::sCurPixelAngle;
- F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw());
- F32 past_edge = center_px - radius - screen_radius;
- mInFrustum = past_edge <= 5.f;
- mFrustumOverflow = llmax(past_edge - 5.f, 0.f) / screen_radius;
- }
-
- mLastCosAngleToViewDir = cos_angle_to_view_dir;
- mLastRadius = radius;
-
return true ;
}
diff --git a/indra/newview/llface.h b/indra/newview/llface.h
index 0b0a17b8c8..71ba3d0f2f 100644
--- a/indra/newview/llface.h
+++ b/indra/newview/llface.h
@@ -269,21 +269,10 @@ public:
// return mSkinInfo->mHash or 0 if mSkinInfo is null
U64 getSkinHash();
- // True if this face's projected bounding disc overlaps the screen - maintained
- // by calcPixelArea() (sticky between its throttled updates). Drives per-texture
- // fetch admission (LLViewerTextureList::updateImageDecodePriority -> mOnScreen).
+ // true if face was recently in the main camera frustum according to LLViewerTextureList updates
bool mInFrustum = false;
- // How far past the screen boundary the projected disc sits, as a fraction of
- // screen size (0 = on screen). Feeds the frustum-allowance falloff so barely
- // out-of-view content keeps its resolution. Maintained with mInFrustum.
- F32 mFrustumOverflow = 0.f;
// value of gFrameCount the last time the face was touched by LLViewerTextureList::updateImageDecodePriority
U32 mLastTextureUpdate = 0;
- // Cached per-channel streaming coverage (repeat-adjusted screen pixels),
- // refreshed at the mLastTextureUpdate cadence and shared by every texture
- // on this face. 0 = degenerate / not yet measured. See
- // update_face_stream_vsize in llviewertexturelist.cpp.
- F32 mStreamVSize[LLRender::NUM_TEXTURE_CHANNELS] = {};
private:
LLPointer<LLVertexBuffer> mVertexBuffer;
@@ -320,11 +309,6 @@ private:
// gFrameTimeSeconds when mPixelArea was last updated
F32 mLastPixelAreaUpdate = 0.f;
- // Last cos-angle-to-view-dir and projected radius computed by calcPixelArea;
- // reused by its throttled early-return so the overlap test gets real values.
- F32 mLastCosAngleToViewDir = 1.f;
- F32 mLastRadius = 0.f;
-
// virtual size of face in texture area (mPixelArea adjusted by texture repeats)
// used to determine desired resolution of texture
F32 mVSize;
diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index be83fd0279..0d50ba6fe2 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -140,27 +140,6 @@ void render_disconnected_background();
void getProfileStatsContext(boost::json::object& stats);
std::string getProfileStatsFilename();
-// Adaptive texture-pipeline budget: spend the frame-time headroom between the
-// frame we're rendering and TextureLoadTargetFPS, clamped [2ms, max]. Headroom
-// is measured (smoothed frame interval minus the pipeline's own last spend),
-// so fast machines get big budgets and machines already at target hold the
-// floor. Only consumed while queues have work - drain loops exit when empty.
-static F32 sTexturePipelineSpent = 0.f;
-static F32 texture_pipeline_budget()
-{
- static LLCachedControl<F32> target_fps(gSavedSettings, "TextureLoadTargetFPS", 60.f);
- static LLCachedControl<F32> max_ms(gSavedSettings, "TextureLoadBudgetMaxMS", 10.f);
- static F32 smoothed_other = 0.008f;
- F32 other = llmax(gFrameIntervalSeconds.value() - sTexturePipelineSpent, 0.f);
- // A single multi-second hitch must not crater the budget for the following
- // frames, so cap the sample before it enters the EMA.
- other = llmin(other, 0.1f);
- smoothed_other = smoothed_other * 0.9f + other * 0.1f;
- F32 target_interval = 1.f / llclamp((F32)target_fps, 15.f, 240.f);
- F32 headroom = target_interval - smoothed_other;
- return llclamp(headroom, 0.002f, llclamp((F32)max_ms, 2.f, 50.f) * 0.001f);
-}
-
void display_startup()
{
if ( !gViewerWindow
@@ -521,10 +500,9 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List");
- F32 max_image_decode_time = texture_pipeline_budget();
- LLTimer tex_timer;
+ F32 max_image_decode_time = 0.050f * gFrameIntervalSeconds.value(); // 50 ms/second decode time
+ max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f); // min 2ms/frame, max 5ms/frame)
gTextureList.updateImages(max_image_decode_time);
- sTexturePipelineSpent = tex_timer.getElapsedTimeF32();
}
{
@@ -884,10 +862,9 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List");
- F32 max_image_decode_time = texture_pipeline_budget();
- LLTimer tex_timer;
+ F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time
+ max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame)
gTextureList.updateImages(max_image_decode_time);
- sTexturePipelineSpent = tex_timer.getElapsedTimeF32();
}
{
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 90facfa333..80daffdbf3 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -571,20 +571,6 @@ void LLViewerTexture::updateClass()
sPixelToTexelRatio += llmax((F32)relax_rate, 0.f) * dt;
}
// else: hold in the hysteresis band.
-
- // Allocation failures outrank the byte estimate: if setManualImage hit
- // GL_OUT_OF_MEMORY since last frame, the CPU-side vram_used estimate has
- // diverged from reality, so step the ratio down now regardless of the band.
- static U32 last_oom_count = 0;
- U32 oom_count = LLImageGL::sOOMErrorCount.load();
- if (oom_count > last_oom_count)
- {
- U32 new_events = llmin(oom_count - last_oom_count, (U32)5);
- sPixelToTexelRatio -= llmax((F32)tighten_rate, 0.f) * 1.0f * (F32)new_events;
- last_oom_count = oom_count;
- LL_WARNS_ONCE("Texture") << "GL out-of-memory during texture upload triggered a pixel:texel ratio backoff." << LL_ENDL;
- }
-
sPixelToTexelRatio = llclamp(sPixelToTexelRatio, 0.f, r_max);
// Keep the GC-suspend frame current while backgrounded. This suppresses
@@ -1497,16 +1483,6 @@ void LLViewerFetchedTexture::postCreateTexture()
setActive();
- // Start the visibility-GC clock at creation. A texture fetched but never
- // drawn (occluded, or the camera moved on) would otherwise keep
- // mLastBindFrame == 0 forever, and the GC skips never-bound textures - it
- // would hold residency indefinitely. Anchoring here means it ages out on
- // the normal GC cooldown unless a real draw stamps it first.
- if (mGLTexturep.notNull() && mGLTexturep->mLastBindFrame == 0)
- {
- mGLTexturep->mLastBindFrame = LLFrameTimer::getFrameCount();
- }
-
// rebuild any volumes that are using this texture for sculpts in case their LoD has changed
for (U32 i = 0; i < mNumVolumes[LLRender::SCULPT_TEX]; ++i)
{
@@ -1966,14 +1942,7 @@ bool LLViewerFetchedTexture::updateFetch()
S32 current_discard = getCurrentDiscardLevelForFetching();
S32 desired_discard = getDesiredDiscardLevel();
-
- // Two-tier fetch priority, constantly drained by the fetch worker (it
- // sorts HTTP dispatch by this value): any on-screen texture outranks every
- // off-screen one. Within the visible tier, coverage orders by size on
- // screen; within the off-screen tier the same geometric coverage
- // (area/dist^2) orders by proximity. addTextureStats clamps
- // mMaxVirtualSize to sMaxVirtualSize, so the band offset is strict.
- F32 decode_priority = mMaxVirtualSize + (mOnScreen ? sMaxVirtualSize : 0.f);
+ F32 decode_priority = mMaxVirtualSize;
if (mIsFetching)
{
@@ -2034,29 +2003,6 @@ bool LLViewerFetchedTexture::updateFetch()
// discards are served from the GL mip pyramid via scaleDown.
desired_discard = llmin(desired_discard, (S32)mCodecMaxDiscardLevel);
- // Progressive refinement: when resident data is much coarser than desired,
- // fetch in steps of TextureFetchStepMips instead of jumping straight to the
- // final discard. Each step is small, decodes fast, and shows on screen
- // while the next chains behind it (the per-frame fast pump makes chaining
- // nearly free). Without this, seen-before textures (dims known, so the
- // coarse first-fetch fallback never applies) sat blurry for the whole
- // full-file read+decode, then popped. Boosted/pinned content still jumps.
- static LLCachedControl<U32> fetch_step(gSavedSettings, "TextureFetchStepMips", 2);
- const S32 step = (S32)fetch_step;
- if (step > 0
- && current_discard >= 0
- && desired_discard < current_discard - step
- && mBoostLevel < LLGLTexture::BOOST_HIGH
- && mUseMipMaps
- && !mDontDiscard
- && !isAgentAvatarBoost(mBoostLevel))
- {
- // Re-clamp: current can sit past codec max after scaleDown (GL
- // pyramid goes deeper than the codestream) - a stepped request
- // above codec max reaches the decoder with an invalid discard.
- desired_discard = llmin(current_discard - step, (S32)mCodecMaxDiscardLevel);
- }
-
bool make_request = true;
if (decode_priority <= 0)
{
@@ -2082,9 +2028,30 @@ bool LLViewerFetchedTexture::updateFetch()
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min");
make_request = false;
}
- // No visibility gate here: off-screen content still fetches, just in the
- // low-priority band (decode_priority above), so the worker services it only
- // after visible work. Residency stays with the GC (computeDesiredDiscard).
+ else
+ {
+ // Only fetch streamed world textures the renderer is actually drawing
+ // (mLastBindFrame is stamped per drawn frame). Out-of-view content gets
+ // no residency. Exempt: boosted/UI, avatar bakes, textures with loaded
+ // callbacks, and bake uploads.
+ static LLCachedControl<U32> vis_frames(gSavedSettings, "TextureFetchVisibilityFrames", 5);
+ const bool visibility_gated = mBoostLevel < LLGLTexture::BOOST_HIGH
+ && mUseMipMaps
+ && !mDontDiscard
+ && !isAgentAvatarBoost(mBoostLevel)
+ && !mForceToSaveRawImage
+ && mLoadedCallbackList.empty();
+ if (visibility_gated && mGLTexturep.notNull())
+ {
+ const U32 last = mGLTexturep->mLastBindFrame;
+ const U32 now = LLFrameTimer::getFrameCount();
+ if (last == 0 || now - last > llmax((U32)vis_frames, 1u))
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - not visible");
+ make_request = false;
+ }
+ }
+ }
if (make_request)
{
@@ -3068,23 +3035,6 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c
// (a 2048 map can't hit its own resolution on a 1080p screen), which reads
// as everything being blurry. Pressure still evicts by lowering R_global.
ideal = llmax(ideal, 0.f);
-
- // Frustum allowance: a falloff on how unloaded out-of-view content gets,
- // by how far out it is. Grazing the edge keeps full resolution; at
- // TextureFrustumAllowance (fraction of a screen) past the edge it reaches
- // the deepest mip, lerped between. Keeps barely-out-of-view textures
- // resident so a camera swing back doesn't refetch them. Applied to the
- // continuous ideal so it shares the hysteresis dead-band below - applied
- // after it, camera motion made desired flap a mip at a time and churned
- // the fetch/scaleDown queues.
- static LLCachedControl<F32> frustum_allowance(gSavedSettings, "TextureFrustumAllowance", 0.2f);
- if (!avatar_bake && mFrustumOverflow > 0.f)
- {
- const F32 f = llclamp(mFrustumOverflow / llmax((F32)frustum_allowance, 0.01f), 0.f, 1.f);
- ideal += f * ((F32)dim_max_i - ideal);
- mLastOffScreenFrame = LLFrameTimer::getFrameCount();
- }
-
const S32 target = (S32)floor(ideal);
// Hysteresis: a texture at discard C is "happy" while floor(ideal) == C,
@@ -3119,12 +3069,7 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c
// mip by gc_step, walking gradually toward the deepest mip instead of slamming.
// Content drawn within the last cooldown stays full-res, so a fast camera pan
// finds it only a step or two coarse on the way back. Resets when drawn again.
- //
- // Out-of-frustum content is governed by the frustum allowance above instead
- // (spatial falloff, not bind staleness) - without this exclusion the GC would
- // walk barely-out-of-view content to the deepest mip within a second and the
- // allowance would protect nothing.
- if (!avatar_bake && mFrustumOverflow <= 0.f)
+ if (!avatar_bake)
{
if (LLImageGL* gli = getGLTexture())
{
@@ -3132,9 +3077,8 @@ S32 LLViewerLODTexture::computeDesiredDiscard(S32 dim_max_i, bool avatar_bake) c
static LLCachedControl<U32> gc_step_mips(gSavedSettings, "TextureGCStepMips", 1);
constexpr U32 GC_RESUME_GRACE_FRAMES = 10;
const U32 now = LLFrameTimer::getFrameCount();
- if (gli->mLastBindFrame > 0 // drawn, or anchored at creation (postCreateTexture)
- && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES // not just back from background
- && now - mLastOffScreenFrame > GC_RESUME_GRACE_FRAMES) // re-entering content gets one grace window to be drawn and re-stamp before staleness is judged
+ if (gli->mLastBindFrame > 0 // drawn at least once
+ && now - sGCSuspendedFrame > GC_RESUME_GRACE_FRAMES) // not just back from background
{
const U32 cooldown = llmax((U32)gc_cooldown_frames, 1u);
const S32 periods = (S32)((now - gli->mLastBindFrame) / cooldown);
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index 90993ed109..cc9fbe5e48 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -215,25 +215,6 @@ protected:
F32 mChannelCoverage[4] = { 0.f, 0.f, 0.f, 0.f };
F32 mChannelCoverageMin[4] = { 0.f, 0.f, 0.f, 0.f };
- // Any face using this texture projects onto the screen (published alongside
- // the coverage above). Selects the fetch-priority band in updateFetch.
- // Defaults true so unmeasured textures (fresh objects, no-face users) are
- // never starved.
- bool mOnScreen = true;
-
- // How far out of frustum the texture's least-out-of-view use sits, as a
- // fraction of screen size (0 = on screen). Drives the frustum-allowance
- // falloff in computeDesiredDiscard.
- F32 mFrustumOverflow = 0.f;
-
- // Last frame this texture was out of frustum (mFrustumOverflow > 0). The
- // GC in computeDesiredDiscard gives re-entering content one grace window to
- // be drawn and re-stamp mLastBindFrame before its staleness is judged.
- mutable U32 mLastOffScreenFrame = 0;
-
- // Membership flag for LLViewerTextureList::mFastFetchList (dedup).
- bool mInFastFetchList = false;
-
ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture
U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS];
LLFrameTimer mLastFaceListUpdateTimer ;
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index ad05a0273b..3e1481f8b4 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -378,12 +378,6 @@ void LLViewerTextureList::shutdown()
}
mFastCacheList.clear();
- for (auto& img : mFastFetchList)
- {
- img->mInFastFetchList = false;
- }
- mFastFetchList.clear();
-
mUUIDMap.clear();
mImageList.clear();
@@ -877,36 +871,6 @@ void LLViewerTextureList::updateImages(F32 max_time)
remaining_time -= updateImagesFetchTextures(remaining_time);
remaining_time = llmax(remaining_time, min_time);
- // Fast pump: advance every in-flight fetch each frame so results are
- // collected and creates scheduled the frame they're ready, instead of
- // one state transition per round-robin visit. Cheap - no face scans -
- // and bounded by the fetch worker's own concurrency.
- LLTimer fast_fetch_timer;
- S32 min_count = 32;
- for (size_t i = 0; i < mFastFetchList.size(); )
- {
- LLViewerFetchedTexture* imagep = mFastFetchList[i];
- if (imagep->getNumRefs() > 1)
- {
- imagep->updateFetch();
- }
- if (imagep->getNumRefs() <= 1 || (!imagep->isFetching() && !imagep->hasFetcher()))
- {
- imagep->mInFastFetchList = false;
- mFastFetchList[i] = mFastFetchList.back();
- mFastFetchList.pop_back();
- }
- else
- {
- ++i;
- }
-
- if (fast_fetch_timer.getElapsedTimeF32() > remaining_time && --min_count <= 0)
- {
- break;
- }
- }
-
//handle results from decode threads
updateImagesCreateTextures(remaining_time);
@@ -951,187 +915,6 @@ void LLViewerTextureList::clearFetchingRequests()
extern bool gCubeSnapshot;
-// Refresh a face's cached per-channel streaming coverage (face->mStreamVSize).
-// This is the most-demanding-point measurement plus each channel's own UV
-// repeat source, computed ONCE per face per update cadence and shared by every
-// texture registered on the face. Doing the material/transform pointer chases
-// per texture visit instead made updateImageDecodePriority several times more
-// expensive per face than develop's, and since the round-robin runs in a fixed
-// per-frame time slice, that directly cut how many textures advance their
-// load state each frame - the whole pipeline paced slower.
-static void update_face_stream_vsize(LLFace* face)
-{
- // Bounds on the per-face UV repeat-area divisor (mined from the old
- // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost
- // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips
- // coarser) so pathological UV scales can't explode either direction.
- constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f;
- constexpr F32 MAX_REPEAT_AREA = 128.f;
-
- LLViewerObject* objp = face->getViewerObject();
-
- // Most-demanding-point measurement: the spec is that the LOWEST pixel:texel
- // ratio governs, so pixel density is evaluated at the face's NEAREST point
- // and applied to the face's true world area. A whole-face average
- // (bounding-disc pixel area) under-resolves perspective surfaces: on a
- // floor, the tile at your feet covers far more screen than the average
- // tile, and the GPU samples fine mips right there.
- const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents;
- LLVector4a diag;
- diag.setSub(ext[1], ext[0]);
- // World area of the face ~ product of the two largest AABB dims (max
- // pairwise product; robust for flat faces).
- F32 dx = diag[0], dy = diag[1], dz = diag[2];
- F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz));
- // Pixels per meter at the nearest point. Distance floored: nearer than
- // this the screen clamp below governs anyway.
- F32 dist = llmax(face->mDistanceToCamera, 0.5f);
- F32 ppm = LLDrawable::sCurPixelAngle / dist;
- F32 face_px = area_world * ppm * ppm;
- if (face_px <= 0.f)
- {
- // Degenerate extents: the face hasn't been through a geometry build
- // yet (or a rigged face has no rigged extents) - it isn't renderable,
- // so it must not be measured. Zero marks "skip": an invented
- // placeholder value would become the texture's least-demanding "use"
- // and, under TextureDownrezCoverageBias, drag the whole texture to
- // its deepest mip (and it poisoned BP and PBR asymmetrically, since
- // the two register faces at different points in the geometry
- // lifecycle).
- for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch)
- {
- face->mStreamVSize[ch] = 0.f;
- }
- return;
- }
-
- S32 te_offset = face->getTEOffset(); // offset is -1 if not inited
- const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset);
-
- // Shared, channel-independent chases - hoisted out of the channel loop.
- const LLGLTFMaterial* gltf_mat = te ? te->getGLTFRenderMaterial() : nullptr;
- const LLMaterial* mat = te ? te->getMaterialParams().get() : nullptr;
-
- // Continuously-animated scale (llSetTextureAnim SCALE) bypasses both
- // static sources via mTextureMatrix - the live animated values win.
- bool anim_scale = false;
- F32 anim_ss = 0.f, anim_st = 0.f;
- if (te)
- {
- if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr)
- {
- LLViewerTextureAnim* anim = vvo->mTextureAnimp;
- if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE)
- && (anim->mFace < 0 || anim->mFace == te_offset))
- {
- anim_scale = true;
- anim_ss = anim->mScaleS;
- anim_st = anim->mScaleT;
- }
- }
- }
-
- // Mesh atlas sub-rect: a face whose intrinsic UVs span only part of
- // [0,1]^2 shows that fraction of the image. Applies identically to all
- // channels - the per-channel transforms stack on the raw face UVs.
- F32 span = 1.f;
- if (te)
- {
- if (LLVolume* vol = objp->getVolume())
- {
- if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces())
- {
- const LLVolumeFace& vf = vol->getVolumeFace(te_offset);
- F32 s = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0])
- * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1]));
- if (s > 0.f)
- {
- span = s;
- }
- }
- }
- }
-
- // Avatar bonus: worn attachments get a coverage multiplier - avatars are
- // what people look at, and rigged extents make attachment coverage
- // measurement unreliable anyway. Multiplicative, not a slam.
- static LLCachedControl<F32> avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f);
- const F32 boost = objp->isAttachment() ? llmax((F32)avatar_boost, 1.f) : 1.f;
-
- for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch)
- {
- // Effective UV repeat AREA: the tiling term of texels-drawn-per-
- // screen-pixel. More tiling => each tile smaller on screen => coarser
- // mips suffice (penalty). Repeats < 1 (atlas/crop) => whole-image
- // residency for a sub-rect legitimately demands more than its screen
- // coverage (boost).
- F32 repeats = 1.f;
- if (te)
- {
- // UV scale source: every channel reads the repeat values ITS
- // renderer actually applies. diffuse -> TE scale; Blinn
- // normal/spec -> LLMaterial per-map repeats; PBR channels -> KHR
- // texture_transform scale. Fallback is the TE scale - never a
- // silent hardcoded 1.
- F32 scale_s = te->getScaleS();
- F32 scale_t = te->getScaleT();
- if (ch >= LLRender::BASECOLOR_MAP)
- {
- // LLRender channel -> LLGLTFMaterial::TextureInfo
- static const S32 gltf_info[4] = {
- LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3)
- LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4)
- LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5)
- LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6)
- };
- if (gltf_mat)
- {
- const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[ch - LLRender::BASECOLOR_MAP]].mScale;
- scale_s = s.mV[0];
- scale_t = s.mV[1];
- }
- }
- else if (ch == LLRender::NORMAL_MAP || ch == LLRender::SPECULAR_MAP)
- {
- // Blinn-Phong normal/specular maps carry their own repeats in
- // LLMaterial - the renderer builds their texture matrices
- // from these, NOT from the TE's diffuse scale.
- if (mat)
- {
- if (ch == LLRender::NORMAL_MAP)
- {
- mat->getNormalRepeat(scale_s, scale_t);
- }
- else
- {
- mat->getSpecularRepeat(scale_s, scale_t);
- }
- }
- }
-
- if (anim_scale)
- {
- scale_s = anim_ss;
- scale_t = anim_st;
- }
-
- repeats = fabsf(scale_s * scale_t) * span;
- }
-
- repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA);
-
- // Apply the two sides of the repeat term in the right order relative
- // to the screen clamp: tiling (repeats > 1) divides the nearest-point
- // footprint BEFORE the clamp (one tile can't draw more pixels than
- // the screen); atlas/crop (repeats < 1) boosts AFTER it (whole-image
- // residency for a crop legitimately demands more than its screen
- // coverage).
- F32 tiling = llmax(repeats, 1.f);
- F32 crop = llmin(repeats, 1.f);
- face->mStreamVSize[ch] = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop * boost;
- }
-}
-
void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep, bool flush_images)
{
llassert(!gCubeSnapshot);
@@ -1148,6 +931,13 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag
if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures
{
+ // Bounds on the per-face UV repeat-area divisor (mined from the old
+ // getTextureVirtualSize texel_area clamp [1/64, 128]): atlas/crop boost
+ // capped at 64x (3 mips finer), tiling penalty at 128x (3.5 mips
+ // coarser) so pathological UV scales can't explode either direction.
+ constexpr F32 MIN_REPEAT_AREA = 1.f / 64.f;
+ constexpr F32 MAX_REPEAT_AREA = 128.f;
+
// Per priority bucket (0=Normal, 1=BaseColor, 2=Specular, 3=Emissive):
// the HIGHEST per-face effective coverage (= the lowest texels-per-pixel
// use, the most demanding variant - drives desired discard) and the
@@ -1160,9 +950,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag
F32 channel_coverage_min[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
bool bucket_used[4] = { false, false, false, false };
F32 max_coverage = 0.f;
- bool on_screen = false; // any face's projected disc overlaps the screen
- bool any_face = false;
- F32 min_overflow = FLT_MAX; // least out-of-frustum use across faces
U32 face_count = 0;
@@ -1213,29 +1000,175 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag
F32 radius;
F32 cos_angle_to_view_dir;
if ((gFrameCount - face->mLastTextureUpdate) > 10)
- { // refresh the face's geometry + cached coverage at most once every
- // 10 frames; every texture/channel sharing this face (GLTF and
- // Blinn-Phong materials) reuses the cache instead of redoing the
- // measurement. (calcPixelArea maintains face->mInFrustum itself.)
- face->calcPixelArea(cos_angle_to_view_dir, radius);
- update_face_stream_vsize(face);
+ { // 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
+ // assigned to them, such as is the case with GLTF materials or Blinn-Phong materials
+ face->mInFrustum = face->calcPixelArea(cos_angle_to_view_dir, radius);
face->mLastTextureUpdate = gFrameCount;
}
- // Cached measurement - see update_face_stream_vsize above.
- // Zero = degenerate extents / not yet through a geometry
- // build: not renderable, must not be measured (a
- // placeholder value would poison the per-bucket MIN bound
- // and drag the texture to its deepest mip).
- F32 vsize = face->mStreamVSize[i];
- if (vsize <= 0.f)
+ // Most-demanding-point measurement: the spec is that the
+ // LOWEST pixel:texel ratio governs, so pixel density is
+ // evaluated at the face's NEAREST point and applied to the
+ // face's true world area. The previous whole-face average
+ // (bounding-disc pixel area) under-resolved perspective
+ // surfaces: on a floor, the tile at your feet covers far
+ // more screen than the average tile, and the GPU samples
+ // fine mips right there - tiled (PBR-heavy) content went
+ // soft while untiled content looked fine.
+ const LLVector4a* ext = face->isState(LLFace::RIGGED) ? face->mRiggedExtents : face->mExtents;
+ LLVector4a diag;
+ diag.setSub(ext[1], ext[0]);
+ // World area of the face ~ product of the two largest AABB
+ // dims (max pairwise product; robust for flat faces).
+ F32 dx = diag[0], dy = diag[1], dz = diag[2];
+ F32 area_world = llmax(dx * dy, llmax(dx * dz, dy * dz));
+ // Pixels per meter at the nearest point. Distance floored:
+ // nearer than this the screen clamp below governs anyway.
+ F32 dist = llmax(face->mDistanceToCamera, 0.5f);
+ F32 ppm = LLDrawable::sCurPixelAngle / dist;
+ F32 face_px = area_world * ppm * ppm;
+ if (face_px <= 0.f)
{
+ // Degenerate extents: the face hasn't been through a
+ // geometry build yet (or a rigged face has no rigged
+ // extents) - it isn't renderable, so it must not be
+ // measured. Skipping matters especially for the
+ // per-bucket MIN bound: any invented placeholder
+ // value (the old fallback hit LLFace::init's 16px
+ // default) becomes the texture's least-demanding
+ // "use" and, under TextureDownrezCoverageBias, drags
+ // the whole texture to its deepest mip - and it
+ // poisoned BP and PBR asymmetrically since the two
+ // systems register faces at different points in the
+ // geometry lifecycle.
continue;
}
- any_face = true;
- on_screen = on_screen || face->mInFrustum;
- min_overflow = llmin(min_overflow, face->mFrustumOverflow);
+ // Effective UV repeat AREA across this face: the tiling
+ // term of texels-drawn-per-screen-pixel. More tiling =>
+ // each tile is smaller on screen => coarser mips suffice
+ // (penalty). Repeats < 1 (atlas/crop) => only a sub-rect
+ // of the image is shown, but discard levels are whole-
+ // image, so the full image must be resident at 1/repeats
+ // times the crop's pixel count (boost).
+ 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 repeats = 1.f;
+ if (te)
+ {
+ // UV scale source: every channel reads the repeat
+ // values ITS renderer actually applies, then flows
+ // through the identical pipeline below. Sources:
+ // diffuse -> TE scale
+ // Blinn normal/spec -> LLMaterial per-map repeats
+ // PBR channels -> KHR texture_transform scale
+ // Fallback for any missing material is the TE scale -
+ // never a silent hardcoded 1.
+ F32 scale_s = te->getScaleS();
+ F32 scale_t = te->getScaleT();
+ if (i >= LLRender::BASECOLOR_MAP)
+ {
+ // LLRender channel -> LLGLTFMaterial::TextureInfo
+ static const S32 gltf_info[4] = {
+ LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR, // BASECOLOR_MAP (3)
+ LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, // METALLIC_ROUGHNESS_MAP (4)
+ LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL, // GLTF_NORMAL_MAP (5)
+ LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE, // EMISSIVE_MAP (6)
+ };
+ if (const LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial())
+ {
+ const LLVector2& s = gltf_mat->mTextureTransform[gltf_info[i - LLRender::BASECOLOR_MAP]].mScale;
+ scale_s = s.mV[0];
+ scale_t = s.mV[1];
+ }
+ }
+ else if (i == LLRender::NORMAL_MAP || i == LLRender::SPECULAR_MAP)
+ {
+ // Blinn-Phong normal/specular maps carry their own
+ // repeats in LLMaterial - the renderer builds their
+ // texture matrices from these, NOT from the TE's
+ // diffuse scale. Reading the diffuse scale here made
+ // Blinn normals scale differently than PBR normals
+ // (whose per-channel transform IS read above).
+ if (const LLMaterial* mat = te->getMaterialParams().get())
+ {
+ if (i == LLRender::NORMAL_MAP)
+ {
+ mat->getNormalRepeat(scale_s, scale_t);
+ }
+ else
+ {
+ mat->getSpecularRepeat(scale_s, scale_t);
+ }
+ }
+ }
+
+ // Continuously-animated scale (llSetTextureAnim SCALE)
+ // bypasses both static sources via mTextureMatrix -
+ // the live animated values win on either path.
+ if (LLVOVolume* vvo = face->getDrawable() ? face->getDrawable()->getVOVolume() : nullptr)
+ {
+ LLViewerTextureAnim* anim = vvo->mTextureAnimp;
+ if (anim && (anim->mMode & LLTextureAnim::ON) && (anim->mMode & LLTextureAnim::SCALE)
+ && (anim->mFace < 0 || anim->mFace == te_offset))
+ {
+ scale_s = anim->mScaleS;
+ scale_t = anim->mScaleT;
+ }
+ }
+
+ repeats = fabsf(scale_s * scale_t);
+
+ // Mesh atlas sub-rect: a face whose intrinsic UVs span
+ // only part of [0,1]^2 shows that fraction of the
+ // image. Applies identically to both paths - the
+ // transforms above stack on the raw face UVs.
+ if (LLVolume* vol = objp->getVolume())
+ {
+ if (te_offset >= 0 && te_offset < vol->getNumVolumeFaces())
+ {
+ const LLVolumeFace& vf = vol->getVolumeFace(te_offset);
+ F32 span = fabsf((vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0])
+ * (vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1]));
+ if (span > 0.f)
+ {
+ repeats *= span;
+ }
+ }
+ }
+ }
+
+ repeats = llclamp(repeats, MIN_REPEAT_AREA, MAX_REPEAT_AREA);
+
+ // Apply the two sides of the repeat term in the right
+ // order relative to the screen clamp:
+ // - tiling (repeats > 1): the per-tile footprint at the
+ // nearest point, THEN clamped - one tile can't draw
+ // more pixels than the screen. (Clamping the whole
+ // face first and then dividing crushed near tiles.)
+ // - atlas/crop (repeats < 1): boost AFTER the clamp -
+ // whole-image residency for a crop legitimately
+ // demands more than its screen coverage.
+ F32 tiling = llmax(repeats, 1.f);
+ F32 crop = llmin(repeats, 1.f);
+ F32 vsize = llmin(face_px / tiling, LLViewerTexture::sWindowPixelArea) / crop;
+
+ // Avatar bonus: worn attachments get a coverage
+ // multiplier - avatars are what people look at, and
+ // rigged extents make attachment coverage measurement
+ // unreliable anyway. Multiplicative, not a slam: a
+ // nearby avatar gains ~a mip of headroom while a distant
+ // one still downrezzes naturally with its coverage.
+ // (System-avatar bakes get the same bonus in the
+ // no-faces branch below.)
+ if (objp->isAttachment())
+ {
+ static LLCachedControl<F32> avatar_boost(gSavedSettings, "TextureAvatarBoost", 4.f);
+ vsize *= llmax((F32)avatar_boost, 1.f);
+ }
if (bucket >= 0 && bucket < 4)
{
@@ -1309,15 +1242,6 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag
imagep->mChannelCoverage[b] = channel_coverage[b];
imagep->mChannelCoverageMin[b] = (channel_coverage_min[b] == FLT_MAX) ? 0.f : channel_coverage_min[b];
}
-
- // Fetch admission signal: false only when faces were actually scanned and
- // every one projects off screen. Textures with no scannable faces (bakes,
- // spotlights, the >1024-face boost path, not-yet-built geometry) stay
- // eligible - blocking them is what stalls load-in.
- imagep->mOnScreen = on_screen || !any_face;
- // Least out-of-frustum use governs the allowance falloff; unknown = 0
- // (no penalty), same reasoning as mOnScreen.
- imagep->mFrustumOverflow = any_face ? min_overflow : 0.f;
}
#if 0
@@ -1586,18 +1510,6 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time)
{
updateImageDecodePriority(imagep);
imagep->updateFetch();
-
- // Fast-pump membership: textures with an active fetch get
- // updateFetch every frame (in updateImages) instead of waiting
- // ~a sweep period per state transition - that wait, times the
- // 2-4 transitions a load needs, was the measured throughput
- // ceiling. Purely additive: the sweep still pumps everything,
- // so fetches started by any other path can never strand.
- if ((imagep->isFetching() || imagep->hasFetcher()) && !imagep->mInFastFetchList)
- {
- imagep->mInFastFetchList = true;
- mFastFetchList.push_back(imagep);
- }
}
if (timer.getElapsedTimeF32() > max_time)
diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h
index 7004238995..931f2ed50e 100644
--- a/indra/newview/llviewertexturelist.h
+++ b/indra/newview/llviewertexturelist.h
@@ -34,7 +34,6 @@
#include "llviewertexture.h"
#include "llui.h"
#include <list>
-#include <map>
#include <unordered_set>
#include "lluiimage.h"
@@ -226,10 +225,6 @@ public:
image_list_t mCallbackList;
image_list_t mFastCacheList;
- // In-flight fetches pumped every frame (additive to the round-robin
- // sweep, which remains the universal pump). See updateImages.
- std::vector<LLPointer<LLViewerFetchedTexture> > mFastFetchList;
-
bool mForceResetTextureStats;
// to make "for (auto& imagep : gTextureList)" work