/** 
 * @file llviewertexture.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 "llviewertexture.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 "llimagegl.h"
#include "lldrawpool.h"
#include "lltexturefetch.h"
#include "llviewertexturelist.h"
#include "llviewercontrol.h"
#include "pipeline.h"
#include "llappviewer.h"
#include "llface.h"
#include "llviewercamera.h"
#include "lltextureatlas.h"
#include "lltextureatlasmanager.h"
#include "lltextureentry.h"
#include "llmediaentry.h"
#include "llvovolume.h"
#include "llviewermedia.h"
///////////////////////////////////////////////////////////////////////////////

// statics
LLPointer<LLViewerTexture>        LLViewerTexture::sNullImagep = NULL;
LLPointer<LLViewerFetchedTexture> LLViewerFetchedTexture::sMissingAssetImagep = NULL;
LLPointer<LLViewerFetchedTexture> LLViewerFetchedTexture::sWhiteImagep = NULL;
LLPointer<LLViewerFetchedTexture> LLViewerFetchedTexture::sDefaultImagep = NULL;
LLPointer<LLViewerFetchedTexture> LLViewerFetchedTexture::sSmokeImagep = NULL;
LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap ;
LLTexturePipelineTester* LLViewerTextureManager::sTesterp = NULL ;

S32 LLViewerTexture::sImageCount = 0;
S32 LLViewerTexture::sRawCount = 0;
S32 LLViewerTexture::sAuxCount = 0;
LLTimer LLViewerTexture::sEvaluationTimer;
F32 LLViewerTexture::sDesiredDiscardBias = 0.f;
F32 LLViewerTexture::sDesiredDiscardScale = 1.1f;
S32 LLViewerTexture::sBoundTextureMemoryInBytes = 0;
S32 LLViewerTexture::sTotalTextureMemoryInBytes = 0;
S32 LLViewerTexture::sMaxBoundTextureMemInMegaBytes = 0;
S32 LLViewerTexture::sMaxTotalTextureMemInMegaBytes = 0;
S32 LLViewerTexture::sMaxDesiredTextureMemInBytes = 0 ;
S8  LLViewerTexture::sCameraMovingDiscardBias = 0 ;
S32 LLViewerTexture::sMaxSculptRez = 128 ; //max sculpt image size
const S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64 ;
const S32 MAX_CACHED_RAW_SCULPT_IMAGE_AREA = LLViewerTexture::sMaxSculptRez * LLViewerTexture::sMaxSculptRez ;
const S32 MAX_CACHED_RAW_TERRAIN_IMAGE_AREA = 128 * 128 ;
S32 LLViewerTexture::sMinLargeImageSize = 65536 ; //256 * 256.
S32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA ;
BOOL LLViewerTexture::sFreezeImageScalingDown = FALSE ;
F32 LLViewerTexture::sCurrentTime = 0.0f ;
BOOL LLViewerTexture::sUseTextureAtlas        = FALSE ;

const F32 desired_discard_bias_min = -2.0f; // -max number of levels to improve image quality by
const F32 desired_discard_bias_max = 1.5f; // max number of levels to reduce image quality by
const F64 log_2 = log(2.0);

//----------------------------------------------------------------------------------------------
//namespace: LLViewerTextureAccess
//----------------------------------------------------------------------------------------------
LLViewerMediaTexture* LLViewerTextureManager::createMediaTexture(const LLUUID &media_id, BOOL usemipmaps, LLImageGL* gl_image)
{
	return new LLViewerMediaTexture(media_id, usemipmaps, gl_image) ;		
}
 
LLViewerTexture*  LLViewerTextureManager::findTexture(const LLUUID& id) 
{
	LLViewerTexture* tex ;
	//search fetched texture list
	tex = gTextureList.findImage(id) ;
	
	//search media texture list
	if(!tex)
	{
		tex = LLViewerTextureManager::findMediaTexture(id) ;
	}
	return tex ;
}
		
LLViewerMediaTexture* LLViewerTextureManager::findMediaTexture(const LLUUID &media_id)
{
	return LLViewerMediaTexture::findMediaTexture(media_id) ;	
}

LLViewerMediaTexture*  LLViewerTextureManager::getMediaTexture(const LLUUID& id, BOOL usemipmaps, LLImageGL* gl_image) 
{
	LLViewerMediaTexture* tex = LLViewerMediaTexture::findMediaTexture(id) ;	
	if(!tex)
	{
		tex = LLViewerTextureManager::createMediaTexture(id, usemipmaps, gl_image) ;
	}

	tex->initVirtualSize() ;

	return tex ;
}

LLViewerFetchedTexture* LLViewerTextureManager::staticCastToFetchedTexture(LLTexture* tex, BOOL report_error)
{
	if(!tex)
	{
		return NULL ;
	}

	S8 type = tex->getType() ;
	if(type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE)
	{
		return static_cast<LLViewerFetchedTexture*>(tex) ;
	}

	if(report_error)
	{
		llerrs << "not a fetched texture type: " << type << llendl ;
	}

	return NULL ;
}

LLPointer<LLViewerTexture> LLViewerTextureManager::getLocalTexture(BOOL usemipmaps, BOOL generate_gl_tex)
{
	LLPointer<LLViewerTexture> tex = new LLViewerTexture(usemipmaps) ;
	if(generate_gl_tex)
	{
		tex->generateGLTexture() ;
		tex->setCategory(LLViewerTexture::LOCAL) ;
	}
	return tex ;
}
LLPointer<LLViewerTexture> LLViewerTextureManager::getLocalTexture(const LLUUID& id, BOOL usemipmaps, BOOL generate_gl_tex) 
{
	LLPointer<LLViewerTexture> tex = new LLViewerTexture(id, usemipmaps) ;
	if(generate_gl_tex)
	{
		tex->generateGLTexture() ;
		tex->setCategory(LLViewerTexture::LOCAL) ;
	}
	return tex ;
}
LLPointer<LLViewerTexture> LLViewerTextureManager::getLocalTexture(const LLImageRaw* raw, BOOL usemipmaps) 
{
	LLPointer<LLViewerTexture> tex = new LLViewerTexture(raw, usemipmaps) ;
	tex->setCategory(LLViewerTexture::LOCAL) ;
	return tex ;
}
LLPointer<LLViewerTexture> LLViewerTextureManager::getLocalTexture(const U32 width, const U32 height, const U8 components, BOOL usemipmaps, BOOL generate_gl_tex) 
{
	LLPointer<LLViewerTexture> tex = new LLViewerTexture(width, height, components, usemipmaps) ;
	if(generate_gl_tex)
	{
		tex->generateGLTexture() ;
		tex->setCategory(LLViewerTexture::LOCAL) ;
	}
	return tex ;
}

LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(
	                                               const LLUUID &image_id,											       
												   BOOL usemipmaps,
												   LLViewerTexture::EBoostLevel boost_priority,
												   S8 texture_type,
												   LLGLint internal_format,
												   LLGLenum primary_format,
												   LLHost request_from_host)
{
	return gTextureList.getImage(image_id, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host) ;
}
	
LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromFile(
	                                               const std::string& filename,												   
												   BOOL usemipmaps,
												   LLViewerTexture::EBoostLevel boost_priority,
												   S8 texture_type,
												   LLGLint internal_format,
												   LLGLenum primary_format, 
												   const LLUUID& force_id)
{
	return gTextureList.getImageFromFile(filename, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id) ;
}

//static 
LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string& url,									 
									 BOOL usemipmaps,
									 LLViewerTexture::EBoostLevel boost_priority,
									 S8 texture_type,
									 LLGLint internal_format,
									 LLGLenum primary_format,
									 const LLUUID& force_id
									 )
{
	return gTextureList.getImageFromUrl(url, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id) ;
}

LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromHost(const LLUUID& image_id, LLHost host) 
{
	return gTextureList.getImageFromHost(image_id, host) ;
}

void LLViewerTextureManager::init()
{
	LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,3);
	raw->clear(0x77, 0x77, 0x77, 0xFF);
	LLViewerTexture::sNullImagep = LLViewerTextureManager::getLocalTexture(raw.get(), TRUE) ;

#if 1
	LLPointer<LLViewerFetchedTexture> imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT);
	LLViewerFetchedTexture::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);
	image_raw = NULL;
#else
 	LLViewerFetchedTexture::sDefaultImagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, TRUE, LLViewerTexture::BOOST_UI);
#endif
	LLViewerFetchedTexture::sDefaultImagep->dontDiscard();
	LLViewerFetchedTexture::sDefaultImagep->setCategory(LLViewerTexture::OTHER) ;

 	LLViewerFetchedTexture::sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE, TRUE, LLViewerTexture::BOOST_UI);
	LLViewerFetchedTexture::sSmokeImagep->setNoDelete() ;

	LLViewerTexture::initClass() ;

	if(LLFastTimer::sMetricLog)
	{
		LLViewerTextureManager::sTesterp = new LLTexturePipelineTester() ;
	}
}

void LLViewerTextureManager::cleanup()
{
	stop_glerror();

	LLImageGL::sDefaultGLTexture = NULL ;
	LLViewerTexture::sNullImagep = NULL;
	LLViewerFetchedTexture::sDefaultImagep = NULL;	
	LLViewerFetchedTexture::sSmokeImagep = NULL;
	LLViewerFetchedTexture::sMissingAssetImagep = NULL;
	LLViewerFetchedTexture::sWhiteImagep = NULL;

	LLViewerMediaTexture::cleanup() ;	

	LLViewerTexture::cleanupClass() ;
}

//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//start of LLViewerTexture
//----------------------------------------------------------------------------------------------
// static
void LLViewerTexture::initClass()
{
	LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture() ;

	if(gAuditTexture)
	{
		LLImageGL::setHighlightTexture(LLViewerTexture::OTHER) ;	
	}
}

// static
void LLViewerTexture::cleanupClass()
{
}

// static
S32 LLViewerTexture::getTotalNumOfCategories() 
{
	return MAX_GL_IMAGE_CATEGORY - (BOOST_HIGH - BOOST_SCULPTED) + 2 ;
}

// static
//index starts from zero.
S32 LLViewerTexture::getIndexFromCategory(S32 category) 
{
	return (category < BOOST_HIGH) ? category : category - (BOOST_HIGH - BOOST_SCULPTED) + 1 ;
}

//static 
S32 LLViewerTexture::getCategoryFromIndex(S32 index)
{
	return (index < BOOST_HIGH) ? index : index + (BOOST_HIGH - BOOST_SCULPTED) - 1 ;
}

// 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 LLViewerTexture::updateClass(const F32 velocity, const F32 angular_velocity)
{
	sCurrentTime = gFrameTimeSeconds ;

	if(LLViewerTextureManager::sTesterp)
	{
		LLViewerTextureManager::sTesterp->update() ;
	}
	LLViewerMediaTexture::updateClass() ;

	sBoundTextureMemoryInBytes = LLImageGL::sBoundTextureMemoryInBytes;//in bytes
	sTotalTextureMemoryInBytes = LLImageGL::sGlobalTextureMemoryInBytes;//in bytes
	sMaxBoundTextureMemInMegaBytes = gTextureList.getMaxResidentTexMem();//in MB	
	sMaxTotalTextureMemInMegaBytes = gTextureList.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, desired_discard_bias_min, desired_discard_bias_max);
	LLViewerTexture::sUseTextureAtlas = gSavedSettings.getBOOL("EnableTextureAtlas") ;
	
	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) ;

	LLViewerTexture::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) ;
}

//end of static functions
//-------------------------------------------------------------------------------------------
const U32 LLViewerTexture::sCurrentFileVersion = 1;

LLViewerTexture::LLViewerTexture(BOOL usemipmaps)
{
	init(true);
	mUseMipMaps = usemipmaps ;

	mID.generate();
	sImageCount++;
}

LLViewerTexture::LLViewerTexture(const LLUUID& id, BOOL usemipmaps)
	: mID(id)
{
	init(true);
	mUseMipMaps = usemipmaps ;
	
	sImageCount++;
}

LLViewerTexture::LLViewerTexture(const U32 width, const U32 height, const U8 components, BOOL usemipmaps) 
{
	init(true);

	mFullWidth = width ;
	mFullHeight = height ;
	mUseMipMaps = usemipmaps ;
	mComponents = components ;

	mID.generate();
	sImageCount++;
}

LLViewerTexture::LLViewerTexture(const LLImageRaw* raw, BOOL usemipmaps)	
{
	init(true);
	mUseMipMaps = usemipmaps ;
	mGLTexturep = new LLImageGL(raw, usemipmaps) ;
	
	// Create an empty image of the specified size and width
	mID.generate();
	sImageCount++;
}

LLViewerTexture::~LLViewerTexture()
{
	sImageCount--;
}

void LLViewerTexture::init(bool firstinit)
{
	mBoostLevel = LLViewerTexture::BOOST_NONE;

	mFullWidth = 0;
	mFullHeight = 0;
	mUseMipMaps = FALSE ;
	mComponents = 0 ;

	mTextureState = NO_DELETE ;
	mDontDiscard = FALSE;
	mMaxVirtualSize = 0.f;
	mNeedsGLTexture = FALSE ;
	mNeedsResetMaxVirtualSize = FALSE ;
	mAdditionalDecodePriority = 0.f ;	
	mParcelMedia = NULL ;
}

//virtual 
S8 LLViewerTexture::getType() const
{
	return LLViewerTexture::LOCAL_TEXTURE ;
}

void LLViewerTexture::cleanup()
{
	mFaceList.clear() ;
	
	if(mGLTexturep)
	{
		mGLTexturep->cleanup();
	}
}

// virtual
void LLViewerTexture::dump()
{
	if(mGLTexturep)
	{
		mGLTexturep->dump();
	}

	llinfos << "LLViewerTexture"
			<< " mID " << mID
			<< llendl;
}

void LLViewerTexture::setBoostLevel(S32 level)
{
	if(mBoostLevel != level)
	{
		mBoostLevel = level ;
		if(mBoostLevel != LLViewerTexture::BOOST_NONE)
		{
			setNoDelete() ;		
		}
		if(gAuditTexture)
		{
			setCategory(mBoostLevel);
		}
	}
}


bool LLViewerTexture::bindDefaultImage(S32 stage) 
{
	if (stage < 0) return false;

	bool res = true;
	if (LLViewerFetchedTexture::sDefaultImagep.notNull() && (this != LLViewerFetchedTexture::sDefaultImagep.get()))
	{
		// use default if we've got it
		res = gGL.getTexUnit(stage)->bind(LLViewerFetchedTexture::sDefaultImagep);
	}
	if (!res && LLViewerTexture::sNullImagep.notNull() && (this != LLViewerTexture::sNullImagep))
	{
		res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sNullImagep) ;
	}
	if (!res)
	{
		llwarns << "LLViewerTexture::bindDefaultImage failed." << llendl;
	}
	stop_glerror();

	//check if there is cached raw image and switch to it if possible
	switchToCachedImage() ;

	if(LLViewerTextureManager::sTesterp)
	{
		LLViewerTextureManager::sTesterp->updateGrayTextureBinding() ;
	}
	return res;
}

//virtual 
BOOL LLViewerTexture::isMissingAsset()const		
{ 
	return FALSE; 
}

//virtual 
void LLViewerTexture::forceImmediateUpdate() 
{
}

void LLViewerTexture::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 LLViewerTexture::resetTextureStats()
{
	mMaxVirtualSize = 0.0f;
	mAdditionalDecodePriority = 0.f ;	
	mNeedsResetMaxVirtualSize = FALSE ;
}

//virtual 
F32 LLViewerTexture::getMaxVirtualSize()
{
	return mMaxVirtualSize ;
}

//virtual 
void LLViewerTexture::setKnownDrawSize(S32 width, S32 height)
{
	//nothing here.
}

//virtual
void LLViewerTexture::addFace(LLFace* facep) 
{
	mFaceList.push_back(facep) ;
}

//virtual
void LLViewerTexture::removeFace(LLFace* facep) 
{
	mFaceList.remove(facep) ;
}

//virtual
void LLViewerTexture::switchToCachedImage()
{
	//nothing here.
}

void LLViewerTexture::forceActive()
{
	mTextureState = ACTIVE ; 
}

void LLViewerTexture::setActive() 
{ 
	if(mTextureState != NO_DELETE)
	{
		mTextureState = ACTIVE ; 
	}
}

//set the texture to stay in memory
void LLViewerTexture::setNoDelete() 
{ 
	mTextureState = NO_DELETE ;
}

void LLViewerTexture::generateGLTexture() 
{	
	if(mGLTexturep.isNull())
	{
		mGLTexturep = new LLImageGL(mFullWidth, mFullHeight, mComponents, mUseMipMaps) ;
	}
}

LLImageGL* LLViewerTexture::getGLTexture() const
{
	llassert_always(mGLTexturep.notNull()) ;
	
	return mGLTexturep ;
}

BOOL LLViewerTexture::createGLTexture() 
{
	if(mGLTexturep.isNull())
	{
		generateGLTexture() ;
	}

	return mGLTexturep->createGLTexture() ;
}

BOOL LLViewerTexture::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename, BOOL to_create, S32 category)
{
	llassert_always(mGLTexturep.notNull()) ;	

	BOOL ret = mGLTexturep->createGLTexture(discard_level, imageraw, usename, to_create, category) ;
	
	if(ret)
	{
		mFullWidth = mGLTexturep->getCurrentWidth() ;
		mFullHeight = mGLTexturep->getCurrentHeight() ; 
		mComponents = mGLTexturep->getComponents() ;
	}

	return ret ;
}

void LLViewerTexture::setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, BOOL swap_bytes)
{
	llassert_always(mGLTexturep.notNull()) ;
	
	mGLTexturep->setExplicitFormat(internal_format, primary_format, type_format, swap_bytes) ;
}
void LLViewerTexture::setAddressMode(LLTexUnit::eTextureAddressMode mode)
{
	llassert_always(mGLTexturep.notNull()) ;
	mGLTexturep->setAddressMode(mode) ;
}
void LLViewerTexture::setFilteringOption(LLTexUnit::eTextureFilterOptions option)
{
	llassert_always(mGLTexturep.notNull()) ;
	mGLTexturep->setFilteringOption(option) ;
}

//virtual
S32	LLViewerTexture::getWidth(S32 discard_level) const
{
	llassert_always(mGLTexturep.notNull()) ;
	return mGLTexturep->getWidth(discard_level) ;
}

//virtual
S32	LLViewerTexture::getHeight(S32 discard_level) const
{
	llassert_always(mGLTexturep.notNull()) ;
	return mGLTexturep->getHeight(discard_level) ;
}

S32 LLViewerTexture::getMaxDiscardLevel() const
{
	llassert_always(mGLTexturep.notNull()) ;
	return mGLTexturep->getMaxDiscardLevel() ;
}
S32 LLViewerTexture::getDiscardLevel() const
{
	llassert_always(mGLTexturep.notNull()) ;
	return mGLTexturep->getDiscardLevel() ;
}
S8  LLViewerTexture::getComponents() const 
{ 
	llassert_always(mGLTexturep.notNull()) ;
	
	return mGLTexturep->getComponents() ;
}

LLGLuint LLViewerTexture::getTexName() const 
{ 
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getTexName() ; 
}

BOOL LLViewerTexture::hasGLTexture() const 
{
	if(mGLTexturep.notNull())
	{
		return mGLTexturep->getHasGLTexture() ;
	}
	return FALSE ;
}

BOOL LLViewerTexture::getBoundRecently() const
{
	if(mGLTexturep.notNull())
	{
		return mGLTexturep->getBoundRecently() ;
	}
	return FALSE ;
}

LLTexUnit::eTextureType LLViewerTexture::getTarget(void) const
{
	llassert_always(mGLTexturep.notNull()) ;
	return mGLTexturep->getTarget() ;
}

BOOL LLViewerTexture::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height)
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->setSubImage(imageraw, x_pos, y_pos, width, height) ;
}

BOOL LLViewerTexture::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height)
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->setSubImage(datap, data_width, data_height, x_pos, y_pos, width, height) ;
}

void LLViewerTexture::setGLTextureCreated (bool initialized)
{
	llassert_always(mGLTexturep.notNull()) ;

	mGLTexturep->setGLTextureCreated (initialized) ;
}

void  LLViewerTexture::setCategory(S32 category) 
{
	llassert_always(mGLTexturep.notNull()) ;

	mGLTexturep->setCategory(category) ;
}

LLTexUnit::eTextureAddressMode LLViewerTexture::getAddressMode(void) const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getAddressMode() ;
}

S32 LLViewerTexture::getTextureMemory() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->mTextureMemory ;
}

LLGLenum LLViewerTexture::getPrimaryFormat() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getPrimaryFormat() ;
}

BOOL LLViewerTexture::getIsAlphaMask() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getIsAlphaMask() ;
}

BOOL LLViewerTexture::getMask(const LLVector2 &tc)
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getMask(tc) ;
}

F32 LLViewerTexture::getTimePassedSinceLastBound()
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getTimePassedSinceLastBound() ;
}
BOOL LLViewerTexture::getMissed() const 
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getMissed() ;
}

BOOL LLViewerTexture::isJustBound() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->isJustBound() ;
}

void LLViewerTexture::forceUpdateBindStats(void) const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->forceUpdateBindStats() ;
}

U32 LLViewerTexture::getTexelsInAtlas() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getTexelsInAtlas() ;
}

U32 LLViewerTexture::getTexelsInGLTexture() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getTexelsInGLTexture() ;
}

BOOL LLViewerTexture::isGLTextureCreated() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->isGLTextureCreated() ;
}

S32  LLViewerTexture::getDiscardLevelInAtlas() const
{
	llassert_always(mGLTexturep.notNull()) ;

	return mGLTexturep->getDiscardLevelInAtlas() ;
}

void LLViewerTexture::destroyGLTexture() 
{
	if(mGLTexturep.notNull() && mGLTexturep->getHasGLTexture())
	{
		mGLTexturep->destroyGLTexture() ;
		mTextureState = DELETED ;	
	}	
}

BOOL LLViewerTexture::isLargeImage()
{
	return mFullWidth * mFullHeight > LLViewerTexture::sMinLargeImageSize ;
}

//virtual 
void LLViewerTexture::updateBindStatsForTester()
{
	if(LLViewerTextureManager::sTesterp)
	{
		LLViewerTextureManager::sTesterp->updateTextureBindingStats(this) ;
	}
}

//----------------------------------------------------------------------------------------------
//end of LLViewerTexture
//----------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------
//start of LLViewerFetchedTexture
//----------------------------------------------------------------------------------------------

//static
F32 LLViewerFetchedTexture::maxDecodePriority()
{
	return 6000000.f;
}

LLViewerFetchedTexture::LLViewerFetchedTexture(const LLUUID& id, const LLHost& host, BOOL usemipmaps)
	: LLViewerTexture(id, usemipmaps),
	mTargetHost(host)
{
	init(TRUE) ;
	generateGLTexture() ;
}
	
LLViewerFetchedTexture::LLViewerFetchedTexture(const LLImageRaw* raw, BOOL usemipmaps)
	: LLViewerTexture(raw, usemipmaps)
{
	init(TRUE) ;
}
	
LLViewerFetchedTexture::LLViewerFetchedTexture(const std::string& url, const LLUUID& id, BOOL usemipmaps)
	: LLViewerTexture(id, usemipmaps),
	mUrl(url)
{
	init(TRUE) ;
	generateGLTexture() ;
}

void LLViewerFetchedTexture::init(bool firstinit)
{
	mOrigWidth = 0;
	mOrigHeight = 0;
	mNeedsAux = FALSE;
	mRequestedDiscardLevel = -1;
	mRequestedDownloadPriority = 0.f;
	mFullyLoaded = FALSE;
	mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1;
	mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1;
	
	mDecodingAux = FALSE;

	mKnownDrawWidth = 0;
	mKnownDrawHeight = 0;
	mKnownDrawSizeChanged = FALSE ;

	if (firstinit)
	{
		mDecodePriority = 0.f;
		mInImageList = 0;
	}

	// 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;
	mForSculpt = FALSE ;
	mIsFetched = FALSE ;

	mCachedRawImage = NULL ;
	mCachedRawDiscardLevel = -1 ;
	mCachedRawImageReady = FALSE ;

	mSavedRawImage = NULL ;
	mForceToSaveRawImage  = FALSE ;
	mSavedRawDiscardLevel = -1 ;
	mDesiredSavedRawDiscardLevel = -1 ;
	mLastReferencedSavedRawImageTime = 0.0f ;
}

LLViewerFetchedTexture::~LLViewerFetchedTexture()
{
	//*NOTE getTextureFetch can return NULL when Viewer is shutting down.
	// This is due to LLWearableList is singleton and is destroyed after 
	// LLAppViewer::cleanup() was called. (see ticket EXT-177)
	if (mHasFetcher && LLAppViewer::getTextureFetch())
	{
		LLAppViewer::getTextureFetch()->deleteRequest(getID(), true);
	}
	cleanup();	
}

//virtual 
S8 LLViewerFetchedTexture::getType() const
{
	return LLViewerTexture::FETCHED_TEXTURE ;
}

void LLViewerFetchedTexture::cleanup()
{
	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 ;
	mSavedRawImage = NULL ;
}

void LLViewerFetchedTexture::setForSculpt()
{
	mForSculpt = TRUE ;
	if(isForSculptOnly() && !getBoundRecently())
	{
		destroyGLTexture() ; //sculpt image does not need gl texture.
	}
	checkCachedRawSculptImage() ;
}

BOOL LLViewerFetchedTexture::isForSculptOnly() const
{
	return mForSculpt && !mNeedsGLTexture ;
}

BOOL LLViewerFetchedTexture::isDeleted()  
{ 
	return mTextureState == DELETED ; 
}

BOOL LLViewerFetchedTexture::isInactive()  
{ 
	return mTextureState == INACTIVE ; 
}

BOOL LLViewerFetchedTexture::isDeletionCandidate()  
{ 
	return mTextureState == DELETION_CANDIDATE ; 
}

void LLViewerFetchedTexture::setDeletionCandidate()  
{ 
	if(mGLTexturep.notNull() && mGLTexturep->getTexName() && (mTextureState == INACTIVE))
	{
		mTextureState = DELETION_CANDIDATE ;		
	}
}

//set the texture inactive
void LLViewerFetchedTexture::setInactive()
{
	if(mTextureState == ACTIVE && mGLTexturep.notNull() && mGLTexturep->getTexName() && !mGLTexturep->getBoundRecently())
	{
		mTextureState = INACTIVE ; 
	}
}

BOOL LLViewerFetchedTexture::isFullyLoaded() const
{
	// Unfortunately, the boolean "mFullyLoaded" is never updated correctly so we use that logic
	// to check if the texture is there and completely downloaded
	return (mFullWidth != 0) && (mFullHeight != 0) && !mIsFetching && !mHasFetcher;
}


// virtual
void LLViewerFetchedTexture::dump()
{
	LLViewerTexture::dump();

	llinfos << "Dump : " << mID 
			<< ", mIsMissingAsset = " << (S32)mIsMissingAsset
			<< ", mFullWidth = " << (S32)mFullWidth
			<< ", mFullHeight = " << (S32)mFullHeight
			<< ", mOrigWidth = " << (S32)mOrigWidth
			<< ", mOrigHeight = " << (S32)mOrigHeight
			<< llendl;
	llinfos << "     : " 
			<< " mFullyLoaded = " << (S32)mFullyLoaded
			<< ", mFetchState = " << (S32)mFetchState
			<< ", mFetchPriority = " << (S32)mFetchPriority
			<< ", mDownloadProgress = " << (F32)mDownloadProgress
			<< llendl;
	llinfos << "     : " 
			<< " mHasFetcher = " << (S32)mHasFetcher
			<< ", mIsFetching = " << (S32)mIsFetching
			<< ", mIsFetched = " << (S32)mIsFetched
			<< ", mBoostLevel = " << (S32)mBoostLevel
			<< llendl;
}

///////////////////////////////////////////////////////////////////////////////
// ONLY called from LLViewerFetchedTextureList
void LLViewerFetchedTexture::destroyTexture() 
{
	if(LLImageGL::sGlobalTextureMemoryInBytes < sMaxDesiredTextureMemInBytes)//not ready to release unused memory.
	{
		return ;
	}
	if (mNeedsCreateTexture)//return if in the process of generating a new texture.
	{
		return ;
	}
	
	destroyGLTexture() ;
	mFullyLoaded = FALSE ;
}

//
//do not change the discard level of the loaded texture image.
BOOL LLViewerFetchedTexture::keepReuestedDiscardLevel()
{
	if (!mLoadedCallbackList.empty())
	{
		return TRUE ;
	}

	return FALSE ;
}

void LLViewerFetchedTexture::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 && !keepReuestedDiscardLevel())
		{
			S32 w = mFullWidth >> mRawDiscardLevel;
			S32 h = mFullHeight >> mRawDiscardLevel;

			//if big image, do not load extra data
			//scale it down to size >= LLViewerTexture::sMinLargeImageSize
			if(w * h > LLViewerTexture::sMinLargeImageSize)
			{
				S32 d_level = llmin(mRequestedDiscardLevel, (S32)mDesiredDiscardLevel) - mRawDiscardLevel ;
				
				if(d_level > 0)
				{
					S32 i = 0 ;
					while((d_level > 0) && ((w >> i) * (h >> i) > LLViewerTexture::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;
		gTextureList.mCreateTextureList.insert(this);
	}	
	return ;
}

// ONLY called from LLViewerTextureList
BOOL LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/)
{
	if (!mNeedsCreateTexture)
	{
		destroyRawImage();
		return FALSE;
	}
	mNeedsCreateTexture	= FALSE;
	if (mRawImage.isNull())
	{
		llerrs << "LLViewerTexture 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);
			
			mFullWidth = mRawImage->getWidth();
			mFullHeight = mRawImage->getHeight();
		}
		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;
		}
		
		if(!(res = insertToAtlas()))
		{
			res = mGLTexturep->createGLTexture(mRawDiscardLevel, mRawImage, usename, TRUE, mBoostLevel);
			resetFaceAtlas() ;
		}
		setActive() ;
	}

	//
	// 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;
}

// Call with 0,0 to turn this feature off.
//virtual
void LLViewerFetchedTexture::setKnownDrawSize(S32 width, S32 height)
{
	if(mKnownDrawWidth != width || mKnownDrawHeight != height)
	{
		mKnownDrawWidth = width;
		mKnownDrawHeight = height;

		mKnownDrawSizeChanged = TRUE ;
		mFullyLoaded = FALSE ;
	}
	addTextureStats((F32)(width * height));
}

//virtual
void LLViewerFetchedTexture::processTextureStats()
{
	if(mFullyLoaded)//already loaded
	{
		return ;
	}

	updateVirtualSize() ;
	
	static LLCachedControl<bool> textures_fullres(gSavedSettings,"TextureLoadFullRes");
	
	if (textures_fullres)
	{
		mDesiredDiscardLevel = 0;
	}
	else if(!mFullWidth || !mFullHeight)
	{
		mDesiredDiscardLevel = 	getMaxDiscardLevel() ;
	}
	else
	{	
		if(!mKnownDrawWidth || !mKnownDrawHeight || mFullWidth <= mKnownDrawWidth || mFullHeight <= mKnownDrawHeight)
		{
			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
			{
				mDesiredDiscardLevel = 0;
			}
		}
		else if(mKnownDrawSizeChanged)//known draw size is set
		{			
			mDesiredDiscardLevel = (S8)llmin(log((F32)mFullWidth / mKnownDrawWidth) / log_2, 
					                             log((F32)mFullHeight / mKnownDrawHeight) / log_2) ;
			mDesiredDiscardLevel = 	llclamp(mDesiredDiscardLevel, (S8)0, (S8)getMaxDiscardLevel()) ;
		}
		mKnownDrawSizeChanged = FALSE ;
		
		if(getDiscardLevel() >= 0 && (getDiscardLevel() <= mDesiredDiscardLevel))
		{
			mFullyLoaded = TRUE ;
		}
	}
}

F32 LLViewerFetchedTexture::calcDecodePriority()
{
#ifndef LL_RELEASE_FOR_DOWNLOAD
	if (mID == LLAppViewer::getTextureFetch()->mDebugID)
	{
		LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints
	}
#endif
	
	if(mFullyLoaded)//already loaded for static texture
	{
		return -4.0f ; //alreay fetched
	}

	if (mNeedsCreateTexture)
	{
		return mDecodePriority; // no change while waiting to create
	}
	if(mForceToSaveRawImage)
	{
		return maxDecodePriority() ;
	}
	
	S32 cur_discard = getDiscardLevel();
	bool have_all_data = (cur_discard >= 0 && (cur_discard <= mDesiredDiscardLevel));
	F32 pixel_priority = fsqrtf(mMaxVirtualSize);

	F32 priority;
	if (mIsMissingAsset)
	{
		priority = 0.0f;
	}
	else if(mDesiredDiscardLevel >= cur_discard && cur_discard > -1)
	{
		priority = -1.0f ;
	}
	else if(mCachedRawDiscardLevel > -1 && mDesiredDiscardLevel >= mCachedRawDiscardLevel)
	{
		priority = -1.0f;
	}
	else if (mDesiredDiscardLevel > getMaxDiscardLevel())
	{
		// Don't decode anything we don't need
		priority = -1.0f;
	}
	else if ((mBoostLevel == LLViewerTexture::BOOST_UI || mBoostLevel == LLViewerTexture::BOOST_ICON) && !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 > BOOST_HIGH)
		{
			// Always want high boosted images
			priority = 1.f;
		}
		else
		{
			// Leave the priority as-is
			return mDecodePriority;
		}
	}
	else if (cur_discard < 0)
	{
		//texture does not have any data, so we don't know the size of the image, treat it like 32 * 32.
		// priority range = 100,000 - 500,000
		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;
		ddiscard = llclamp(ddiscard, 0, 4);
		priority = (ddiscard+1)*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 = 100,000 - 500,000
		S32 desired_discard = mDesiredDiscardLevel;
		if (getDontDiscard())
		{
			desired_discard -= 2;
		}
		else if (!isJustBound() && mCachedRawImageReady && !mBoostLevel)
		{
			// We haven't rendered this in the last half second, and we have a cached raw image, leave the desired discard as-is
			desired_discard = cur_discard;
		}
		else if (mGLTexturep.notNull() && !mGLTexturep->getBoundRecently() && mBoostLevel == LLViewerTexture::BOOST_NONE)
		{
			// We haven't rendered this in a while, de-prioritize it
			desired_discard += 2;
		}
		S32 ddiscard = cur_discard - desired_discard;
		ddiscard = llclamp(ddiscard, 0, 4);
		priority = (ddiscard+1)*100000.f;
	}

	// Priority Formula:
	// BOOST_HIGH  +  ADDITIONAL PRI + DELTA DISCARD + BOOST LEVEL + PIXELS
	// [10,000,000] + [1-9,000,000]  + [1-400,000]   + [1-20,000]  + [0-999]
	if (priority > 0.0f)
	{
		pixel_priority = llclamp(pixel_priority, 0.0f, 999.f); 

		priority = pixel_priority + 1000.f * mBoostLevel;

		if ( mBoostLevel > BOOST_HIGH)
		{
			priority += 10000000.f;
		}		
		else if(mAdditionalDecodePriority > 0.0f)
		{
			// 1-9
			S32 additional_priority = (S32)(1.0f + mAdditionalDecodePriority*8.0f + .5f); // round
			// priority range += 0-9,000,000
			priority += 1000000.f * (F32)additional_priority;
		}
	}
	return priority;
}
//============================================================================

void LLViewerFetchedTexture::setDecodePriority(F32 priority)
{
	llassert(!mInImageList);
	mDecodePriority = priority;
}

void LLViewerFetchedTexture::setAdditionalDecodePriority(F32 priority)
{
	priority = llclamp(priority, 0.f, 1.f);
	if(mAdditionalDecodePriority < priority)
	{
		mAdditionalDecodePriority = priority;
	}
}

void LLViewerFetchedTexture::updateVirtualSize() 
{	
	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 ;
}

bool LLViewerFetchedTexture::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 (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 (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);
	
	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())
		{
			if(LLViewerTextureManager::sTesterp)
			{
				mIsFetched = TRUE ;
				LLViewerTextureManager::sTesterp->updateTextureLoadingStats(this, mRawImage, LLAppViewer::getTextureFetch()->isFromLocalCache(mID)) ;
			}
			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();
					mGLTexturep->setComponents(mComponents) ;

					for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter)
					{
						(*iter)->dirtyTexture() ;
					}
				}			
				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.notNull())
			{
				// We have data, but our fetch failed to return raw data
				// *TODO: FIgure out why this is happening and fix it
				destroyRawImage();
			}
		}
		else
		{
// 			// Useful debugging code for undesired deprioritization of textures.
// 			if (decode_priority <= 0.0f && desired_discard >= 0 && desired_discard < current_discard)
// 			{
// 				llinfos << "Calling updateRequestPriority() with decode_priority = 0.0f" << llendl;
// 				calcDecodePriority();
// 			}
			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 = mGLTexturep->getWidth(0);
			h = mGLTexturep->getHeight(0);
			c = mComponents;
		}
		
		// 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());
		
		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
//
BOOL LLViewerFetchedTexture::forceFetch()
{
	if(!mForceToSaveRawImage)
	{
		return false ;
	}
	if(mDesiredSavedRawDiscardLevel < getDiscardLevel())
	{
		//no need to force fetching. normal fetching flow will do the work.
		//return false ;
	}
	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(mIsFetching)
	{
		return false ;
	}
	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
	}

	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());

	if (fetch_request_created)
	{				
		mHasFetcher = TRUE;
		mIsFetching = TRUE;
		mRequestedDiscardLevel = desired_discard ;

		mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority,
																	mFetchPriority, mFetchDeltaTime, mRequestDeltaTime);
	}	

	return mIsFetching ? true : false;
}

void LLViewerFetchedTexture::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 LLViewerFetchedTexture::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
		gTextureList.mCallbackList.insert(this);
		mLoadedCallbackDesiredDiscardLevel = (S8)discard_level;
	}
	else
	{
		mLoadedCallbackDesiredDiscardLevel = llmin(mLoadedCallbackDesiredDiscardLevel, (S8)discard_level) ;
	}

	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;
	}
}

bool LLViewerFetchedTexture::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
		gTextureList.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 && (getMaxDiscardLevel() > gl_discard))
	{
		// Do a readback to get the GL data into the raw image
		// We have GL data.

		destroyRawImage();
		reloadRawImage(mLoadedCallbackDesiredDiscardLevel);
		llassert_always(mRawImage.notNull());
		llassert_always(!mNeedsAux || mAuxRawImage.notNull());
	}

	//
	// Run raw/auxiliary data callbacks
	//
	if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= getMaxDiscardLevel()))
	{
		// 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 <= getMaxDiscardLevel()))
	{
		//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())
	{
		gTextureList.mCallbackList.erase(this);
	}

	// Done with any raw image data at this point (will be re-created if we still have callbacks)
	destroyRawImage();
	
	return res;
}

//virtual
void LLViewerFetchedTexture::forceImmediateUpdate()
{
	//only immediately update a deleted texture which is now being re-used.
	if(!isDeleted())
	{
		return ;
	}
	//if already called forceImmediateUpdate()
	if(mInImageList && mDecodePriority == LLViewerFetchedTexture::maxDecodePriority())
	{
		return ;
	}

	gTextureList.forceImmediateUpdate(this) ;
	return ;
}

LLImageRaw* LLViewerFetchedTexture::reloadRawImage(S8 discard_level)
{
	llassert_always(mGLTexturep.notNull()) ;
	llassert_always(discard_level >= 0);
	llassert_always(mComponents > 0);

	if (mRawImage.notNull())
	{
		//mRawImage is in use by somebody else, do not delete it.
		return NULL ;
	}

	if(mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= discard_level)
	{
		mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()) ;
		mRawImage->copy(getSavedRawImage()) ;
		mRawDiscardLevel = discard_level ;
	}
	else
	{		
		//force to fetch raw image again if cached raw image is not good enough.
		if(mCachedRawDiscardLevel > discard_level)
		{
			mRawImage = mCachedRawImage ;
			mRawDiscardLevel = mCachedRawDiscardLevel;

			forceToSaveRawImage(discard_level) ;
		}
		else //cached raw image is good enough, copy it.
		{
			mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()) ;
			mRawImage->copy(mCachedRawImage) ;
			mRawDiscardLevel = discard_level ;
		}
	}
	mIsRawImageValid = TRUE ;
	sRawCount++;	
	
	return mRawImage;
}

void LLViewerFetchedTexture::destroyRawImage()
{	
	if (mAuxRawImage.notNull()) sAuxCount--;

	if (mRawImage.notNull()) 
	{
		sRawCount--;
		setCachedRawImage() ;

		if(mForceToSaveRawImage)
		{
			saveRawImage() ;
		}		
	}

	mRawImage = NULL;
	mAuxRawImage = NULL;
	mIsRawImageValid = FALSE;
	mRawDiscardLevel = INVALID_DISCARD_LEVEL;

	if(mForceToSaveRawImage)
	{
		forceFetch() ;
	}
}

//use the mCachedRawImage to (re)generate the gl texture.
//virtual
void LLViewerFetchedTexture::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();
			mGLTexturep->setComponents(mComponents) ;
			gTextureList.dirtyImage(this);
		}			

		mIsRawImageValid = TRUE;
		mRawDiscardLevel = mCachedRawDiscardLevel ;
		gTextureList.mCreateTextureList.insert(this);
		mNeedsCreateTexture = TRUE;		
	}
}

void LLViewerFetchedTexture::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(LLViewerTexture::BOOST_TERRAIN == mBoostLevel)
		{
			max_size = MAX_CACHED_RAW_TERRAIN_IMAGE_AREA ;
		}		
		if(mForSculpt)
		{
			max_size = MAX_CACHED_RAW_SCULPT_IMAGE_AREA ;
			mCachedRawImageReady = !mRawDiscardLevel ;
		}
		else
		{
			mCachedRawImageReady = (!mRawDiscardLevel || ((w * h) >= max_size)) ;
		}

		while(((w >> i) * (h >> i)) > max_size)
		{
			++i ;
		}
		
		if(i)
		{
			if(!(w >> i) || !(h >> i))
			{
				--i ;
			}
			//if(mForSculpt)
			//{
			//	mRawImage->scaleDownWithoutBlending(w >> i, h >> i) ;
			//}
			//else
			{
				mRawImage->scale(w >> i, h >> i) ;
			}
		}
		mCachedRawImage = mRawImage ;
		mCachedRawDiscardLevel = mRawDiscardLevel + i ;			
	}
}

void LLViewerFetchedTexture::checkCachedRawSculptImage()
{
	if(mCachedRawImageReady && mCachedRawDiscardLevel > 0)
	{
		if(getDiscardLevel() != 0)
		{
			mCachedRawImageReady = FALSE ;
		}
		else if(isForSculptOnly())
		{
			resetTextureStats() ; //do not update this image any more.
		}
	}
}

void LLViewerFetchedTexture::saveRawImage() 
{
	if(mRawImage.isNull() || mSavedRawDiscardLevel == mRawDiscardLevel)
	{
		return ;
	}

	mSavedRawDiscardLevel = mRawDiscardLevel ;
	mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()) ;

	if(mSavedRawDiscardLevel <= mDesiredSavedRawDiscardLevel)
	{
		mForceToSaveRawImage = FALSE ;
	}

	mLastReferencedSavedRawImageTime = sCurrentTime ;
}

void LLViewerFetchedTexture::forceToSaveRawImage(S32 desired_discard) 
{ 
	if(!mForceToSaveRawImage && (mDesiredSavedRawDiscardLevel < 0 || mDesiredSavedRawDiscardLevel > desired_discard))
	{
		mForceToSaveRawImage = TRUE ;
		mDesiredSavedRawDiscardLevel = desired_discard ;
	
		forceFetch() ;
	}
}
void LLViewerFetchedTexture::destroySavedRawImage()
{
	mSavedRawImage = NULL ;
	mForceToSaveRawImage  = FALSE ;
	mSavedRawDiscardLevel = -1 ;
	mDesiredSavedRawDiscardLevel = -1 ;
	mLastReferencedSavedRawImageTime = 0.0f ;
}

LLImageRaw* LLViewerFetchedTexture::getSavedRawImage() 
{
	mLastReferencedSavedRawImageTime = sCurrentTime ;

	return mSavedRawImage ;
}
	
BOOL LLViewerFetchedTexture::hasSavedRawImage() const
{
	return mSavedRawImage.notNull() ;
}
	
F32 LLViewerFetchedTexture::getElapsedLastReferencedSavedRawImageTime() const
{ 
	return mLastReferencedSavedRawImageTime - sCurrentTime ;
}
//----------------------------------------------------------------------------------------------
//atlasing
//----------------------------------------------------------------------------------------------
void LLViewerFetchedTexture::resetFaceAtlas()
{
	//Nothing should be done here.
}

//invalidate all atlas slots for this image.
void LLViewerFetchedTexture::invalidateAtlas(BOOL rebuild_geom)
{
	for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter)
	{
		if(*iter)
		{
			LLFace* facep = (LLFace*)*iter ;
			facep->removeAtlas() ;
			if(rebuild_geom && facep->getDrawable() && facep->getDrawable()->getSpatialGroup())
			{
				facep->getDrawable()->getSpatialGroup()->setState(LLSpatialGroup::GEOM_DIRTY);
			}
		}
	}
}

BOOL LLViewerFetchedTexture::insertToAtlas()
{
	if(!LLViewerTexture::sUseTextureAtlas)
	{
		return FALSE ;
	}
	if(mFaceList.size() < 1)
	{
		return FALSE ;
	}						
	if(mGLTexturep->getDiscardLevelInAtlas() > 0 && mRawDiscardLevel >= mGLTexturep->getDiscardLevelInAtlas())
	{
		return FALSE ;
	}
	if(!LLTextureAtlasManager::getInstance()->canAddToAtlas(mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents(), mGLTexturep->getTexTarget()))
	{
		return FALSE ;
	}

	BOOL ret = TRUE ;//if ret is set to false, will generate a gl texture for this image.
	S32 raw_w = mRawImage->getWidth() ;
	S32 raw_h = mRawImage->getHeight() ;
	F32 xscale = 1.0f, yscale = 1.0f ;
	LLPointer<LLTextureAtlasSlot> slot_infop;
	LLTextureAtlasSlot* cur_slotp ;//no need to be smart pointer.
	LLSpatialGroup* groupp ;
	LLFace* facep;

	//if the atlas slot pointers for some faces are null, process them later.
	ll_face_list_t waiting_list ;

	for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter)
	{
		if(*iter)
		{
			facep = (LLFace*)*iter ;
			
			//face can not use atlas.
			if(!facep->canUseAtlas())
			{
				if(facep->getAtlasInfo())
				{
					facep->removeAtlas() ;	
				}
				ret = FALSE ;
				continue ;
			}

			//the atlas slot is updated
			slot_infop = facep->getAtlasInfo() ;
			groupp = facep->getDrawable()->getSpatialGroup() ;	

			if(slot_infop) 
			{
				if(slot_infop->getSpatialGroup() != groupp)
				{
					if((cur_slotp = groupp->getCurUpdatingSlot(this))) //switch slot
					{
						facep->setAtlasInfo(cur_slotp) ;
						facep->setAtlasInUse(TRUE) ;
						continue ;
					}
					else //do not forget to update slot_infop->getSpatialGroup().
					{
						LLSpatialGroup* gp = slot_infop->getSpatialGroup() ;
						gp->setCurUpdatingTime(gFrameCount) ;
						gp->setCurUpdatingTexture(this) ;
						gp->setCurUpdatingSlot(slot_infop) ;
					}
				}
				else //same group
				{
					if(gFrameCount && slot_infop->getUpdatedTime() == gFrameCount)//slot is just updated
					{
						facep->setAtlasInUse(TRUE) ;
						continue ;
					}
				}
			}				
			else
			{
				//if the slot is null, wait to process them later.
				waiting_list.push_back(facep) ;
				continue ;
			}
						
			//----------
			//insert to atlas
			if(!slot_infop->getAtlas()->insertSubTexture(mGLTexturep, mRawDiscardLevel, mRawImage, slot_infop->getSlotCol(), slot_infop->getSlotRow()))			
			{
				
				//the texture does not qualify to add to atlas, do not bother to try for other faces.
				//invalidateAtlas();
				return FALSE ;
			}
			
			//update texture scale		
			slot_infop->getAtlas()->getTexCoordScale(raw_w, raw_h, xscale, yscale) ;
			slot_infop->setTexCoordScale(xscale, yscale) ;
			slot_infop->setValid() ;
			slot_infop->setUpdatedTime(gFrameCount) ;
			
			//update spatial group atlas info
			groupp->setCurUpdatingTime(gFrameCount) ;
			groupp->setCurUpdatingTexture(this) ;
			groupp->setCurUpdatingSlot(slot_infop) ;

			//make the face to switch to the atlas.
			facep->setAtlasInUse(TRUE) ;
		}
	}

	//process the waiting_list
	for(ll_face_list_t::iterator iter = waiting_list.begin(); iter != waiting_list.end(); ++iter)
	{
		facep = (LLFace*)*iter ;	
		groupp = facep->getDrawable()->getSpatialGroup() ;

		//check if this texture already inserted to atlas for this group
		if((cur_slotp = groupp->getCurUpdatingSlot(this)))
		{
			facep->setAtlasInfo(cur_slotp) ;
			facep->setAtlasInUse(TRUE) ;		
			continue ;
		}

		//need to reserve a slot from atlas
		slot_infop = LLTextureAtlasManager::getInstance()->reserveAtlasSlot(llmax(mFullWidth, mFullHeight), getComponents(), groupp, this) ;	

		facep->setAtlasInfo(slot_infop) ;
		
		groupp->setCurUpdatingTime(gFrameCount) ;
		groupp->setCurUpdatingTexture(this) ;
		groupp->setCurUpdatingSlot(slot_infop) ;

		//slot allocation failed.
		if(!slot_infop || !slot_infop->getAtlas())
		{			
			ret = FALSE ;
			facep->setAtlasInUse(FALSE) ;
			continue ;
		}
				
		//insert to atlas
		if(!slot_infop->getAtlas()->insertSubTexture(mGLTexturep, mRawDiscardLevel, mRawImage, slot_infop->getSlotCol(), slot_infop->getSlotRow()))
		{
			//the texture does not qualify to add to atlas, do not bother to try for other faces.
			ret = FALSE ;
			//invalidateAtlas();
			break ; 
		}
		
		//update texture scale		
		slot_infop->getAtlas()->getTexCoordScale(raw_w, raw_h, xscale, yscale) ;
		slot_infop->setTexCoordScale(xscale, yscale) ;
		slot_infop->setValid() ;
		slot_infop->setUpdatedTime(gFrameCount) ;

		//make the face to switch to the atlas.
		facep->setAtlasInUse(TRUE) ;
	}
	
	return ret ;
}

//----------------------------------------------------------------------------------------------
//end of LLViewerFetchedTexture
//----------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------
//start of LLViewerLODTexture
//----------------------------------------------------------------------------------------------
LLViewerLODTexture::LLViewerLODTexture(const LLUUID& id, const LLHost& host, BOOL usemipmaps)
	: LLViewerFetchedTexture(id, host, usemipmaps)
{
	init(TRUE) ;
}

LLViewerLODTexture::LLViewerLODTexture(const std::string& url, const LLUUID& id, BOOL usemipmaps)
	: LLViewerFetchedTexture(url, id, usemipmaps)
{
	init(TRUE) ;
}

void LLViewerLODTexture::init(bool firstinit)
{
	mTexelsPerImage = 64.f*64.f;
	mDiscardVirtualSize = 0.f;
	mCalculatedDiscardLevel = -1.f;
}

//virtual 
S8 LLViewerLODTexture::getType() const
{
	return LLViewerTexture::LOD_TEXTURE ;
}

BOOL LLViewerLODTexture::isUpdateFrozen()
{
	return LLViewerTexture::sFreezeImageScalingDown && !getDiscardLevel() ;
}

// This is gauranteed to get called periodically for every texture
//virtual
void LLViewerLODTexture::processTextureStats()
{
	updateVirtualSize() ;

	static LLCachedControl<bool> textures_fullres(gSavedSettings,"TextureLoadFullRes");
	
	if (textures_fullres)
	{
		mDesiredDiscardLevel = 0;
	}
	// Generate the request priority and render priority
	else if (mDontDiscard || !mUseMipMaps)
	{
		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 < LLViewerTexture::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  || !mFullHeight)
	{
		mDesiredDiscardLevel = 	getMaxDiscardLevel() ;
	}
	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 (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)LLViewerTexture::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 < LLViewerTexture::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(getMaxDiscardLevel() + 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 LLViewerLODTexture::scaleDown()
{
	if(hasGLTexture() && mCachedRawDiscardLevel > getDiscardLevel())
	{		
		switchToCachedImage() ;	

		if(LLViewerTextureManager::sTesterp)
		{
			LLViewerTextureManager::sTesterp->setStablizingTime() ;
		}
	}
}
//----------------------------------------------------------------------------------------------
//end of LLViewerLODTexture
//----------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------
//start of LLViewerMediaTexture
//----------------------------------------------------------------------------------------------
//static
void LLViewerMediaTexture::updateClass()
{
	static const F32 MAX_INACTIVE_TIME = 30.f ;

#if 0
	//force to play media.
	gSavedSettings.setBOOL("AudioSteamingMedia", true) ;
	gSavedSettings.setBOOL("AudioStreamingVideo", true) ;
#endif

	for(media_map_t::iterator iter = sMediaMap.begin() ; iter != sMediaMap.end(); )
	{
		LLViewerMediaTexture* mediap = iter->second;	
		
		if(mediap->getNumRefs() == 1) //one reference by sMediaMap
		{
			//
			//Note: delay some time to delete the media textures to stop endlessly creating and immediately removing media texture.
			//
			if(mediap->getLastReferencedTimer()->getElapsedTimeF32() > MAX_INACTIVE_TIME)
			{
				media_map_t::iterator cur = iter++ ;
				sMediaMap.erase(cur) ;
				continue ;
			}
		}
		++iter ;
	}
}

//static 
void LLViewerMediaTexture::removeMediaImplFromTexture(const LLUUID& media_id) 
{
	LLViewerMediaTexture* media_tex = findMediaTexture(media_id) ;
	if(media_tex)
	{
		media_tex->invalidateMediaImpl() ;
	}
}

//static
void LLViewerMediaTexture::cleanup()
{
	sMediaMap.clear() ;
}

//static
LLViewerMediaTexture* LLViewerMediaTexture::findMediaTexture(const LLUUID& media_id)
{
	media_map_t::iterator iter = sMediaMap.find(media_id);
	if(iter == sMediaMap.end())
	{
		return NULL;
	}

	LLViewerMediaTexture* media_tex = iter->second ;
	media_tex->setMediaImpl() ;
	media_tex->getLastReferencedTimer()->reset() ;

	return media_tex;
}

LLViewerMediaTexture::LLViewerMediaTexture(const LLUUID& id, BOOL usemipmaps, LLImageGL* gl_image) 
	: LLViewerTexture(id, usemipmaps),
	mMediaImplp(NULL),
	mUpdateVirtualSizeTime(0)
{
	sMediaMap.insert(std::make_pair(id, this));

	mGLTexturep = gl_image ;
	if(mGLTexturep.isNull())
	{
		generateGLTexture() ;
	}
	mGLTexturep->setNeedsAlphaAndPickMask(FALSE) ;

	mIsPlaying = FALSE ;

	setMediaImpl() ;

	setCategory(LLViewerTexture::MEDIA) ;
	
	LLViewerTexture* tex = gTextureList.findImage(mID) ;
	if(tex) //this media is a parcel media for tex.
	{
		tex->setParcelMedia(this) ;
	}
}

//virtual 
LLViewerMediaTexture::~LLViewerMediaTexture() 
{	
	LLViewerTexture* tex = gTextureList.findImage(mID) ;
	if(tex) //this media is a parcel media for tex.
	{
		tex->setParcelMedia(NULL) ;
	}
}

void LLViewerMediaTexture::reinit(BOOL usemipmaps /* = TRUE */)
{
	mGLTexturep = NULL ;
	init(false);
	mUseMipMaps = usemipmaps ;
	getLastReferencedTimer()->reset() ;

	generateGLTexture() ;
	mGLTexturep->setNeedsAlphaAndPickMask(FALSE) ;
}

void LLViewerMediaTexture::setUseMipMaps(BOOL mipmap) 
{
	mUseMipMaps = mipmap;

	if(mGLTexturep.notNull())
	{
		mGLTexturep->setUseMipMaps(mipmap) ;
	}
}

//virtual 
S8 LLViewerMediaTexture::getType() const
{
	return LLViewerTexture::MEDIA_TEXTURE ;
}

void LLViewerMediaTexture::invalidateMediaImpl() 
{
	mMediaImplp = NULL ;
}

void LLViewerMediaTexture::setMediaImpl()
{
	if(!mMediaImplp)
	{
		mMediaImplp = LLViewerMedia::getMediaImplFromTextureID(mID) ;
	}
}

//return true if all faces to reference to this media texture are found
//Note: mMediaFaceList is valid only for the current instant 
//      because it does not check the face validity after the current frame.
BOOL LLViewerMediaTexture::findFaces()
{	
	mMediaFaceList.clear() ;

	BOOL ret = TRUE ;
	
	LLViewerTexture* tex = gTextureList.findImage(mID) ;
	if(tex) //this media is a parcel media for tex.
	{
		const ll_face_list_t* face_list = tex->getFaceList() ;
		for(ll_face_list_t::const_iterator iter = face_list->begin(); iter != face_list->end(); ++iter)
		{
			mMediaFaceList.push_back(*iter) ;
		}
	}
	
	if(!mMediaImplp)
	{
		return TRUE ; 
	}

	//for media on a face.
	const std::list< LLVOVolume* >* obj_list = mMediaImplp->getObjectList() ;
	std::list< LLVOVolume* >::const_iterator iter = obj_list->begin() ;
	for(; iter != obj_list->end(); ++iter)
	{
		LLVOVolume* obj = *iter ;
		if(obj->mDrawable.isNull())
		{
			ret = FALSE ;
			continue ;
		}

		S32 face_id = -1 ;
		while((face_id = obj->getFaceIndexWithMediaImpl(mMediaImplp, face_id)) > -1)
		{
			LLFace* facep = obj->mDrawable->getFace(face_id) ;
			if(facep)
			{
				mMediaFaceList.push_back(facep) ;
			}
			else
			{
				ret = FALSE ;
			}
		}
	}

	return ret ;
}

void LLViewerMediaTexture::initVirtualSize()
{
	if(mIsPlaying)
	{
		return ;
	}

	findFaces() ;
	for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter)
	{
		addTextureStats((*iter)->getVirtualSize()) ;
	}
}

void LLViewerMediaTexture::addMediaToFace(LLFace* facep) 
{
	if(!mIsPlaying)
	{
		return ; //no need to add the face because the media is not in playing.
	}

	switchTexture(facep) ;
}
	
void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) 
{
	if(!mIsPlaying)
	{
		return ; //no need to remove the face because the media is not in playing.
	}
	if(!facep)
	{
		return ;
	}

	mIsPlaying = FALSE ; //set to remove the media from the face.
	switchTexture(facep) ;
	mIsPlaying = TRUE ; //set the flag back.

	if(mFaceList.empty()) //no face referencing to this media
	{
		stopPlaying() ;
	}
}

//virtual 
void LLViewerMediaTexture::addFace(LLFace* facep) 
{
	LLViewerTexture::addFace(facep) ;

	const LLTextureEntry* te = facep->getTextureEntry() ;
	if(te)
	{
		LLViewerTexture* tex = gTextureList.findImage(te->getID()) ;
		if(tex)
		{
			mTextureList.push_back(tex) ;//increase the reference number by one for tex to avoid deleting it.
			return ;
		}
	}

	//check if it is a parcel media
	if(facep->getTexture() && facep->getTexture() != this && facep->getTexture()->getID() == mID)
	{
		mTextureList.push_back(facep->getTexture()) ; //a parcel media.
		return ;
	}
	
	llerrs << "The face does not have a valid texture before media texture." << llendl ;
}

//virtual 
void LLViewerMediaTexture::removeFace(LLFace* facep) 
{
	LLViewerTexture::removeFace(facep) ;

	const LLTextureEntry* te = facep->getTextureEntry() ;
	if(te)
	{
		LLViewerTexture* tex = gTextureList.findImage(te->getID()) ;
		if(tex)
		{
			for(std::list< LLPointer<LLViewerTexture> >::iterator iter = mTextureList.begin();
				iter != mTextureList.end(); ++iter)
			{
				if(*iter == tex)
				{
					mTextureList.erase(iter) ; //decrease the reference number for tex by one.
					return ;
				}
			}

			//
			//we have some trouble here: the texture of the face is changed.
			//we need to find the former texture, and remove it from the list to avoid memory leaking.
			if(mFaceList.empty())
			{
				mTextureList.clear() ;
				return ;
			}
			S32 end = mFaceList.size() ;
			std::vector<const LLTextureEntry*> te_list(end) ;
			S32 i = 0 ;			
			for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter)
			{
				te_list[i++] = (*iter)->getTextureEntry() ;//all textures are in use.
			}
			for(std::list< LLPointer<LLViewerTexture> >::iterator iter = mTextureList.begin();
				iter != mTextureList.end(); ++iter)
			{
				for(i = 0 ; i < end ; i++)
				{
					if(te_list[i] && te_list[i]->getID() == (*iter)->getID())//the texture is in use.
					{
						te_list[i] = NULL ;
						break ;
					}
				}
				if(i == end) //no hit for this texture, remove it.
				{
					mTextureList.erase(iter) ; //decrease the reference number for tex by one.
					return ;
				}
			}
		}
	}

	//check if it is a parcel media
	for(std::list< LLPointer<LLViewerTexture> >::iterator iter = mTextureList.begin();
				iter != mTextureList.end(); ++iter)
	{
		if((*iter)->getID() == mID)
		{
			mTextureList.erase(iter) ; //decrease the reference number for tex by one.
			return ;
		}
	}

	llerrs << "mTextureList texture reference number is corrupted." << llendl ;
}

void LLViewerMediaTexture::stopPlaying()
{
	// Don't stop the media impl playing here -- this breaks non-inworld media (login screen, search, and media browser).
//	if(mMediaImplp)
//	{
//		mMediaImplp->stop() ;
//	}
	mIsPlaying = FALSE ;			
}

void LLViewerMediaTexture::switchTexture(LLFace* facep)
{
	if(facep)
	{
		//check if another media is playing on this face.
		if(facep->getTexture() && facep->getTexture() != this 
			&& facep->getTexture()->getType() == LLViewerTexture::MEDIA_TEXTURE)
		{
			if(mID == facep->getTexture()->getID()) //this is a parcel media
			{
				return ; //let the prim media win.
			}
		}

		if(mIsPlaying) //old textures switch to the media texture
		{
			facep->switchTexture(this) ;
		}
		else //switch to old textures.
		{
			const LLTextureEntry* te = facep->getTextureEntry() ;
			if(te)
			{
				LLViewerTexture* tex = gTextureList.findImage(te->getID()) ;
				if(!tex && te->getID() != mID)//try parcel media.
				{
					tex = gTextureList.findImage(mID) ;
				}
				facep->switchTexture(tex) ;
			}
		}
	}
}

void LLViewerMediaTexture::setPlaying(BOOL playing) 
{
	if(!mMediaImplp)
	{
		return ; 
	}
	if(!playing && !mIsPlaying)
	{
		return ; //media is already off
	}

	if(playing == mIsPlaying && !mMediaImplp->isUpdated())
	{
		return ; //nothing has changed since last time.
	}	

	mIsPlaying = playing ;
	if(mIsPlaying) //is about to play this media
	{
		if(findFaces())
		{
			//about to update all faces.
			mMediaImplp->setUpdated(FALSE) ;
		}

		if(mMediaFaceList.empty())//no face pointing to this media
		{
			stopPlaying() ;
			return ;
		}

		for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter)
		{
			switchTexture(*iter) ;
		}
	}
	else //stop playing this media
	{
		if(mFaceList.empty())
		{
			return ;
		}

		ll_face_list_t::iterator cur ;
		for(ll_face_list_t::iterator iter = mFaceList.begin(); iter!= mFaceList.end(); )
		{
			cur = iter++ ; 
			switchTexture(*cur) ; //cur could be removed in this function.
		}
	}
	return ;
}

//virtual 
F32 LLViewerMediaTexture::getMaxVirtualSize() 
{	
	if(LLFrameTimer::getFrameCount() == mUpdateVirtualSizeTime)
	{
		return mMaxVirtualSize ;
	}
	mUpdateVirtualSizeTime = LLFrameTimer::getFrameCount() ;

	if(mNeedsResetMaxVirtualSize)
	{
		addTextureStats(0.f, FALSE) ;//reset
	}

	if(mIsPlaying) //media is playing
	{
		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()) ;
				}
			}	
		}
	}
	else //media is not in playing
	{
		findFaces() ;
	
		if(!mMediaFaceList.empty())
		{
			for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter)
			{
				LLFace* facep = *iter ;
				if(facep->getDrawable()->isRecentlyVisible())
				{
					addTextureStats(facep->getVirtualSize()) ;
				}
			}
		}
	}

	mNeedsResetMaxVirtualSize = TRUE ;

	return mMaxVirtualSize ;
}
//----------------------------------------------------------------------------------------------
//end of LLViewerMediaTexture
//----------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------
//start of LLTexturePipelineTester
//----------------------------------------------------------------------------------------------
LLTexturePipelineTester::LLTexturePipelineTester() :
	LLMetricPerformanceTester("TextureTester", FALSE) 
{
	addMetricString("TotalBytesLoaded") ;
	addMetricString("TotalBytesLoadedFromCache") ;
	addMetricString("TotalBytesLoadedForLargeImage") ;
	addMetricString("TotalBytesLoadedForSculpties") ;
	addMetricString("StartFetchingTime") ;
	addMetricString("TotalGrayTime") ;
	addMetricString("TotalStablizingTime") ;
	addMetricString("StartTimeLoadingSculpties") ;
	addMetricString("EndTimeLoadingSculpties") ;

	addMetricString("Time") ;
	addMetricString("TotalBytesBound") ;
	addMetricString("TotalBytesBoundForLargeImage") ;
	addMetricString("PercentageBytesBound") ;
	
	mTotalBytesLoaded = 0 ;
	mTotalBytesLoadedFromCache = 0 ;	
	mTotalBytesLoadedForLargeImage = 0 ;
	mTotalBytesLoadedForSculpties = 0 ;

	reset() ;
}

LLTexturePipelineTester::~LLTexturePipelineTester()
{
	LLViewerTextureManager::sTesterp = NULL ;
}

void LLTexturePipelineTester::update()
{
	mLastTotalBytesUsed = mTotalBytesUsed ;
	mLastTotalBytesUsedForLargeImage = mTotalBytesUsedForLargeImage ;
	mTotalBytesUsed = 0 ;
	mTotalBytesUsedForLargeImage = 0 ;
	
	if(LLAppViewer::getTextureFetch()->getNumRequests() > 0) //fetching list is not empty
	{
		if(mPause)
		{
			//start a new fetching session
			reset() ;
			mStartFetchingTime = LLImageGL::sLastFrameTime ;
			mPause = FALSE ;
		}

		//update total gray time		
		if(mUsingDefaultTexture)
		{
			mUsingDefaultTexture = FALSE ;
			mTotalGrayTime = LLImageGL::sLastFrameTime - mStartFetchingTime ;		
		}

		//update the stablizing timer.
		updateStablizingTime() ;

		outputTestResults() ;
	}
	else if(!mPause)
	{
		//stop the current fetching session
		mPause = TRUE ;
		outputTestResults() ;
		reset() ;
	}		
}
	
void LLTexturePipelineTester::reset() 
{
	mPause = TRUE ;

	mUsingDefaultTexture = FALSE ;
	mStartStablizingTime = 0.0f ;
	mEndStablizingTime = 0.0f ;

	mTotalBytesUsed = 0 ;
	mTotalBytesUsedForLargeImage = 0 ;
	mLastTotalBytesUsed = 0 ;
	mLastTotalBytesUsedForLargeImage = 0 ;
	
	mStartFetchingTime = 0.0f ;
	
	mTotalGrayTime = 0.0f ;
	mTotalStablizingTime = 0.0f ;

	mStartTimeLoadingSculpties = 1.0f ;
	mEndTimeLoadingSculpties = 0.0f ;
}

//virtual 
void LLTexturePipelineTester::outputTestRecord(LLSD *sd) 
{	
	(*sd)[mCurLabel]["TotalBytesLoaded"]              = (LLSD::Integer)mTotalBytesLoaded ;
	(*sd)[mCurLabel]["TotalBytesLoadedFromCache"]     = (LLSD::Integer)mTotalBytesLoadedFromCache ;
	(*sd)[mCurLabel]["TotalBytesLoadedForLargeImage"] = (LLSD::Integer)mTotalBytesLoadedForLargeImage ;
	(*sd)[mCurLabel]["TotalBytesLoadedForSculpties"]  = (LLSD::Integer)mTotalBytesLoadedForSculpties ;

	(*sd)[mCurLabel]["StartFetchingTime"]             = (LLSD::Real)mStartFetchingTime ;
	(*sd)[mCurLabel]["TotalGrayTime"]                 = (LLSD::Real)mTotalGrayTime ;
	(*sd)[mCurLabel]["TotalStablizingTime"]           = (LLSD::Real)mTotalStablizingTime ;

	(*sd)[mCurLabel]["StartTimeLoadingSculpties"]     = (LLSD::Real)mStartTimeLoadingSculpties ;
	(*sd)[mCurLabel]["EndTimeLoadingSculpties"]       = (LLSD::Real)mEndTimeLoadingSculpties ;

	(*sd)[mCurLabel]["Time"]                          = LLImageGL::sLastFrameTime ;
	(*sd)[mCurLabel]["TotalBytesBound"]               = (LLSD::Integer)mLastTotalBytesUsed ;
	(*sd)[mCurLabel]["TotalBytesBoundForLargeImage"]  = (LLSD::Integer)mLastTotalBytesUsedForLargeImage ;
	(*sd)[mCurLabel]["PercentageBytesBound"]          = (LLSD::Real)(100.f * mLastTotalBytesUsed / mTotalBytesLoaded) ;
}

void LLTexturePipelineTester::updateTextureBindingStats(const LLViewerTexture* imagep) 
{
	U32 mem_size = (U32)imagep->getTextureMemory() ;
	mTotalBytesUsed += mem_size ; 

	if(MIN_LARGE_IMAGE_AREA <= (U32)(mem_size / (U32)imagep->getComponents()))
	{
		mTotalBytesUsedForLargeImage += mem_size ;
	}
}
	
void LLTexturePipelineTester::updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, BOOL from_cache) 
{
	U32 data_size = (U32)raw_imagep->getDataSize() ;
	mTotalBytesLoaded += data_size ;

	if(from_cache)
	{
		mTotalBytesLoadedFromCache += data_size ;
	}

	if(MIN_LARGE_IMAGE_AREA <= (U32)(data_size / (U32)raw_imagep->getComponents()))
	{
		mTotalBytesLoadedForLargeImage += data_size ;
	}

	if(imagep->forSculpt())
	{
		mTotalBytesLoadedForSculpties += data_size ;

		if(mStartTimeLoadingSculpties > mEndTimeLoadingSculpties)
		{
			mStartTimeLoadingSculpties = LLImageGL::sLastFrameTime ;
		}
		mEndTimeLoadingSculpties = LLImageGL::sLastFrameTime ;
	}
}

void LLTexturePipelineTester::updateGrayTextureBinding()
{
	mUsingDefaultTexture = TRUE ;
}

void LLTexturePipelineTester::setStablizingTime()
{
	if(mStartStablizingTime <= mStartFetchingTime)
	{
		mStartStablizingTime = LLImageGL::sLastFrameTime ;
	}
	mEndStablizingTime = LLImageGL::sLastFrameTime ;
}

void LLTexturePipelineTester::updateStablizingTime()
{
	if(mStartStablizingTime > mStartFetchingTime)
	{
		F32 t = mEndStablizingTime - mStartStablizingTime ;

		if(t > 0.0001f && (t - mTotalStablizingTime) < 0.0001f)
		{
			//already stablized
			mTotalStablizingTime = LLImageGL::sLastFrameTime - mStartStablizingTime ;

			//cancel the timer
			mStartStablizingTime = 0.f ;
			mEndStablizingTime = 0.f ;
		}
		else
		{
			mTotalStablizingTime = t ;
		}
	}
	mTotalStablizingTime = 0.f ;
}

//virtual 
void LLTexturePipelineTester::compareTestSessions(std::ofstream* os) 
{	
	LLTexturePipelineTester::LLTextureTestSession* base_sessionp = dynamic_cast<LLTexturePipelineTester::LLTextureTestSession*>(mBaseSessionp) ;
	LLTexturePipelineTester::LLTextureTestSession* current_sessionp = dynamic_cast<LLTexturePipelineTester::LLTextureTestSession*>(mCurrentSessionp) ;
	if(!base_sessionp || !current_sessionp)
	{
		llerrs << "type of test session does not match!" << llendl ;
	}

	//compare and output the comparison
	*os << llformat("%s\n", mName.c_str()) ;
	*os << llformat("AggregateResults\n") ;

	compareTestResults(os, "TotalFetchingTime", base_sessionp->mTotalFetchingTime, current_sessionp->mTotalFetchingTime) ;
	compareTestResults(os, "TotalGrayTime", base_sessionp->mTotalGrayTime, current_sessionp->mTotalGrayTime) ;
	compareTestResults(os, "TotalStablizingTime", base_sessionp->mTotalStablizingTime, current_sessionp->mTotalStablizingTime);
	compareTestResults(os, "StartTimeLoadingSculpties", base_sessionp->mStartTimeLoadingSculpties, current_sessionp->mStartTimeLoadingSculpties) ;		
	compareTestResults(os, "TotalTimeLoadingSculpties", base_sessionp->mTotalTimeLoadingSculpties, current_sessionp->mTotalTimeLoadingSculpties) ;
	
	compareTestResults(os, "TotalBytesLoaded", base_sessionp->mTotalBytesLoaded, current_sessionp->mTotalBytesLoaded) ;
	compareTestResults(os, "TotalBytesLoadedFromCache", base_sessionp->mTotalBytesLoadedFromCache, current_sessionp->mTotalBytesLoadedFromCache) ;
	compareTestResults(os, "TotalBytesLoadedForLargeImage", base_sessionp->mTotalBytesLoadedForLargeImage, current_sessionp->mTotalBytesLoadedForLargeImage) ;
	compareTestResults(os, "TotalBytesLoadedForSculpties", base_sessionp->mTotalBytesLoadedForSculpties, current_sessionp->mTotalBytesLoadedForSculpties) ;
		
	*os << llformat("InstantResults\n") ;
	S32 size = llmin(base_sessionp->mInstantPerformanceListCounter, current_sessionp->mInstantPerformanceListCounter) ;
	for(S32 i = 0 ; i < size ; i++)
	{
		*os << llformat("Time(B-T)-%.4f-%.4f\n", base_sessionp->mInstantPerformanceList[i].mTime, current_sessionp->mInstantPerformanceList[i].mTime) ;

		compareTestResults(os, "AverageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond,
			current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ;
			
		compareTestResults(os, "AverageBytesUsedForLargeImagePerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond,
			current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ;
			
		compareTestResults(os, "AveragePercentageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond,
			current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ;
	}
	
	if(size < base_sessionp->mInstantPerformanceListCounter)
	{
		for(S32 i = size ; i < base_sessionp->mInstantPerformanceListCounter ; i++)
		{
			*os << llformat("Time(B-T)-%.4f- \n", base_sessionp->mInstantPerformanceList[i].mTime) ;

			*os << llformat(", AverageBytesUsedPerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ;
			*os << llformat(", AverageBytesUsedForLargeImagePerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ;				
			*os << llformat(", AveragePercentageBytesUsedPerSecond, %.4f, N/A \n", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ;			
		}
	}
	else if(size < current_sessionp->mInstantPerformanceListCounter)
	{
		for(S32 i = size ; i < current_sessionp->mInstantPerformanceListCounter ; i++)
		{
			*os << llformat("Time(B-T)- -%.4f\n", current_sessionp->mInstantPerformanceList[i].mTime) ;

			*os << llformat(", AverageBytesUsedPerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ;
			*os << llformat(", AverageBytesUsedForLargeImagePerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ;				
			*os << llformat(", AveragePercentageBytesUsedPerSecond, N/A, %.4f\n", current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ;			
		}
	}
}

//virtual 
LLMetricPerformanceTester::LLTestSession* LLTexturePipelineTester::loadTestSession(LLSD* log)
{
	LLTexturePipelineTester::LLTextureTestSession* sessionp = new LLTexturePipelineTester::LLTextureTestSession() ;
	if(!sessionp)
	{
		return NULL ;
	}
	
	F32 total_fetching_time = 0.f ;
	F32 total_gray_time = 0.f ;
	F32 total_stablizing_time = 0.f ;
	F32 total_loading_sculpties_time = 0.f ;

	F32 start_fetching_time = -1.f ;
	F32 start_fetching_sculpties_time = 0.f ;

	F32 last_time = 0.0f ;
	S32 frame_count = 0 ;

	sessionp->mInstantPerformanceListCounter = 0 ;
	sessionp->mInstantPerformanceList.resize(128) ;
	sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0 ;
	sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0 ;
	sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f ;
	sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f ;
	
	//load a session
	BOOL in_log = (*log).has(mCurLabel) ;
	while(in_log)
	{
		LLSD::String label = mCurLabel ;		
		incLabel() ;
		in_log = (*log).has(mCurLabel) ;

		if(sessionp->mInstantPerformanceListCounter >= (S32)sessionp->mInstantPerformanceList.size())
		{
			sessionp->mInstantPerformanceList.resize(sessionp->mInstantPerformanceListCounter + 128) ;
		}
		
		//time
		F32 start_time = (*log)[label]["StartFetchingTime"].asReal() ;
		F32 cur_time   = (*log)[label]["Time"].asReal() ;
		if(start_time - start_fetching_time > 0.0001f) //fetching has paused for a while
		{
			sessionp->mTotalFetchingTime += total_fetching_time ;
			sessionp->mTotalGrayTime += total_gray_time ;
			sessionp->mTotalStablizingTime += total_stablizing_time ;

			sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time ; 
			sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time ;

			start_fetching_time = start_time ;
			total_fetching_time = 0.0f ;
			total_gray_time = 0.f ;
			total_stablizing_time = 0.f ;
			total_loading_sculpties_time = 0.f ;
		}
		else
		{
			total_fetching_time = cur_time - start_time ;
			total_gray_time = (*log)[label]["TotalGrayTime"].asReal() ;
			total_stablizing_time = (*log)[label]["TotalStablizingTime"].asReal() ;

			total_loading_sculpties_time = (*log)[label]["EndTimeLoadingSculpties"].asReal() - (*log)[label]["StartTimeLoadingSculpties"].asReal() ;
			if(start_fetching_sculpties_time < 0.f && total_loading_sculpties_time > 0.f)
			{
				start_fetching_sculpties_time = (*log)[label]["StartTimeLoadingSculpties"].asReal() ;
			}			
		}
		
		//total loaded bytes
		sessionp->mTotalBytesLoaded = (*log)[label]["TotalBytesLoaded"].asInteger() ; 
		sessionp->mTotalBytesLoadedFromCache = (*log)[label]["TotalBytesLoadedFromCache"].asInteger() ;
		sessionp->mTotalBytesLoadedForLargeImage = (*log)[label]["TotalBytesLoadedForLargeImage"].asInteger() ;
		sessionp->mTotalBytesLoadedForSculpties = (*log)[label]["TotalBytesLoadedForSculpties"].asInteger() ; 

		//instant metrics			
		sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond +=
			(*log)[label]["TotalBytesBound"].asInteger() ;
		sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond +=
			(*log)[label]["TotalBytesBoundForLargeImage"].asInteger() ;
		sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond +=
			(*log)[label]["PercentageBytesBound"].asReal() ;
		frame_count++ ;
		if(cur_time - last_time >= 1.0f)
		{
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond /= frame_count ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond /= frame_count ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond /= frame_count ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = last_time ;

			frame_count = 0 ;
			last_time = cur_time ;
			sessionp->mInstantPerformanceListCounter++ ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0 ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0 ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f ;
			sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f ;
		}		
	}

	sessionp->mTotalFetchingTime += total_fetching_time ;
	sessionp->mTotalGrayTime += total_gray_time ;
	sessionp->mTotalStablizingTime += total_stablizing_time ;

	if(sessionp->mStartTimeLoadingSculpties < 0.f)
	{
		sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time ; 
	}
	sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time ;

	return sessionp;
}

LLTexturePipelineTester::LLTextureTestSession::LLTextureTestSession() 
{
	reset() ;
}
LLTexturePipelineTester::LLTextureTestSession::~LLTextureTestSession() 
{
}
void LLTexturePipelineTester::LLTextureTestSession::reset() 
{
	mTotalFetchingTime = 0.0f ;

	mTotalGrayTime = 0.0f ;
	mTotalStablizingTime = 0.0f ;

	mStartTimeLoadingSculpties = 0.0f ; 
	mTotalTimeLoadingSculpties = 0.0f ;

	mTotalBytesLoaded = 0 ; 
	mTotalBytesLoadedFromCache = 0 ;
	mTotalBytesLoadedForLargeImage = 0 ;
	mTotalBytesLoadedForSculpties = 0 ; 

	mInstantPerformanceListCounter = 0 ;
}
//----------------------------------------------------------------------------------------------
//end of LLTexturePipelineTester
//----------------------------------------------------------------------------------------------