Skip to content

Commit a937b23

Browse files
authored
Geenz/texture loading speed (#5985)
* Add more controls for texture loading budgets. Should yield much faster loading within a given FPS target - should generally self regulate depending on your framerate. * Harden texture pipeline against stalls and OOM. Generally makes texture loading faster, at the expense of some budgeting (which we weren't doing a great job at anyways).
1 parent 4452ec6 commit a937b23

11 files changed

Lines changed: 482 additions & 209 deletions

File tree

indra/llrender/llgl.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,6 +2370,12 @@ void clear_glerror()
23702370
glGetError();
23712371
}
23722372

2373+
void drain_glerror()
2374+
{
2375+
// bounded: a lost/reset context can return errors indefinitely
2376+
for (S32 i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {}
2377+
}
2378+
23732379
///////////////////////////////////////////////////////////////
23742380
//
23752381
// LLGLState

indra/llrender/llgl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ void log_glerror();
159159
void assert_glerror();
160160

161161
void clear_glerror();
162+
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.
162163

163164

164165
# define stop_glerror() assert_glerror()

indra/llrender/llimagegl.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
15071507
// Drain stale GL errors so an OOM detected below belongs to this alloc.
15081508
// Otherwise a failed glTexImage2D is swallowed in release while
15091509
// alloc_tex_image still counts the bytes, inflating the used-VRAM figure.
1510-
while (glGetError() != GL_NO_ERROR) {}
1510+
drain_glerror();
15111511

15121512
const bool use_sub_image = should_stagger_image_set(compress);
15131513
if (!use_sub_image)
@@ -1919,7 +1919,8 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
19191919

19201920
//-----------------------------------------------------------------------------------------------
19211921
GLenum error ;
1922-
while((error = glGetError()) != GL_NO_ERROR)
1922+
S32 error_count = 0 ;
1923+
while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16)
19231924
{
19241925
LL_WARNS() << "GL Error happens before reading back texture. Error code: " << error << LL_ENDL ;
19251926
}
@@ -1979,7 +1980,8 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
19791980
LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ;
19801981
imageraw->deleteData() ;
19811982

1982-
while((error = glGetError()) != GL_NO_ERROR)
1983+
error_count = 0 ;
1984+
while((error = glGetError()) != GL_NO_ERROR && ++error_count <= 16)
19831985
{
19841986
LL_WARNS() << "GL Error happens after reading back texture. Error code: " << error << LL_ENDL ;
19851987
}
@@ -2548,8 +2550,10 @@ bool LLImageGL::scaleDown(S32 desired_discard)
25482550
{
25492551
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
25502552

2551-
// Don't let eviction re-arm visibility: the glGenerateMipmap re-bind below
2552-
// would otherwise stamp mLastBindFrame and keep the texture fetch-eligible.
2553+
// Don't let eviction re-arm the GC: the glGenerateMipmap re-bind below would
2554+
// otherwise stamp mLastBindFrame, so the next computeDesiredDiscard treats the
2555+
// just-evicted texture as freshly drawn, un-floors it, and re-fetches - the
2556+
// evict/refetch oscillation.
25532557
LLImageGLStampBypass no_stamp;
25542558

25552559
if (mTarget != GL_TEXTURE_2D

indra/newview/app_settings/settings.xml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11992,16 +11992,49 @@
1199211992
<key>Value</key>
1199311993
<integer>1</integer>
1199411994
</map>
11995-
<key>TextureFetchVisibilityFrames</key>
11995+
<key>TextureLoadTargetFPS</key>
1199611996
<map>
1199711997
<key>Comment</key>
11998-
<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>
11998+
<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>
11999+
<key>Persist</key>
12000+
<integer>1</integer>
12001+
<key>Type</key>
12002+
<string>F32</string>
12003+
<key>Value</key>
12004+
<real>30.0</real>
12005+
</map>
12006+
<key>TextureLoadBudgetMaxMS</key>
12007+
<map>
12008+
<key>Comment</key>
12009+
<string>Hard cap, in milliseconds per frame, on the adaptive texture-pipeline budget.</string>
12010+
<key>Persist</key>
12011+
<integer>1</integer>
12012+
<key>Type</key>
12013+
<string>F32</string>
12014+
<key>Value</key>
12015+
<real>10.0</real>
12016+
</map>
12017+
<key>TextureFetchStepMips</key>
12018+
<map>
12019+
<key>Comment</key>
12020+
<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>
1199912021
<key>Persist</key>
1200012022
<integer>1</integer>
1200112023
<key>Type</key>
1200212024
<string>U32</string>
1200312025
<key>Value</key>
12026+
<integer>2</integer>
12027+
</map>
12028+
<key>TextureFrustumAllowance</key>
12029+
<map>
12030+
<key>Comment</key>
12031+
<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>
12032+
<key>Persist</key>
1200412033
<integer>1</integer>
12034+
<key>Type</key>
12035+
<string>F32</string>
12036+
<key>Value</key>
12037+
<real>0.5</real>
1200512038
</map>
1200612039
<key>TextureDecodeDisabled</key>
1200712040
<map>

indra/newview/llface.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,6 +2297,8 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
22972297
// don't update every frame
22982298
if (gFrameTimeSeconds - mLastPixelAreaUpdate < PIXEL_AREA_UPDATE_PERIOD)
22992299
{
2300+
cos_angle_to_view_dir = mLastCosAngleToViewDir;
2301+
radius = mLastRadius;
23002302
return true;
23012303
}
23022304

@@ -2368,6 +2370,7 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
23682370
// no rigged extents, zero out bounding box and skip update
23692371
mRiggedExtents[0] = mRiggedExtents[1] = LLVector4a(0.f, 0.f, 0.f);
23702372

2373+
mInFrustum = false;
23712374
return false;
23722375
}
23732376

@@ -2422,6 +2425,7 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
24222425
if(!camera->AABBInFrustum(center, size))
24232426
{
24242427
mImportanceToCamera = 0.f ;
2428+
mInFrustum = false;
24252429
return false ;
24262430
}
24272431
if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum
@@ -2450,6 +2454,24 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius)
24502454
mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ;
24512455
}
24522456

2457+
// On-screen test: does the face's projected disc overlap the screen disc?
2458+
// (Same construction as adjustPartialOverlapPixelArea.) Behind-camera faces
2459+
// get acos(cos) near pi and fall out; the generous screen radius errs toward
2460+
// "on screen" so fetch admission never starves edge content. mFrustumOverflow
2461+
// is how far past the boundary the disc sits, as a fraction of screen size -
2462+
// 0 on screen, 0.1 = 10% of a screen out - and feeds the frustum allowance
2463+
// falloff in computeDesiredDiscard.
2464+
{
2465+
F32 center_px = acosf(llclamp(cos_angle_to_view_dir, -1.f, 1.f)) * LLDrawable::sCurPixelAngle;
2466+
F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw());
2467+
F32 past_edge = center_px - radius - screen_radius;
2468+
mInFrustum = past_edge <= 5.f;
2469+
mFrustumOverflow = llmax(past_edge - 5.f, 0.f) / screen_radius;
2470+
}
2471+
2472+
mLastCosAngleToViewDir = cos_angle_to_view_dir;
2473+
mLastRadius = radius;
2474+
24532475
return true ;
24542476
}
24552477

indra/newview/llface.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,21 @@ class alignas(16) LLFace
269269
// return mSkinInfo->mHash or 0 if mSkinInfo is null
270270
U64 getSkinHash();
271271

272-
// true if face was recently in the main camera frustum according to LLViewerTextureList updates
272+
// True if this face's projected bounding disc overlaps the screen - maintained
273+
// by calcPixelArea() (sticky between its throttled updates). Drives per-texture
274+
// fetch admission (LLViewerTextureList::updateImageDecodePriority -> mOnScreen).
273275
bool mInFrustum = false;
276+
// How far past the screen boundary the projected disc sits, as a fraction of
277+
// screen size (0 = on screen). Feeds the frustum-allowance falloff so barely
278+
// out-of-view content keeps its resolution. Maintained with mInFrustum.
279+
F32 mFrustumOverflow = 0.f;
274280
// value of gFrameCount the last time the face was touched by LLViewerTextureList::updateImageDecodePriority
275281
U32 mLastTextureUpdate = 0;
282+
// Cached per-channel streaming coverage (repeat-adjusted screen pixels),
283+
// refreshed at the mLastTextureUpdate cadence and shared by every texture
284+
// on this face. 0 = degenerate / not yet measured. See
285+
// update_face_stream_vsize in llviewertexturelist.cpp.
286+
F32 mStreamVSize[LLRender::NUM_TEXTURE_CHANNELS] = {};
276287

277288
private:
278289
LLPointer<LLVertexBuffer> mVertexBuffer;
@@ -309,6 +320,11 @@ class alignas(16) LLFace
309320
// gFrameTimeSeconds when mPixelArea was last updated
310321
F32 mLastPixelAreaUpdate = 0.f;
311322

323+
// Last cos-angle-to-view-dir and projected radius computed by calcPixelArea;
324+
// reused by its throttled early-return so the overlap test gets real values.
325+
F32 mLastCosAngleToViewDir = 1.f;
326+
F32 mLastRadius = 0.f;
327+
312328
// virtual size of face in texture area (mPixelArea adjusted by texture repeats)
313329
// used to determine desired resolution of texture
314330
F32 mVSize;

indra/newview/llviewerdisplay.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,27 @@ void render_disconnected_background();
140140
void getProfileStatsContext(boost::json::object& stats);
141141
std::string getProfileStatsFilename();
142142

143+
// Adaptive texture-pipeline budget: spend the frame-time headroom between the
144+
// frame we're rendering and TextureLoadTargetFPS, clamped [2ms, max]. Headroom
145+
// is measured (smoothed frame interval minus the pipeline's own last spend),
146+
// so fast machines get big budgets and machines already at target hold the
147+
// floor. Only consumed while queues have work - drain loops exit when empty.
148+
static F32 sTexturePipelineSpent = 0.f;
149+
static F32 texture_pipeline_budget()
150+
{
151+
static LLCachedControl<F32> target_fps(gSavedSettings, "TextureLoadTargetFPS", 60.f);
152+
static LLCachedControl<F32> max_ms(gSavedSettings, "TextureLoadBudgetMaxMS", 10.f);
153+
static F32 smoothed_other = 0.008f;
154+
F32 other = llmax(gFrameIntervalSeconds.value() - sTexturePipelineSpent, 0.f);
155+
// A single multi-second hitch must not crater the budget for the following
156+
// frames, so cap the sample before it enters the EMA.
157+
other = llmin(other, 0.1f);
158+
smoothed_other = smoothed_other * 0.9f + other * 0.1f;
159+
F32 target_interval = 1.f / llclamp((F32)target_fps, 15.f, 240.f);
160+
F32 headroom = target_interval - smoothed_other;
161+
return llclamp(headroom, 0.002f, llclamp((F32)max_ms, 2.f, 50.f) * 0.001f);
162+
}
163+
143164
void display_startup()
144165
{
145166
if ( !gViewerWindow
@@ -500,9 +521,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
500521

501522
{
502523
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List");
503-
F32 max_image_decode_time = 0.050f * gFrameIntervalSeconds.value(); // 50 ms/second decode time
504-
max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f); // min 2ms/frame, max 5ms/frame)
524+
F32 max_image_decode_time = texture_pipeline_budget();
525+
LLTimer tex_timer;
505526
gTextureList.updateImages(max_image_decode_time);
527+
sTexturePipelineSpent = tex_timer.getElapsedTimeF32();
506528
}
507529

508530
{
@@ -862,9 +884,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
862884

863885
{
864886
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List");
865-
F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time
866-
max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame)
887+
F32 max_image_decode_time = texture_pipeline_budget();
888+
LLTimer tex_timer;
867889
gTextureList.updateImages(max_image_decode_time);
890+
sTexturePipelineSpent = tex_timer.getElapsedTimeF32();
868891
}
869892

870893
{

0 commit comments

Comments
 (0)