diff options
| -rw-r--r-- | indra/llimage/llimage.cpp | 2 | ||||
| -rw-r--r-- | indra/llrender/llimagegl.cpp | 66 | ||||
| -rw-r--r-- | indra/llrender/llimagegl.h | 10 | ||||
| -rw-r--r-- | indra/newview/llviewermedia.cpp | 331 | ||||
| -rw-r--r-- | indra/newview/llviewermedia.h | 7 | ||||
| -rw-r--r-- | indra/newview/llviewertexture.cpp | 1 | 
6 files changed, 234 insertions, 183 deletions
| diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 2855612158..fb02a131fd 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -925,7 +925,7 @@ bool LLImageRaw::setSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height,  void LLImageRaw::clear(U8 r, U8 g, U8 b, U8 a)  { -    LL_PROFILE_ZONE_NAMED("djh imageraw clear") +    LL_PROFILE_ZONE_SCOPED;      S8 components = getComponents();      llassert( components > 0 && components <= 4 ); diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 1e9b9f642e..4b4f071171 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1215,9 +1215,8 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt          if (pixformat == GL_ALPHA && pixtype == GL_UNSIGNED_BYTE)          { //GL_ALPHA is deprecated, convert to RGBA              use_scratch = true; -            scratch = new U32[width * height]; -              U32 pixel_count = (U32)(width * height); +            scratch = new U32[pixel_count];              for (U32 i = 0; i < pixel_count; i++)              {                  U8* pix = (U8*)&scratch[i]; @@ -1232,9 +1231,8 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt          if (pixformat == GL_LUMINANCE_ALPHA && pixtype == GL_UNSIGNED_BYTE)          { //GL_LUMINANCE_ALPHA is deprecated, convert to RGBA              use_scratch = true; -            scratch = new U32[width * height]; -              U32 pixel_count = (U32)(width * height); +            scratch = new U32[pixel_count];              for (U32 i = 0; i < pixel_count; i++)              {                  U8 lum = ((U8*)pixels)[i * 2 + 0]; @@ -1252,9 +1250,8 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt          if (pixformat == GL_LUMINANCE && pixtype == GL_UNSIGNED_BYTE)          { //GL_LUMINANCE_ALPHA is deprecated, convert to RGB              use_scratch = true; -            scratch = new U32[width * height]; -              U32 pixel_count = (U32)(width * height); +            scratch = new U32[pixel_count];              for (U32 i = 0; i < pixel_count; i++)              {                  U8 lum = ((U8*)pixels)[i]; @@ -1314,10 +1311,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt      }      stop_glerror(); -    if (use_scratch) -    { -        delete[] scratch; -    } +    if (scratch) delete[] scratch;  }  //create an empty GL texture: just create a texture name @@ -1471,7 +1465,49 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S  	return createGLTexture(discard_level, rawdata, FALSE, usename);  } -BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename) +// Create a new GL name and allocate tex storage (unitialized) for it +// Used to break resource contention stall in background media tex updates +void LLImageGL::allocNewTexName() +{ +    LL_PROFILE_ZONE_SCOPED; +    if (on_main_thread()) return;       // Should only be called on bg update thread +    if (!mGLTextureCreated) return; +    if (0 != mNewTexName) return;       // only 1 rename in flight at a time + +    generateTextures(1, &mNewTexName);  // create new GL name + +    mHasMipMaps = false;                // Media textures aren't mipped +    mMipLevels = 0; +    // Set state to match current +    gGL.getTexUnit(0)->bind(this, false, false, mNewTexName); +    glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_BASE_LEVEL, 0); +    glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_MAX_LEVEL, mMaxDiscardLevel); +    glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_WIDTH, mWidth); +    glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_HEIGHT, mHeight); +    gGL.getTexUnit(0)->setHasMipMaps(mHasMipMaps); +    gGL.getTexUnit(0)->setTextureAddressMode(mAddressMode); +    gGL.getTexUnit(0)->setTextureFilteringOption(mFilterOption); + +    setManualImage(mTarget, 0, mFormatInternal, mWidth, mHeight, mFormatPrimary, mFormatType,  +                   (GLvoid *) nullptr, mAllowCompression); +     +    gGL.getTexUnit(0)->unbind(mBindTarget); + +    mLastBindTime = sLastFrameTime; +} + +// Delete no-longer-needed GL tex name (on main thread)  +void LLImageGL::swapTexName() +{ +    if (mNewTexName) +    { +        deleteTextures(1, &mTexName); +        mTexName = mNewTexName; +        mNewTexName = 0; +    } +} + +BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips /* = FALSE */, S32 usename /* = 0 */)  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;      checkActiveThread(); @@ -1715,6 +1751,14 @@ void LLImageGL::destroyGLTexture()  		mTexName = 0;		  		mGLTextureCreated = FALSE ;  	}	 + +    // clean up any in-flight name change +    if (0 != mNewTexName) +    { +        // Memory is transient, not tracked by sGlobalTextuerMemory +        LLImageGL::deleteTextures(1, &mNewTexName); +        mNewTexName = 0; +    }  }  //force to invalidate the gl texture, most likely a sculpty texture diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index d6f4b13a51..f311170823 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -109,15 +109,17 @@ public:  	static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression = true);  	BOOL createGLTexture() ; -	BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, -		S32 category = sMaxCategories-1); +	BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, S32 category = sMaxCategories-1);  	BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0); -	void setImage(const LLImageRaw* imageraw); +    void setImage(const LLImageRaw* imageraw);  	BOOL setImage(const U8* data_in, BOOL data_hasmips = FALSE, S32 usename = 0);  	BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE);  	BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE);  	BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height); -	 + +    void allocNewTexName(); +    void swapTexName(); +  	// Read back a raw image for this discard level, if it exists  	BOOL readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const;  	void destroyGLTexture(); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 8515d61f64..de7aaf3173 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -39,6 +39,7 @@  #include "llfilepicker.h"  #include "llfloaterwebcontent.h"	// for handling window close requests and geometry change requests in media browser windows.  #include "llfocusmgr.h" +#include "llimagegl.h"  #include "llkeyboard.h"  #include "lllogininstance.h"  #include "llmarketplacefunctions.h" @@ -2824,165 +2825,156 @@ static LLTrace::BlockTimerStatHandle FTM_MEDIA_SET_SUBIMAGE("Set Subimage");  void LLViewerMediaImpl::update()  { -    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_UPDATE); -	if(mMediaSource == NULL) -	{ -		if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) -		{ -			// This media source should not be loaded. -		} -		else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW) -		{ -			// Don't load new instances that are at PRIORITY_SLIDESHOW or below.  They're just kept around to preserve state. -		} +    LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_UPDATE); +    if(mMediaSource == NULL) +    { +        if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) +        { +            // This media source should not be loaded. +        } +        else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW) +        { +            // Don't load new instances that are at PRIORITY_SLIDESHOW or below.  They're just kept around to preserve state. +        }          else if (!mMimeProbe.expired()) -		{ -			// this media source is doing a MIME type probe -- don't try loading it again. -		} -		else -		{ -			// This media may need to be loaded. -			if(sMediaCreateTimer.hasExpired()) -			{ -				LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL; -				createMediaSource(); -				sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY); -			} -			else -			{ -				LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL; -			} -		} -	} -	else -	{ -		updateVolume(); - -		// TODO: this is updated every frame - is this bad? -		// Removing this as part of the post viewer64 media update -		// Removed as not implemented in CEF embedded browser -		// See MAINT-8194 for a more fuller description -		// updateJavascriptObject(); -	} - - -	if(mMediaSource == NULL) -	{ -		return; -	} +        { +            // this media source is doing a MIME type probe -- don't try loading it again. +        } +        else +        { +            // This media may need to be loaded. +            if(sMediaCreateTimer.hasExpired()) +            { +                LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL; +                createMediaSource(); +                sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY); +            } +            else +            { +                LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL; +            } +        } +    } +    else +    { +        updateVolume(); -	// Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash. -	setNavigateSuspended(true); +        // TODO: this is updated every frame - is this bad? +        // Removing this as part of the post viewer64 media update +        // Removed as not implemented in CEF embedded browser +        // See MAINT-8194 for a more fuller description +        // updateJavascriptObject(); +    } -	mMediaSource->idle(); -	setNavigateSuspended(false); +    if(mMediaSource == NULL) +    { +        return; +    } -	if(mMediaSource == NULL) -	{ -		return; -	} +    // Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash. +    setNavigateSuspended(true); -	if(mMediaSource->isPluginExited()) -	{ -		resetPreviousMediaState(); -		destroyMediaSource(); -		return; -	} +    mMediaSource->idle(); -	if(!mMediaSource->textureValid()) -	{ -		return; -	} +    setNavigateSuspended(false); -	if(mSuspendUpdates || !mVisible) -	{ -		return; -	} +    if(mMediaSource == NULL) +    { +        return; +    } -    ref(); +    if(mMediaSource->isPluginExited()) +    { +        resetPreviousMediaState(); +        destroyMediaSource(); +        return; +    } -    if (preUpdateMediaTexture()) +    if(!mMediaSource->textureValid())      { -        // Push update to worker thread -        auto main_queue = LLMediaTextureUpdateThread::sEnabled ? mMainQueue.lock() : nullptr; -        if (main_queue) -        { -            main_queue->postTo( -                mTexUpdateQueue, // Worker thread queue -                [this]() // work done on update worker thread -                { -                    doMediaTexUpdate(); -                }, -                [this]() // callback to main thread -                { -                    postUpdateMediaTexture(); -                    //unref(); -                }); -        } -        else -        { -            doMediaTexUpdate(); // otherwise, update on main thread -            //unref(); -        } +        return;      } -    unref(); -} +    if(mSuspendUpdates || !mVisible) +    { +        return; +    } -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::preUpdateMediaTexture() -{ -    return true; -} -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::postUpdateMediaTexture() -{ -    return true; +    // Push update to worker thread +    auto main_queue = LLMediaTextureUpdateThread::sEnabled ? mMainQueue.lock() : nullptr; +    ref();  // protect texture from deletion while active on bg queue +    if (main_queue) +    { +        // replace GL name +        //llassert(!mTextureId.isNull()); +        //LLImageGL* base_image = LLViewerTextureManager::getMediaTexture(mTextureId)->getGLTexture(); +        //GLuint retired_name = base_image->allocNew(); +        main_queue->postTo( +            mTexUpdateQueue, // Worker thread queue +            [this]() // work done on update worker thread +            { +                doMediaTexUpdate(); +            }, +            [this]() // callback to main thread +            { +                endMediaTexUpdate(); +                unref(); +            }); +    } +    else +    { +        doMediaTexUpdate(); // otherwise, update on main thread +        unref(); +    }  }  //////////////////////////////////////////////////////////////////////////////////////////  void LLViewerMediaImpl::doMediaTexUpdate()  { -    LLViewerMediaTexture* placeholder_image = updatePlaceholderImage(); -    if (placeholder_image) +    LLViewerMediaTexture* media_tex = updateMediaImage(); + +    if (media_tex && mMediaSource)      { +        llassert(mMediaSource);  +                  LLRect dirty_rect; +        S32 media_width = mMediaSource->getTextureWidth(); +        S32 media_height = mMediaSource->getTextureHeight(); +        S32 media_depth = mMediaSource->getTextureDepth();          // Since we're updating this texture, we know it's playing.  Tell the texture to do its replacement magic so it gets rendered. -        placeholder_image->setPlaying(TRUE); +        media_tex->setPlaying(TRUE);          if (mMediaSource->getDirty(&dirty_rect))          {              // Constrain the dirty rect to be inside the texture              S32 x_pos = llmax(dirty_rect.mLeft, 0);              S32 y_pos = llmax(dirty_rect.mBottom, 0); -            S32 width = llmin(dirty_rect.mRight, placeholder_image->getWidth()) - x_pos; -            S32 height = llmin(dirty_rect.mTop, placeholder_image->getHeight()) - y_pos; +            S32 width = llmin(dirty_rect.mRight, media_width) - x_pos; +            S32 height = llmin(dirty_rect.mTop, media_height) - y_pos;              if (width > 0 && height > 0)              { -                  U8* data = NULL; -                { -                    LL_RECORD_BLOCK_TIME(FTM_MEDIA_GET_DATA); -                    data = mMediaSource->getBitsData(); -                } +                S32 data_width = mMediaSource->getBitsWidth(); +                S32 data_height = mMediaSource->getBitsHeight(); +                data = mMediaSource->getBitsData();                  if (data != NULL)                  {                      // Offset the pixels pointer to match x_pos and y_pos -                    data += (x_pos * mMediaSource->getTextureDepth() * mMediaSource->getBitsWidth()); -                    data += (y_pos * mMediaSource->getTextureDepth()); +                    data += (x_pos * media_depth * data_width); +                    data += (y_pos * media_depth);                      {                          LL_RECORD_BLOCK_TIME(FTM_MEDIA_SET_SUBIMAGE); -                        placeholder_image->setSubImage( +                        media_tex->setSubImage(                              data, -                            mMediaSource->getBitsWidth(), -                            mMediaSource->getBitsHeight(), +                            data_width, +                            data_height,                              x_pos,                              y_pos,                              width, @@ -2998,74 +2990,86 @@ void LLViewerMediaImpl::doMediaTexUpdate()  }  ////////////////////////////////////////////////////////////////////////////////////////// +// runs on main thread, but only called when bg thread updates are active +void LLViewerMediaImpl::endMediaTexUpdate() +{ +    LLViewerMediaTexture* base_image = LLViewerTextureManager::findMediaTexture(mTextureId); +    llassert(base_image); + +    LLImageGL* image = base_image->getGLTexture(); +    image->swapTexName(); // retire old GL name +} + +//////////////////////////////////////////////////////////////////////////////////////////  void LLViewerMediaImpl::updateImagesMediaStreams()  {  }  ////////////////////////////////////////////////////////////////////////////////////////// -LLViewerMediaTexture* LLViewerMediaImpl::updatePlaceholderImage() +LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage()  { -	if(mTextureId.isNull()) -	{ -		// The code that created this instance will read from the plugin's bits. -		return NULL; -	} +    if (!mMediaSource) +    { +        return nullptr; // not ready for updating +    } -	LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mTextureId ); -     -	if (mNeedsNewTexture -		|| placeholder_image->getUseMipMaps() -		|| (placeholder_image->getWidth() != mMediaSource->getTextureWidth()) -		|| (placeholder_image->getHeight() != mMediaSource->getTextureHeight()) -		|| (mTextureUsedWidth != mMediaSource->getWidth()) -		|| (mTextureUsedHeight != mMediaSource->getHeight()) -		) -	{ -		LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL; -		LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL; - -		int texture_width = mMediaSource->getTextureWidth(); -		int texture_height = mMediaSource->getTextureHeight(); -		int texture_depth = mMediaSource->getTextureDepth(); - -		// MEDIAOPT: check to see if size actually changed before doing work -            placeholder_image->destroyGLTexture(); -		// MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? -		placeholder_image->reinit(FALSE);	// probably not needed +    llassert(!mTextureId.isNull()); +    LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId ); +  +    // if on bg thread, create new GL tex image resource to avoid resource contention +    if (!on_main_thread()) +    { +        LLImageGL* image = media_tex->getGLTexture(); +        image->allocNewTexName(); +    } + +    if ( mNeedsNewTexture +        || media_tex->getUseMipMaps() +        || (media_tex->getWidth() != mMediaSource->getTextureWidth()) +        || (media_tex->getHeight() != mMediaSource->getTextureHeight()) +        || (mTextureUsedWidth != mMediaSource->getWidth()) +        || (mTextureUsedHeight != mMediaSource->getHeight()) +        ) +    { +        LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL; +        LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL; + +        int texture_width = mMediaSource->getTextureWidth(); +        int texture_height = mMediaSource->getTextureHeight(); +        int texture_depth = mMediaSource->getTextureDepth(); + +        // MEDIAOPT: check to see if size actually changed before doing work +        media_tex->destroyGLTexture(); +        // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? +        media_tex->reinit(FALSE);	// probably not needed          // MEDIAOPT: seems insane that we actually have to make an imageraw then          // immediately discard it          LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth);          // Clear the texture to the background color, ignoring alpha.          // convert background color channels from [0.0, 1.0] to [0, 255]; -        {LL_PROFILE_ZONE_NAMED("djh clear raw");          raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff); -        } +          // ask media source for correct GL image format constants -        {LL_PROFILE_ZONE_NAMED("djh setformat raw"); -        placeholder_image->setExplicitFormat(mMediaSource->getTextureFormatInternal(), +        media_tex->setExplicitFormat(mMediaSource->getTextureFormatInternal(),              mMediaSource->getTextureFormatPrimary(),              mMediaSource->getTextureFormatType(),              mMediaSource->getTextureFormatSwapBytes()); -        } -        { -            LL_PROFILE_ZONE_NAMED("djh create ph"); -            int discard_level = 0; -            placeholder_image->createGLTexture(discard_level, raw); -        } -		// MEDIAOPT: set this dynamically on play/stop -		// FIXME -//		placeholder_image->mIsMediaTexture = true; -		mNeedsNewTexture = false; +        int discard_level = 0; +        media_tex->createGLTexture(discard_level, raw); -		// If the amount of the texture being drawn by the media goes down in either width or height, -		// recreate the texture to avoid leaving parts of the old image behind. -		mTextureUsedWidth = mMediaSource->getWidth(); -		mTextureUsedHeight = mMediaSource->getHeight(); -	} +        // MEDIAOPT: set this dynamically on play/stop +        // FIXME +//		media_tex->mIsMediaTexture = true; +        mNeedsNewTexture = false; -	return placeholder_image; +        // If the amount of the texture being drawn by the media goes down in either width or height, +        // recreate the texture to avoid leaving parts of the old image behind. +        mTextureUsedWidth = mMediaSource->getWidth(); +        mTextureUsedHeight = mMediaSource->getHeight(); +    } +    return media_tex;  } @@ -4017,6 +4021,7 @@ void LLMediaTextureUpdateThread::run()      mWindow->makeContextCurrent(mContext);      gGL.init();      ThreadPool::run(); +    LLMediaTextureUpdateThread::sEnabled = false;      gGL.shutdown();      mWindow->destroySharedContext(mContext);  } diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index c2e6564166..03a14421ff 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -269,9 +269,8 @@ public:  	void scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y);  	void update(); -    bool preUpdateMediaTexture();      void doMediaTexUpdate(); -    bool postUpdateMediaTexture(); +    void endMediaTexUpdate();  	void updateImagesMediaStreams();  	LLUUID getMediaTextureID() const; @@ -495,13 +494,13 @@ private:      bool mCanceling;  private: -	LLViewerMediaTexture *updatePlaceholderImage(); +	LLViewerMediaTexture *updateMediaImage();      LL::WorkQueue::weak_t mMainQueue;      LL::WorkQueue::weak_t mTexUpdateQueue;  }; -// Define a worker thread pool for media updates ( LLImageGLThread) +// Define a worker thread pool for media updates (ref LLImageGLThread)  class LLMediaTextureUpdateThread : public LLSimpleton<LLMediaTextureUpdateThread>, LL::ThreadPool  {  public: diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 5fed46f437..d652b76794 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -1643,6 +1643,7 @@ void LLViewerFetchedTexture::scheduleCreateTexture()  #endif              mNeedsCreateTexture = TRUE;              auto mainq = LLImageGLThread::sEnabled ? mMainQueue.lock() : nullptr; +            ref(); // protect texture from deletion while active on bg queue              if (mainq)              {                  mainq->postTo( | 
