/** * @file lltexturecache.cpp * @brief Object which handles local texture caching * * $LicenseInfo:firstyear=2000&license=viewergpl$ * * Copyright (c) 2000-2008, 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 "lltexturecache.h" #include "llapr.h" #include "lldir.h" #include "llimage.h" #include "lllfsthread.h" #include "llviewercontrol.h" #define USE_LFS_READ 0 #define USE_LFS_WRITE 0 // Note: first 4 bytes store file size, rest is j2c data const S32 TEXTURE_CACHE_ENTRY_SIZE = FIRST_PACKET_SIZE; //1024; class LLTextureCacheWorker : public LLWorkerClass { friend class LLTextureCache; private: class ReadResponder : public LLLFSThread::Responder { public: ReadResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} ~ReadResponder() {} void completed(S32 bytes) { mCache->lockWorkers(); LLTextureCacheWorker* reader = mCache->getReader(mHandle); if (reader) reader->ioComplete(bytes); mCache->unlockWorkers(); } LLTextureCache* mCache; LLTextureCacheWorker::handle_t mHandle; }; class WriteResponder : public LLLFSThread::Responder { public: WriteResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} ~WriteResponder() {} void completed(S32 bytes) { mCache->lockWorkers(); LLTextureCacheWorker* writer = mCache->getWriter(mHandle); if (writer) writer->ioComplete(bytes); mCache->unlockWorkers(); } LLTextureCache* mCache; LLTextureCacheWorker::handle_t mHandle; }; public: LLTextureCacheWorker(LLTextureCache* cache, U32 priority, const LLUUID& id, U8* data, S32 datasize, S32 offset, S32 imagesize, // for writes LLTextureCache::Responder* responder) : LLWorkerClass(cache, "LLTextureCacheWorker"), mID(id), mCache(cache), mPriority(priority), mReadData(NULL), mWriteData(data), mDataSize(datasize), mOffset(offset), mImageSize(imagesize), mImageFormat(IMG_CODEC_J2C), mImageLocal(FALSE), mResponder(responder), mFileHandle(LLLFSThread::nullHandle()), mBytesToRead(0), mBytesRead(0) { mPriority &= LLWorkerThread::PRIORITY_LOWBITS; } ~LLTextureCacheWorker() { llassert_always(!haveWork()); delete[] mReadData; } // override this interface virtual bool doRead() = 0; virtual bool doWrite() = 0; virtual bool doWork(S32 param); // Called from LLWorkerThread::processRequest() handle_t read() { addWork(0, LLWorkerThread::PRIORITY_HIGH | mPriority); return mRequestHandle; } handle_t write() { addWork(1, LLWorkerThread::PRIORITY_HIGH | mPriority); return mRequestHandle; } bool complete() { return checkWork(); } void ioComplete(S32 bytes) { mBytesRead = bytes; setPriority(LLWorkerThread::PRIORITY_HIGH | mPriority); } private: virtual void startWork(S32 param); // called from addWork() (MAIN THREAD) virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) virtual void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) protected: LLTextureCache* mCache; U32 mPriority; LLUUID mID; U8* mReadData; U8* mWriteData; S32 mDataSize; S32 mOffset; S32 mImageSize; EImageCodec mImageFormat; BOOL mImageLocal; LLPointer mResponder; LLLFSThread::handle_t mFileHandle; S32 mBytesToRead; LLAtomicS32 mBytesRead; }; class LLTextureCacheLocalFileWorker : public LLTextureCacheWorker { public: LLTextureCacheLocalFileWorker(LLTextureCache* cache, U32 priority, const LLString& filename, const LLUUID& id, U8* data, S32 datasize, S32 offset, S32 imagesize, // for writes LLTextureCache::Responder* responder) : LLTextureCacheWorker(cache, priority, id, data, datasize, offset, imagesize, responder), mFileName(filename) { } virtual bool doRead(); virtual bool doWrite(); private: LLString mFileName; }; bool LLTextureCacheLocalFileWorker::doRead() { S32 local_size = ll_apr_file_size(mFileName, mCache->getFileAPRPool()); if (local_size > 0 && mFileName.size() > 4) { mDataSize = local_size; // Only a complete file is valid LLString extension = mFileName.substr(mFileName.size() - 3, 3); mImageFormat = LLImageBase::getCodecFromExtension(extension); if (mImageFormat == IMG_CODEC_INVALID) { llwarns << "Unrecognized file extension " << extension << " for local texture " << mFileName << llendl; mDataSize = 0; // no data return true; } } else { // file doesn't exist mDataSize = 0; // no data return true; } #if USE_LFS_READ if (mFileHandle == LLLFSThread::nullHandle()) { mImageLocal = TRUE; mImageSize = local_size; if (!mDataSize || mDataSize + mOffset > local_size) { mDataSize = local_size - mOffset; } if (mDataSize <= 0) { // no more data to read mDataSize = 0; return true; } mReadData = new U8[mDataSize]; mBytesRead = -1; mBytesToRead = mDataSize; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); mFileHandle = LLLFSThread::sLocal->read(local_filename, mReadData, mOffset, mDataSize, new ReadResponder(mCache, mRequestHandle)); return false; } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "Error reading file from local cache: " << local_filename << " Bytes: " << mDataSize << " Offset: " << mOffset << " / " << mDataSize << llendl; mDataSize = 0; // failed delete[] mReadData; mReadData = NULL; } return true; } else { return false; } } #else if (!mDataSize || mDataSize > local_size) { mDataSize = local_size; } mReadData = new U8[mDataSize]; S32 bytes_read = ll_apr_file_read_ex(mFileName, mCache->getFileAPRPool(), mReadData, mOffset, mDataSize); if (bytes_read != mDataSize) { llwarns << "Error reading file from local cache: " << mFileName << " Bytes: " << mDataSize << " Offset: " << mOffset << " / " << mDataSize << llendl; mDataSize = 0; delete[] mReadData; mReadData = NULL; } else { mImageSize = local_size; mImageLocal = TRUE; } return true; #endif } bool LLTextureCacheLocalFileWorker::doWrite() { // no writes for local files return false; } class LLTextureCacheRemoteWorker : public LLTextureCacheWorker { public: LLTextureCacheRemoteWorker(LLTextureCache* cache, U32 priority, const LLUUID& id, U8* data, S32 datasize, S32 offset, S32 imagesize, // for writes LLTextureCache::Responder* responder) : LLTextureCacheWorker(cache, priority, id, data, datasize, offset, imagesize, responder), mState(INIT) { } virtual bool doRead(); virtual bool doWrite(); private: enum e_state { INIT = 0, LOCAL = 1, CACHE = 2, HEADER = 3, BODY = 4 }; e_state mState; }; //virtual void LLTextureCacheWorker::startWork(S32 param) { } bool LLTextureCacheRemoteWorker::doRead() { S32 local_size = 0; std::string local_filename; if (mState == INIT) { std::string filename = mCache->getLocalFileName(mID); local_filename = filename + ".j2c"; local_size = ll_apr_file_size(local_filename, mCache->getFileAPRPool()); if (local_size == 0) { local_filename = filename + ".tga"; local_size = ll_apr_file_size(local_filename, mCache->getFileAPRPool()); if (local_size > 0) { mImageFormat = IMG_CODEC_TGA; mDataSize = local_size; // Only a complete .tga file is valid } } if (local_size > 0) { mState = LOCAL; } else { mState = CACHE; } } if (mState == LOCAL) { #if USE_LFS_READ if (mFileHandle == LLLFSThread::nullHandle()) { mImageLocal = TRUE; mImageSize = local_size; if (!mDataSize || mDataSize + mOffset > local_size) { mDataSize = local_size - mOffset; } if (mDataSize <= 0) { // no more data to read mDataSize = 0; return true; } mReadData = new U8[mDataSize]; mBytesRead = -1; mBytesToRead = mDataSize; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); mFileHandle = LLLFSThread::sLocal->read(local_filename, mReadData, mOffset, mDataSize, new ReadResponder(mCache, mRequestHandle)); return false; } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "Error reading file from local cache: " << local_filename << " Bytes: " << mDataSize << " Offset: " << mOffset << " / " << mDataSize << llendl; mDataSize = 0; // failed delete[] mReadData; mReadData = NULL; } return true; } else { return false; } } #else if (!mDataSize || mDataSize > local_size) { mDataSize = local_size; } mReadData = new U8[mDataSize]; S32 bytes_read = ll_apr_file_read_ex(local_filename, mCache->getFileAPRPool(), mReadData, mOffset, mDataSize); if (bytes_read != mDataSize) { llwarns << "Error reading file from local cache: " << local_filename << " Bytes: " << mDataSize << " Offset: " << mOffset << " / " << mDataSize << llendl; mDataSize = 0; delete[] mReadData; mReadData = NULL; } else { mImageSize = local_size; mImageLocal = TRUE; } return true; #endif } S32 idx = -1; if (mState == CACHE) { llassert_always(mImageSize == 0); idx = mCache->getHeaderCacheEntry(mID, false, &mImageSize); if (idx >= 0 && mImageSize > mOffset) { llassert_always(mImageSize > 0); if (!mDataSize || mDataSize > mImageSize) { mDataSize = mImageSize; } mState = mOffset < TEXTURE_CACHE_ENTRY_SIZE ? HEADER : BODY; } else { mDataSize = 0; // no data return true; } } if (mState == HEADER) { #if USE_LFS_READ if (mFileHandle == LLLFSThread::nullHandle()) { llassert_always(idx >= 0); llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; llassert_always(mReadData == NULL); mReadData = new U8[size]; mBytesRead = -1; mBytesToRead = size; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); mFileHandle = LLLFSThread::sLocal->read(mCache->mHeaderDataFileName, mReadData, offset, mBytesToRead, new ReadResponder(mCache, mRequestHandle)); return false; } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes read from header: " << mBytesRead << " != " << mBytesToRead << llendl; mDataSize = -1; // failed return true; } if (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE) { return true; // done } else { mFileHandle = LLLFSThread::nullHandle(); mState = BODY; } } else { return false; } } #else llassert_always(idx >= 0); llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; mReadData = new U8[size]; S32 bytes_read = ll_apr_file_read_ex(mCache->mHeaderDataFileName, mCache->getFileAPRPool(), mReadData, offset, size); if (bytes_read != size) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes read from header: " << bytes_read << " / " << size << llendl; mDataSize = -1; // failed return true; } if (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE) { return true; // done } else { mState = BODY; } #endif } if (mState == BODY) { #if USE_LFS_READ if (mFileHandle == LLLFSThread::nullHandle()) { std::string filename = mCache->getTextureFileName(mID); S32 filesize = ll_apr_file_size(filename, mCache->getFileAPRPool()); if (filesize > mOffset) { S32 datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize; mDataSize = llmin(datasize, mDataSize); S32 data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; data_offset = llmax(data_offset, 0); S32 file_size = mDataSize - data_offset; S32 file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; file_offset = llmax(file_offset, 0); llassert_always(mDataSize > 0); U8* data = new U8[mDataSize]; if (data_offset > 0) { llassert_always(mReadData); llassert_always(data_offset <= mDataSize); memcpy(data, mReadData, data_offset); delete[] mReadData; mReadData = NULL; } llassert_always(mReadData == NULL); mReadData = data; mBytesRead = -1; mBytesToRead = file_size; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); llassert_always(data_offset + mBytesToRead <= mDataSize); mFileHandle = LLLFSThread::sLocal->read(filename, mReadData + data_offset, file_offset, mBytesToRead, new ReadResponder(mCache, mRequestHandle)); return false; } else { mDataSize = TEXTURE_CACHE_ENTRY_SIZE; return true; // done } } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes read from body: " << mBytesRead << " != " << mBytesToRead << llendl; mDataSize = -1; // failed } return true; } else { return false; } } #else std::string filename = mCache->getTextureFileName(mID); S32 filesize = ll_apr_file_size(filename, mCache->getFileAPRPool()); S32 bytes_read = 0; if (filesize > mOffset) { S32 datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize; mDataSize = llmin(datasize, mDataSize); S32 data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; data_offset = llmax(data_offset, 0); S32 file_size = mDataSize - data_offset; S32 file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; file_offset = llmax(file_offset, 0); U8* data = new U8[mDataSize]; if (data_offset > 0) { llassert_always(mReadData); memcpy(data, mReadData, data_offset); delete[] mReadData; } mReadData = data; bytes_read = ll_apr_file_read_ex(filename, mCache->getFileAPRPool(), mReadData + data_offset, file_offset, file_size); if (bytes_read != file_size) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes read from body: " << bytes_read << " / " << file_size << llendl; mDataSize = -1; // failed return true; } } else { mDataSize = TEXTURE_CACHE_ENTRY_SIZE; } return true; #endif } return false; } bool LLTextureCacheRemoteWorker::doWrite() { S32 idx = -1; // No LOCAL state for write() if (mState == INIT) { S32 cur_imagesize = 0; S32 offset = mOffset; idx = mCache->getHeaderCacheEntry(mID, false, &cur_imagesize); if (idx >= 0 && cur_imagesize > 0) { offset = TEXTURE_CACHE_ENTRY_SIZE; // don't re-write header } idx = mCache->getHeaderCacheEntry(mID, true, &mImageSize); // touch entry if (idx >= 0) { if(cur_imagesize > 0 && mImageSize != cur_imagesize) { llwarns << "Header cache entry size: " << cur_imagesize << " != mImageSize: " << mImageSize << llendl; offset = 0; // re-write header } mState = offset < TEXTURE_CACHE_ENTRY_SIZE ? HEADER : BODY; } else { mDataSize = -1; // failed return true; } } if (mState == HEADER) { #if USE_LFS_WRITE if (mFileHandle == LLLFSThread::nullHandle()) { llassert_always(idx >= 0); llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; mBytesRead = -1; mBytesToRead = size; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); mFileHandle = LLLFSThread::sLocal->write(mCache->mHeaderDataFileName, mWriteData, offset, mBytesToRead, new WriteResponder(mCache, mRequestHandle)); return false; } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes written to header: " << mBytesRead << " != " << mBytesToRead << llendl; mDataSize = -1; // failed return true; } if (mDataSize <= mBytesToRead) { return true; // done } else { mFileHandle = LLLFSThread::nullHandle(); mState = BODY; } } else { return false; } } #else llassert_always(idx >= 0); llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; S32 bytes_written = ll_apr_file_write_ex(mCache->mHeaderDataFileName, mCache->getFileAPRPool(), mWriteData, offset, size); if (bytes_written <= 0) { llwarns << "LLTextureCacheWorker: missing entry: " << mID << llendl; mDataSize = -1; // failed return true; } if (mDataSize <= size) { return true; // done } else { mState = BODY; } #endif } if (mState == BODY) { #if USE_LFS_WRITE if (mFileHandle == LLLFSThread::nullHandle()) { S32 data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; data_offset = llmax(data_offset, 0); S32 file_size = mDataSize - data_offset; S32 file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; file_offset = llmax(file_offset, 0); if (file_size > 0 && mCache->appendToTextureEntryList(mID, file_size)) { std::string filename = mCache->getTextureFileName(mID); mBytesRead = -1; mBytesToRead = file_size; setPriority(LLWorkerThread::PRIORITY_LOW | mPriority); mFileHandle = LLLFSThread::sLocal->write(filename, mWriteData + data_offset, file_offset, mBytesToRead, new WriteResponder(mCache, mRequestHandle)); return false; } else { mDataSize = 0; // no data written return true; // done } } else { if (mBytesRead >= 0) { if (mBytesRead != mBytesToRead) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes written to body: " << mBytesRead << " != " << mBytesToRead << llendl; mDataSize = -1; // failed } return true; } else { return false; } } #else S32 data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; data_offset = llmax(data_offset, 0); S32 file_size = mDataSize - data_offset; S32 file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; file_offset = llmax(file_offset, 0); S32 bytes_written = 0; if (file_size > 0 && mCache->appendToTextureEntryList(mID, file_size)) { std::string filename = mCache->getTextureFileName(mID); bytes_written = ll_apr_file_write_ex(filename, mCache->getFileAPRPool(), mWriteData + data_offset, file_offset, file_size); if (bytes_written <= 0) { mDataSize = -1; // failed } } else { mDataSize = 0; // no data written } return true; #endif } return false; } //virtual bool LLTextureCacheWorker::doWork(S32 param) { bool res = false; if (param == 0) // read { res = doRead(); } else if (param == 1) // write { res = doWrite(); } else { llassert_always(0); } return res; } //virtual (WORKER THREAD) void LLTextureCacheWorker::finishWork(S32 param, bool completed) { if (mResponder.notNull()) { bool success = (completed && mDataSize > 0); if (param == 0) { // read if (success) { mResponder->setData(mReadData, mDataSize, mImageSize, mImageFormat, mImageLocal); mReadData = NULL; // responder owns data mDataSize = 0; } else { delete[] mReadData; mReadData = NULL; } } else { // write mWriteData = NULL; // we never owned data mDataSize = 0; } mCache->addCompleted(mResponder, success); } } //virtual (MAIN THREAD) void LLTextureCacheWorker::endWork(S32 param, bool aborted) { if (aborted) { // Let the destructor handle any cleanup return; } switch(param) { default: case 0: // read case 1: // write { if (mDataSize < 0) { // failed mCache->removeFromCache(mID); } break; } } } ////////////////////////////////////////////////////////////////////////////// LLTextureCache::LLTextureCache(bool threaded) : LLWorkerThread("TextureCache", threaded), mWorkersMutex(getAPRPool()), mHeaderMutex(getAPRPool()), mListMutex(getAPRPool()), mFileAPRPool(NULL), mReadOnly(FALSE), mTexturesSizeTotal(0), mDoPurge(FALSE) { apr_pool_create(&mFileAPRPool, NULL); } LLTextureCache::~LLTextureCache() { apr_pool_destroy(mFileAPRPool); } ////////////////////////////////////////////////////////////////////////////// //virtual S32 LLTextureCache::update(U32 max_time_ms) { S32 res; res = LLWorkerThread::update(max_time_ms); mListMutex.lock(); handle_list_t priorty_list = mPrioritizeWriteList; // copy list mPrioritizeWriteList.clear(); responder_list_t completed_list = mCompletedList; // copy list mCompletedList.clear(); mListMutex.unlock(); lockWorkers(); for (handle_list_t::iterator iter1 = priorty_list.begin(); iter1 != priorty_list.end(); ++iter1) { handle_t handle = *iter1; handle_map_t::iterator iter2 = mWriters.find(handle); if(iter2 != mWriters.end()) { LLTextureCacheWorker* worker = iter2->second; worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mPriority); } } for (responder_list_t::iterator iter1 = completed_list.begin(); iter1 != completed_list.end(); ++iter1) { Responder *responder = iter1->first; bool success = iter1->second; responder->completed(success); } unlockWorkers(); return res; } ////////////////////////////////////////////////////////////////////////////// std::string LLTextureCache::getLocalFileName(const LLUUID& id) { // Does not include extension std::string idstr = id.asString(); std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "textures", idstr); return filename; } std::string LLTextureCache::getTextureFileName(const LLUUID& id) { std::string idstr = id.asString(); std::string delem = gDirUtilp->getDirDelimiter(); std::string filename = mTexturesDirName + delem + idstr[0] + delem + idstr; return filename; } bool LLTextureCache::appendToTextureEntryList(const LLUUID& id, S32 bodysize) { bool res = false; bool purge = false; // Append UUID to end of texture entries { LLMutexLock lock(&mHeaderMutex); size_map_t::iterator iter = mTexturesSizeMap.find(id); if (iter == mTexturesSizeMap.end() || iter->second < bodysize) { llassert_always(bodysize > 0); Entry* entry = new Entry(id, bodysize, time(NULL)); ll_apr_file_write_ex(mTexturesDirEntriesFileName, getFileAPRPool(), (U8*)entry, -1, 1*sizeof(Entry)); delete entry; if (iter != mTexturesSizeMap.end()) { mTexturesSizeTotal -= iter->second; } mTexturesSizeTotal += bodysize; mTexturesSizeMap[id] = bodysize; if (mTexturesSizeTotal > sCacheMaxTexturesSize) { purge = true; } res = true; } } if (purge) { mDoPurge = TRUE; } return res; } ////////////////////////////////////////////////////////////////////////////// //static const S32 MAX_REASONABLE_FILE_SIZE = 512*1024*1024; // 512 MB F32 LLTextureCache::sHeaderCacheVersion = 1.0f; U32 LLTextureCache::sCacheMaxEntries = MAX_REASONABLE_FILE_SIZE / TEXTURE_CACHE_ENTRY_SIZE; S64 LLTextureCache::sCacheMaxTexturesSize = 0; // no limit const char* entries_filename = "texture.entries"; const char* cache_filename = "texture.cache"; const char* textures_dirname = "textures"; void LLTextureCache::setDirNames(ELLPath location) { std::string delem = gDirUtilp->getDirDelimiter(); mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, entries_filename); mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, cache_filename); mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname); mTexturesDirEntriesFileName = mTexturesDirName + delem + entries_filename; } void LLTextureCache::purgeCache(ELLPath location) { if (!mReadOnly) { setDirNames(location); ll_apr_file_remove(mHeaderEntriesFileName, NULL); ll_apr_file_remove(mHeaderDataFileName, NULL); } purgeAllTextures(true); } S64 LLTextureCache::initCache(ELLPath location, S64 max_size, BOOL read_only) { mReadOnly = read_only; S64 header_size = (max_size * 2) / 10; S64 max_entries = header_size / TEXTURE_CACHE_ENTRY_SIZE; sCacheMaxEntries = (S32)(llmin((S64)sCacheMaxEntries, max_entries)); header_size = sCacheMaxEntries * TEXTURE_CACHE_ENTRY_SIZE; max_size -= header_size; if (sCacheMaxTexturesSize > 0) sCacheMaxTexturesSize = llmin(sCacheMaxTexturesSize, max_size); else sCacheMaxTexturesSize = max_size; max_size -= sCacheMaxTexturesSize; llinfos << "TEXTURE CACHE: Headers: " << sCacheMaxEntries << " Textures size: " << sCacheMaxTexturesSize/(1024*1024) << " MB" << llendl; setDirNames(location); if (!mReadOnly) { LLFile::mkdir(mTexturesDirName.c_str()); const char* subdirs = "0123456789abcdef"; for (S32 i=0; i<16; i++) { std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; LLFile::mkdir(dirname.c_str()); } } readHeaderCache(); purgeTextures(true); // calc mTexturesSize and make some room in the texture cache if we need it return max_size; // unused cache space } struct lru_data { lru_data(U32 t, S32 i, const LLUUID& id) { time=t; index=i; uuid=id; } U32 time; S32 index; LLUUID uuid; struct Compare { // lhs < rhs typedef const lru_data* lru_data_ptr; bool operator()(const lru_data_ptr& a, const lru_data_ptr& b) const { if (a->time > b->time) return true; else if (b->time > a->time) return false; else return a->index < b->index; } }; }; // Called from either the main thread or the worker thread void LLTextureCache::readHeaderCache(apr_pool_t* poolp) { LLMutexLock lock(&mHeaderMutex); mHeaderEntriesInfo.mVersion = 0.f; mHeaderEntriesInfo.mEntries = 0; if (ll_apr_file_exists(mHeaderEntriesFileName, poolp)) { ll_apr_file_read_ex(mHeaderEntriesFileName, poolp, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); } if (mHeaderEntriesInfo.mVersion != sHeaderCacheVersion) { if (!mReadOnly) { // Info with 0 entries mHeaderEntriesInfo.mVersion = sHeaderCacheVersion; ll_apr_file_write_ex(mHeaderEntriesFileName, poolp, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); } } else { S32 num_entries = mHeaderEntriesInfo.mEntries; if (num_entries) { Entry* entries = new Entry[num_entries]; ll_apr_file_read_ex(mHeaderEntriesFileName, poolp, (U8*)entries, sizeof(EntriesInfo), num_entries*sizeof(Entry)); typedef std::set lru_set_t; lru_set_t lru; for (S32 i=0; i= 0) // -1 indicates erased entry, skip { const LLUUID& id = entries[i].mID; lru.insert(new lru_data(entries[i].mTime, i, id)); mHeaderIDMap[id] = i; } } mLRU.clear(); S32 lru_entries = sCacheMaxEntries / 10; for (lru_set_t::iterator iter = lru.begin(); iter != lru.end(); ++iter) { lru_data* data = *iter; mLRU[data->index] = data->uuid; if (--lru_entries <= 0) break; } for_each(lru.begin(), lru.end(), DeletePointer()); delete[] entries; } } } ////////////////////////////////////////////////////////////////////////////// void LLTextureCache::purgeAllTextures(bool purge_directories) { if (!mReadOnly) { const char* subdirs = "0123456789abcdef"; std::string delem = gDirUtilp->getDirDelimiter(); std::string mask = delem + "*"; for (S32 i=0; i<16; i++) { std::string dirname = mTexturesDirName + delem + subdirs[i]; gDirUtilp->deleteFilesInDir(dirname.c_str(),mask); if (purge_directories) { LLFile::rmdir(dirname.c_str()); } } ll_apr_file_remove(mTexturesDirEntriesFileName, NULL); if (purge_directories) { LLFile::rmdir(mTexturesDirName.c_str()); } } mTexturesSizeMap.clear(); } void LLTextureCache::purgeTextures(bool validate) { if (mReadOnly) { return; } LLMutexLock lock(&mHeaderMutex); S32 filesize = ll_apr_file_size(mTexturesDirEntriesFileName, NULL); S32 num_entries = filesize / sizeof(Entry); if (num_entries * (S32)sizeof(Entry) != filesize) { llwarns << "Bad cache file: " << mTexturesDirEntriesFileName << " Purging." << llendl; purgeAllTextures(false); return; } if (num_entries == 0) { return; // nothing to do } Entry* entries = new Entry[num_entries]; S32 bytes_read = ll_apr_file_read_ex(mTexturesDirEntriesFileName, NULL, (U8*)entries, 0, num_entries*sizeof(Entry)); if (bytes_read != filesize) { llwarns << "Bad cache file (2): " << mTexturesDirEntriesFileName << " Purging." << llendl; purgeAllTextures(false); return; } llinfos << "TEXTURE CACHE: Reading Entries..." << llendl; std::map entry_idx_map; S64 total_size = 0; for (S32 idx=0; idx::iterator iter = entry_idx_map.find(id); if (iter != entry_idx_map.end()) { // Newer entry replacing older entry S32 pidx = iter->second; total_size -= entries[pidx].mSize; entries[pidx].mSize = 0; // flag: skip older entry } entry_idx_map[id] = idx; total_size += entries[idx].mSize; } U32 validate_idx = 0; if (validate) { validate_idx = gSavedSettings.getU32("CacheValidateCounter"); U32 next_idx = (++validate_idx) % 256; gSavedSettings.setU32("CacheValidateCounter", next_idx); llinfos << "TEXTURE CACHE: Validating: " << validate_idx << llendl; } S64 min_cache_size = (sCacheMaxTexturesSize * 9) / 10; S32 purge_count = 0; S32 next_idx = 0; for (S32 idx=0; idx= min_cache_size) { purge_entry = true; } else if (validate) { // make sure file exists and is the correct size S32 uuididx = entries[idx].mID.mData[0]; if (uuididx == validate_idx) { // llinfos << "Validating: " << filename << "Size: " << entries[idx].mSize << llendl; S32 bodysize = ll_apr_file_size(filename, NULL); if (bodysize != entries[idx].mSize) { llwarns << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mSize << filename << llendl; purge_entry = true; } } } if (purge_entry) { purge_count++; // llinfos << "PURGING: " << filename << llendl; ll_apr_file_remove(filename, NULL); total_size -= entries[idx].mSize; entries[idx].mSize = 0; } else { if (next_idx != idx) { entries[next_idx] = entries[idx]; } ++next_idx; } } num_entries = next_idx; llinfos << "TEXTURE CACHE: Writing Entries: " << num_entries << llendl; ll_apr_file_remove(mTexturesDirEntriesFileName, NULL); ll_apr_file_write_ex(mTexturesDirEntriesFileName, NULL, (U8*)&entries[0], 0, num_entries*sizeof(Entry)); mTexturesSizeTotal = 0; mTexturesSizeMap.clear(); for (S32 idx=0; idxsecond; } return res; } LLTextureCacheWorker* LLTextureCache::getWriter(handle_t handle) { LLTextureCacheWorker* res = NULL; handle_map_t::iterator iter = mWriters.find(handle); if (iter != mWriters.end()) { res = iter->second; } return res; } ////////////////////////////////////////////////////////////////////////////// // Called from work thread S32 LLTextureCache::getHeaderCacheEntry(const LLUUID& id, bool touch, S32* imagesize) { bool retry = false; S32 idx = -1; { LLMutexLock lock(&mHeaderMutex); id_map_t::iterator iter = mHeaderIDMap.find(id); if (iter != mHeaderIDMap.end()) { idx = iter->second; } else if (touch && !mReadOnly) { if (mHeaderEntriesInfo.mEntries < sCacheMaxEntries) { // Add an entry idx = mHeaderEntriesInfo.mEntries++; mHeaderIDMap[id] = idx; // Update Info ll_apr_file_write_ex(mHeaderEntriesFileName, getFileAPRPool(), (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); } else if (!mLRU.empty()) { idx = mLRU.begin()->first; // will be erased below const LLUUID& oldid = mLRU.begin()->second; mHeaderIDMap.erase(oldid); mTexturesSizeMap.erase(oldid); mHeaderIDMap[id] = idx; } else { idx = -1; retry = true; } } if (idx >= 0) { if (touch && !mReadOnly) { // Update the lru entry mLRU.erase(idx); llassert_always(imagesize && *imagesize > 0); Entry* entry = new Entry(id, *imagesize, time(NULL)); S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); ll_apr_file_write_ex(mHeaderEntriesFileName, getFileAPRPool(), (U8*)entry, offset, sizeof(Entry)); delete entry; } else if (imagesize) { // Get the image size Entry entry; S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); ll_apr_file_read_ex(mHeaderEntriesFileName, getFileAPRPool(), (U8*)&entry, offset, sizeof(Entry)); *imagesize = entry.mSize; } } } if (retry) { readHeaderCache(getFileAPRPool()); // updates the lru llassert_always(!mLRU.empty() || mHeaderEntriesInfo.mEntries < sCacheMaxEntries); idx = getHeaderCacheEntry(id, touch, imagesize); // assert above ensures no inf. recursion } return idx; } ////////////////////////////////////////////////////////////////////////////// // Calls from texture pipeline thread (i.e. LLTextureFetch) LLTextureCache::handle_t LLTextureCache::readFromCache(const LLString& filename, const LLUUID& id, U32 priority, S32 offset, S32 size, ReadResponder* responder) { // Note: checking to see if an entry exists can cause a stall, // so let the thread handle it LLMutexLock lock(&mWorkersMutex); LLTextureCacheWorker* worker = new LLTextureCacheLocalFileWorker(this, priority, filename, id, NULL, size, offset, 0, responder); handle_t handle = worker->read(); mReaders[handle] = worker; return handle; } LLTextureCache::handle_t LLTextureCache::readFromCache(const LLUUID& id, U32 priority, S32 offset, S32 size, ReadResponder* responder) { // Note: checking to see if an entry exists can cause a stall, // so let the thread handle it LLMutexLock lock(&mWorkersMutex); LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, priority, id, NULL, size, offset, 0, responder); handle_t handle = worker->read(); mReaders[handle] = worker; return handle; } bool LLTextureCache::readComplete(handle_t handle, bool abort) { lockWorkers(); handle_map_t::iterator iter = mReaders.find(handle); llassert_always(iter != mReaders.end()); LLTextureCacheWorker* worker = iter->second; bool res = worker->complete(); if (res || abort) { mReaders.erase(handle); unlockWorkers(); worker->scheduleDelete(); return true; } else { unlockWorkers(); return false; } } LLTextureCache::handle_t LLTextureCache::writeToCache(const LLUUID& id, U32 priority, U8* data, S32 datasize, S32 imagesize, WriteResponder* responder) { if (mReadOnly) { delete responder; return LLWorkerThread::nullHandle(); } if (mDoPurge) { // NOTE: This may cause an occasional hiccup, // but it really needs to be done on the control thread // (i.e. here) purgeTextures(false); mDoPurge = FALSE; } if (datasize >= TEXTURE_CACHE_ENTRY_SIZE) { LLMutexLock lock(&mWorkersMutex); llassert_always(imagesize > 0); LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, priority, id, data, datasize, 0, imagesize, responder); handle_t handle = worker->write(); mWriters[handle] = worker; return handle; } delete responder; return LLWorkerThread::nullHandle(); } bool LLTextureCache::writeComplete(handle_t handle, bool abort) { lockWorkers(); handle_map_t::iterator iter = mWriters.find(handle); llassert_always(iter != mWriters.end()); LLTextureCacheWorker* worker = iter->second; if (worker->complete() || abort) { mWriters.erase(handle); unlockWorkers(); worker->scheduleDelete(); return true; } else { unlockWorkers(); return false; } } void LLTextureCache::prioritizeWrite(handle_t handle) { // Don't prioritize yet, we might be working on this now // which could create a deadlock LLMutexLock lock(&mListMutex); mPrioritizeWriteList.push_back(handle); } void LLTextureCache::addCompleted(Responder* responder, bool success) { LLMutexLock lock(&mListMutex); mCompletedList.push_back(std::make_pair(responder,success)); } ////////////////////////////////////////////////////////////////////////////// // Called from MAIN thread (endWork()) bool LLTextureCache::removeHeaderCacheEntry(const LLUUID& id) { if (mReadOnly) { return false; } LLMutexLock lock(&mHeaderMutex); id_map_t::iterator iter = mHeaderIDMap.find(id); if (iter != mHeaderIDMap.end()) { S32 idx = iter->second; if (idx >= 0) { Entry* entry = new Entry(id, -1, time(NULL)); S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); ll_apr_file_write_ex(mHeaderEntriesFileName, NULL, (U8*)entry, offset, sizeof(Entry)); delete entry; mLRU[idx] = id; mHeaderIDMap.erase(id); mTexturesSizeMap.erase(id); return true; } } return false; } void LLTextureCache::removeFromCache(const LLUUID& id) { llwarns << "Removing texture from cache: " << id << llendl; if (!mReadOnly) { removeHeaderCacheEntry(id); ll_apr_file_remove(getTextureFileName(id), NULL); } } ////////////////////////////////////////////////////////////////////////////// LLTextureCache::ReadResponder::ReadResponder() : mImageSize(0), mImageLocal(FALSE) { } void LLTextureCache::ReadResponder::setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, BOOL imagelocal) { if (mFormattedImage.notNull()) { llassert_always(mFormattedImage->getCodec() == imageformat); mFormattedImage->appendData(data, datasize); } else { mFormattedImage = LLImageFormatted::createFromType(imageformat); mFormattedImage->setData(data,datasize); } mImageSize = imagesize; mImageLocal = imagelocal; } //////////////////////////////////////////////////////////////////////////////