summaryrefslogtreecommitdiff
path: root/indra/llrender
diff options
context:
space:
mode:
authorAndrey Lihatskiy <alihatskiy@productengine.com>2026-07-25 13:15:43 +0300
committerAndrey Lihatskiy <alihatskiy@productengine.com>2026-07-25 13:15:43 +0300
commit398bf4f76a275ac08524f6218a707251b171271e (patch)
tree31ed47010ec5db0afac932b1c1d87b4a3fd75c75 /indra/llrender
parent34b74530d216a1edc5dfffe327baa615e3654460 (diff)
parent92154ed36ffddac349ac2336a3cc5a1eb6eebb72 (diff)
Merge branch 'release/26.3' into marchcat/slua-26.3
# Conflicts: # indra/llrender/llimagegl.cpp # indra/llrender/llimagegl.h # indra/newview/featuretable.txt # indra/newview/featuretable_linux.txt # indra/newview/featuretable_mac.txt
Diffstat (limited to 'indra/llrender')
-rw-r--r--indra/llrender/llgl.cpp6
-rw-r--r--indra/llrender/llgl.h1
-rw-r--r--indra/llrender/llimagegl.cpp160
-rw-r--r--indra/llrender/llimagegl.h41
-rw-r--r--indra/llrender/llrender.cpp21
5 files changed, 31 insertions, 198 deletions
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index b43f448305..5357a79bae 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -2423,12 +2423,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 f76ac11de9..469802657a 100644
--- a/indra/llrender/llgl.h
+++ b/indra/llrender/llgl.h
@@ -174,7 +174,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 bf8d15ffa9..868284031d 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -66,8 +66,8 @@ static LLMutex sTexMemMutex;
static std::unordered_map<U32, U64> sTextureAllocs;
static U64 sTextureBytes = 0;
-// Per-mip upload paths call this once per level; only free_tex_image
-// removes a texture's accounting entirely.
+// track a texture alloc on the currently bound texture.
+// asserts that no currently tracked alloc exists
void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count)
{
U32 texUnit = gGL.getCurrentTexUnitIndex();
@@ -80,43 +80,12 @@ void LLImageGLMemory::alloc_tex_image(U32 width, U32 height, U32 intformat, U32
sTexMemMutex.lock();
- auto iter = sTextureAllocs.find(texName);
- if (iter != sTextureAllocs.end())
- {
- iter->second += size;
- }
- else
- {
- sTextureAllocs[texName] = size;
- }
- sTextureBytes += size;
-
- sTexMemMutex.unlock();
-}
-
-// Add mip 1..N bytes to existing accounting. Use after glGenerateMipmap.
-void LLImageGLMemory::account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat)
-{
- U64 extra = 0;
- U32 w = base_width;
- U32 h = base_height;
- while (w > 1 || h > 1)
- {
- w = w > 1 ? w >> 1 : 1;
- h = h > 1 ? h >> 1 : 1;
- extra += LLImageGL::dataFormatBytes(intformat, w, h);
- }
+ // it is a precondition that no existing allocation exists for this texture
+ llassert(sTextureAllocs.find(texName) == sTextureAllocs.end());
- U32 texUnit = gGL.getCurrentTexUnitIndex();
- U32 texName = gGL.getTexUnit(texUnit)->getCurrTexture();
+ sTextureAllocs[texName] = size;
+ sTextureBytes += size;
- sTexMemMutex.lock();
- auto iter = sTextureAllocs.find(texName);
- if (iter != sTextureAllocs.end())
- {
- iter->second += extra;
- sTextureBytes += extra;
- }
sTexMemMutex.unlock();
}
@@ -166,8 +135,6 @@ U64 LLImageGL::getTextureBytesAllocated()
//statics
U32 LLImageGL::sUniqueCount = 0;
-std::atomic<U32> LLImageGL::sOOMErrorCount(0);
-thread_local bool LLImageGL::sStampBindFrame = true;
U32 LLImageGL::sBindCount = 0;
S32 LLImageGL::sCount = 0;
@@ -492,7 +459,7 @@ bool LLImageGL::create(LLPointer<LLImageGL>& dest, const LLImageRaw* imageraw, b
//----------------------------------------------------------------------------
LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true*/)
-: mExternalTexture(false)
+: mSaveData(0), mExternalTexture(false)
{
init(usemipmaps, allow_compression);
setSize(0, 0, 0);
@@ -501,7 +468,7 @@ LLImageGL::LLImageGL(bool usemipmaps/* = true*/, bool allow_compression/* = true
}
LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = true*/, bool allow_compression/* = true*/)
-: mExternalTexture(false)
+: mSaveData(0), mExternalTexture(false)
{
llassert( components <= 4 );
init(usemipmaps, allow_compression);
@@ -511,7 +478,7 @@ LLImageGL::LLImageGL(U32 width, U32 height, U8 components, bool usemipmaps/* = t
}
LLImageGL::LLImageGL(const LLImageRaw* imageraw, bool usemipmaps/* = true*/, bool allow_compression/* = true*/)
-: mExternalTexture(false)
+: mSaveData(0), mExternalTexture(false)
{
init(usemipmaps, allow_compression);
setSize(0, 0, 0);
@@ -620,6 +587,8 @@ void LLImageGL::cleanup()
destroyGLTexture();
}
freePickMask();
+
+ mSaveData = NULL; // deletes data
}
//----------------------------------------------------------------------------
@@ -714,10 +683,7 @@ void LLImageGL::dump()
//----------------------------------------------------------------------------
void LLImageGL::forceUpdateBindStats(void) const
{
- // Intentionally a no-op: mLastBindTime is written only by real bind
- // paths so the staleness signal reflects actual GPU use. Callers that
- // still invoke this (avatar "keep alive" sites, deleted-texture
- // fallback) no longer falsely refresh staleness.
+ mLastBindTime = sLastFrameTime;
}
bool LLImageGL::updateBindStats() const
@@ -776,7 +742,6 @@ void LLImageGL::setImage(const LLImageRaw* imageraw)
bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32 usename /* = 0 */)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
- LLImageGLStampBypass no_stamp; // upload binds are not visibility
const bool is_compressed = isCompressed();
@@ -889,7 +854,6 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
{
LL_PROFILE_GPU_ZONE("generate mip map");
glGenerateMipmap(mTarget);
- account_extra_mip_bytes(w, h, mFormatInternal);
}
stop_glerror();
}
@@ -1491,18 +1455,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
LL_PROFILE_ZONE_NUM(width);
LL_PROFILE_ZONE_NUM(height);
- // Release prior accounting only on the base mip; per-mip iteration
- // accumulates the rest via the additive alloc_tex_image.
- if (miplevel == 0)
- {
- free_cur_tex_image();
- }
-
- // 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();
-
+ free_cur_tex_image();
const bool use_sub_image = should_stagger_image_set(compress);
if (!use_sub_image)
{
@@ -1512,30 +1465,19 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
else
{
// break up calls to a manageable size for the GL command buffer
- LL_PROFILE_ZONE_NAMED("glTexImage2D alloc");
- glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr);
- }
+ {
+ LL_PROFILE_ZONE_NAMED("glTexImage2D alloc");
+ glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr);
+ }
- if (glGetError() == GL_OUT_OF_MEMORY)
- {
- ++sOOMErrorCount;
- LL_WARNS_ONCE("Texture") << "glTexImage2D failed with GL_OUT_OF_MEMORY ("
- << width << "x" << height << " mip " << miplevel
- << ") - not counting bytes" << LL_ENDL;
- }
- else
- {
- if (use_sub_image)
+ U8* src = (U8*)(pixels);
+ if (src)
{
- U8* src = (U8*)(pixels);
- if (src)
- {
- LL_PROFILE_ZONE_NAMED("glTexImage2D copy");
- sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width);
- }
+ LL_PROFILE_ZONE_NAMED("glTexImage2D copy");
+ sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width);
}
- alloc_tex_image(width, height, intformat, 1);
}
+ alloc_tex_image(width, height, intformat, 1);
}
stop_glerror();
}
@@ -1665,6 +1607,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S
{
destroyGLTexture();
mCurrentDiscardLevel = discard_level;
+ mLastBindTime = sLastFrameTime;
mGLTextureCreated = false;
return true ;
}
@@ -1680,7 +1623,6 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
LL_PROFILE_GPU_ZONE("createGLTexture");
checkActiveThread();
- LLImageGLStampBypass no_stamp; // creation binds are not visibility
bool main_thread = on_main_thread();
@@ -1781,7 +1723,9 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_
mTextureMemory = (S64Bytes)getMipBytes(mCurrentDiscardLevel);
- mGLCreateTime = sLastFrameTime;
+
+ // mark this as bound at this point, so we don't throw it out immediately
+ mLastBindTime = sLastFrameTime;
checkActiveThread();
return true;
@@ -1913,8 +1857,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 ;
}
@@ -1974,8 +1917,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 ;
}
@@ -2091,37 +2033,6 @@ S32 LLImageGL::getWidth(S32 discard_level) const
return width;
}
-// static
-S32 LLImageGL::dimDerivedMaxDiscard(S32 width, S32 height)
-{
- if (width <= 0 || height <= 0)
- {
- return 0;
- }
- // max(w,h) - min() caps short on rectangular textures
- // (1024x512 reaches 1x1 at discard 10, not 9).
- return (S32)floorf(log2f((F32)llmax(width, height)));
-}
-
-void LLImageGL::stampBound() const
-{
- // Both stamps skip same-frame re-binds (bindFast runs per draw). They dedupe
- // separately, so a non-camera pass touching the time stamp first doesn't stop
- // a real camera bind from setting the frame stamp later the same frame.
- if (mLastBindTime != sLastFrameTime)
- {
- mLastBindTime = sLastFrameTime;
- }
- if (sStampBindFrame)
- {
- const U32 frame = LLFrameTimer::getFrameCount();
- if (mLastBindFrame != frame)
- {
- mLastBindFrame = frame;
- }
- }
-}
-
S64 LLImageGL::getBytes(S32 discard_level) const
{
if (discard_level < 0)
@@ -2544,12 +2455,6 @@ 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.
- LLImageGLStampBypass no_stamp;
-
if (mTarget != GL_TEXTURE_2D
|| mFormatInternal == -1 // not initialized
)
@@ -2557,12 +2462,7 @@ bool LLImageGL::scaleDown(S32 desired_discard)
return false;
}
- // GL pyramid reaches 1x1 regardless of codec levels;
- // mMaxDiscardLevel is hardcapped at MAX_DISCARD_LEVEL.
- S32 dim_max_discard = (mWidth > 0 && mHeight > 0)
- ? dimDerivedMaxDiscard(mWidth, mHeight)
- : (S32)mMaxDiscardLevel;
- desired_discard = llmin(desired_discard, dim_max_discard);
+ desired_discard = llmin(desired_discard, mMaxDiscardLevel);
if (desired_discard <= mCurrentDiscardLevel)
{
@@ -2595,7 +2495,6 @@ bool LLImageGL::scaleDown(S32 desired_discard)
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap");
gGL.getTexUnit(0)->bind(this);
glGenerateMipmap(mTarget);
- account_extra_mip_bytes(desired_width, desired_height, mFormatInternal);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
}
}
@@ -2641,7 +2540,6 @@ bool LLImageGL::scaleDown(S32 desired_discard)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap");
glGenerateMipmap(mTarget);
- account_extra_mip_bytes(desired_width, desired_height, mFormatInternal);
}
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 67f86cd646..5db81ff56d 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -39,7 +39,6 @@
#include "llrender.h"
#include "threadpool.h"
#include "workqueue.h"
-#include <atomic>
#include <unordered_set>
#define LL_IMAGEGL_THREAD_CHECK 0 //set to 1 to enable thread debugging for ImageGL
@@ -52,11 +51,6 @@ class LLWindow;
namespace LLImageGLMemory
{
void alloc_tex_image(U32 width, U32 height, U32 intformat, U32 count);
-
- // Add mip 1..N bytes to existing accounting. Call after glGenerateMipmap
- // when only the base mip was accounted; without this the bytes counter
- // undercounts mipmap-generated textures by ~25%.
- void account_extra_mip_bytes(U32 base_width, U32 base_height, U32 intformat);
void free_tex_image(U32 texName);
void free_tex_images(U32 count, const U32* texNames);
void free_cur_tex_image();
@@ -157,15 +151,6 @@ public:
S32 getDiscardLevel() const { return mCurrentDiscardLevel; }
S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; }
- // floor(log2(max(w, h))) - deepest GL pyramid level (down to 1x1).
- // Returns 0 for non-positive inputs.
- static S32 dimDerivedMaxDiscard(S32 width, S32 height);
-
- // Record the wall-clock bind time - every bind path that touches a
- // streaming-managed texture must call this, or the staleness signal
- // sees the texture as never-bound and ramps it toward eviction.
- void stampBound() const;
-
// override the current discard level
// should only be used for local textures where you know exactly what you're doing
void setDiscardLevel(S32 level) { mCurrentDiscardLevel = level; }
@@ -239,23 +224,14 @@ public:
public:
// Various GL/Rendering options
S64Bytes mTextureMemory;
- mutable F32 mLastBindTime = 0.f; // wall-clock time at last stampBound (bind or bind-attempt)
- mutable U32 mLastBindFrame = 0; // frame index (LLFrameTimer::getFrameCount) at last CAMERA-pass
- // stampBound; 0 = never. Drives visibility GC + fetch gating.
- F32 mGLCreateTime = 0.f; // wall-clock time the GL texture was created
-
- // When false, stampBound skips the mLastBindFrame stamp (mLastBindTime still
- // updates). Set false around non-camera passes (probes, shadows, impostors)
- // and administrative binds (upload, scaleDown - via LLImageGLStampBypass) so
- // those binds don't count as camera visibility. Thread-local so a GL upload
- // thread can't flip it on the render thread mid-frame.
- static thread_local bool sStampBindFrame;
+ mutable F32 mLastBindTime; // last time this was bound, by discard level
private:
U32 createPickMask(S32 pWidth, S32 pHeight);
void freePickMask();
bool isCompressed();
+ LLPointer<LLImageRaw> mSaveData; // used for destroyGL/restoreGL
LL::WorkQueue::weak_t mMainQueue;
U8* mPickMask; //downsampled bitmap approximation of alpha channel. NULL if no alpha channel
U16 mPickMaskWidth;
@@ -368,19 +344,6 @@ public:
};
-// RAII: suppress the mLastBindFrame stamp for the current scope. Use around
-// administrative binds (upload, create, scaleDown) so they don't count as
-// camera visibility - otherwise the GC's own scaleDown re-stamps what it just
-// aged out and oscillates. Saves/restores, so it nests correctly.
-class LLImageGLStampBypass
-{
-public:
- LLImageGLStampBypass() : mPrev(LLImageGL::sStampBindFrame) { LLImageGL::sStampBindFrame = false; }
- ~LLImageGLStampBypass() { LLImageGL::sStampBindFrame = mPrev; }
-private:
- bool mPrev;
-};
-
class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
{
public:
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 8b2fdc8c9d..8bbc9a8176 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -194,9 +194,6 @@ void LLTexUnit::bindFast(LLTexture* texture)
gGL.mCurrTextureUnitIndex = mIndex;
mCurrTexture = gl_tex->getTexName();
mCurrTexType = gl_tex->getTarget();
- // bindFast bypasses updateBindStats(); stamp directly so the staleness
- // signal sees per-frame use of batched textures.
- gl_tex->stampBound();
if (!mCurrTexture)
{
LL_PROFILE_ZONE_NAMED("MISSING TEXTURE");
@@ -241,11 +238,6 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind)
texture->setActive() ;
texture->updateBindStatsForTester() ;
}
- // updateBindStats only stamps time; the GC and fetch gate use
- // the frame stamp, so stamp it here too or bind()-drawn faces
- // (bump/material/media) oscillate. Admin/non-camera binds are
- // already suppressed via LLImageGLStampBypass / sStampBindFrame.
- gl_tex->stampBound();
mHasMipMaps = gl_tex->mHasMipMaps;
if (gl_tex->mTexOptionsDirty)
{
@@ -254,17 +246,11 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind)
setTextureFilteringOption(gl_tex->mFilterOption);
}
}
- else
- {
- // Already current - still being used, keep it fresh.
- gl_tex->stampBound();
- }
}
else
{
//if deleted, will re-generate it immediately
texture->forceImmediateUpdate() ;
- gl_tex->stampBound();
gl_tex->forceUpdateBindStats() ;
return texture->bindDefaultImage(mIndex);
@@ -326,8 +312,6 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32
glBindTexture(sGLTextureType[texture->getTarget()], mCurrTexture);
stop_glerror();
texture->updateBindStats();
- // Frame-stamp fresh binds too - see bind(LLTexture*) above.
- texture->stampBound();
mHasMipMaps = texture->mHasMipMaps;
if (texture->mTexOptionsDirty)
{
@@ -338,11 +322,6 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32
stop_glerror();
}
}
- else
- {
- // Already current - still being used, keep it fresh.
- texture->stampBound();
- }
stop_glerror();