Added SEND_UDP_REQ and WAIT_UDP_REQ fetch states. Parse 'content-range' from headers. Purge 'complete' textures from the cache if they lack 'end of codestream' marker. Added boostlevel/category to textureview display. More diagnostic output. Discard handling tweaks/bugfixes from v3.
1940 lines
52 KiB
C++
1940 lines
52 KiB
C++
/**
|
|
* @file lltexturecache.cpp
|
|
* @brief Object which handles local texture caching
|
|
*
|
|
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/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"
|
|
#include "llmemory.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
|
|
// cache/textures/[0-F]/UUID.texture
|
|
// Actual texture body files
|
|
|
|
//note: there is no good to define 1024 for TEXTURE_CACHE_ENTRY_SIZE while FIRST_PACKET_SIZE is 600 on sim side.
|
|
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)
|
|
|
|
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());
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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<LLTextureCache::Responder> 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 = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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
|
|
FREE_MEM(LLImageBase::getPrivatePool(), mReadData);
|
|
mReadData = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
if (!mDataSize || mDataSize > local_size)
|
|
{
|
|
mDataSize = local_size;
|
|
}
|
|
mReadData = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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;
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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)
|
|
{
|
|
#if 0
|
|
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);
|
|
|
|
llassert_always(mState == CACHE) ;
|
|
#else
|
|
mState = CACHE;
|
|
#endif
|
|
}
|
|
|
|
// 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...
|
|
if (!mDataSize || mDataSize > local_size/* - mOffset*/)
|
|
{
|
|
mDataSize = local_size/* - mOffset*/;
|
|
}
|
|
// Allocate read buffer
|
|
mReadData = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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;
|
|
FREE_MEM(LLImageBase::getPrivatePool(), mReadData);
|
|
mReadData = NULL;
|
|
}
|
|
else
|
|
{
|
|
//llinfos << "texture " << mID.asString() << " found in local_assets" << llendl;
|
|
mImageSize = local_size;
|
|
mImageLocal = TRUE;
|
|
}
|
|
// We're done...
|
|
done = true;
|
|
}
|
|
|
|
// Second state / stage : identify the cache or not...
|
|
if (!done && (mState == CACHE))
|
|
{
|
|
LLTextureCache::Entry entry;
|
|
idx = mCache->getHeaderCacheEntry(mID, entry);
|
|
if (idx < 0)
|
|
{
|
|
// The texture is *not* cached. We're done here...
|
|
mDataSize = 0; // no data
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
mImageSize = entry.mImageSize;
|
|
// 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 = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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;
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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 = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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);
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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)
|
|
{
|
|
LL_DEBUGS("TextureCache") << "LLTextureCacheWorker: " << mID
|
|
<< " incorrect number of bytes read from body: " << bytes_read
|
|
<< " / " << file_size << llendl;
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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...
|
|
llassert_always(mImageSize >= mDataSize);
|
|
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;
|
|
LLTextureCache::Entry entry;
|
|
|
|
// Checks if this image is already in the entry list
|
|
idx = mCache->getHeaderCacheEntry(mID, entry);
|
|
if(idx < 0)
|
|
{
|
|
idx = mCache->setHeaderCacheEntry(mID, entry, mImageSize, mDataSize); // create the new entry.
|
|
}
|
|
else
|
|
{
|
|
alreadyCached = mCache->updateEntry(idx, entry, mImageSize, mDataSize); // update the existing entry.
|
|
}
|
|
|
|
if (idx < 0)
|
|
{
|
|
llwarns << "LLTextureCacheWorker: " << mID
|
|
<< " Unable to create header entry for writing!" << llendl;
|
|
mDataSize = -1; // failed
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
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 = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), 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);
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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;
|
|
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
{
|
|
FREE_MEM(LLImageBase::getPrivatePool(), 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),
|
|
mHeaderAPRFile(NULL),
|
|
mReadOnly(TRUE), //do not allow to change the texture cache until setReadOnly() is called.
|
|
mTexturesSizeTotal(0),
|
|
mDoPurge(FALSE)
|
|
{
|
|
}
|
|
|
|
LLTextureCache::~LLTextureCache()
|
|
{
|
|
clearDeleteList();
|
|
writeUpdatedEntries();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//virtual
|
|
S32 LLTextureCache::update(F32 max_time_ms)
|
|
{
|
|
static LLFrameTimer timer;
|
|
static const F32 MAX_TIME_INTERVAL = 300.f; //seconds.
|
|
|
|
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);
|
|
}
|
|
|
|
if(!res && timer.getElapsedTimeF32() > MAX_TIME_INTERVAL)
|
|
{
|
|
timer.reset();
|
|
writeUpdatedEntries();
|
|
}
|
|
|
|
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_LOCAL_ASSETS, 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;
|
|
}
|
|
|
|
//debug
|
|
BOOL LLTextureCache::isInCache(const LLUUID& id)
|
|
{
|
|
LLMutexLock lock(&mHeaderMutex);
|
|
id_map_t::const_iterator iter = mHeaderIDMap.find(id);
|
|
|
|
return (iter != mHeaderIDMap.end()) ;
|
|
}
|
|
|
|
//debug
|
|
BOOL LLTextureCache::isInLocal(const LLUUID& id)
|
|
{
|
|
S32 local_size = 0;
|
|
std::string local_filename;
|
|
|
|
std::string filename = getLocalFileName(id);
|
|
// Is it a JPEG2000 file?
|
|
{
|
|
local_filename = filename + ".j2c";
|
|
local_size = LLAPRFile::size(local_filename);
|
|
if (local_size > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// If not, is it a jpeg file?
|
|
{
|
|
local_filename = filename + ".jpg";
|
|
local_size = LLAPRFile::size(local_filename);
|
|
if (local_size > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// Hmm... What about a targa file? (used for UI texture mostly)
|
|
{
|
|
local_filename = filename + ".tga";
|
|
local_size = LLAPRFile::size(local_filename);
|
|
if (local_size > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//static
|
|
const S32 MAX_REASONABLE_FILE_SIZE = 512*1024*1024; // 512 MB
|
|
F32 LLTextureCache::sHeaderCacheVersion = 1.8f;
|
|
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* 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";
|
|
|
|
void LLTextureCache::setDirNames(ELLPath location)
|
|
{
|
|
std::string delem = gDirUtilp->getDirDelimiter();
|
|
|
|
mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, entries_filename);
|
|
mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, cache_filename);
|
|
mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname);
|
|
}
|
|
|
|
void LLTextureCache::purgeCache(ELLPath location)
|
|
{
|
|
LLMutexLock lock(&mHeaderMutex);
|
|
|
|
if (!mReadOnly)
|
|
{
|
|
setDirNames(location);
|
|
llassert_always(mHeaderAPRFile == NULL);
|
|
|
|
//remove the legacy cache if exists
|
|
std::string texture_dir = mTexturesDirName;
|
|
mTexturesDirName = gDirUtilp->getExpandedFilename(location, old_textures_dirname);
|
|
if(LLFile::isdir(mTexturesDirName))
|
|
{
|
|
std::string file_name = gDirUtilp->getExpandedFilename(location, entries_filename);
|
|
if(LLAPRFile::isExist(file_name))
|
|
LLAPRFile::remove(file_name);
|
|
|
|
file_name = gDirUtilp->getExpandedFilename(location, cache_filename);
|
|
if(LLAPRFile::isExist(file_name))
|
|
LLAPRFile::remove(file_name);
|
|
|
|
purgeAllTextures(true);
|
|
}
|
|
mTexturesDirName = texture_dir;
|
|
}
|
|
|
|
//remove the current texture cache.
|
|
purgeAllTextures(true);
|
|
}
|
|
|
|
//is called in the main thread before initCache(...) is called.
|
|
void LLTextureCache::setReadOnly(BOOL read_only)
|
|
{
|
|
mReadOnly = read_only;
|
|
}
|
|
|
|
//called in the main thread.
|
|
S64 LLTextureCache::initCache(ELLPath location, S64 max_size, BOOL texture_cache_mismatch)
|
|
{
|
|
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;
|
|
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(texture_cache_mismatch)
|
|
{
|
|
//if readonly, disable the texture cache,
|
|
//otherwise wipe out the texture cache.
|
|
purgeAllTextures(true);
|
|
|
|
if(mReadOnly)
|
|
{
|
|
return max_size;
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
llassert_always(getPending() == 0); //should not start accessing the texture cache before initialized.
|
|
|
|
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;
|
|
// All code calling openHeaderEntriesFile, immediately calls closeHeaderEntriesFile,
|
|
// so this file is very short-lived.
|
|
mHeaderAPRFile = new LLAPRFile(mHeaderEntriesFileName, flags);
|
|
if(offset > 0)
|
|
{
|
|
mHeaderAPRFile->seek(APR_SET, offset);
|
|
}
|
|
return mHeaderAPRFile;
|
|
}
|
|
|
|
void LLTextureCache::closeHeaderEntriesFile()
|
|
{
|
|
if(!mHeaderAPRFile)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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));
|
|
}
|
|
else //create an empty entries header.
|
|
{
|
|
mHeaderEntriesInfo.mVersion = sHeaderCacheVersion;
|
|
mHeaderEntriesInfo.mEntries = 0;
|
|
writeEntriesHeader();
|
|
}
|
|
}
|
|
|
|
void LLTextureCache::writeEntriesHeader()
|
|
{
|
|
llassert_always(mHeaderAPRFile == NULL);
|
|
if (!mReadOnly)
|
|
{
|
|
LLAPRFile::writeEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo));
|
|
}
|
|
}
|
|
|
|
//mHeaderMutex is locked before calling this.
|
|
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<LLUUID>::iterator iter2 = mLRU.begin(); iter2 != mLRU.end();)
|
|
{
|
|
std::set<LLUUID>::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;
|
|
removeCachedTexture(oldid);//remove the existing cached texture to release the entry index.
|
|
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)
|
|
{
|
|
entry.mID = id;
|
|
entry.mImageSize = -1; //mark it is a brand-new entry.
|
|
entry.mBodySize = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove this entry from the LRU if it exists
|
|
mLRU.erase(id);
|
|
// Read the entry
|
|
idx_entry_map_t::iterator iter = mUpdatedEntryMap.find(idx);
|
|
if(iter != mUpdatedEntryMap.end())
|
|
{
|
|
entry = iter->second;
|
|
}
|
|
else
|
|
{
|
|
readEntryFromHeaderImmediately(idx, entry);
|
|
}
|
|
if(entry.mImageSize <= entry.mBodySize)//it happens on 64-bit systems, do not know why
|
|
{
|
|
llwarns << "corrupted entry: " << id << " entry image size: " << entry.mImageSize << " entry body size: " << entry.mBodySize << llendl;
|
|
|
|
//erase this entry and the cached texture from the cache.
|
|
std::string tex_filename = getTextureFileName(id);
|
|
removeEntry(idx, entry, tex_filename);
|
|
mUpdatedEntryMap.erase(idx);
|
|
idx = -1;
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
//mHeaderMutex is locked before calling this.
|
|
void LLTextureCache::writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool write_header)
|
|
{
|
|
LLAPRFile* aprfile;
|
|
S32 bytes_written;
|
|
S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry);
|
|
if(write_header)
|
|
{
|
|
aprfile = openHeaderEntriesFile(false, 0);
|
|
bytes_written = aprfile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo));
|
|
if(bytes_written != sizeof(EntriesInfo))
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
idx = -1; //mark the idx invalid.
|
|
return;
|
|
}
|
|
|
|
mHeaderAPRFile->seek(APR_SET, offset);
|
|
}
|
|
else
|
|
{
|
|
aprfile = openHeaderEntriesFile(false, offset);
|
|
}
|
|
bytes_written = aprfile->write((void*)&entry, (S32)sizeof(Entry));
|
|
if(bytes_written != sizeof(Entry))
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
idx = -1; //mark the idx invalid.
|
|
|
|
return;
|
|
}
|
|
|
|
closeHeaderEntriesFile();
|
|
mUpdatedEntryMap.erase(idx);
|
|
}
|
|
|
|
//mHeaderMutex is locked before calling this.
|
|
void LLTextureCache::readEntryFromHeaderImmediately(S32& idx, Entry& entry)
|
|
{
|
|
S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry);
|
|
LLAPRFile* aprfile = openHeaderEntriesFile(true, offset);
|
|
S32 bytes_read = aprfile->read((void*)&entry, (S32)sizeof(Entry));
|
|
closeHeaderEntriesFile();
|
|
|
|
if(bytes_read != sizeof(Entry))
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
idx = -1;//mark the idx invalid.
|
|
}
|
|
}
|
|
|
|
//mHeaderMutex is locked before calling this.
|
|
//update an existing entry time stamp, delay writing.
|
|
void LLTextureCache::updateEntryTimeStamp(S32 idx, Entry& entry)
|
|
{
|
|
static const U32 MAX_ENTRIES_WITHOUT_TIME_STAMP = (U32)(LLTextureCache::sCacheMaxEntries * 0.75f);
|
|
|
|
if(mHeaderEntriesInfo.mEntries < MAX_ENTRIES_WITHOUT_TIME_STAMP)
|
|
{
|
|
return; //there are enough empty entry index space, no need to stamp time.
|
|
}
|
|
|
|
if (idx >= 0)
|
|
{
|
|
if (!mReadOnly)
|
|
{
|
|
entry.mTime = time(NULL);
|
|
mUpdatedEntryMap[idx] = entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
//update an existing entry, write to header file immediately.
|
|
bool LLTextureCache::updateEntry(S32& idx, Entry& entry, S32 new_image_size, S32 new_data_size)
|
|
{
|
|
S32 new_body_size = llmax(0, new_data_size - TEXTURE_CACHE_ENTRY_SIZE);
|
|
|
|
if(new_image_size == entry.mImageSize && new_body_size == entry.mBodySize)
|
|
{
|
|
return true; //nothing changed.
|
|
}
|
|
else
|
|
{
|
|
bool purge = false;
|
|
|
|
lockHeaders();
|
|
|
|
bool update_header = false;
|
|
if(entry.mImageSize < 0) //is a brand-new entry
|
|
{
|
|
mHeaderIDMap[entry.mID] = idx;
|
|
mTexturesSizeMap[entry.mID] = new_body_size;
|
|
mTexturesSizeTotal += new_body_size;
|
|
|
|
// Update Header
|
|
update_header = true;
|
|
}
|
|
else if (entry.mBodySize != new_body_size)
|
|
{
|
|
//already in mHeaderIDMap.
|
|
mTexturesSizeMap[entry.mID] = new_body_size;
|
|
mTexturesSizeTotal -= entry.mBodySize;
|
|
mTexturesSizeTotal += new_body_size;
|
|
}
|
|
entry.mTime = time(NULL);
|
|
entry.mImageSize = new_image_size;
|
|
entry.mBodySize = new_body_size;
|
|
|
|
writeEntryToHeaderImmediately(idx, entry, update_header);
|
|
|
|
if (mTexturesSizeTotal > sCacheMaxTexturesSize)
|
|
{
|
|
purge = true;
|
|
}
|
|
|
|
unlockHeaders();
|
|
|
|
if (purge)
|
|
{
|
|
mDoPurge = TRUE;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
U32 LLTextureCache::openAndReadEntries(std::vector<Entry>& entries)
|
|
{
|
|
U32 num_entries = mHeaderEntriesInfo.mEntries;
|
|
|
|
mHeaderIDMap.clear();
|
|
mTexturesSizeMap.clear();
|
|
mFreeList.clear();
|
|
mTexturesSizeTotal = 0;
|
|
|
|
LLAPRFile* aprfile = NULL;
|
|
if(mUpdatedEntryMap.empty())
|
|
{
|
|
aprfile = openHeaderEntriesFile(true, (S32)sizeof(EntriesInfo));
|
|
}
|
|
else //update the header file first.
|
|
{
|
|
aprfile = openHeaderEntriesFile(false, 0);
|
|
updatedHeaderEntriesFile();
|
|
if(!aprfile)
|
|
{
|
|
return 0;
|
|
}
|
|
aprfile->seek(APR_SET, (S32)sizeof(EntriesInfo));
|
|
}
|
|
for (U32 idx=0; idx<num_entries; idx++)
|
|
{
|
|
Entry entry;
|
|
S32 bytes_read = aprfile->read((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 > entry.mBodySize)
|
|
{
|
|
mHeaderIDMap[entry.mID] = idx;
|
|
mTexturesSizeMap[entry.mID] = entry.mBodySize;
|
|
mTexturesSizeTotal += entry.mBodySize;
|
|
}
|
|
else
|
|
{
|
|
mFreeList.insert(idx);
|
|
}
|
|
}
|
|
closeHeaderEntriesFile();
|
|
return num_entries;
|
|
}
|
|
|
|
void LLTextureCache::writeEntriesAndClose(const std::vector<Entry>& 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; idx<num_entries; idx++)
|
|
{
|
|
S32 bytes_written = aprfile->write((void*)(&entries[idx]), (S32)sizeof(Entry));
|
|
if(bytes_written != sizeof(Entry))
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
return;
|
|
}
|
|
}
|
|
closeHeaderEntriesFile();
|
|
}
|
|
}
|
|
|
|
void LLTextureCache::writeUpdatedEntries()
|
|
{
|
|
lockHeaders();
|
|
if (!mReadOnly && !mUpdatedEntryMap.empty())
|
|
{
|
|
openHeaderEntriesFile(false, 0);
|
|
updatedHeaderEntriesFile();
|
|
closeHeaderEntriesFile();
|
|
}
|
|
unlockHeaders();
|
|
}
|
|
|
|
//mHeaderMutex is locked and mHeaderAPRFile is created before calling this.
|
|
void LLTextureCache::updatedHeaderEntriesFile()
|
|
{
|
|
if (!mReadOnly && !mUpdatedEntryMap.empty() && mHeaderAPRFile)
|
|
{
|
|
//entriesInfo
|
|
mHeaderAPRFile->seek(APR_SET, 0);
|
|
S32 bytes_written = mHeaderAPRFile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo));
|
|
if(bytes_written != sizeof(EntriesInfo))
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
return;
|
|
}
|
|
|
|
//write each updated entry
|
|
S32 entry_size = (S32)sizeof(Entry);
|
|
S32 prev_idx = -1;
|
|
S32 delta_idx;
|
|
for (idx_entry_map_t::iterator iter = mUpdatedEntryMap.begin(); iter != mUpdatedEntryMap.end(); ++iter)
|
|
{
|
|
delta_idx = iter->first - prev_idx - 1;
|
|
prev_idx = iter->first;
|
|
if(delta_idx)
|
|
{
|
|
mHeaderAPRFile->seek(APR_CUR, delta_idx * entry_size);
|
|
}
|
|
|
|
bytes_written = mHeaderAPRFile->write((void*)(&iter->second), entry_size);
|
|
if(bytes_written != entry_size)
|
|
{
|
|
clearCorruptedCache(); //clear the cache.
|
|
return;
|
|
}
|
|
}
|
|
mUpdatedEntryMap.clear();
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
|
|
// 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<Entry> entries;
|
|
U32 num_entries = openAndReadEntries(entries);
|
|
if (num_entries)
|
|
{
|
|
U32 empty_entries = 0;
|
|
typedef std::pair<U32, S32> lru_data_t;
|
|
std::set<lru_data_t> lru;
|
|
std::set<U32> purge_list;
|
|
for (U32 i=0; i<num_entries; i++)
|
|
{
|
|
Entry& entry = entries[i];
|
|
if (entry.mImageSize <= 0)
|
|
{
|
|
// This will be in the Free List, don't put it in the LRU
|
|
++empty_entries;
|
|
}
|
|
else
|
|
{
|
|
lru.insert(std::make_pair(entry.mTime, i));
|
|
if (entry.mBodySize > 0)
|
|
{
|
|
if (entry.mBodySize > entry.mImageSize)
|
|
{
|
|
// Shouldn't happen, failsafe only
|
|
llwarns << "Bad entry: " << i << ": " << entry.mID << ": BodySize: " << entry.mBodySize << llendl;
|
|
purge_list.insert(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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
|
|
// So, it's certain that iter will never reach lru.end() first.
|
|
std::set<lru_data_t>::iterator iter = lru.begin();
|
|
while (purge_list.size() < entries_to_purge)
|
|
{
|
|
purge_list.insert(iter->second);
|
|
++iter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE);
|
|
for (std::set<lru_data_t>::iterator iter = lru.begin(); iter != lru.end(); ++iter)
|
|
{
|
|
mLRU.insert(entries[iter->second].mID);
|
|
// llinfos << "LRU: " << iter->first << " : " << iter->second << llendl;
|
|
if (--lru_entries <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (purge_list.size() > 0)
|
|
{
|
|
for (std::set<U32>::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter)
|
|
{
|
|
std::string tex_filename = getTextureFileName(entries[*iter].mID);
|
|
removeEntry((S32)*iter, entries[*iter], tex_filename);
|
|
}
|
|
// If we removed any entries, we need to rebuild the entries list,
|
|
// write the header, and call this again
|
|
std::vector<Entry> new_entries;
|
|
for (U32 i=0; i<num_entries; i++)
|
|
{
|
|
const Entry& entry = entries[i];
|
|
if (entry.mImageSize > 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
|
|
mHeaderMutex.lock();
|
|
}
|
|
else
|
|
{
|
|
//entries are not changed, nothing here.
|
|
}
|
|
}
|
|
}
|
|
mHeaderMutex.unlock();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//the header mutex is locked before calling this.
|
|
void LLTextureCache::clearCorruptedCache()
|
|
{
|
|
llwarns << "the texture cache is corrupted, need to be cleared." << llendl;
|
|
|
|
closeHeaderEntriesFile();//close possible file handler
|
|
purgeAllTextures(false); //clear the cache.
|
|
|
|
if (!mReadOnly) //regenerate the directory tree if not exists.
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void LLTextureCache::purgeAllTextures(bool purge_directories)
|
|
{
|
|
if (!mReadOnly)
|
|
{
|
|
const char* subdirs = "0123456789abcdef";
|
|
std::string delem = gDirUtilp->getDirDelimiter();
|
|
std::string mask = "*";
|
|
for (S32 i=0; i<16; i++)
|
|
{
|
|
std::string dirname = mTexturesDirName + delem + subdirs[i];
|
|
llinfos << "Deleting files in directory: " << dirname << llendl;
|
|
gDirUtilp->deleteFilesInDir(dirname, mask);
|
|
if (purge_directories)
|
|
{
|
|
LLFile::rmdir(dirname);
|
|
}
|
|
}
|
|
if (purge_directories)
|
|
{
|
|
gDirUtilp->deleteFilesInDir(mTexturesDirName, mask);
|
|
LLFile::rmdir(mTexturesDirName);
|
|
}
|
|
}
|
|
mHeaderIDMap.clear();
|
|
mTexturesSizeMap.clear();
|
|
mTexturesSizeTotal = 0;
|
|
mFreeList.clear();
|
|
mTexturesSizeTotal = 0;
|
|
mUpdatedEntryMap.clear();
|
|
|
|
// Info with 0 entries
|
|
mHeaderEntriesInfo.mVersion = sHeaderCacheVersion;
|
|
mHeaderEntriesInfo.mEntries = 0;
|
|
writeEntriesHeader();
|
|
|
|
llinfos << "The entire texture cache is cleared." << llendl;
|
|
}
|
|
|
|
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<Entry> entries;
|
|
U32 num_entries = openAndReadEntries(entries);
|
|
if (!num_entries)
|
|
{
|
|
return; // nothing to purge
|
|
}
|
|
|
|
// Use mTexturesSizeMap to collect UUIDs of textures with bodies
|
|
typedef std::set<std::pair<U32,S32> > time_idx_set_t;
|
|
std::set<std::pair<U32,S32> > 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;
|
|
}
|
|
else
|
|
{
|
|
llerrs << "mTexturesSizeMap / mHeaderIDMap corrupted." << 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
|
|
U32 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;
|
|
cache_size -= entries[idx].mBodySize;
|
|
removeEntry(idx, entries[idx], filename) ;
|
|
}
|
|
}
|
|
|
|
LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Writing Entries: " << num_entries << LL_ENDL;
|
|
|
|
writeEntriesAndClose(entries);
|
|
|
|
// *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, Entry& entry)
|
|
{
|
|
LLMutexLock lock(&mHeaderMutex);
|
|
S32 idx = openAndReadEntry(id, entry, false);
|
|
if (idx >= 0)
|
|
{
|
|
updateEntryTimeStamp(idx, entry); // updates time
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
// Writes imagesize to the header, updates timestamp
|
|
S32 LLTextureCache::setHeaderCacheEntry(const LLUUID& id, Entry& entry, S32 imagesize, S32 datasize)
|
|
{
|
|
mHeaderMutex.lock();
|
|
S32 idx = openAndReadEntry(id, entry, true);
|
|
mHeaderMutex.unlock();
|
|
|
|
if (idx >= 0)
|
|
{
|
|
updateEntry(idx, entry, imagesize, datasize);
|
|
}
|
|
|
|
if(idx < 0) // retry
|
|
{
|
|
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, entry, imagesize, datasize); // 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;
|
|
}
|
|
|
|
|
|
bool LLTextureCache::readComplete(handle_t handle, bool abort)
|
|
{
|
|
lockWorkers();
|
|
handle_map_t::iterator iter = mReaders.find(handle);
|
|
LLTextureCacheWorker* worker = NULL;
|
|
bool complete = false;
|
|
if (iter != mReaders.end())
|
|
{
|
|
worker = iter->second;
|
|
complete = worker->complete();
|
|
|
|
if(!complete && abort)
|
|
{
|
|
abortRequest(handle, true);
|
|
}
|
|
}
|
|
if (worker && (complete || abort))
|
|
{
|
|
mReaders.erase(iter);
|
|
unlockWorkers();
|
|
worker->scheduleDelete();
|
|
}
|
|
else
|
|
{
|
|
unlockWorkers();
|
|
}
|
|
return (complete || abort);
|
|
}
|
|
|
|
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(iter != mWriters.end());
|
|
if (iter != mWriters.end())
|
|
{
|
|
LLTextureCacheWorker* worker = iter->second;
|
|
if (worker->complete() || abort)
|
|
{
|
|
mWriters.erase(handle);
|
|
unlockWorkers();
|
|
worker->scheduleDelete();
|
|
return true;
|
|
}
|
|
}
|
|
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 after mHeaderMutex is locked.
|
|
void LLTextureCache::removeCachedTexture(const LLUUID& id)
|
|
{
|
|
if (mTexturesSizeMap.find(id) != mTexturesSizeMap.end())
|
|
{
|
|
mTexturesSizeTotal -= mTexturesSizeMap[id];
|
|
mTexturesSizeMap.erase(id);
|
|
}
|
|
mHeaderIDMap.erase(id);
|
|
LLAPRFile::remove(getTextureFileName(id));
|
|
}
|
|
|
|
//called after mHeaderMutex is locked.
|
|
void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename)
|
|
{
|
|
bool file_maybe_exists = true; // Always attempt to remove when idx is invalid.
|
|
|
|
if(idx >= 0) //valid entry
|
|
{
|
|
if (entry.mBodySize == 0) // Always attempt to remove when mBodySize > 0.
|
|
{
|
|
if (LLAPRFile::isExist(filename)) // Sanity check. Shouldn't exist when body size is 0.
|
|
{
|
|
LL_WARNS("TextureCache") << "Entry has body size of zero but file " << filename << " exists. Deleting this file, too." << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
file_maybe_exists = false;
|
|
}
|
|
}
|
|
mTexturesSizeTotal -= entry.mBodySize;
|
|
|
|
entry.mImageSize = -1;
|
|
entry.mBodySize = 0;
|
|
mHeaderIDMap.erase(entry.mID);
|
|
mTexturesSizeMap.erase(entry.mID);
|
|
mFreeList.insert(idx);
|
|
}
|
|
|
|
if (file_maybe_exists)
|
|
{
|
|
LLAPRFile::remove(filename);
|
|
}
|
|
}
|
|
|
|
bool LLTextureCache::removeFromCache(const LLUUID& id)
|
|
{
|
|
//llwarns << "Removing texture from cache: " << id << llendl;
|
|
bool ret = false;
|
|
if (!mReadOnly)
|
|
{
|
|
lockHeaders();
|
|
|
|
Entry entry;
|
|
S32 idx = openAndReadEntry(id, entry, false);
|
|
std::string tex_filename = getTextureFileName(id);
|
|
removeEntry(idx, entry, tex_filename);
|
|
if (idx >= 0)
|
|
{
|
|
writeEntryToHeaderImmediately(idx, entry);
|
|
ret = true;
|
|
}
|
|
|
|
unlockHeaders();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
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;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|