Commits

BaoLinden committed 0496846

for SH-3073: implement a fast cache system for texture fetching

  • Participants
  • Parent commits ce3a694

Comments (0)

Files changed (10)

File indra/llimage/llimage.cpp

 	++sRawImageCount;
 }
 
-LLImageRaw::LLImageRaw(U8 *data, U16 width, U16 height, S8 components)
+LLImageRaw::LLImageRaw(U8 *data, U16 width, U16 height, S8 components, bool no_copy)
 	: LLImageBase()
 {
 	mMemType = LLMemType::MTYPE_IMAGERAW;
-	if(allocateDataSize(width, height, components))
+
+	if(no_copy)
+	{
+		setDataAndSize(data, width, height, components);
+	}
+	else if(allocateDataSize(width, height, components))
 	{
 		memcpy(getData(), data, width*height*components);
 	}

File indra/llimage/llimage.h

 public:
 	LLImageRaw();
 	LLImageRaw(U16 width, U16 height, S8 components);
-	LLImageRaw(U8 *data, U16 width, U16 height, S8 components);
+	LLImageRaw(U8 *data, U16 width, U16 height, S8 components, bool no_copy = false);
 	// Construct using createFromFile (used by tools)
 	//LLImageRaw(const std::string& filename, bool j2c_lowest_mip_only = false);
 

File indra/newview/app_settings/settings.xml

         <key>Value</key>
         <integer>0</integer>
     </map>
+    <key>FastCacheFetchEnabled</key>
+    <map>
+      <key>Comment</key>
+      <string>Enable texture fast cache fetching if set</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <string>1</string>
+    </map>
 	<key>FeatureManagerHTTPTable</key>
       <map>
         <key>Comment</key>

File indra/newview/lltexturecache.cpp

 const S32 TEXTURE_CACHE_ENTRY_SIZE = FIRST_PACKET_SIZE;//1024;
 const F32 TEXTURE_CACHE_PURGE_AMOUNT = .20f; // % amount to reduce the cache by when it exceeds its limit
 const F32 TEXTURE_CACHE_LRU_SIZE = .10f; // % amount for LRU list (low overhead to regenerate)
+const S32 TEXTURE_FAST_CACHE_ENTRY_OVERHEAD = sizeof(S32) * 4; //w, h, c, level
+const S32 TEXTURE_FAST_CACHE_ENTRY_SIZE = 16 * 16 * 4 + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD;
 
 class LLTextureCacheWorker : public LLWorkerClass
 {
 	LLTextureCacheRemoteWorker(LLTextureCache* cache, U32 priority, const LLUUID& id,
 						 U8* data, S32 datasize, S32 offset,
 						 S32 imagesize, // for writes
+						 LLPointer<LLImageRaw> raw, S32 discardlevel,
 						 LLTextureCache::Responder* responder) 
 			: LLTextureCacheWorker(cache, priority, id, data, datasize, offset, imagesize, responder),
-			mState(INIT)
+			mState(INIT),
+			mRawImage(raw),
+			mRawDiscardLevel(discardlevel)
 	{
 	}
 
 	};
 
 	e_state mState;
+	LLPointer<LLImageRaw> mRawImage;
+	S32 mRawDiscardLevel;
 };
 
 
 		if(idx < 0)
 		{
 			idx = mCache->setHeaderCacheEntry(mID, entry, mImageSize, mDataSize); // create the new entry.
+			if(idx >= 0)
+			{
+				//write to the fast cache.
+				llassert_always(mCache->writeToFastCache(idx, mRawImage, mRawDiscardLevel));
+			}
 		}
 		else
 		{
 		// Nothing else to do at that point...
 		done = true;
 	}
+	mRawImage = NULL;
 
 	// Clean up and exit
 	return done;
 	  mWorkersMutex(NULL),
 	  mHeaderMutex(NULL),
 	  mListMutex(NULL),
+	  mFastCacheMutex(NULL),
 	  mHeaderAPRFile(NULL),
 	  mReadOnly(TRUE), //do not allow to change the texture cache until setReadOnly() is called.
 	  mTexturesSizeTotal(0),
-	  mDoPurge(FALSE)
+	  mDoPurge(FALSE),
+	  mFastCachep(NULL),
+	  mFastCachePoolp(NULL),
+	  mFastCachePadBuffer(NULL)
 {
 }
 
 {
 	clearDeleteList() ;
 	writeUpdatedEntries() ;
+	delete mFastCachep;
+	delete mFastCachePoolp;
+	FREE_MEM(LLImageBase::getPrivatePool(), mFastCachePadBuffer);
 }
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 
 //static
-const S32 MAX_REASONABLE_FILE_SIZE = 512*1024*1024; // 512 MB
-F32 LLTextureCache::sHeaderCacheVersion = 1.4f;
-U32 LLTextureCache::sCacheMaxEntries = MAX_REASONABLE_FILE_SIZE / TEXTURE_CACHE_ENTRY_SIZE;
+F32 LLTextureCache::sHeaderCacheVersion = 1.7f;
+U32 LLTextureCache::sCacheMaxEntries = 1024 * 1024; //~1 million textures.
 S64 LLTextureCache::sCacheMaxTexturesSize = 0; // no limit
 const char* entries_filename = "texture.entries";
 const char* cache_filename = "texture.cache";
 const char* old_textures_dirname = "textures";
 //change the location of the texture cache to prevent from being deleted by old version viewers.
 const char* textures_dirname = "texturecache";
+const char* fast_cache_filename = "FastCache.cache";
 
 void LLTextureCache::setDirNames(ELLPath location)
 {
 	mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, entries_filename);
 	mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, cache_filename);
 	mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname);
+	mFastCacheFileName =  gDirUtilp->getExpandedFilename(location, textures_dirname, fast_cache_filename);
 }
 
 void LLTextureCache::purgeCache(ELLPath location)
 {
 	llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized.
 
-	S64 header_size = (max_size * 2) / 10;
-	S64 max_entries = header_size / TEXTURE_CACHE_ENTRY_SIZE;
+	S64 header_size = (max_size / 100) * 36; //0.36 * max_size
+	S64 max_entries = header_size / (TEXTURE_CACHE_ENTRY_SIZE + TEXTURE_FAST_CACHE_ENTRY_SIZE);
 	sCacheMaxEntries = (S32)(llmin((S64)sCacheMaxEntries, max_entries));
 	header_size = sCacheMaxEntries * TEXTURE_CACHE_ENTRY_SIZE;
 	max_size -= header_size;
 	purgeTextures(true); // calc mTexturesSize and make some room in the texture cache if we need it
 
 	llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized.
+	openFastCache(true);
 
 	return max_size; // unused cache space
 }
 	LLMutexLock lock(&mWorkersMutex);
 	LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, priority, id,
 																  NULL, size, offset,
-																  0, responder);
+																  0, NULL, 0, responder);
 	handle_t handle = worker->read();
 	mReaders[handle] = worker;
 	return handle;
 
 LLTextureCache::handle_t LLTextureCache::writeToCache(const LLUUID& id, U32 priority,
 													  U8* data, S32 datasize, S32 imagesize,
+													  LLPointer<LLImageRaw> rawimage, S32 discardlevel,
 													  WriteResponder* responder)
 {
 	if (mReadOnly)
 	LLMutexLock lock(&mWorkersMutex);
 	LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, priority, id,
 																  data, datasize, 0,
-																  imagesize, responder);
+																  imagesize, rawimage, discardlevel, responder);
 	handle_t handle = worker->write();
 	mWriters[handle] = worker;
 	return handle;
 }
 
+//called in the main thread
+LLPointer<LLImageRaw> LLTextureCache::readFromFastCache(const LLUUID& id, S32& discardlevel)
+{
+	U32 offset;
+	{
+		LLMutexLock lock(&mHeaderMutex);
+		id_map_t::const_iterator iter = mHeaderIDMap.find(id);
+		if(iter == mHeaderIDMap.end())
+		{
+			return NULL; //not in the cache
+		}
+
+		offset = iter->second;
+	}
+	offset *= TEXTURE_FAST_CACHE_ENTRY_SIZE;
+
+	U8* data;
+	S32 head[4];
+	{
+		LLMutexLock lock(&mFastCacheMutex);
+
+		openFastCache();
+
+		mFastCachep->seek(APR_SET, offset);		
+	
+		llassert_always(mFastCachep->read(head, TEXTURE_FAST_CACHE_ENTRY_OVERHEAD) == TEXTURE_FAST_CACHE_ENTRY_OVERHEAD);
+		
+		S32 image_size = head[0] * head[1] * head[2];
+		if(!image_size) //invalid
+		{
+			closeFastCache();
+			return NULL;
+		}
+		discardlevel = head[3];
+		
+		data =  (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), image_size);
+		llassert_always(mFastCachep->read(data, image_size) == image_size);
+		closeFastCache();
+	}
+	LLPointer<LLImageRaw> raw = new LLImageRaw(data, head[0], head[1], head[2], true);
+
+	return raw;
+}
+
+//return the fast cache location
+bool LLTextureCache::writeToFastCache(S32 id, LLPointer<LLImageRaw> raw, S32 discardlevel)
+{
+	//rescale image if needed
+	S32 w, h, c;
+	w = raw->getWidth();
+	h = raw->getHeight();
+	c = raw->getComponents();
+	S32 i = 0 ;
+	
+	while(((w >> i) * (h >> i) * c) > TEXTURE_FAST_CACHE_ENTRY_SIZE - TEXTURE_FAST_CACHE_ENTRY_OVERHEAD)
+	{
+		++i ;
+	}
+		
+	if(i)
+	{
+		w >>= i;
+		h >>= i;
+		if(w * h *c > 0) //valid
+		{
+			LLPointer<LLImageRaw> newraw = new LLImageRaw(raw->getData(), raw->getWidth(), raw->getHeight(), raw->getComponents());
+			newraw->scale(w, h) ;
+			raw = newraw;
+
+			discardlevel += i ;
+		}
+	}
+	
+	//copy data
+	memcpy(mFastCachePadBuffer, &w, sizeof(S32));
+	memcpy(mFastCachePadBuffer + sizeof(S32), &h, sizeof(S32));
+	memcpy(mFastCachePadBuffer + sizeof(S32) * 2, &c, sizeof(S32));
+	memcpy(mFastCachePadBuffer + sizeof(S32) * 3, &discardlevel, sizeof(S32));
+	if(w * h * c > 0) //valid
+	{
+		memcpy(mFastCachePadBuffer + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD, raw->getData(), w * h * c);
+	}
+	S32 offset = id * TEXTURE_FAST_CACHE_ENTRY_SIZE;
+
+	{
+		LLMutexLock lock(&mFastCacheMutex);
+
+		openFastCache();
+
+		mFastCachep->seek(APR_SET, offset);	
+		llassert_always(mFastCachep->write(mFastCachePadBuffer, TEXTURE_FAST_CACHE_ENTRY_SIZE) == TEXTURE_FAST_CACHE_ENTRY_SIZE);
+
+		closeFastCache(true);
+	}
+
+	return true;
+}
+
+void LLTextureCache::openFastCache(bool first_time)
+{
+	if(!mFastCachep)
+	{
+		if(first_time)
+		{
+			if(!mFastCachePadBuffer)
+			{
+				mFastCachePadBuffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), TEXTURE_FAST_CACHE_ENTRY_SIZE);
+			}
+			mFastCachePoolp = new LLVolatileAPRPool();
+			if (LLAPRFile::isExist(mFastCacheFileName, mFastCachePoolp))
+			{
+				mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ;				
+			}
+			else
+			{
+				mFastCachep = new LLAPRFile(mFastCacheFileName, APR_CREATE|APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ;
+			}
+		}
+		else
+		{
+			mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ;
+		}
+
+		mFastCacheTimer.reset();
+	}
+	return;
+}
+	
+void LLTextureCache::closeFastCache(bool forced)
+{	
+	static const F32 timeout = 10.f ; //seconds
+
+	if(!mFastCachep)
+	{
+		return ;
+	}
+
+	if(!forced && mFastCacheTimer.getElapsedTimeF32() < timeout)
+	{
+		return ;
+	}
+
+	delete mFastCachep;
+	mFastCachep = NULL;
+	return;
+}
+	
 bool LLTextureCache::writeComplete(handle_t handle, bool abort)
 {
 	lockWorkers();

File indra/newview/lltexturecache.h

 
 class LLImageFormatted;
 class LLTextureCacheWorker;
+class LLImageRaw;
 
 class LLTextureCache : public LLWorkerThread
 {
 	handle_t readFromCache(const LLUUID& id, U32 priority, S32 offset, S32 size,
 						   ReadResponder* responder);
 	bool readComplete(handle_t handle, bool abort);
-	handle_t writeToCache(const LLUUID& id, U32 priority, U8* data, S32 datasize, S32 imagesize,
+	handle_t writeToCache(const LLUUID& id, U32 priority, U8* data, S32 datasize, S32 imagesize, LLPointer<LLImageRaw> rawimage, S32 discardlevel,
 						  WriteResponder* responder);
+	LLPointer<LLImageRaw> readFromFastCache(const LLUUID& id, S32& discardlevel);
 	bool writeComplete(handle_t handle, bool abort = false);
 	void prioritizeWrite(handle_t handle);
 
 	void lockHeaders() { mHeaderMutex.lock(); }
 	void unlockHeaders() { mHeaderMutex.unlock(); }
 	
+	void openFastCache(bool first_time = false);
+	void closeFastCache(bool forced = false);
+	bool writeToFastCache(S32 id, LLPointer<LLImageRaw> raw, S32 discardlevel);	
+
 private:
 	// Internal
 	LLMutex mWorkersMutex;
 	LLMutex mHeaderMutex;
 	LLMutex mListMutex;
+	LLMutex mFastCacheMutex;
 	LLAPRFile* mHeaderAPRFile;
+	LLVolatileAPRPool* mFastCachePoolp;
 	
 	typedef std::map<handle_t, LLTextureCacheWorker*> handle_map_t;
 	handle_map_t mReaders;
 	// HEADERS (Include first mip)
 	std::string mHeaderEntriesFileName;
 	std::string mHeaderDataFileName;
+	std::string mFastCacheFileName;
 	EntriesInfo mHeaderEntriesInfo;
 	std::set<S32> mFreeList; // deleted entries
 	std::set<LLUUID> mLRU;
-	typedef std::map<LLUUID,S32> id_map_t;
+	typedef std::map<LLUUID, S32> id_map_t;
 	id_map_t mHeaderIDMap;
 
+	LLAPRFile*   mFastCachep;
+	LLFrameTimer mFastCacheTimer;
+	U8*          mFastCachePadBuffer;
+
 	// BODIES (TEXTURES minus headers)
 	std::string mTexturesDirName;
 	typedef std::map<LLUUID,S32> size_map_t;

File indra/newview/lltexturefetch.cpp

 		CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
 		mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
 																  mFormattedImage->getData(), datasize,
-																  mFileSize, responder);
+																  mFileSize, mRawImage, mDecodedDiscard, responder);
 		// fall through
 	}
 	
 			mFetchingHistory[i].mCacheHandle = mTextureCache->writeToCache(mFetchingHistory[i].mID, LLWorkerThread::PRIORITY_NORMAL, 
 				mFetchingHistory[i].mFormattedImage->getData(), mFetchingHistory[i].mFetchedSize,
 				mFetchingHistory[i].mDecodedLevel == 0 ? mFetchingHistory[i].mFetchedSize : mFetchingHistory[i].mFetchedSize + 1, 
-				new LLDebuggerCacheWriteResponder(this, i));					
+				NULL, 0, new LLDebuggerCacheWriteResponder(this, i));					
 		}
 	}
 }

File indra/newview/llviewertexture.cpp

 #include "llmediaentry.h"
 #include "llvovolume.h"
 #include "llviewermedia.h"
+#include "lltexturecache.h"
 ///////////////////////////////////////////////////////////////////////////////
 
 // statics
 	mRequestDeltaTime = 0.f;
 	mForSculpt = FALSE ;
 	mIsFetched = FALSE ;
+	mInFastCacheList = FALSE;
 
 	mCachedRawImage = NULL ;
 	mCachedRawDiscardLevel = -1 ;
 	mCachedRawDiscardLevel = -1 ;
 	mCachedRawImageReady = FALSE ;
 	mSavedRawImage = NULL ;
-	mSavedRawDiscardLevel = -1;
+}
+
+//access the fast cache
+void LLViewerFetchedTexture::loadFromFastCache()
+{
+	if(!mInFastCacheList)
+	{
+		return; //no need to access the fast cache.
+	}
+	mInFastCacheList = FALSE;
+
+	mRawImage = LLAppViewer::getTextureCache()->readFromFastCache(getID(), mRawDiscardLevel) ;
+	if(mRawImage.notNull())
+	{
+		mFullWidth = mRawImage->getWidth() << mRawDiscardLevel;
+		mFullHeight = mRawImage->getHeight() << mRawDiscardLevel;
+		setTexelsPerImage();
+
+		if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE)
+		{ 
+			//discard all oversized textures.
+			destroyRawImage();
+			setIsMissingAsset();
+			mRawDiscardLevel = INVALID_DISCARD_LEVEL ;
+		}
+		else
+		{
+			mRequestedDiscardLevel = mDesiredDiscardLevel + 1;
+			mIsRawImageValid = TRUE;			
+			addToCreateTexture() ;
+		}
+	}
 }
 
 void LLViewerFetchedTexture::setForSculpt()
 		S32 ddiscard = MAX_DISCARD_LEVEL - (S32)desired;
 		ddiscard = llclamp(ddiscard, 0, MAX_DELTA_DISCARD_LEVEL_FOR_PRIORITY);
 		priority = (ddiscard + 1) * PRIORITY_DELTA_DISCARD_LEVEL_FACTOR;
-		setAdditionalDecodePriority(1.0f) ;//boost the textures without any data so far.
+		setAdditionalDecodePriority(0.1f) ;//boost the textures without any data so far.
 	}
 	else if ((mMinDiscardLevel > 0) && (cur_discard <= mMinDiscardLevel))
 	{
 	{
 		return false; // process any raw image data in callbacks before replacing
 	}
+	if(mInFastCacheList)
+	{
+		return false;
+	}
 	
 	S32 current_discard = getCurrentDiscardLevelForFetching() ;
 	S32 desired_discard = getDesiredDiscardLevel();
 	{
 		make_request = false;
 	}
+	else if(mCachedRawImage.notNull() && (current_discard < 0 || current_discard > mCachedRawDiscardLevel))
+	{
+		make_request = false;
+		switchToCachedImage() ; //use the cached raw data first
+	}
 	//else if (!isJustBound() && mCachedRawImageReady)
 	//{
 	//	make_request = false;

File indra/newview/llviewertexture.h

 	void        setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;}
 
 	void        forceToDeleteRequest();
+	void        loadFromFastCache();
+	void        setInFastCacheList(bool in_list) { mInFastCacheList = in_list; }
+	bool        isInFastCacheList() { return mInFastCacheList; }
 protected:
 	/*virtual*/ void switchToCachedImage();
 	S32 getCurrentDiscardLevelForFetching() ;
 private:
 	BOOL  mFullyLoaded;
 	BOOL  mInDebug;
+	BOOL  mInFastCacheList;
 
 protected:		
 	std::string mLocalFileName;

File indra/newview/llviewertexturelist.cpp

 	// Flush all of the references
 	mLoadingStreamList.clear();
 	mCreateTextureList.clear();
+	mFastCacheList.clear();
 	
 	mUUIDMap.clear();
 	
 												   LLGLenum primary_format,
 												   LLHost request_from_host)
 {
+	static LLCachedControl<bool> fast_cache_fetching_enabled(gSavedSettings, "FastCacheFetchEnabled");
+
 	LLPointer<LLViewerFetchedTexture> imagep ;
 	switch(texture_type)
 	{
 		imagep->forceActive() ;
 	}
 
+	if(fast_cache_fetching_enabled)
+	{
+		mFastCacheList.insert(imagep);
+		imagep->setInFastCacheList(true);
+	}
 	return imagep ;
 }
 
 static LLFastTimer::DeclareTimer FTM_IMAGE_UPDATE_PRIORITIES("Prioritize");
 static LLFastTimer::DeclareTimer FTM_IMAGE_CALLBACKS("Callbacks");
 static LLFastTimer::DeclareTimer FTM_IMAGE_FETCH("Fetch");
+static LLFastTimer::DeclareTimer FTM_FAST_CACHE_IMAGE_FETCH("Fast Cache Fetch");
 static LLFastTimer::DeclareTimer FTM_IMAGE_CREATE("Create");
 static LLFastTimer::DeclareTimer FTM_IMAGE_STATS("Stats");
 
 	LLViewerStats::getInstance()->mRawMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageRaw::sGlobalRawMemory));
 	LLViewerStats::getInstance()->mFormattedMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageFormatted::sGlobalFormattedMemory));
 
+	{
+		//loading from fast cache 
+		LLFastTimer t(FTM_FAST_CACHE_IMAGE_FETCH);
+		max_time -= updateImagesLoadingFastCache(max_time);
+	}
 
 	{
 		LLFastTimer t(FTM_IMAGE_UPDATE_PRIORITIES);
 			{
 				continue;
 			}
+			if(imagep->isInFastCacheList())
+			{
+				continue; //wait for loading from the fast cache.
+			}
 
 			imagep->processTextureStats();
 			F32 old_priority = imagep->getDecodePriority();
 	return create_timer.getElapsedTimeF32();
 }
 
+F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time)
+{
+	if (gGLManager.mIsDisabled) return 0.0f;
+	if(mFastCacheList.empty())
+	{
+		return 0.f;
+	}
+	
+	//
+	// loading texture raw data from the fast cache directly.
+	//
+		
+	LLTimer timer;
+	image_list_t::iterator enditer = mFastCacheList.begin();
+	for (image_list_t::iterator iter = mFastCacheList.begin();
+		 iter != mFastCacheList.end();)
+	{
+		image_list_t::iterator curiter = iter++;
+		enditer = iter;
+		LLViewerFetchedTexture *imagep = *curiter;
+		imagep->loadFromFastCache();
+		if (timer.getElapsedTimeF32() > max_time)
+		{
+			break;
+		}
+	}
+	mFastCacheList.erase(mFastCacheList.begin(), enditer);
+	return timer.getElapsedTimeF32();
+}
+
 void LLViewerTextureList::forceImmediateUpdate(LLViewerFetchedTexture* imagep)
 {
 	if(!imagep)
 {
 	LLTimer timer;
 
+	//loading from fast cache 
+	updateImagesLoadingFastCache(max_time);
+
 	// Update texture stats and priorities
 	std::vector<LLPointer<LLViewerFetchedTexture> > image_list;
 	for (image_priority_list_t::iterator iter = mImageList.begin();

File indra/newview/llviewertexturelist.h

 	F32  updateImagesCreateTextures(F32 max_time);
 	F32  updateImagesFetchTextures(F32 max_time);
 	void updateImagesUpdateStats();
+	F32  updateImagesLoadingFastCache(F32 max_time);
 
 	void addImage(LLViewerFetchedTexture *image);
 	void deleteImage(LLViewerFetchedTexture *image);
 	image_list_t mLoadingStreamList;
 	image_list_t mCreateTextureList;
 	image_list_t mCallbackList;
+	image_list_t mFastCacheList;
 
 	// Note: just raw pointers because they are never referenced, just compared against
 	std::set<LLViewerFetchedTexture*> mDirtyTextureList;