1884 lines
50 KiB
C++
1884 lines
50 KiB
C++
/**
|
|
* @file llviewerimage.cpp
|
|
* @brief Object which handles a received image (and associated texture(s))
|
|
*
|
|
* $LicenseInfo:firstyear=2000&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2000-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llviewerimage.h"
|
|
|
|
// Library includes
|
|
#include "imageids.h"
|
|
#include "llmath.h"
|
|
#include "llerror.h"
|
|
#include "llgl.h"
|
|
#include "llglheaders.h"
|
|
#include "llhost.h"
|
|
#include "llimage.h"
|
|
#include "llimagebmp.h"
|
|
#include "llimagej2c.h"
|
|
#include "llimagetga.h"
|
|
#include "llmemtype.h"
|
|
#include "llstl.h"
|
|
#include "llvfile.h"
|
|
#include "llvfs.h"
|
|
#include "message.h"
|
|
#include "lltimer.h"
|
|
|
|
// viewer includes
|
|
#include "lldrawpool.h"
|
|
#include "lltexturefetch.h"
|
|
#include "llviewerimagelist.h"
|
|
#include "llviewercontrol.h"
|
|
#include "pipeline.h"
|
|
#include "llappviewer.h"
|
|
#include "llface.h"
|
|
#include "llviewercamera.h"
|
|
|
|
// <edit>
|
|
#include "llimagemetadatareader.h"
|
|
#include "lltexturecache.h"
|
|
// </edit>
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// statics
|
|
LLPointer<LLViewerImage> LLViewerImage::sMissingAssetImagep = NULL;
|
|
LLPointer<LLViewerImage> LLViewerImage::sWhiteImagep = NULL;
|
|
LLPointer<LLImageGL> LLViewerImage::sDefaultImagep = NULL;
|
|
LLPointer<LLViewerImage> LLViewerImage::sSmokeImagep = NULL;
|
|
LLPointer<LLImageGL> LLViewerImage::sNullImagep = NULL;
|
|
|
|
S32 LLViewerImage::sImageCount = 0;
|
|
S32 LLViewerImage::sRawCount = 0;
|
|
S32 LLViewerImage::sAuxCount = 0;
|
|
LLTimer LLViewerImage::sEvaluationTimer;
|
|
S8 LLViewerImage::sCameraMovingDiscardBias = 0 ;
|
|
F32 LLViewerImage::sDesiredDiscardBias = 0.f;
|
|
static F32 sDesiredDiscardBiasMin = -2.0f; // -max number of levels to improve image quality by
|
|
static F32 sDesiredDiscardBiasMax = 1.5f; // max number of levels to reduce image quality by
|
|
F32 LLViewerImage::sDesiredDiscardScale = 1.1f;
|
|
S32 LLViewerImage::sBoundTextureMemoryInBytes = 0;
|
|
S32 LLViewerImage::sTotalTextureMemoryInBytes = 0;
|
|
S32 LLViewerImage::sMaxBoundTextureMemInMegaBytes = 0;
|
|
S32 LLViewerImage::sMaxTotalTextureMemInMegaBytes = 0;
|
|
S32 LLViewerImage::sMaxDesiredTextureMemInBytes = 0 ;
|
|
BOOL LLViewerImage::sDontLoadVolumeTextures = FALSE;
|
|
|
|
S32 LLViewerImage::sMaxSculptRez = 128 ; //max sculpt image size
|
|
const S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64 ;
|
|
const S32 MAX_CACHED_RAW_SCULPT_IMAGE_AREA = LLViewerImage::sMaxSculptRez * LLViewerImage::sMaxSculptRez ;
|
|
const S32 MAX_CACHED_RAW_TERRAIN_IMAGE_AREA = 128 * 128 ;
|
|
S32 LLViewerImage::sMinLargeImageSize = 65536 ; //256 * 256.
|
|
S32 LLViewerImage::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA ;
|
|
BOOL LLViewerImage::sFreezeImageScalingDown = FALSE ;
|
|
//debug use
|
|
S32 LLViewerImage::sLLViewerImageCount = 0 ;
|
|
|
|
// <edit>
|
|
class CommentCacheReadResponder : public LLTextureCache::ReadResponder
|
|
{
|
|
public:
|
|
CommentCacheReadResponder(LLPointer<LLViewerImage> image)
|
|
: mViewerImage(image)
|
|
{
|
|
mID = image->getID();
|
|
mFormattedImage = new LLImageJ2C;
|
|
setImage(mFormattedImage);
|
|
}
|
|
void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, BOOL imagelocal)
|
|
{
|
|
if(imageformat==IMG_CODEC_TGA && mFormattedImage->getCodec()==IMG_CODEC_J2C)
|
|
{
|
|
//llwarns<<"Bleh its a tga not saving"<<llendl;
|
|
mFormattedImage=NULL;
|
|
mImageSize=0;
|
|
return;
|
|
}
|
|
|
|
if (mFormattedImage.notNull())
|
|
{
|
|
llassert_always(mFormattedImage->getCodec() == imageformat);
|
|
mFormattedImage->appendData(data, datasize);
|
|
}
|
|
else
|
|
{
|
|
mFormattedImage = LLImageFormatted::createFromType(imageformat);
|
|
mFormattedImage->setData(data,datasize);
|
|
}
|
|
mImageSize = imagesize;
|
|
mImageLocal = imagelocal;
|
|
}
|
|
|
|
virtual void completed(bool success)
|
|
{
|
|
if(success && (mFormattedImage.notNull()) && mImageSize>0 && mViewerImage.notNull())
|
|
{
|
|
|
|
//llinfos << "SUCCESS getting texture "<<mID<< llendl;
|
|
mViewerImage->commentEncryptionType = LLImageMetaDataReader::ExtractEncodedComment(
|
|
mFormattedImage->getData(),
|
|
mFormattedImage->getDataSize(),
|
|
mViewerImage->decodedComment
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
//if(!success)
|
|
// llwarns << "FAIL NOT SUCCESSFUL getting texture "<<mID<< llendl;
|
|
}
|
|
}
|
|
private:
|
|
LLPointer<LLImageFormatted> mFormattedImage;
|
|
LLPointer<LLViewerImage> mViewerImage;
|
|
LLUUID mID;
|
|
};
|
|
// </edit>
|
|
|
|
// static
|
|
void LLViewerImage::initClass()
|
|
{
|
|
sNullImagep = new LLImageGL(1,1,3,TRUE);
|
|
LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,3);
|
|
raw->clear(0x77, 0x77, 0x77, 0xFF);
|
|
sNullImagep->createGLTexture(0, raw, 0, TRUE, LLViewerImageBoostLevel::OTHER);
|
|
|
|
#if 1
|
|
LLPointer<LLViewerImage> imagep = new LLViewerImage(IMG_DEFAULT);
|
|
sDefaultImagep = imagep;
|
|
const S32 dim = 128;
|
|
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
|
|
U8* data = image_raw->getData();
|
|
for (S32 i = 0; i<dim; i++)
|
|
{
|
|
for (S32 j = 0; j<dim; j++)
|
|
{
|
|
#if 0
|
|
const S32 border = 2;
|
|
if (i<border || j<border || i>=(dim-border) || j>=(dim-border))
|
|
{
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
*data++ = 0x7f;
|
|
*data++ = 0x7f;
|
|
*data++ = 0x7f;
|
|
}
|
|
}
|
|
}
|
|
imagep->createGLTexture(0, image_raw, 0, TRUE, LLViewerImageBoostLevel::OTHER);
|
|
image_raw = NULL;
|
|
gImageList.addImage(imagep);
|
|
imagep->dontDiscard();
|
|
#else
|
|
sDefaultImagep = gImageList.getImage(IMG_DEFAULT, TRUE, TRUE);
|
|
#endif
|
|
sSmokeImagep = gImageList.getImage(IMG_SMOKE, TRUE, TRUE);
|
|
sSmokeImagep->setNoDelete() ;
|
|
|
|
if(gAuditTexture)
|
|
{
|
|
sDefaultTexturep = new LLImageGL() ;
|
|
image_raw = new LLImageRaw(dim,dim,3);
|
|
data = image_raw->getData();
|
|
for (S32 i = 0; i<dim; i++)
|
|
{
|
|
for (S32 j = 0; j<dim; j++)
|
|
{
|
|
const S32 border = 2;
|
|
if (i<border || j<border || i>=(dim-border) || j>=(dim-border))
|
|
{
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
}
|
|
else
|
|
{
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
*data++ = 0x00;
|
|
}
|
|
}
|
|
}
|
|
sDefaultTexturep->createGLTexture(0, image_raw, 0, TRUE, LLViewerImageBoostLevel::OTHER);
|
|
image_raw = NULL;
|
|
sDefaultTexturep->dontDiscard();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLViewerImage::cleanupClass()
|
|
{
|
|
stop_glerror();
|
|
LLImageGL::cleanupClass() ;
|
|
|
|
sNullImagep = NULL;
|
|
sDefaultImagep = NULL;
|
|
sSmokeImagep = NULL;
|
|
sMissingAssetImagep = NULL;
|
|
sWhiteImagep = NULL;
|
|
sDefaultTexturep = NULL ;
|
|
}
|
|
|
|
// tuning params
|
|
const F32 discard_bias_delta = .05f;
|
|
const F32 discard_delta_time = 0.5f;
|
|
const S32 min_non_tex_system_mem = (128<<20); // 128 MB
|
|
// non-const (used externally
|
|
F32 texmem_lower_bound_scale = 0.85f;
|
|
F32 texmem_middle_bound_scale = 0.925f;
|
|
|
|
//static
|
|
void LLViewerImage::updateClass(const F32 velocity, const F32 angular_velocity)
|
|
{
|
|
llpushcallstacks ;
|
|
sBoundTextureMemoryInBytes = LLImageGL::sBoundTextureMemoryInBytes;//in bytes
|
|
sTotalTextureMemoryInBytes = LLImageGL::sGlobalTextureMemoryInBytes;//in bytes
|
|
sMaxBoundTextureMemInMegaBytes = gImageList.getMaxResidentTexMem();//in MB
|
|
sMaxTotalTextureMemInMegaBytes = gImageList.getMaxTotalTextureMem() ;//in MB
|
|
sMaxDesiredTextureMemInBytes = MEGA_BYTES_TO_BYTES(sMaxTotalTextureMemInMegaBytes) ; //in Bytes, by default and when total used texture memory is small.
|
|
|
|
if (BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) >= sMaxBoundTextureMemInMegaBytes ||
|
|
BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) >= sMaxTotalTextureMemInMegaBytes)
|
|
{
|
|
//when texture memory overflows, lower down the threashold to release the textures more aggressively.
|
|
sMaxDesiredTextureMemInBytes = llmin((S32)(sMaxDesiredTextureMemInBytes * 0.75f) , MEGA_BYTES_TO_BYTES(MAX_VIDEO_RAM_IN_MEGA_BYTES)) ;//512 MB
|
|
|
|
// If we are using more texture memory than we should,
|
|
// scale up the desired discard level
|
|
if (sEvaluationTimer.getElapsedTimeF32() > discard_delta_time)
|
|
{
|
|
sDesiredDiscardBias += discard_bias_delta;
|
|
sEvaluationTimer.reset();
|
|
}
|
|
}
|
|
else if (sDesiredDiscardBias > 0.0f &&
|
|
BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) < sMaxBoundTextureMemInMegaBytes * texmem_lower_bound_scale &&
|
|
BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) < sMaxTotalTextureMemInMegaBytes * texmem_lower_bound_scale)
|
|
{
|
|
// If we are using less texture memory than we should,
|
|
// scale down the desired discard level
|
|
if (sEvaluationTimer.getElapsedTimeF32() > discard_delta_time)
|
|
{
|
|
sDesiredDiscardBias -= discard_bias_delta;
|
|
sEvaluationTimer.reset();
|
|
}
|
|
}
|
|
sDesiredDiscardBias = llclamp(sDesiredDiscardBias, sDesiredDiscardBiasMin, sDesiredDiscardBiasMax);
|
|
|
|
F32 camera_moving_speed = LLViewerCamera::getInstance()->getAverageSpeed() ;
|
|
F32 camera_angular_speed = LLViewerCamera::getInstance()->getAverageAngularSpeed();
|
|
sCameraMovingDiscardBias = (S8)llmax(0.2f * camera_moving_speed, 2.0f * camera_angular_speed - 1) ;
|
|
|
|
LLViewerImage::sFreezeImageScalingDown = (BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) < 0.75f * sMaxBoundTextureMemInMegaBytes * texmem_middle_bound_scale) &&
|
|
(BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) < 0.75f * sMaxTotalTextureMemInMegaBytes * texmem_middle_bound_scale) ;
|
|
}
|
|
|
|
// static
|
|
LLViewerImage* LLViewerImage::getImage(const LLUUID& image_id)
|
|
{
|
|
return gImageList.getImage(image_id);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
const U32 LLViewerImage::sCurrentFileVersion = 1;
|
|
|
|
LLViewerImage::LLViewerImage(const LLUUID& id, const LLHost& host, BOOL usemipmaps)
|
|
: LLImageGL(usemipmaps),
|
|
mID(id),
|
|
mTargetHost(host)
|
|
{
|
|
init(true);
|
|
sImageCount++;
|
|
if (host != LLHost::invalid)
|
|
{
|
|
mCanUseHTTP = false; // this is a baked texture
|
|
}
|
|
}
|
|
|
|
LLViewerImage::LLViewerImage(const std::string& url, const LLUUID& id, BOOL usemipmaps)
|
|
: LLImageGL(usemipmaps),
|
|
mID(id),
|
|
mUrl(url)
|
|
{
|
|
init(true);
|
|
sImageCount++;
|
|
}
|
|
|
|
|
|
LLViewerImage::LLViewerImage(const U32 width, const U32 height, const U8 components, BOOL usemipmaps)
|
|
: LLImageGL(width, height, components, usemipmaps)
|
|
{
|
|
init(true);
|
|
// Create an empty image of the specified size and width
|
|
mID.generate();
|
|
mFullyLoaded = TRUE;
|
|
sImageCount++;
|
|
}
|
|
|
|
LLViewerImage::LLViewerImage(const LLImageRaw* raw, BOOL usemipmaps)
|
|
: LLImageGL(raw, usemipmaps)
|
|
{
|
|
init(true);
|
|
// Create an empty image of the specified size and width
|
|
mID.generate();
|
|
mFullyLoaded = TRUE;
|
|
sImageCount++;
|
|
}
|
|
|
|
void LLViewerImage::init(bool firstinit)
|
|
{
|
|
mFullWidth = 0;
|
|
mFullHeight = 0;
|
|
mOrigWidth = 0;
|
|
mOrigHeight = 0;
|
|
mNeedsAux = FALSE;
|
|
mTexelsPerImage = 64.f*64.f;
|
|
mMaxVirtualSize = 0.f;
|
|
mDiscardVirtualSize = 0.f;
|
|
mRequestedDiscardLevel = -1;
|
|
mRequestedDownloadPriority = 0.f;
|
|
mFullyLoaded = FALSE;
|
|
mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1;
|
|
mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1;
|
|
mCalculatedDiscardLevel = -1.f;
|
|
|
|
mDecodingAux = FALSE;
|
|
|
|
mKnownDrawWidth = 0;
|
|
mKnownDrawHeight = 0;
|
|
|
|
if (firstinit)
|
|
{
|
|
mDecodePriority = 0.f;
|
|
mInImageList = 0;
|
|
}
|
|
mIsMediaTexture = FALSE;
|
|
|
|
mBoostLevel = LLViewerImageBoostLevel::BOOST_NONE;
|
|
|
|
// Only set mIsMissingAsset true when we know for certain that the database
|
|
// does not contain this image.
|
|
mIsMissingAsset = FALSE;
|
|
|
|
mNeedsCreateTexture = FALSE;
|
|
|
|
mIsRawImageValid = FALSE;
|
|
mRawDiscardLevel = INVALID_DISCARD_LEVEL;
|
|
mMinDiscardLevel = 0;
|
|
|
|
mHasFetcher = FALSE;
|
|
mIsFetching = FALSE;
|
|
mFetchState = 0;
|
|
mFetchPriority = 0;
|
|
mDownloadProgress = 0.f;
|
|
mFetchDeltaTime = 999999.f;
|
|
mDecodeFrame = 0;
|
|
mVisibleFrame = 0;
|
|
mForSculpt = FALSE ;
|
|
mCachedRawImage = NULL ;
|
|
mCachedRawDiscardLevel = -1 ;
|
|
mCachedRawImageReady = FALSE ;
|
|
mNeedsResetMaxVirtualSize = FALSE ;
|
|
|
|
mForceToSaveRawImage = FALSE ;
|
|
mSavedRawDiscardLevel = -1 ;
|
|
mDesiredSavedRawDiscardLevel = -1 ;
|
|
|
|
mCanUseHTTP = true; //default on if cap/settings allows us
|
|
}
|
|
|
|
// virtual
|
|
void LLViewerImage::dump()
|
|
{
|
|
LLImageGL::dump();
|
|
|
|
llinfos << "LLViewerImage"
|
|
<< " mID " << mID
|
|
<< " mIsMissingAsset " << (S32)mIsMissingAsset
|
|
<< " mFullWidth " << mFullWidth
|
|
<< " mFullHeight " << mFullHeight
|
|
<< " mOrigWidth" << mOrigWidth
|
|
<< " mOrigHeight" << mOrigHeight
|
|
<< llendl;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
LLViewerImage::~LLViewerImage()
|
|
{
|
|
if (mHasFetcher)
|
|
{
|
|
LLAppViewer::getTextureFetch()->deleteRequest(getID(), true);
|
|
}
|
|
// Explicitly call LLViewerImage::cleanup since we're in a destructor and cleanup is virtual
|
|
LLViewerImage::cleanup();
|
|
sImageCount--;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLViewerImage::cleanup()
|
|
{
|
|
mFaceList.clear() ;
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
LLLoadedCallbackEntry *entryp = *iter++;
|
|
// We never finished loading the image. Indicate failure.
|
|
// Note: this allows mLoadedCallbackUserData to be cleaned up.
|
|
entryp->mCallback( FALSE, this, NULL, NULL, 0, TRUE, entryp->mUserData );
|
|
delete entryp;
|
|
}
|
|
mLoadedCallbackList.clear();
|
|
mNeedsAux = FALSE;
|
|
|
|
// Clean up image data
|
|
destroyRawImage();
|
|
mCachedRawImage = NULL ;
|
|
mCachedRawDiscardLevel = -1 ;
|
|
mCachedRawImageReady = FALSE ;
|
|
// LLImageGL::cleanup will get called more than once when this is used in the destructor.
|
|
LLImageGL::cleanup();
|
|
}
|
|
|
|
void LLViewerImage::reinit(BOOL usemipmaps /* = TRUE */)
|
|
{
|
|
LLViewerImage::cleanup();
|
|
LLImageGL::init(usemipmaps);
|
|
init(false);
|
|
setSize(0,0,0);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// ONLY called from LLViewerImageList
|
|
void LLViewerImage::destroyTexture()
|
|
{
|
|
if(sGlobalTextureMemoryInBytes < sMaxDesiredTextureMemInBytes)//not ready to release unused memory.
|
|
{
|
|
return ;
|
|
}
|
|
if (mNeedsCreateTexture)//return if in the process of generating a new texture.
|
|
{
|
|
return ;
|
|
}
|
|
|
|
destroyGLTexture() ;
|
|
}
|
|
|
|
void LLViewerImage::addToCreateTexture()
|
|
{
|
|
if(isForSculptOnly())
|
|
{
|
|
//just update some variables, not to create a real GL texture.
|
|
createGLTexture(mRawDiscardLevel, mRawImage, 0, FALSE) ;
|
|
mNeedsCreateTexture = FALSE ;
|
|
destroyRawImage();
|
|
}
|
|
else
|
|
{
|
|
#if 1
|
|
//
|
|
//if mRequestedDiscardLevel > mDesiredDiscardLevel, we assume the required image res keep going up,
|
|
//so do not scale down the over qualified image.
|
|
//Note: scaling down image is expensensive. Do it only when very necessary.
|
|
//
|
|
if(mRequestedDiscardLevel <= mDesiredDiscardLevel)
|
|
{
|
|
S32 w = mFullWidth >> mRawDiscardLevel;
|
|
S32 h = mFullHeight >> mRawDiscardLevel;
|
|
|
|
//if big image, do not load extra data
|
|
//scale it down to size >= LLViewerImage::sMinLargeImageSize
|
|
if(w * h > LLViewerImage::sMinLargeImageSize)
|
|
{
|
|
S32 d_level = llmin(mRequestedDiscardLevel, (S32)mDesiredDiscardLevel) - mRawDiscardLevel ;
|
|
|
|
if(d_level > 0)
|
|
{
|
|
S32 i = 0 ;
|
|
while((d_level > 0) && ((w >> i) * (h >> i) > LLViewerImage::sMinLargeImageSize))
|
|
{
|
|
i++;
|
|
d_level--;
|
|
}
|
|
if(i > 0)
|
|
{
|
|
mRawDiscardLevel += i ;
|
|
if(mRawDiscardLevel >= getDiscardLevel() && getDiscardLevel() > 0)
|
|
{
|
|
mNeedsCreateTexture = FALSE ;
|
|
destroyRawImage();
|
|
return ;
|
|
}
|
|
mRawImage->scale(w >> i, h >> i) ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
mNeedsCreateTexture = TRUE;
|
|
gImageList.mCreateTextureList.insert(this);
|
|
}
|
|
return ;
|
|
}
|
|
|
|
// ONLY called from LLViewerImageList
|
|
BOOL LLViewerImage::createTexture(S32 usename/*= 0*/)
|
|
{
|
|
if (!mNeedsCreateTexture)
|
|
{
|
|
destroyRawImage();
|
|
return FALSE;
|
|
}
|
|
mNeedsCreateTexture = FALSE;
|
|
if (mRawImage.isNull())
|
|
{
|
|
llerrs << "LLViewerImage trying to create texture with no Raw Image" << llendl;
|
|
}
|
|
// llinfos << llformat("IMAGE Creating (%d) [%d x %d] Bytes: %d ",
|
|
// mRawDiscardLevel,
|
|
// mRawImage->getWidth(), mRawImage->getHeight(),mRawImage->getDataSize())
|
|
// << mID.getString() << llendl;
|
|
BOOL res = TRUE;
|
|
if (!gNoRender)
|
|
{
|
|
// store original size only for locally-sourced images
|
|
if (mUrl.compare(0, 7, "file://") == 0)
|
|
{
|
|
mOrigWidth = mRawImage->getWidth();
|
|
mOrigHeight = mRawImage->getHeight();
|
|
|
|
// leave black border, do not scale image content
|
|
mRawImage->expandToPowerOfTwo(MAX_IMAGE_SIZE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
mOrigWidth = mFullWidth;
|
|
mOrigHeight = mFullHeight;
|
|
}
|
|
|
|
bool size_okay = true;
|
|
|
|
U32 raw_width = mRawImage->getWidth() << mRawDiscardLevel;
|
|
U32 raw_height = mRawImage->getHeight() << mRawDiscardLevel;
|
|
|
|
if( raw_width > MAX_IMAGE_SIZE || raw_height > MAX_IMAGE_SIZE )
|
|
{
|
|
llinfos << "Width or height is greater than " << MAX_IMAGE_SIZE << ": (" << raw_width << "," << raw_height << ")" << llendl;
|
|
size_okay = false;
|
|
}
|
|
|
|
if (!LLImageGL::checkSize(mRawImage->getWidth(), mRawImage->getHeight()))
|
|
{
|
|
// A non power-of-two image was uploaded (through a non standard client)
|
|
llinfos << "Non power of two width or height: (" << mRawImage->getWidth() << "," << mRawImage->getHeight() << ")" << llendl;
|
|
size_okay = false;
|
|
}
|
|
|
|
if( !size_okay )
|
|
{
|
|
// An inappropriately-sized image was uploaded (through a non standard client)
|
|
// We treat these images as missing assets which causes them to
|
|
// be renderd as 'missing image' and to stop requesting data
|
|
setIsMissingAsset();
|
|
destroyRawImage();
|
|
return FALSE;
|
|
}
|
|
|
|
// <edit>
|
|
CommentCacheReadResponder* responder = new CommentCacheReadResponder(this);
|
|
LLAppViewer::getTextureCache()->readFromCache(getID(),LLWorkerThread::PRIORITY_HIGH,0,999999,responder);
|
|
// </edit>
|
|
|
|
res = LLImageGL::createGLTexture(mRawDiscardLevel, mRawImage, usename);
|
|
}
|
|
|
|
//
|
|
// Iterate through the list of image loading callbacks to see
|
|
// what sort of data they need.
|
|
//
|
|
// *TODO: Fix image callback code
|
|
BOOL imageraw_callbacks = FALSE;
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
LLLoadedCallbackEntry *entryp = *iter++;
|
|
if (entryp->mNeedsImageRaw)
|
|
{
|
|
imageraw_callbacks = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!imageraw_callbacks)
|
|
{
|
|
mNeedsAux = FALSE;
|
|
destroyRawImage();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
void LLViewerImage::addTextureStats(F32 virtual_size, BOOL needs_gltexture) const
|
|
{
|
|
if(needs_gltexture)
|
|
{
|
|
mNeedsGLTexture = TRUE ;
|
|
}
|
|
|
|
if(mNeedsResetMaxVirtualSize)
|
|
{
|
|
//flag to reset the values because the old values are used.
|
|
mNeedsResetMaxVirtualSize = FALSE ;
|
|
mMaxVirtualSize = virtual_size;
|
|
mAdditionalDecodePriority = 0.f ;
|
|
mNeedsGLTexture = needs_gltexture ;
|
|
}
|
|
else if (virtual_size > mMaxVirtualSize)
|
|
{
|
|
mMaxVirtualSize = virtual_size;
|
|
}
|
|
}
|
|
|
|
void LLViewerImage::resetTextureStats()
|
|
{
|
|
mMaxVirtualSize = 0.0f;
|
|
mAdditionalDecodePriority = 0.f ;
|
|
mNeedsResetMaxVirtualSize = FALSE ;
|
|
}
|
|
|
|
BOOL LLViewerImage::isUpdateFrozen()
|
|
{
|
|
return LLViewerImage::sFreezeImageScalingDown && !getDiscardLevel() ;
|
|
}
|
|
|
|
BOOL LLViewerImage::isLargeImage()
|
|
{
|
|
return mTexelsPerImage > LLViewerImage::sMinLargeImageSize ;
|
|
}
|
|
|
|
// This is gauranteed to get called periodically for every texture
|
|
void LLViewerImage::processTextureStats()
|
|
{
|
|
//no need to update if the texture reaches its highest res and the memory is sufficient.
|
|
//if(isUpdateFrozen())
|
|
//{
|
|
// return ;
|
|
//}
|
|
|
|
updateVirtualSize() ;
|
|
// Generate the request priority and render priority
|
|
if (mDontDiscard || !getUseMipMaps())
|
|
{
|
|
mDesiredDiscardLevel = 0;
|
|
if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT)
|
|
mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048
|
|
}
|
|
else if (mBoostLevel < LLViewerImageBoostLevel::BOOST_HIGH && mMaxVirtualSize <= 10.f)
|
|
{
|
|
// If the image has not been significantly visible in a while, we don't want it
|
|
mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1));
|
|
}
|
|
else if ((!mFullWidth && !getCurrentWidth()) || (!mFullHeight && !getCurrentHeight()))
|
|
{
|
|
mDesiredDiscardLevel = mMaxDiscardLevel;
|
|
}
|
|
else
|
|
{
|
|
//static const F64 log_2 = log(2.0);
|
|
static const F64 log_4 = log(4.0);
|
|
|
|
S32 fullwidth = llmin(mFullWidth,(S32)MAX_IMAGE_SIZE_DEFAULT);
|
|
S32 fullheight = llmin(mFullHeight,(S32)MAX_IMAGE_SIZE_DEFAULT);
|
|
mTexelsPerImage = (F32)fullwidth * fullheight;
|
|
F32 discard_level = 0.f;
|
|
|
|
// If we know the output width and height, we can force the discard
|
|
// level to the correct value, and thus not decode more texture
|
|
// data than we need to.
|
|
if (mBoostLevel == LLViewerImageBoostLevel::BOOST_UI ||
|
|
mBoostLevel == LLViewerImageBoostLevel::BOOST_PREVIEW ||
|
|
mBoostLevel == LLViewerImageBoostLevel::BOOST_AVATAR_SELF) // JAMESDEBUG what about AVATAR_BAKED_SELF?
|
|
{
|
|
discard_level = 0; // full res
|
|
}
|
|
else if (mKnownDrawWidth && mKnownDrawHeight)
|
|
{
|
|
S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight;
|
|
|
|
// Use log_4 because we're in square-pixel space, so an image
|
|
// with twice the width and twice the height will have mTexelsPerImage
|
|
// 4 * draw_size
|
|
discard_level = (F32)(log(mTexelsPerImage/draw_texels) / log_4);
|
|
}
|
|
else
|
|
{
|
|
if(isLargeImage() && !isJustBound() && mAdditionalDecodePriority < 1.0f)
|
|
{
|
|
//if is a big image and not being used recently, nor close to the view point, do not load hi-res data.
|
|
mMaxVirtualSize = llmin(mMaxVirtualSize, (F32)LLViewerImage::sMinLargeImageSize) ;
|
|
}
|
|
|
|
if ((mCalculatedDiscardLevel >= 0.f) &&
|
|
(llabs(mMaxVirtualSize - mDiscardVirtualSize) < mMaxVirtualSize*.20f))
|
|
{
|
|
// < 20% change in virtual size = no change in desired discard
|
|
discard_level = mCalculatedDiscardLevel;
|
|
}
|
|
else
|
|
{
|
|
// Calculate the required scale factor of the image using pixels per texel
|
|
discard_level = (F32)(log(mTexelsPerImage/mMaxVirtualSize) / log_4);
|
|
mDiscardVirtualSize = mMaxVirtualSize;
|
|
mCalculatedDiscardLevel = discard_level;
|
|
}
|
|
}
|
|
if (mBoostLevel < LLViewerImageBoostLevel::BOOST_HIGH)
|
|
{
|
|
discard_level += sDesiredDiscardBias;
|
|
discard_level *= sDesiredDiscardScale; // scale
|
|
|
|
discard_level += sCameraMovingDiscardBias ;
|
|
}
|
|
discard_level = floorf(discard_level);
|
|
|
|
F32 min_discard = 0.f;
|
|
if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT)
|
|
min_discard = 1.f; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048
|
|
|
|
discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL);
|
|
|
|
// Can't go higher than the max discard level
|
|
mDesiredDiscardLevel = llmin((S32)mMaxDiscardLevel+1, (S32)discard_level);
|
|
// Clamp to min desired discard
|
|
mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel);
|
|
|
|
//
|
|
// At this point we've calculated the quality level that we want,
|
|
// if possible. Now we check to see if we have it, and take the
|
|
// proper action if we don't.
|
|
//
|
|
S32 current_discard = getDiscardLevel();
|
|
if ((sDesiredDiscardBias > 0.0f) &&
|
|
(current_discard >= 0 && mDesiredDiscardLevel >= current_discard))
|
|
{
|
|
// Limit the amount of GL memory bound each frame
|
|
if ( (BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) > sMaxBoundTextureMemInMegaBytes * texmem_middle_bound_scale) &&
|
|
(!getBoundRecently() || mDesiredDiscardLevel >= mCachedRawDiscardLevel))
|
|
{
|
|
scaleDown() ;
|
|
}
|
|
// Only allow GL to have 2x the video card memory
|
|
else if ( (BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) > sMaxTotalTextureMemInMegaBytes*texmem_middle_bound_scale) &&
|
|
(!getBoundRecently() || mDesiredDiscardLevel >= mCachedRawDiscardLevel))
|
|
{
|
|
scaleDown() ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void LLViewerImage::updateVirtualSize()
|
|
{
|
|
#if 1
|
|
if(mNeedsResetMaxVirtualSize)
|
|
{
|
|
addTextureStats(0.f, FALSE) ;//reset
|
|
}
|
|
if(mFaceList.size() > 0)
|
|
{
|
|
for(std::list<LLFace*>::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter)
|
|
{
|
|
LLFace* facep = *iter ;
|
|
if(facep->getDrawable()->isRecentlyVisible())
|
|
{
|
|
addTextureStats(facep->getVirtualSize()) ;
|
|
setAdditionalDecodePriority(facep->getImportanceToCamera()) ;
|
|
}
|
|
}
|
|
}
|
|
mNeedsResetMaxVirtualSize = TRUE ;
|
|
#endif
|
|
}
|
|
void LLViewerImage::scaleDown()
|
|
{
|
|
if(getHasGLTexture() && mCachedRawDiscardLevel > getDiscardLevel())
|
|
{
|
|
switchToCachedImage() ;
|
|
}
|
|
}
|
|
|
|
//use the mCachedRawImage to (re)generate the gl texture.
|
|
void LLViewerImage::switchToCachedImage()
|
|
{
|
|
if(mCachedRawImage.notNull())
|
|
{
|
|
mRawImage = mCachedRawImage ;
|
|
|
|
if (getComponents() != mRawImage->getComponents())
|
|
{
|
|
// We've changed the number of components, so we need to move any
|
|
// objects using this pool to a different pool.
|
|
mComponents = mRawImage->getComponents();
|
|
gImageList.dirtyImage(this);
|
|
}
|
|
|
|
mIsRawImageValid = TRUE;
|
|
mRawDiscardLevel = mCachedRawDiscardLevel ;
|
|
gImageList.mCreateTextureList.insert(this);
|
|
mNeedsCreateTexture = TRUE;
|
|
}
|
|
}
|
|
//============================================================================
|
|
|
|
F32 LLViewerImage::calcDecodePriority()
|
|
{
|
|
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
|
if (mID == LLAppViewer::getTextureFetch()->mDebugID)
|
|
{
|
|
LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints
|
|
}
|
|
#endif
|
|
|
|
if (mNeedsCreateTexture)
|
|
{
|
|
return mDecodePriority; // no change while waiting to create
|
|
}
|
|
if(mForceToSaveRawImage)
|
|
{
|
|
return maxDecodePriority() ;
|
|
}
|
|
|
|
S32 cur_discard = getDiscardLevel();
|
|
|
|
//no need to update if the texture reaches its highest res and the memory is sufficient.
|
|
//if(LLViewerImage::sFreezeImageScalingDown && !cur_discard)
|
|
//{
|
|
// return -5.0f ;
|
|
//}
|
|
|
|
bool have_all_data = (cur_discard >= 0 && (cur_discard <= mDesiredDiscardLevel));
|
|
F32 pixel_priority = fsqrtf(mMaxVirtualSize);
|
|
const S32 MIN_NOT_VISIBLE_FRAMES = 30; // NOTE: this function is not called every frame
|
|
mDecodeFrame++;
|
|
if (pixel_priority > 0.f)
|
|
{
|
|
mVisibleFrame = mDecodeFrame;
|
|
}
|
|
|
|
F32 priority = 0.f;
|
|
if (mIsMissingAsset)
|
|
{
|
|
priority = 0.0f;
|
|
}
|
|
else if(mDesiredDiscardLevel >= cur_discard && cur_discard > -1)
|
|
{
|
|
priority = -1.0f ;
|
|
}
|
|
else if (!isJustBound() && mCachedRawImageReady)
|
|
{
|
|
priority = -1.0f;
|
|
}
|
|
else if(mCachedRawDiscardLevel > -1 && mDesiredDiscardLevel >= mCachedRawDiscardLevel)
|
|
{
|
|
priority = -1.0f;
|
|
}
|
|
else if (mDesiredDiscardLevel > mMaxDiscardLevel)
|
|
{
|
|
// Don't decode anything we don't need
|
|
priority = -1.0f;
|
|
}
|
|
else if (mBoostLevel == LLViewerImageBoostLevel::BOOST_UI && !have_all_data)
|
|
{
|
|
priority = 1.f;
|
|
}
|
|
else if (pixel_priority <= 0.f && !have_all_data)
|
|
{
|
|
// Not on screen but we might want some data
|
|
if (mBoostLevel > LLViewerImageBoostLevel::BOOST_HIGH)
|
|
{
|
|
// Always want high boosted images
|
|
priority = 1.f;
|
|
}
|
|
else if (mVisibleFrame == 0 || (mDecodeFrame - mVisibleFrame > MIN_NOT_VISIBLE_FRAMES))
|
|
{
|
|
// Don't decode anything that isn't visible unless it's important
|
|
priority = -2.0f;
|
|
}
|
|
else
|
|
{
|
|
// Leave the priority as-is
|
|
return mDecodePriority;
|
|
}
|
|
}
|
|
else if (cur_discard < 0)
|
|
{
|
|
// We don't have any data yet, so we don't know the size of the image, treat as 32x32
|
|
// priority = 900000.f;
|
|
static const F64 log_2 = log(2.0);
|
|
F32 desired = (F32)(log(32.0/pixel_priority) / log_2);
|
|
S32 ddiscard = MAX_DISCARD_LEVEL - (S32)desired + 1;
|
|
ddiscard = llclamp(ddiscard, 1, 9);
|
|
priority = ddiscard*100000.f;
|
|
}
|
|
else if ((mMinDiscardLevel > 0) && (cur_discard <= mMinDiscardLevel))
|
|
{
|
|
// larger mips are corrupted
|
|
priority = -3.0f;
|
|
}
|
|
else if (cur_discard <= mDesiredDiscardLevel)
|
|
{
|
|
priority = -4.0f;
|
|
}
|
|
else
|
|
{
|
|
// priority range = 100000-400000
|
|
S32 ddiscard = cur_discard - mDesiredDiscardLevel;
|
|
if (getDontDiscard())
|
|
{
|
|
ddiscard+=2;
|
|
}
|
|
else if (!getBoundRecently() && mBoostLevel == 0)
|
|
{
|
|
ddiscard-=2;
|
|
}
|
|
ddiscard = llclamp(ddiscard, 0, 4);
|
|
|
|
priority = ddiscard*100000.f;
|
|
}
|
|
if (priority > 0.0f)
|
|
{
|
|
// priority range = 100000-900000
|
|
pixel_priority = llclamp(pixel_priority, 0.0f, priority-1.f);
|
|
|
|
// priority range = [100000.f, 2000000.f]
|
|
if ( mBoostLevel > LLViewerImageBoostLevel::BOOST_HIGH)
|
|
{
|
|
priority = 1000000.f + pixel_priority + 1000.f * mBoostLevel;
|
|
}
|
|
else
|
|
{
|
|
priority += 0.f + pixel_priority + 1000.f * mBoostLevel;
|
|
}
|
|
|
|
// priority range = [2100000.f, 5000000.f] if mAdditionalDecodePriority > 1.0
|
|
if(mAdditionalDecodePriority > 1.0f)
|
|
{
|
|
priority += 2000000.f + mAdditionalDecodePriority ;
|
|
}
|
|
}
|
|
|
|
return priority;
|
|
}
|
|
|
|
// A value >= max value calculated above for normalization
|
|
//static
|
|
F32 LLViewerImage::maxDecodePriority()
|
|
{
|
|
return 6000000.f;
|
|
}
|
|
|
|
void LLViewerImage::setDecodePriority(F32 priority)
|
|
{
|
|
llassert(!mInImageList);
|
|
mDecodePriority = priority;
|
|
}
|
|
|
|
F32 LLViewerImage::maxAdditionalDecodePriority()
|
|
{
|
|
return 2000000.f;
|
|
}
|
|
void LLViewerImage::setAdditionalDecodePriority(F32 priority)
|
|
{
|
|
priority *= maxAdditionalDecodePriority();
|
|
if(mAdditionalDecodePriority < priority)
|
|
{
|
|
mAdditionalDecodePriority = priority;
|
|
}
|
|
}
|
|
//------------------------------------------------------------
|
|
|
|
void LLViewerImage::setBoostLevel(S32 level)
|
|
{
|
|
mBoostLevel = level;
|
|
|
|
if(gAuditTexture)
|
|
{
|
|
setCategory(mBoostLevel);
|
|
}
|
|
|
|
if(mBoostLevel != LLViewerImageBoostLevel::BOOST_NONE)
|
|
{
|
|
setNoDelete() ;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
bool LLViewerImage::updateFetch()
|
|
{
|
|
mFetchState = 0;
|
|
mFetchPriority = 0;
|
|
mFetchDeltaTime = 999999.f;
|
|
mRequestDeltaTime = 999999.f;
|
|
|
|
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
|
if (mID == LLAppViewer::getTextureFetch()->mDebugID)
|
|
{
|
|
LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints
|
|
}
|
|
#endif
|
|
|
|
if (mIsMediaTexture)
|
|
{
|
|
llassert_always(!mHasFetcher);
|
|
return false; // skip
|
|
}
|
|
if (mNeedsCreateTexture)
|
|
{
|
|
// We may be fetching still (e.g. waiting on write)
|
|
// but don't check until we've processed the raw data we have
|
|
return false;
|
|
}
|
|
if (mFullyLoaded)
|
|
{
|
|
llassert_always(!mHasFetcher);
|
|
return false;
|
|
}
|
|
if (mIsMissingAsset)
|
|
{
|
|
llassert_always(!mHasFetcher);
|
|
return false; // skip
|
|
}
|
|
if (!mLoadedCallbackList.empty() && mRawImage.notNull())
|
|
{
|
|
return false; // process any raw image data in callbacks before replacing
|
|
}
|
|
|
|
S32 current_discard = getDiscardLevel();
|
|
S32 desired_discard = getDesiredDiscardLevel();
|
|
F32 decode_priority = getDecodePriority();
|
|
decode_priority = llmax(decode_priority, 0.0f);
|
|
decode_priority = llmin(decode_priority, maxDecodePriority());
|
|
|
|
if (mIsFetching)
|
|
{
|
|
// Sets mRawDiscardLevel, mRawImage, mAuxRawImage
|
|
S32 fetch_discard = current_discard;
|
|
if(mForceToSaveRawImage)
|
|
{
|
|
if(fetch_discard >= 0)
|
|
{
|
|
fetch_discard = llmax(fetch_discard, mSavedRawDiscardLevel) ;
|
|
}
|
|
}
|
|
|
|
if (mRawImage.notNull()) sRawCount--;
|
|
if (mAuxRawImage.notNull()) sAuxCount--;
|
|
bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mRawImage, mAuxRawImage);
|
|
if (mRawImage.notNull()) sRawCount++;
|
|
if (mAuxRawImage.notNull()) sAuxCount++;
|
|
if (finished)
|
|
{
|
|
mIsFetching = FALSE;
|
|
}
|
|
else
|
|
{
|
|
mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority,
|
|
mFetchPriority, mFetchDeltaTime, mRequestDeltaTime);
|
|
}
|
|
|
|
// We may have data ready regardless of whether or not we are finished (e.g. waiting on write)
|
|
if (mRawImage.notNull())
|
|
{
|
|
mRawDiscardLevel = fetch_discard;
|
|
|
|
if ((mRawImage->getDataSize() > 0 && mRawDiscardLevel >= 0) &&
|
|
(current_discard < 0 || mRawDiscardLevel < current_discard))
|
|
{
|
|
if (getComponents() != mRawImage->getComponents())
|
|
{
|
|
// We've changed the number of components, so we need to move any
|
|
// objects using this pool to a different pool.
|
|
mComponents = mRawImage->getComponents();
|
|
gImageList.dirtyImage(this);
|
|
}
|
|
|
|
mFullWidth = mRawImage->getWidth() << mRawDiscardLevel;
|
|
mFullHeight = mRawImage->getHeight() << mRawDiscardLevel;
|
|
|
|
if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE)
|
|
{
|
|
//discard all oversized textures.
|
|
destroyRawImage();
|
|
setIsMissingAsset();
|
|
mRawDiscardLevel = INVALID_DISCARD_LEVEL ;
|
|
mIsFetching = FALSE ;
|
|
}
|
|
else
|
|
{
|
|
mIsRawImageValid = TRUE;
|
|
addToCreateTexture() ;
|
|
}
|
|
|
|
return TRUE ;
|
|
}
|
|
else
|
|
{
|
|
// Data is ready but we don't need it
|
|
// (received it already while fetcher was writing to disk)
|
|
destroyRawImage();
|
|
return false; // done
|
|
}
|
|
}
|
|
|
|
if (!mIsFetching)
|
|
{
|
|
if ((decode_priority > 0) && (mRawDiscardLevel < 0 || mRawDiscardLevel == INVALID_DISCARD_LEVEL))
|
|
{
|
|
// We finished but received no data
|
|
if (current_discard < 0)
|
|
{
|
|
setIsMissingAsset();
|
|
desired_discard = -1;
|
|
}
|
|
else
|
|
{
|
|
//llwarns << mID << ": Setting min discard to " << current_discard << llendl;
|
|
mMinDiscardLevel = current_discard;
|
|
desired_discard = current_discard;
|
|
}
|
|
destroyRawImage();
|
|
}
|
|
else if (mRawImage.isNull())
|
|
{
|
|
// We have data, but our fetch failed to return raw data
|
|
// *TODO: FIgure out why this is happening and fix it
|
|
destroyRawImage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLAppViewer::getTextureFetch()->updateRequestPriority(mID, decode_priority);
|
|
}
|
|
}
|
|
|
|
if (!mDontDiscard)
|
|
{
|
|
if (mBoostLevel == 0)
|
|
{
|
|
desired_discard = llmax(desired_discard, current_discard-1);
|
|
}
|
|
else
|
|
{
|
|
desired_discard = llmax(desired_discard, current_discard-2);
|
|
}
|
|
}
|
|
|
|
bool make_request = true;
|
|
if (decode_priority <= 0)
|
|
{
|
|
make_request = false;
|
|
}
|
|
else if (mNeedsCreateTexture || mIsMissingAsset)
|
|
{
|
|
make_request = false;
|
|
}
|
|
else if (current_discard >= 0 && current_discard <= mMinDiscardLevel)
|
|
{
|
|
make_request = false;
|
|
}
|
|
else if (!isJustBound() && mCachedRawImageReady)
|
|
{
|
|
make_request = false;
|
|
}
|
|
else
|
|
{
|
|
if (mIsFetching)
|
|
{
|
|
if (mRequestedDiscardLevel <= desired_discard)
|
|
{
|
|
make_request = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (current_discard >= 0 && current_discard <= desired_discard)
|
|
{
|
|
make_request = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (make_request)
|
|
{
|
|
S32 w=0, h=0, c=0;
|
|
if (current_discard >= 0)
|
|
{
|
|
w = getWidth(0);
|
|
h = getHeight(0);
|
|
c = getComponents();
|
|
}
|
|
|
|
// bypass texturefetch directly by pulling from LLTextureCache
|
|
bool fetch_request_created = false;
|
|
fetch_request_created = LLAppViewer::getTextureFetch()->createRequest(mUrl, getID(),getTargetHost(), decode_priority,
|
|
w, h, c, desired_discard, needsAux(), mCanUseHTTP);
|
|
|
|
if (fetch_request_created)
|
|
{
|
|
mHasFetcher = TRUE;
|
|
mIsFetching = TRUE;
|
|
mRequestedDiscardLevel = desired_discard;
|
|
|
|
mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority,
|
|
mFetchPriority, mFetchDeltaTime, mRequestDeltaTime);
|
|
}
|
|
|
|
// if createRequest() failed, we're finishing up a request for this UUID,
|
|
// wait for it to complete
|
|
}
|
|
else if (mHasFetcher && !mIsFetching)
|
|
{
|
|
// Only delete requests that haven't receeived any network data for a while
|
|
const F32 FETCH_IDLE_TIME = 5.f;
|
|
if (mLastPacketTimer.getElapsedTimeF32() > FETCH_IDLE_TIME)
|
|
{
|
|
// llinfos << "Deleting request: " << getID() << " Discard: " << current_discard << " <= min:" << mMinDiscardLevel << " or priority == 0: " << decode_priority << llendl;
|
|
LLAppViewer::getTextureFetch()->deleteRequest(getID(), true);
|
|
mHasFetcher = FALSE;
|
|
}
|
|
}
|
|
|
|
llassert_always(mRawImage.notNull() || (!mNeedsCreateTexture && !mIsRawImageValid));
|
|
|
|
return mIsFetching ? true : false;
|
|
}
|
|
|
|
//
|
|
//force to fetch a new raw image for this texture
|
|
//this function is to replace readBackRaw().
|
|
//
|
|
BOOL LLViewerImage::forceFetch()
|
|
{
|
|
if(!mForceToSaveRawImage)
|
|
{
|
|
return false ;
|
|
}
|
|
if (mIsMediaTexture)
|
|
{
|
|
mForceToSaveRawImage = false ;
|
|
llassert_always(!mHasFetcher);
|
|
return false; // skip
|
|
}
|
|
if (mIsMissingAsset)
|
|
{
|
|
mForceToSaveRawImage = false ;
|
|
llassert_always(!mHasFetcher);
|
|
return false; // skip
|
|
}
|
|
if (!mLoadedCallbackList.empty() && mRawImage.notNull())
|
|
{
|
|
return false; // process any raw image data in callbacks before replacing
|
|
}
|
|
if(mRawImage.notNull() && mRawDiscardLevel <= mDesiredSavedRawDiscardLevel)
|
|
{
|
|
return false ; // mRawImage is enough
|
|
}
|
|
if(mIsFetching)
|
|
{
|
|
return false ;
|
|
}
|
|
|
|
S32 desired_discard = mDesiredSavedRawDiscardLevel ;
|
|
S32 current_discard = getDiscardLevel();
|
|
|
|
bool fetch_request_created = false;
|
|
S32 w=0, h=0, c=0;
|
|
if (current_discard >= 0)
|
|
{
|
|
w = getWidth(0);
|
|
h = getHeight(0);
|
|
c = getComponents();
|
|
}
|
|
fetch_request_created = LLAppViewer::getTextureFetch()->createRequest(mUrl, getID(),getTargetHost(), maxDecodePriority(),
|
|
w, h, c, desired_discard, needsAux(), mCanUseHTTP);
|
|
|
|
if (fetch_request_created)
|
|
{
|
|
mHasFetcher = TRUE;
|
|
mIsFetching = TRUE;
|
|
// Set the image's decode priority to maxDecodePriority() too, or updateFetch() will set
|
|
// the request priority to 0 and terminate the fetch before we even started (SNOW-203).
|
|
gImageList.bumpToMaxDecodePriority(this);
|
|
mRequestedDiscardLevel = desired_discard ;
|
|
|
|
mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority,
|
|
mFetchPriority, mFetchDeltaTime, mRequestDeltaTime);
|
|
}
|
|
|
|
return mIsFetching ? true : false;
|
|
}
|
|
|
|
void LLViewerImage::setIsMissingAsset()
|
|
{
|
|
if (mUrl.empty())
|
|
{
|
|
llwarns << mID << ": Marking image as missing" << llendl;
|
|
}
|
|
else
|
|
{
|
|
llwarns << mUrl << ": Marking image as missing" << llendl;
|
|
}
|
|
if (mHasFetcher)
|
|
{
|
|
LLAppViewer::getTextureFetch()->deleteRequest(getID(), true);
|
|
mHasFetcher = FALSE;
|
|
mIsFetching = FALSE;
|
|
mFetchState = 0;
|
|
mFetchPriority = 0;
|
|
}
|
|
mIsMissingAsset = TRUE;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
void LLViewerImage::setLoadedCallback( loaded_callback_func loaded_callback,
|
|
S32 discard_level, BOOL keep_imageraw, BOOL needs_aux, void* userdata)
|
|
{
|
|
//
|
|
// Don't do ANYTHING here, just add it to the global callback list
|
|
//
|
|
if (mLoadedCallbackList.empty())
|
|
{
|
|
// Put in list to call this->doLoadedCallbacks() periodically
|
|
gImageList.mCallbackList.insert(this);
|
|
}
|
|
LLLoadedCallbackEntry* entryp = new LLLoadedCallbackEntry(loaded_callback, discard_level, keep_imageraw, userdata);
|
|
mLoadedCallbackList.push_back(entryp);
|
|
mNeedsAux |= needs_aux;
|
|
if (mNeedsAux && mAuxRawImage.isNull() && getDiscardLevel() >= 0)
|
|
{
|
|
// We need aux data, but we've already loaded the image, and it didn't have any
|
|
llwarns << "No aux data available for callback for image:" << getID() << llendl;
|
|
}
|
|
}
|
|
|
|
// this method is stupid, remove it if at all possible -Day
|
|
void LLViewerImage::setLoadedCallbackNoAux( loaded_callback_func loaded_callback,
|
|
S32 discard_level, BOOL keep_imageraw, BOOL needs_aux, void* userdata)
|
|
{
|
|
//
|
|
// Don't do ANYTHING here, just add it to the global callback list
|
|
//
|
|
if (mLoadedCallbackList.empty())
|
|
{
|
|
// Put in list to call this->doLoadedCallbacks() periodically
|
|
gImageList.mCallbackList.insert(this);
|
|
}
|
|
|
|
LLLoadedCallbackEntry* entryp = new LLLoadedCallbackEntry(loaded_callback, discard_level, keep_imageraw, userdata);
|
|
mLoadedCallbackList.push_back(entryp);
|
|
mNeedsAux = needs_aux;
|
|
|
|
if (mNeedsAux && mAuxRawImage.isNull() && getDiscardLevel() >= 0)
|
|
{
|
|
// We need aux data, but we've already loaded the image, and it didn't have any
|
|
llwarns << "No aux data available for callback for image:" << getID() << llendl;
|
|
}
|
|
}
|
|
// </edit>
|
|
|
|
bool LLViewerImage::doLoadedCallbacks()
|
|
{
|
|
if (mNeedsCreateTexture)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool res = false;
|
|
|
|
if (isMissingAsset())
|
|
{
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
LLLoadedCallbackEntry *entryp = *iter++;
|
|
// We never finished loading the image. Indicate failure.
|
|
// Note: this allows mLoadedCallbackUserData to be cleaned up.
|
|
entryp->mCallback(FALSE, this, NULL, NULL, 0, TRUE, entryp->mUserData);
|
|
delete entryp;
|
|
}
|
|
mLoadedCallbackList.clear();
|
|
|
|
// Remove ourself from the global list of textures with callbacks
|
|
gImageList.mCallbackList.erase(this);
|
|
}
|
|
|
|
S32 gl_discard = getDiscardLevel();
|
|
|
|
// If we don't have a legit GL image, set it to be lower than the worst discard level
|
|
if (gl_discard == -1)
|
|
{
|
|
gl_discard = MAX_DISCARD_LEVEL + 1;
|
|
}
|
|
|
|
//
|
|
// Determine the quality levels of textures that we can provide to callbacks
|
|
// and whether we need to do decompression/readback to get it
|
|
//
|
|
S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard
|
|
S32 best_raw_discard = gl_discard; // Current GL quality level
|
|
S32 current_aux_discard = MAX_DISCARD_LEVEL + 1;
|
|
S32 best_aux_discard = MAX_DISCARD_LEVEL + 1;
|
|
|
|
if (mIsRawImageValid)
|
|
{
|
|
// If we have an existing raw image, we have a baseline for the raw and auxiliary quality levels.
|
|
best_raw_discard = llmin(best_raw_discard, mRawDiscardLevel);
|
|
best_aux_discard = llmin(best_aux_discard, mRawDiscardLevel); // We always decode the aux when we decode the base raw
|
|
current_aux_discard = llmin(current_aux_discard, best_aux_discard);
|
|
}
|
|
else
|
|
{
|
|
// We have no data at all, we need to get it
|
|
// Do this by forcing the best aux discard to be 0.
|
|
best_aux_discard = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// See if any of the callbacks would actually run using the data that we can provide,
|
|
// and also determine if we need to perform any readbacks or decodes.
|
|
//
|
|
bool run_gl_callbacks = false;
|
|
bool run_raw_callbacks = false;
|
|
bool need_readback = false;
|
|
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
LLLoadedCallbackEntry *entryp = *iter++;
|
|
if (entryp->mNeedsImageRaw)
|
|
{
|
|
if (mNeedsAux)
|
|
{
|
|
//
|
|
// Need raw and auxiliary channels
|
|
//
|
|
if (entryp->mLastUsedDiscard > current_aux_discard)
|
|
{
|
|
// We have useful data, run the callbacks
|
|
run_raw_callbacks = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (entryp->mLastUsedDiscard > current_raw_discard)
|
|
{
|
|
// We have useful data, just run the callbacks
|
|
run_raw_callbacks = true;
|
|
}
|
|
else if (entryp->mLastUsedDiscard > best_raw_discard)
|
|
{
|
|
// We can readback data, and then run the callbacks
|
|
need_readback = true;
|
|
run_raw_callbacks = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Needs just GL
|
|
if (entryp->mLastUsedDiscard > gl_discard)
|
|
{
|
|
// We have enough data, run this callback requiring GL data
|
|
run_gl_callbacks = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do a readback if required, OR start off a texture decode
|
|
//
|
|
if (need_readback && (mMaxDiscardLevel > gl_discard))
|
|
{
|
|
// Do a readback to get the GL data into the raw image
|
|
// We have GL data.
|
|
|
|
destroyRawImage();
|
|
readBackRawImage(gl_discard);
|
|
llassert_always(mRawImage.notNull());
|
|
llassert_always(!mNeedsAux || mAuxRawImage.notNull());
|
|
}
|
|
|
|
//
|
|
// Run raw/auxiliary data callbacks
|
|
//
|
|
if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= mMaxDiscardLevel))
|
|
{
|
|
// Do callbacks which require raw image data.
|
|
//llinfos << "doLoadedCallbacks raw for " << getID() << llendl;
|
|
|
|
// Call each party interested in the raw data.
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
callback_list_t::iterator curiter = iter++;
|
|
LLLoadedCallbackEntry *entryp = *curiter;
|
|
if (entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > mRawDiscardLevel))
|
|
{
|
|
// If we've loaded all the data there is to load or we've loaded enough
|
|
// to satisfy the interested party, then this is the last time that
|
|
// we're going to call them.
|
|
|
|
llassert_always(mRawImage.notNull());
|
|
if(mNeedsAux && mAuxRawImage.isNull())
|
|
{
|
|
llwarns << "Raw Image with no Aux Data for callback" << llendl;
|
|
}
|
|
BOOL final = mRawDiscardLevel <= entryp->mDesiredDiscard ? TRUE : FALSE;
|
|
//llinfos << "Running callback for " << getID() << llendl;
|
|
//llinfos << mRawImage->getWidth() << "x" << mRawImage->getHeight() << llendl;
|
|
if (final)
|
|
{
|
|
//llinfos << "Final!" << llendl;
|
|
}
|
|
entryp->mLastUsedDiscard = mRawDiscardLevel;
|
|
entryp->mCallback(TRUE, this, mRawImage, mAuxRawImage, mRawDiscardLevel, final, entryp->mUserData);
|
|
if (final)
|
|
{
|
|
iter = mLoadedCallbackList.erase(curiter);
|
|
delete entryp;
|
|
}
|
|
res = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Run GL callbacks
|
|
//
|
|
if (run_gl_callbacks && (gl_discard <= mMaxDiscardLevel))
|
|
{
|
|
//llinfos << "doLoadedCallbacks GL for " << getID() << llendl;
|
|
|
|
// Call the callbacks interested in GL data.
|
|
for(callback_list_t::iterator iter = mLoadedCallbackList.begin();
|
|
iter != mLoadedCallbackList.end(); )
|
|
{
|
|
callback_list_t::iterator curiter = iter++;
|
|
LLLoadedCallbackEntry *entryp = *curiter;
|
|
if (!entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > gl_discard))
|
|
{
|
|
BOOL final = gl_discard <= entryp->mDesiredDiscard ? TRUE : FALSE;
|
|
entryp->mLastUsedDiscard = gl_discard;
|
|
entryp->mCallback(TRUE, this, NULL, NULL, gl_discard, final, entryp->mUserData);
|
|
if (final)
|
|
{
|
|
iter = mLoadedCallbackList.erase(curiter);
|
|
delete entryp;
|
|
}
|
|
res = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have no callbacks, take us off of the image callback list.
|
|
//
|
|
if (mLoadedCallbackList.empty())
|
|
{
|
|
gImageList.mCallbackList.erase(this);
|
|
}
|
|
|
|
// Done with any raw image data at this point (will be re-created if we still have callbacks)
|
|
destroyRawImage();
|
|
|
|
return res;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
// Call with 0,0 to turn this feature off.
|
|
void LLViewerImage::setKnownDrawSize(S32 width, S32 height)
|
|
{
|
|
mKnownDrawWidth = width;
|
|
mKnownDrawHeight = height;
|
|
addTextureStats((F32)(width * height));
|
|
}
|
|
|
|
// virtual
|
|
bool LLViewerImage::bindError(S32 stage) const
|
|
{
|
|
if (stage < 0) return false;
|
|
|
|
if (gNoRender)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool res = true;
|
|
|
|
// On failure to bind, what should we set the currently bound texture to?
|
|
if (mIsMissingAsset && !sMissingAssetImagep.isNull() && (this != (LLImageGL *)sMissingAssetImagep))
|
|
{
|
|
res = gGL.getTexUnit(stage)->bind(sMissingAssetImagep.get());
|
|
}
|
|
if (!res && !sDefaultImagep.isNull() && (this != (LLImageGL *)sDefaultImagep))
|
|
{
|
|
// use default if we've got it
|
|
res = gGL.getTexUnit(stage)->bind(sDefaultImagep.get());
|
|
}
|
|
if (!res && !sNullImagep.isNull() && (this != (LLImageGL *)sNullImagep))
|
|
{
|
|
res = gGL.getTexUnit(stage)->bind(sNullImagep.get());
|
|
}
|
|
if (!res)
|
|
{
|
|
llwarns << "LLViewerImage::bindError failed." << llendl;
|
|
}
|
|
stop_glerror();
|
|
return res;
|
|
}
|
|
|
|
bool LLViewerImage::bindDefaultImage(S32 stage)
|
|
{
|
|
if (stage < 0) return false;
|
|
|
|
bool res = true;
|
|
if (!sDefaultImagep.isNull() && (this != (LLImageGL *)sDefaultImagep))
|
|
{
|
|
// use default if we've got it
|
|
res = gGL.getTexUnit(stage)->bind(sDefaultImagep.get());
|
|
}
|
|
if (!res && !sNullImagep.isNull() && (this != (LLImageGL *)sNullImagep))
|
|
{
|
|
res = gGL.getTexUnit(stage)->bind(sNullImagep.get());
|
|
}
|
|
if (!res)
|
|
{
|
|
llwarns << "LLViewerImage::bindError failed." << llendl;
|
|
}
|
|
stop_glerror();
|
|
|
|
//check if there is cached raw image and switch to it if possible
|
|
switchToCachedImage() ;
|
|
|
|
return res;
|
|
}
|
|
|
|
//virtual
|
|
void LLViewerImage::forceImmediateUpdate()
|
|
{
|
|
gImageList.bumpToMaxDecodePriority(this) ;
|
|
return ;
|
|
}
|
|
|
|
// Was in LLImageGL
|
|
LLImageRaw* LLViewerImage::readBackRawImage(S8 discard_level)
|
|
{
|
|
llassert_always(discard_level >= 0);
|
|
llassert_always(mComponents > 0);
|
|
if (mRawImage.notNull())
|
|
{
|
|
llerrs << "called with existing mRawImage" << llendl;
|
|
mRawImage = NULL;
|
|
}
|
|
|
|
if(mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= discard_level)
|
|
{
|
|
mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()) ;
|
|
mRawImage->copy(mSavedRawImage) ;
|
|
mRawDiscardLevel = discard_level ;
|
|
}
|
|
else
|
|
{
|
|
mRawImage = mCachedRawImage ;
|
|
mRawDiscardLevel = mCachedRawDiscardLevel;
|
|
}
|
|
|
|
sRawCount++;
|
|
mIsRawImageValid = TRUE;
|
|
|
|
return mRawImage;
|
|
}
|
|
|
|
void LLViewerImage::saveRawImage()
|
|
{
|
|
if(mRawImage.isNull() || mRawDiscardLevel > mDesiredSavedRawDiscardLevel)
|
|
{
|
|
forceFetch() ;
|
|
}
|
|
|
|
if(mRawImage.isNull() || mSavedRawDiscardLevel == mRawDiscardLevel)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
mSavedRawDiscardLevel = mRawDiscardLevel ;
|
|
mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()) ;
|
|
|
|
if(mSavedRawDiscardLevel <= mDesiredSavedRawDiscardLevel)
|
|
{
|
|
mForceToSaveRawImage = FALSE ;
|
|
}
|
|
}
|
|
|
|
void LLViewerImage::forceToSaveRawImage(S32 desired_discard)
|
|
{
|
|
mForceToSaveRawImage = TRUE ;
|
|
mDesiredSavedRawDiscardLevel = desired_discard ;
|
|
|
|
forceFetch() ;
|
|
}
|
|
void LLViewerImage::destroySavedRawImage()
|
|
{
|
|
mSavedRawImage = NULL ;
|
|
mForceToSaveRawImage = FALSE ;
|
|
mSavedRawDiscardLevel = -1 ;
|
|
mDesiredSavedRawDiscardLevel = -1 ;
|
|
}
|
|
|
|
void LLViewerImage::destroyRawImage()
|
|
{
|
|
if (mRawImage.notNull()) sRawCount--;
|
|
if (mAuxRawImage.notNull()) sAuxCount--;
|
|
|
|
if(mForceToSaveRawImage)
|
|
{
|
|
saveRawImage() ;
|
|
}
|
|
|
|
setCachedRawImage() ;
|
|
|
|
mRawImage = NULL;
|
|
mAuxRawImage = NULL;
|
|
mIsRawImageValid = FALSE;
|
|
mRawDiscardLevel = INVALID_DISCARD_LEVEL;
|
|
}
|
|
|
|
void LLViewerImage::setCachedRawImage()
|
|
{
|
|
if(mRawImage == mCachedRawImage)
|
|
{
|
|
return ;
|
|
}
|
|
if(!mIsRawImageValid)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
if(mCachedRawImageReady)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
if(mCachedRawDiscardLevel < 0 || mCachedRawDiscardLevel > mRawDiscardLevel)
|
|
{
|
|
S32 i = 0 ;
|
|
S32 w = mRawImage->getWidth() ;
|
|
S32 h = mRawImage->getHeight() ;
|
|
|
|
S32 max_size = MAX_CACHED_RAW_IMAGE_AREA ;
|
|
if(LLViewerImageBoostLevel::BOOST_TERRAIN == mBoostLevel)
|
|
{
|
|
max_size = MAX_CACHED_RAW_TERRAIN_IMAGE_AREA ;
|
|
}
|
|
if(mForSculpt)
|
|
{
|
|
max_size = MAX_CACHED_RAW_SCULPT_IMAGE_AREA ;
|
|
}
|
|
|
|
while(((w >> i) * (h >> i)) > max_size)
|
|
{
|
|
++i ;
|
|
}
|
|
mCachedRawImageReady = (!mRawDiscardLevel || ((w * h) >= max_size)) ;
|
|
|
|
if(i)
|
|
{
|
|
if(!(w >> i) || !(h >> i))
|
|
{
|
|
--i ;
|
|
}
|
|
mRawImage->scale(w >> i, h >> i) ;
|
|
}
|
|
mCachedRawImage = mRawImage ;
|
|
mCachedRawDiscardLevel = mRawDiscardLevel + i ;
|
|
}
|
|
}
|
|
|
|
void LLViewerImage::checkCachedRawSculptImage()
|
|
{
|
|
if(mCachedRawImageReady && mCachedRawDiscardLevel > 0)
|
|
{
|
|
if(mCachedRawImage->getWidth() * mCachedRawImage->getHeight() < MAX_CACHED_RAW_SCULPT_IMAGE_AREA)
|
|
{
|
|
mCachedRawImageReady = FALSE ;
|
|
}
|
|
else if(isForSculptOnly())
|
|
{
|
|
resetTextureStats() ; //do not update this image any more.
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL LLViewerImage::isForSculptOnly() const
|
|
{
|
|
return mForSculpt && !mNeedsGLTexture ;
|
|
}
|
|
|
|
void LLViewerImage::setForSculpt()
|
|
{
|
|
mForSculpt = TRUE ;
|
|
if(isForSculptOnly() && !getBoundRecently())
|
|
{
|
|
destroyGLTexture() ; //sculpt image does not need gl texture.
|
|
}
|
|
checkCachedRawSculptImage() ;
|
|
}
|
|
|
|
void LLViewerImage::addFace(LLFace* facep)
|
|
{
|
|
mFaceList.push_back(facep) ;
|
|
}
|
|
void LLViewerImage::removeFace(LLFace* facep)
|
|
{
|
|
mFaceList.remove(facep) ;
|
|
}
|