/** * @file lltexturecache.cpp * @brief Object which handles local texture caching * * $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 "lltexturecache.h" #include "llapr.h" #include "lldir.h" #include "llimage.h" #include "lllfsthread.h" #include "llviewercontrol.h" // Included to allow LLTextureCache::purgeTextures() to pause watchdog timeout #include "llappviewer.h" // Cache organization: // cache/texture.entries // Unordered array of Entry structs // cache/texture.cache // First TEXTURE_CACHE_ENTRY_SIZE bytes of each texture in texture.entries in same order // Entry size same as header packet, so we're not 0-padding unless whole image is contained in header. // cache/textures/[0-F]/UUID.texture // Actual texture body files const S32 TEXTURE_CACHE_ENTRY_SIZE = FIRST_PACKET_SIZE; 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) 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 std::string& 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: std::string mFileName; }; bool LLTextureCacheLocalFileWorker::doRead() { S32 local_size = LLAPRFile::size(mFileName); if (local_size > 0 && mFileName.size() > 4) { mDataSize = local_size; // Only a complete file is valid std::string 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 = LLAPRFile::readEx(mFileName, 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) { } // This is where a texture is read from the cache system (header and body) // Current assumption are: // - the whole data are in a raw form, will be stored at mReadData // - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) // - the code supports offset reading but this is actually never exercised in the viewer bool LLTextureCacheRemoteWorker::doRead() { bool done = false; S32 idx = -1; S32 local_size = 0; std::string local_filename; // First state / stage : find out if the file is local if (mState == INIT) { std::string filename = mCache->getLocalFileName(mID); // Is it a JPEG2000 file? { local_filename = filename + ".j2c"; local_size = LLAPRFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_J2C; } } // If not, is it a jpeg file? if (local_size == 0) { local_filename = filename + ".jpg"; local_size = LLAPRFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_JPEG; mDataSize = local_size; // Only a complete .jpg file is valid } } // Hmm... What about a targa file? (used for UI texture mostly) if (local_size == 0) { local_filename = filename + ".tga"; local_size = LLAPRFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_TGA; mDataSize = local_size; // Only a complete .tga file is valid } } // Determine the next stage: if we found a file, then LOCAL else CACHE mState = (local_size > 0 ? LOCAL : CACHE); } // Second state / stage : if the file is local, load it and leave if (!done && (mState == LOCAL)) { llassert(local_size != 0); // we're assuming there is a non empty local file here... llassert(mReadData == NULL); if (!mDataSize || mDataSize > (local_size - mOffset)) { mDataSize = local_size - mOffset; } // Allocate read buffer mReadData = new U8[mDataSize]; S32 bytes_read = LLAPRFile::readEx(local_filename, 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; } // We're done... done = true; } // Second state / stage : identify the cache or not... if (!done && (mState == CACHE)) { idx = mCache->getHeaderCacheEntry(mID, mImageSize); if (idx < 0) { // The texture is *not* cached. We're done here... mDataSize = 0; // no data done = true; } else { // If the read offset is bigger than the header cache, we read directly from the body // Note that currently, we *never* read with offset from the cache, so the result is *always* HEADER mState = mOffset < TEXTURE_CACHE_ENTRY_SIZE ? HEADER : BODY; } } // Third state / stage : read data from the header cache (texture.entries) file if (!done && (mState == HEADER)) { llassert_always(idx >= 0); // we need an entry here or reading the header makes no sense llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; // Compute the size we need to read (in bytes) S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; size = llmin(size, mDataSize); // Allocate the read buffer mReadData = new U8[size]; S32 bytes_read = LLAPRFile::readEx(mCache->mHeaderDataFileName, mReadData, offset, size); if (bytes_read != size) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes read from header: " << bytes_read << " / " << size << llendl; delete[] mReadData; mReadData = NULL; mDataSize = -1; // failed done = true; } // If we already read all we expected, we're actually done if (mDataSize <= bytes_read) { done = true; } else { mState = BODY; } } // Fourth state / stage : read the rest of the data from the UUID based cached file if (!done && (mState == BODY)) { std::string filename = mCache->getTextureFileName(mID); S32 filesize = LLAPRFile::size(filename); if (filesize && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) { S32 max_datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize - mOffset; mDataSize = llmin(max_datasize, mDataSize); S32 data_offset, file_size, file_offset; // Reserve the whole data buffer first U8* data = new U8[mDataSize]; // Set the data file pointers taking the read offset into account. 2 cases: if (mOffset < TEXTURE_CACHE_ENTRY_SIZE) { // Offset within the header record. That means we read something from the header cache. // Note: most common case is (mOffset = 0), so this is the "normal" code path. data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; // i.e. TEXTURE_CACHE_ENTRY_SIZE if mOffset nul (common case) file_offset = 0; file_size = mDataSize - data_offset; // Copy the raw data we've been holding from the header cache into the new sized buffer llassert_always(mReadData); memcpy(data, mReadData, data_offset); delete[] mReadData; mReadData = NULL; } else { // Offset bigger than the header record. That means we haven't read anything yet. data_offset = 0; file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; file_size = mDataSize; // No data from header cache to copy in that case, we skipped it all } // Now use that buffer as the object read buffer llassert_always(mReadData == NULL); mReadData = data; // Read the data at last S32 bytes_read = LLAPRFile::readEx(filename, 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; delete[] mReadData; mReadData = NULL; mDataSize = -1; // failed done = true; } } else { // No body, we're done. mDataSize = llmax(TEXTURE_CACHE_ENTRY_SIZE - mOffset, 0); lldebugs << "No body file for: " << filename << llendl; } // Nothing else to do at that point... done = true; } // Clean up and exit return done; } // This is where *everything* about a texture is written down in the cache system (entry map, header and body) // Current assumption are: // - the whole data are in a raw form, starting at mWriteData // - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) // - the code *does not* support offset writing so there are no difference between buffer addresses and start of data bool LLTextureCacheRemoteWorker::doWrite() { bool done = false; S32 idx = -1; // First state / stage : check that what we're trying to cache is in an OK shape if (mState == INIT) { llassert_always(mOffset == 0); // We currently do not support write offsets llassert_always(mDataSize > 0); // Things will go badly wrong if mDataSize is nul or negative... mState = CACHE; } // No LOCAL state for write(): because it doesn't make much sense to cache a local file... // Second state / stage : set an entry in the headers entry (texture.entries) file if (!done && (mState == CACHE)) { bool alreadyCached = false; S32 cur_imagesize = 0; // Checks if this image is already in the entry list idx = mCache->getHeaderCacheEntry(mID, cur_imagesize); if (idx >= 0 && (cur_imagesize >= 0)) { alreadyCached = true; // already there and non empty } idx = mCache->setHeaderCacheEntry(mID, mImageSize); // create or touch the entry if (idx < 0) { llwarns << "LLTextureCacheWorker: " << mID << " Unable to create header entry for writing!" << llendl; mDataSize = -1; // failed done = true; } else { if (cur_imagesize > 0 && (mImageSize != cur_imagesize)) { alreadyCached = false; // re-write the header if the size changed in all cases } if (alreadyCached && (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE)) { // Small texture already cached case: we're done with writing done = true; } else { // If the texture has already been cached, we don't resave the header and go directly to the body part mState = alreadyCached ? BODY : HEADER; } } } // Third stage / state : write the header record in the header file (texture.cache) if (!done && (mState == HEADER)) { llassert_always(idx >= 0); // we need an entry here or storing the header makes no sense S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE; // skip to the correct spot in the header file S32 size = TEXTURE_CACHE_ENTRY_SIZE; // record size is fixed for the header S32 bytes_written; if (mDataSize < TEXTURE_CACHE_ENTRY_SIZE) { // We need to write a full record in the header cache so, if the amount of data is smaller // than a record, we need to transfer the data to a buffer padded with 0 and write that U8* padBuffer = new U8[TEXTURE_CACHE_ENTRY_SIZE]; memset(padBuffer, 0, TEXTURE_CACHE_ENTRY_SIZE); // Init with zeros memcpy(padBuffer, mWriteData, mDataSize); // Copy the write buffer bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, padBuffer, offset, size); delete [] padBuffer; } else { // Write the header record (== first TEXTURE_CACHE_ENTRY_SIZE bytes of the raw file) in the header file bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, mWriteData, offset, size); } if (bytes_written <= 0) { llwarns << "LLTextureCacheWorker: " << mID << " Unable to write header entry!" << llendl; mDataSize = -1; // failed done = true; } // If we wrote everything (may be more with padding) in the header cache, // we're done so we don't have a body to store if (mDataSize <= bytes_written) { done = true; } else { mState = BODY; } } // Fourth stage / state : write the body file, i.e. the rest of the texture in a "UUID" file name if (!done && (mState == BODY)) { llassert(mDataSize > TEXTURE_CACHE_ENTRY_SIZE); // wouldn't make sense to be here otherwise... S32 file_size = mDataSize - TEXTURE_CACHE_ENTRY_SIZE; if ((file_size > 0) && mCache->updateTextureEntryList(mID, file_size)) { // build the cache file name from the UUID std::string filename = mCache->getTextureFileName(mID); // llinfos << "Writing Body: " << filename << " Bytes: " << file_offset+file_size << llendl; S32 bytes_written = LLAPRFile::writeEx( filename, mWriteData + TEXTURE_CACHE_ENTRY_SIZE, 0, file_size); if (bytes_written <= 0) { llwarns << "LLTextureCacheWorker: " << mID << " incorrect number of bytes written to body: " << bytes_written << " / " << file_size << llendl; mDataSize = -1; // failed done = true; } } else { mDataSize = 0; // no data written } // Nothing else to do at that point... done = true; } // Clean up and exit return done; } //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(NULL), mHeaderMutex(NULL), mListMutex(NULL), mHeaderAPRFile(NULL), mReadOnly(FALSE), mTexturesSizeTotal(0), mDoPurge(FALSE) { } LLTextureCache::~LLTextureCache() { } ////////////////////////////////////////////////////////////////////////////// //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); } } unlockWorkers(); // call 'completed' with workers list unlocked (may call readComplete() or writeComplete() 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); } return res; } ////////////////////////////////////////////////////////////////////////////// // search for local copy of UUID-based image file std::string LLTextureCache::getLocalFileName(const LLUUID& id) { // Does not include extension std::string idstr = id.asString(); // TODO: should we be storing cached textures in skin directory? std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "default", "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 + ".texture"; return filename; } bool LLTextureCache::updateTextureEntryList(const LLUUID& id, S32 bodysize) { bool res = false; bool purge = false; { mHeaderMutex.lock(); size_map_t::iterator iter1 = mTexturesSizeMap.find(id); if (iter1 == mTexturesSizeMap.end() || iter1->second < bodysize) { llassert_always(bodysize > 0); S32 oldbodysize = 0; if (iter1 != mTexturesSizeMap.end()) { oldbodysize = iter1->second; } Entry entry; S32 idx = openAndReadEntry(id, entry, false); if (idx < 0) { llwarns << "Failed to open entry: " << id << llendl; mHeaderMutex.unlock(); removeFromCache(id); return false; } else if (oldbodysize != entry.mBodySize) { llwarns << "Entry mismatch in mTextureSizeMap / mHeaderIDMap" << " idx=" << idx << " oldsize=" << oldbodysize << " entrysize=" << entry.mBodySize << llendl; } entry.mBodySize = bodysize; writeEntryAndClose(idx, entry); mTexturesSizeTotal -= oldbodysize; mTexturesSizeTotal += bodysize; if (mTexturesSizeTotal > sCacheMaxTexturesSize) { purge = true; } res = true; } } if (purge) { mDoPurge = TRUE; } mHeaderMutex.unlock(); return res; } ////////////////////////////////////////////////////////////////////////////// //static const S32 MAX_REASONABLE_FILE_SIZE = 512*1024*1024; // 512 MB F32 LLTextureCache::sHeaderCacheVersion = 1.3f; 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); } void LLTextureCache::purgeCache(ELLPath location) { LLMutexLock lock(&mHeaderMutex); if (!mReadOnly) { setDirNames(location); llassert_always(mHeaderAPRFile == NULL); LLAPRFile::remove(mHeaderEntriesFileName); LLAPRFile::remove(mHeaderDataFileName); } 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; LL_INFOS("TextureCache") << "Headers: " << sCacheMaxEntries << " Textures size: " << sCacheMaxTexturesSize/(1024*1024) << " MB" << LL_ENDL; setDirNames(location); if (!mReadOnly) { LLFile::mkdir(mTexturesDirName); const char* subdirs = "0123456789abcdef"; for (S32 i=0; i<16; i++) { std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; LLFile::mkdir(dirname); } } readHeaderCache(); purgeTextures(true); // calc mTexturesSize and make some room in the texture cache if we need it return max_size; // unused cache space } //---------------------------------------------------------------------------- // mHeaderMutex must be locked for the following functions! LLAPRFile* LLTextureCache::openHeaderEntriesFile(bool readonly, S32 offset) { llassert_always(mHeaderAPRFile == NULL); apr_int32_t flags = readonly ? APR_READ|APR_BINARY : APR_READ|APR_WRITE|APR_BINARY; mHeaderAPRFile = new LLAPRFile(mHeaderEntriesFileName, flags, LLAPRFile::local); mHeaderAPRFile->seek(APR_SET, offset); return mHeaderAPRFile; } void LLTextureCache::closeHeaderEntriesFile() { llassert_always(mHeaderAPRFile != NULL); delete mHeaderAPRFile; mHeaderAPRFile = NULL; } void LLTextureCache::readEntriesHeader() { // mHeaderEntriesInfo initializes to default values so safe not to read it llassert_always(mHeaderAPRFile == NULL); if (LLAPRFile::isExist(mHeaderEntriesFileName)) { LLAPRFile::readEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); } } void LLTextureCache::writeEntriesHeader() { llassert_always(mHeaderAPRFile == NULL); if (!mReadOnly) { LLAPRFile::writeEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); } } static S32 mHeaderEntriesMaxWriteIdx = 0; S32 LLTextureCache::openAndReadEntry(const LLUUID& id, Entry& entry, bool create) { S32 idx = -1; id_map_t::iterator iter1 = mHeaderIDMap.find(id); if (iter1 != mHeaderIDMap.end()) { idx = iter1->second; } if (idx < 0) { if (create && !mReadOnly) { if (mHeaderEntriesInfo.mEntries < sCacheMaxEntries) { // Add an entry to the end of the list idx = mHeaderEntriesInfo.mEntries++; } else if (!mFreeList.empty()) { idx = *(mFreeList.begin()); mFreeList.erase(mFreeList.begin()); } else { // Look for a still valid entry in the LRU for (std::set::iterator iter2 = mLRU.begin(); iter2 != mLRU.end();) { std::set::iterator curiter2 = iter2++; LLUUID oldid = *curiter2; // Erase entry from LRU regardless mLRU.erase(curiter2); // Look up entry and use it if it is valid id_map_t::iterator iter3 = mHeaderIDMap.find(oldid); if (iter3 != mHeaderIDMap.end() && iter3->second >= 0) { idx = iter3->second; mHeaderIDMap.erase(oldid); mTexturesSizeMap.erase(oldid); break; } } // if (idx < 0) at this point, we will rebuild the LRU // and retry if called from setHeaderCacheEntry(), // otherwise this shouldn't happen and will trigger an error } if (idx >= 0) { // Set the header index mHeaderIDMap[id] = idx; llassert_always(mTexturesSizeMap.erase(id) == 0); // Initialize the entry (will get written later) entry.init(id, time(NULL)); // Update Header writeEntriesHeader(); // Write Entry S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); LLAPRFile* aprfile = openHeaderEntriesFile(false, offset); S32 bytes_written = aprfile->write((void*)&entry, (S32)sizeof(Entry)); llassert_always(bytes_written == sizeof(Entry)); mHeaderEntriesMaxWriteIdx = llmax(mHeaderEntriesMaxWriteIdx, idx); closeHeaderEntriesFile(); } } } else { // Remove this entry from the LRU if it exists mLRU.erase(id); // Read the entry S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); LLAPRFile* aprfile = openHeaderEntriesFile(true, offset); S32 bytes_read = aprfile->read((void*)&entry, (S32)sizeof(Entry)); llassert_always(bytes_read == sizeof(Entry)); llassert_always(entry.mImageSize == 0 || entry.mImageSize == -1 || entry.mImageSize > entry.mBodySize); closeHeaderEntriesFile(); } return idx; } void LLTextureCache::writeEntryAndClose(S32 idx, Entry& entry) { if (idx >= 0) { if (!mReadOnly) { entry.mTime = time(NULL); if(entry.mImageSize < entry.mBodySize) { // Just say no, due to my messing around to cache discards other than 0 we can end up here // after recalling an image from cache at a lower discard than cached. RC return; } llassert_always(entry.mImageSize == 0 || entry.mImageSize == -1 || entry.mImageSize > entry.mBodySize); if (entry.mBodySize > 0) { mTexturesSizeMap[entry.mID] = entry.mBodySize; } // llinfos << "Updating TE: " << idx << ": " << id << " Size: " << entry.mBodySize << " Time: " << entry.mTime << llendl; S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); LLAPRFile* aprfile = openHeaderEntriesFile(false, offset); S32 bytes_written = aprfile->write((void*)&entry, (S32)sizeof(Entry)); llassert_always(bytes_written == sizeof(Entry)); mHeaderEntriesMaxWriteIdx = llmax(mHeaderEntriesMaxWriteIdx, idx); closeHeaderEntriesFile(); } } } U32 LLTextureCache::openAndReadEntries(std::vector& entries) { U32 num_entries = mHeaderEntriesInfo.mEntries; mHeaderIDMap.clear(); mTexturesSizeMap.clear(); mFreeList.clear(); mTexturesSizeTotal = 0; LLAPRFile* aprfile = openHeaderEntriesFile(false, (S32)sizeof(EntriesInfo)); for (U32 idx=0; idxread((void*)(&entry), (S32)sizeof(Entry)); if (bytes_read < sizeof(Entry)) { llwarns << "Corrupted header entries, failed at " << idx << " / " << num_entries << llendl; closeHeaderEntriesFile(); purgeAllTextures(false); return 0; } entries.push_back(entry); // llinfos << "ENTRY: " << entry.mTime << " TEX: " << entry.mID << " IDX: " << idx << " Size: " << entry.mImageSize << llendl; if (entry.mImageSize < 0) { mFreeList.insert(idx); } else { mHeaderIDMap[entry.mID] = idx; if (entry.mBodySize > 0) { mTexturesSizeMap[entry.mID] = entry.mBodySize; mTexturesSizeTotal += entry.mBodySize; } llassert_always(entry.mImageSize == 0 || entry.mImageSize > entry.mBodySize); } } closeHeaderEntriesFile(); return num_entries; } void LLTextureCache::writeEntriesAndClose(const std::vector& entries) { S32 num_entries = entries.size(); llassert_always(num_entries == mHeaderEntriesInfo.mEntries); if (!mReadOnly) { LLAPRFile* aprfile = openHeaderEntriesFile(false, (S32)sizeof(EntriesInfo)); for (S32 idx=0; idxwrite((void*)(&entries[idx]), (S32)sizeof(Entry)); llassert_always(bytes_written == sizeof(Entry)); } mHeaderEntriesMaxWriteIdx = llmax(mHeaderEntriesMaxWriteIdx, num_entries-1); closeHeaderEntriesFile(); } } //---------------------------------------------------------------------------- // Called from either the main thread or the worker thread void LLTextureCache::readHeaderCache() { mHeaderMutex.lock(); mLRU.clear(); // always clear the LRU readEntriesHeader(); if (mHeaderEntriesInfo.mVersion != sHeaderCacheVersion) { if (!mReadOnly) { purgeAllTextures(false); } } else { std::vector entries; U32 num_entries = openAndReadEntries(entries); if (num_entries) { U32 empty_entries = 0; typedef std::pair lru_data_t; std::set lru; std::set purge_list; for (U32 i=0; i 0) { if (entry.mBodySize > entry.mImageSize) { // Shouldn't happen, failsafe only llwarns << "Bad entry: " << i << ": " << entry.mID << ": BodySize: " << entry.mBodySize << llendl; purge_list.insert(id); } } } } if (num_entries - empty_entries > sCacheMaxEntries) { // Special case: cache size was reduced, need to remove entries // Note: After we prune entries, we will call this again and create the LRU U32 entries_to_purge = (num_entries - empty_entries) - sCacheMaxEntries; llinfos << "Texture Cache Entries: " << num_entries << " Max: " << sCacheMaxEntries << " Empty: " << empty_entries << " Purging: " << entries_to_purge << llendl; // We can exit the following loop with the given condition, since if we'd reach the end of the lru set we'd have: // purge_list.size() = lru.size() = num_entries - empty_entries = entries_to_purge + sCacheMaxEntries >= entries_to_purge for (std::set::iterator iter = lru.begin(); purge_list.size() < entries_to_purge; ++iter) { purge_list.insert(iter->second); } } else { S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE); for (std::set::iterator iter = lru.begin(); iter != lru.end(); ++iter) { mLRU.insert(iter->second); // llinfos << "LRU: " << iter->first << " : " << iter->second << llendl; if (--lru_entries <= 0) break; } } if (purge_list.size() > 0) { for (std::set::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter) { removeFromCacheLocked(*iter); } // If we removed any entries, we need to rebuild the entries list, // write the header, and call this again std::vector new_entries; for (U32 i=0; i 0) { new_entries.push_back(entry); } } llassert_always(new_entries.size() <= sCacheMaxEntries); mHeaderEntriesInfo.mEntries = new_entries.size(); writeEntriesHeader(); writeEntriesAndClose(new_entries); mHeaderMutex.unlock(); // unlock the mutex before calling again readHeaderCache(); // repeat with new entries file return; } else { //entries are not changed, nothing here. } } } mHeaderMutex.unlock(); } ////////////////////////////////////////////////////////////////////////////// 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,mask); if (purge_directories) { LLFile::rmdir(dirname); } } if (purge_directories) { LLFile::rmdir(mTexturesDirName); } } mHeaderIDMap.clear(); mTexturesSizeMap.clear(); mTexturesSizeTotal = 0; mFreeList.clear(); mTexturesSizeTotal = 0; // Info with 0 entries mHeaderEntriesInfo.mVersion = sHeaderCacheVersion; mHeaderEntriesInfo.mEntries = 0; writeEntriesHeader(); } void LLTextureCache::purgeTextures(bool validate) { if (mReadOnly) { return; } if (!mThreaded) { // *FIX:Mani - watchdog off. LLAppViewer::instance()->pauseMainloopTimeout(); } LLMutexLock lock(&mHeaderMutex); llinfos << "TEXTURE CACHE: Purging." << llendl; // Read the entries list std::vector entries; U32 num_entries = openAndReadEntries(entries); if (!num_entries) { writeEntriesAndClose(entries); return; // nothing to purge } // Use mTexturesSizeMap to collect UUIDs of textures with bodies typedef std::set > time_idx_set_t; std::set > time_idx_set; for (size_map_t::iterator iter1 = mTexturesSizeMap.begin(); iter1 != mTexturesSizeMap.end(); ++iter1) { if (iter1->second > 0) { id_map_t::iterator iter2 = mHeaderIDMap.find(iter1->first); if (iter2 != mHeaderIDMap.end()) { S32 idx = iter2->second; time_idx_set.insert(std::make_pair(entries[idx].mTime, idx)); // llinfos << "TIME: " << entries[idx].mTime << " TEX: " << entries[idx].mID << " IDX: " << idx << " Size: " << entries[idx].mImageSize << llendl; } } } // Validate 1/256th of the files on startup U32 validate_idx = 0; if (validate) { validate_idx = gSavedSettings.getU32("CacheValidateCounter"); U32 next_idx = (validate_idx + 1) % 256; gSavedSettings.setU32("CacheValidateCounter", next_idx); LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Validating: " << validate_idx << LL_ENDL; } S64 cache_size = mTexturesSizeTotal; S64 purged_cache_size = (sCacheMaxTexturesSize * (S64)((1.f-TEXTURE_CACHE_PURGE_AMOUNT)*100)) / 100; S32 purge_count = 0; for (time_idx_set_t::iterator iter = time_idx_set.begin(); iter != time_idx_set.end(); ++iter) { S32 idx = iter->second; bool purge_entry = false; std::string filename = getTextureFileName(entries[idx].mID); if (cache_size >= purged_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) { LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; S32 bodysize = LLAPRFile::size(filename); if (bodysize != entries[idx].mBodySize) { LL_WARNS("TextureCache") << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mBodySize << filename << LL_ENDL; purge_entry = true; } } } else { break; } if (purge_entry) { purge_count++; LL_DEBUGS("TextureCache") << "PURGING: " << filename << LL_ENDL; if (entries[idx].mBodySize > 0) { LLAPRFile::remove(filename); } else if (LLAPRFile::isExist(filename)) // Sanity check. Shouldn't exist. { LL_WARNS("TextureCache") << "Entry has zero body size but existing " << filename << ". Deleting file too..." << LL_ENDL; LLAPRFile::remove(filename); } cache_size -= entries[idx].mBodySize; mTexturesSizeTotal -= entries[idx].mBodySize; entries[idx].mBodySize = 0; mTexturesSizeMap.erase(entries[idx].mID); } } LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Writing Entries: " << num_entries << LL_ENDL; writeEntriesAndClose(entries); if (!mThreaded) { // *FIX:Mani - watchdog back on. LLAppViewer::instance()->resumeMainloopTimeout(); } LL_INFOS("TextureCache") << "TEXTURE CACHE:" << " PURGED: " << purge_count << " ENTRIES: " << num_entries << " CACHE SIZE: " << mTexturesSizeTotal / 1024*1024 << " MB" << llendl; } ////////////////////////////////////////////////////////////////////////////// // call lockWorkers() first! LLTextureCacheWorker* LLTextureCache::getReader(handle_t handle) { LLTextureCacheWorker* res = NULL; handle_map_t::iterator iter = mReaders.find(handle); if (iter != mReaders.end()) { res = iter->second; } 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 // Reads imagesize from the header, updates timestamp S32 LLTextureCache::getHeaderCacheEntry(const LLUUID& id, S32& imagesize) { LLMutexLock lock(&mHeaderMutex); Entry entry; S32 idx = openAndReadEntry(id, entry, false); if (idx >= 0) { imagesize = entry.mImageSize; writeEntryAndClose(idx, entry); // updates time } return idx; } // Writes imagesize to the header, updates timestamp S32 LLTextureCache::setHeaderCacheEntry(const LLUUID& id, S32 imagesize) { mHeaderMutex.lock(); llassert_always(imagesize >= 0); Entry entry; S32 idx = openAndReadEntry(id, entry, true); if (idx >= 0) { entry.mImageSize = imagesize; writeEntryAndClose(idx, entry); mHeaderMutex.unlock(); } else // retry { mHeaderMutex.unlock(); readHeaderCache(); // We couldn't write an entry, so refresh the LRU mHeaderMutex.lock(); llassert_always(!mLRU.empty() || mHeaderEntriesInfo.mEntries < sCacheMaxEntries); mHeaderMutex.unlock(); idx = setHeaderCacheEntry(id, imagesize); // assert above ensures no inf. recursion } return idx; } ////////////////////////////////////////////////////////////////////////////// // Calls from texture pipeline thread (i.e. LLTextureFetch) LLTextureCache::handle_t LLTextureCache::readFromCache(const std::string& 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; } // Return true if the handle is not valid, which is the case // when the worker was already deleted or is scheduled for deletion. // // If the handle exists and a call to worker->complete() returns // true or abort is true, then the handle is removed and the worker // scheduled for deletion. bool LLTextureCache::readComplete(handle_t handle, bool abort) { lockWorkers(); // Needed for access to mReaders. handle_map_t::iterator iter = mReaders.find(handle); bool handle_is_valid = iter != mReaders.end(); llassert_always(handle_is_valid || abort); LLTextureCacheWorker* worker = NULL; bool delete_worker = false; if (handle_is_valid) { worker = iter->second; delete_worker = worker->complete() || abort; if (delete_worker) { mReaders.erase(handle); handle_is_valid = false; } } unlockWorkers(); if (delete_worker) worker->scheduleDelete(); // Return false if the handle is (still) valid. return !handle_is_valid; } 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; } LLMutexLock lock(&mWorkersMutex); LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, priority, id, data, datasize, 0, imagesize, responder); handle_t handle = worker->write(); mWriters[handle] = worker; return handle; } 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) { Entry entry; S32 idx = openAndReadEntry(id, entry, false); if (idx >= 0) { entry.mImageSize = -1; entry.mBodySize = 0; writeEntryAndClose(idx, entry); mFreeList.insert(idx); mHeaderIDMap.erase(id); mTexturesSizeMap.erase(id); return true; } } return false; } void LLTextureCache::removeFromCacheLocked(const LLUUID& id) { //llwarns << "Removing texture from cache: " << id << llendl; if (!mReadOnly) { removeHeaderCacheEntry(id); LLAPRFile::remove(getTextureFileName(id)); } } void LLTextureCache::removeFromCache(const LLUUID& id) { //llwarns << "Removing texture from cache: " << id << llendl; if (!mReadOnly) { LLMutexLock lock(&mHeaderMutex); LLTextureCache::removeFromCacheLocked(id); } } ////////////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////////////