1836 lines
47 KiB
C++
1836 lines
47 KiB
C++
/**
|
|
* @file lltexturecache.cpp
|
|
* @brief Object which fetches textures from the cache and/or network
|
|
*
|
|
* Copyright (c) 2000-$CurrentYear$, Linden Research, Inc.
|
|
* $License$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llstl.h"
|
|
|
|
#include "lltexturefetch.h"
|
|
|
|
#include "llcurl.h"
|
|
#include "llhttpclient.h"
|
|
#include "llimage.h"
|
|
#include "llimageworker.h"
|
|
#include "llworkerthread.h"
|
|
|
|
#include "llagent.h"
|
|
#include "lltexturecache.h"
|
|
#include "llviewerimagelist.h"
|
|
#include "llviewerimage.h"
|
|
#include "llviewerregion.h"
|
|
#include "viewer.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//static
|
|
class LLTextureFetchWorker : public LLWorkerClass
|
|
{
|
|
friend class LLTextureFetch;
|
|
|
|
private:
|
|
class URLResponder : public LLHTTPClient::Responder
|
|
{
|
|
public:
|
|
URLResponder(LLTextureFetch* fetcher, const LLUUID& id)
|
|
: mFetcher(fetcher), mID(id)
|
|
{
|
|
}
|
|
~URLResponder()
|
|
{
|
|
}
|
|
virtual void error(U32 status, const std::string& reason)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
llinfos << "LLTextureFetchWorker::URLResponder::error " << status << ": " << reason << llendl;
|
|
worker->callbackURLReceived(LLSD(), false);
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
virtual void result(const LLSD& content)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
worker->callbackURLReceived(content, true);
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
private:
|
|
LLTextureFetch* mFetcher;
|
|
LLUUID mID;
|
|
};
|
|
|
|
class HTTPGetResponder : public LLCurl::Responder
|
|
{
|
|
public:
|
|
HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id)
|
|
: mFetcher(fetcher), mID(id)
|
|
{
|
|
}
|
|
~HTTPGetResponder()
|
|
{
|
|
}
|
|
virtual void completed(U32 status, const std::stringstream& content)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
const std::string& cstr = content.str();
|
|
if (200 <= status && status < 300)
|
|
{
|
|
if (203 == status) // partial information (i.e. last block)
|
|
{
|
|
worker->callbackHttpGet((U8*)cstr.c_str(), cstr.length(), true);
|
|
}
|
|
else
|
|
{
|
|
worker->callbackHttpGet((U8*)cstr.c_str(), cstr.length(), false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llinfos << "LLTextureFetchWorker::HTTPGetResponder::error " << status << ": " << cstr << llendl;
|
|
worker->callbackHttpGet(NULL, -1, true);
|
|
}
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
private:
|
|
LLTextureFetch* mFetcher;
|
|
LLUUID mID;
|
|
};
|
|
|
|
class CacheReadResponder : public LLTextureCache::ReadResponder
|
|
{
|
|
public:
|
|
CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image)
|
|
: mFetcher(fetcher), mID(id)
|
|
{
|
|
setImage(image);
|
|
}
|
|
virtual void completed(bool success)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal);
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
private:
|
|
LLTextureFetch* mFetcher;
|
|
LLUUID mID;
|
|
};
|
|
|
|
class CacheWriteResponder : public LLTextureCache::WriteResponder
|
|
{
|
|
public:
|
|
CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id)
|
|
: mFetcher(fetcher), mID(id)
|
|
{
|
|
}
|
|
virtual void completed(bool success)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
worker->callbackCacheWrite(success);
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
private:
|
|
LLTextureFetch* mFetcher;
|
|
LLUUID mID;
|
|
};
|
|
|
|
class DecodeResponder : public LLResponder
|
|
{
|
|
public:
|
|
DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker)
|
|
: mFetcher(fetcher), mID(id), mWorker(worker)
|
|
{
|
|
}
|
|
virtual void completed(bool success)
|
|
{
|
|
mFetcher->lockQueue();
|
|
LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
|
|
if (worker)
|
|
{
|
|
worker->callbackDecoded(success);
|
|
}
|
|
mFetcher->unlockQueue();
|
|
}
|
|
private:
|
|
LLTextureFetch* mFetcher;
|
|
LLUUID mID;
|
|
LLTextureFetchWorker* mWorker; // debug only (may get deleted from under us, use mFetcher/mID)
|
|
};
|
|
|
|
struct Compare
|
|
{
|
|
// lhs < rhs
|
|
bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const
|
|
{
|
|
// greater priority is "less"
|
|
const F32 lpriority = lhs->mImagePriority;
|
|
const F32 rpriority = rhs->mImagePriority;
|
|
if (lpriority > rpriority) // higher priority
|
|
return true;
|
|
else if (lpriority < rpriority)
|
|
return false;
|
|
else
|
|
return lhs < rhs;
|
|
}
|
|
};
|
|
|
|
public:
|
|
/*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest()
|
|
/*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
|
|
/*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
|
|
|
|
~LLTextureFetchWorker();
|
|
void relese() { --mActiveCount; }
|
|
|
|
protected:
|
|
LLTextureFetchWorker(LLTextureFetch* fetcher, const LLUUID& id, const LLHost& host,
|
|
F32 priority, S32 discard, S32 size);
|
|
|
|
private:
|
|
/*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD)
|
|
/*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD)
|
|
|
|
void resetFormattedData();
|
|
|
|
void setImagePriority(F32 priority);
|
|
void setDesiredDiscard(S32 discard, S32 size);
|
|
bool insertPacket(S32 index, U8* data, S32 size);
|
|
void clearPackets();
|
|
U32 calcWorkPriority();
|
|
void removeFromCache();
|
|
bool processSimulatorPackets();
|
|
bool decodeImage();
|
|
bool writeToCacheComplete();
|
|
|
|
void lockWorkData() { mWorkMutex.lock(); }
|
|
void unlockWorkData() { mWorkMutex.unlock(); }
|
|
|
|
void callbackURLReceived(const LLSD& data, bool success);
|
|
void callbackHttpGet(U8* data, S32 data_size, bool last_block);
|
|
void callbackCacheRead(bool success, LLImageFormatted* image,
|
|
S32 imagesize, BOOL islocal);
|
|
void callbackCacheWrite(bool success);
|
|
void callbackDecoded(bool success);
|
|
|
|
private:
|
|
enum e_state // mState
|
|
{
|
|
// NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack)
|
|
INVALID = 0,
|
|
INIT,
|
|
LOAD_FROM_TEXTURE_CACHE,
|
|
CACHE_POST,
|
|
LOAD_FROM_NETWORK,
|
|
LOAD_FROM_SIMULATOR,
|
|
LOAD_FROM_HTTP_GET_URL,
|
|
LOAD_FROM_HTTP_GET_DATA,
|
|
DECODE_IMAGE,
|
|
DECODE_IMAGE_UPDATE,
|
|
WRITE_TO_CACHE,
|
|
WAIT_ON_WRITE,
|
|
DONE
|
|
};
|
|
enum e_request_state // mSentRequest
|
|
{
|
|
UNSENT = 0,
|
|
QUEUED = 1,
|
|
SENT_SIM = 2,
|
|
SENT_URL = 3,
|
|
SENT_HTTP = 4
|
|
};
|
|
static const char* sStateDescs[];
|
|
e_state mState;
|
|
LLTextureFetch* mFetcher;
|
|
LLImageWorker* mImageWorker;
|
|
LLPointer<LLImageFormatted> mFormattedImage;
|
|
LLPointer<LLImageRaw> mRawImage;
|
|
LLPointer<LLImageRaw> mAuxImage;
|
|
LLUUID mID;
|
|
LLHost mHost;
|
|
U8 mType;
|
|
F32 mImagePriority;
|
|
U32 mWorkPriority;
|
|
F32 mRequestedPriority;
|
|
S32 mDesiredDiscard;
|
|
S32 mSimRequestedDiscard;
|
|
S32 mRequestedDiscard;
|
|
S32 mLoadedDiscard;
|
|
S32 mDecodedDiscard;
|
|
LLFrameTimer mRequestedTimer;
|
|
LLFrameTimer mFetchTimer;
|
|
LLTextureCache::handle_t mCacheReadHandle;
|
|
LLTextureCache::handle_t mCacheWriteHandle;
|
|
U8* mBuffer;
|
|
S32 mBufferSize;
|
|
S32 mRequestedSize;
|
|
S32 mDesiredSize;
|
|
S32 mFileSize;
|
|
S32 mCachedSize;
|
|
BOOL mLoaded;
|
|
e_request_state mSentRequest;
|
|
BOOL mDecoded;
|
|
BOOL mWritten;
|
|
BOOL mNeedsAux;
|
|
BOOL mHaveAllData;
|
|
BOOL mInLocalCache;
|
|
S32 mRetryAttempt;
|
|
std::string mURL;
|
|
S32 mActiveCount;
|
|
|
|
// Work Data
|
|
LLMutex mWorkMutex;
|
|
struct PacketData
|
|
{
|
|
PacketData(U8* data, S32 size) { mData = data; mSize = size; }
|
|
~PacketData() { clearData(); }
|
|
void clearData() { delete[] mData; mData = NULL; }
|
|
U8* mData;
|
|
U32 mSize;
|
|
};
|
|
std::vector<PacketData*> mPackets;
|
|
S32 mFirstPacket;
|
|
S32 mLastPacket;
|
|
U16 mTotalPackets;
|
|
U8 mImageCodec;
|
|
};
|
|
|
|
//static
|
|
const char* LLTextureFetchWorker::sStateDescs[] = {
|
|
"INVALID",
|
|
"INIT",
|
|
"LOAD_FROM_TEXTURE_CACHE",
|
|
"CACHE_POST",
|
|
"LOAD_FROM_NETWORK",
|
|
"LOAD_FROM_SIMULATOR",
|
|
"LOAD_FROM_HTTP_URL",
|
|
"LOAD_FROM_HTTP_DATA",
|
|
"DECODE_IMAGE",
|
|
"DECODE_IMAGE_UPDATE",
|
|
"WRITE_TO_CACHE",
|
|
"WAIT_ON_WRITE",
|
|
"DONE",
|
|
};
|
|
|
|
// called from MAIN THREAD
|
|
|
|
LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
|
|
const LLUUID& id, // Image UUID
|
|
const LLHost& host, // Simulator host
|
|
F32 priority, // Priority
|
|
S32 discard, // Desired discard
|
|
S32 size) // Desired size
|
|
: LLWorkerClass(fetcher, "TextureFetch"),
|
|
mState(INIT),
|
|
mFetcher(fetcher),
|
|
mImageWorker(NULL),
|
|
mID(id),
|
|
mHost(host),
|
|
mImagePriority(priority),
|
|
mWorkPriority(0),
|
|
mRequestedPriority(0.f),
|
|
mDesiredDiscard(-1),
|
|
mSimRequestedDiscard(-1),
|
|
mRequestedDiscard(-1),
|
|
mLoadedDiscard(-1),
|
|
mDecodedDiscard(-1),
|
|
mCacheReadHandle(LLTextureCache::nullHandle()),
|
|
mCacheWriteHandle(LLTextureCache::nullHandle()),
|
|
mBuffer(NULL),
|
|
mBufferSize(0),
|
|
mRequestedSize(0),
|
|
mDesiredSize(FIRST_PACKET_SIZE),
|
|
mFileSize(0),
|
|
mCachedSize(0),
|
|
mLoaded(FALSE),
|
|
mSentRequest(UNSENT),
|
|
mDecoded(FALSE),
|
|
mWritten(FALSE),
|
|
mNeedsAux(FALSE),
|
|
mHaveAllData(FALSE),
|
|
mInLocalCache(FALSE),
|
|
mRetryAttempt(0),
|
|
mActiveCount(0),
|
|
mWorkMutex(fetcher->getWorkerAPRPool()),
|
|
mFirstPacket(0),
|
|
mLastPacket(-1),
|
|
mTotalPackets(0),
|
|
mImageCodec(IMG_CODEC_INVALID)
|
|
{
|
|
calcWorkPriority();
|
|
mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL;
|
|
// llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << llendl;
|
|
if (!mFetcher->mDebugPause)
|
|
{
|
|
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
|
|
addWork(0, work_priority );
|
|
}
|
|
setDesiredDiscard(discard, size);
|
|
}
|
|
|
|
LLTextureFetchWorker::~LLTextureFetchWorker()
|
|
{
|
|
// llinfos << "Destroy: " << mID
|
|
// << " Decoded=" << mDecodedDiscard
|
|
// << " Requested=" << mRequestedDiscard
|
|
// << " Desired=" << mDesiredDiscard << llendl;
|
|
llassert_always(!haveWork());
|
|
lockWorkData();
|
|
if (mCacheReadHandle != LLTextureCache::nullHandle())
|
|
{
|
|
mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
|
|
}
|
|
if (mCacheWriteHandle != LLTextureCache::nullHandle())
|
|
{
|
|
mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
|
|
}
|
|
if (mImageWorker)
|
|
{
|
|
mImageWorker->scheduleDelete();
|
|
}
|
|
mFormattedImage = NULL;
|
|
clearPackets();
|
|
unlockWorkData();
|
|
}
|
|
|
|
void LLTextureFetchWorker::clearPackets()
|
|
{
|
|
for_each(mPackets.begin(), mPackets.end(), DeletePointer());
|
|
mPackets.clear();
|
|
mTotalPackets = 0;
|
|
mLastPacket = -1;
|
|
mFirstPacket = 0;
|
|
}
|
|
|
|
U32 LLTextureFetchWorker::calcWorkPriority()
|
|
{
|
|
// llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerImage::maxDecodePriority());
|
|
F32 priority_scale = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerImage::maxDecodePriority();
|
|
mWorkPriority = (U32)(mImagePriority * priority_scale);
|
|
return mWorkPriority;
|
|
}
|
|
|
|
// mWorkMutex is locked
|
|
void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
|
|
{
|
|
bool prioritize = false;
|
|
if (mDesiredDiscard != discard)
|
|
{
|
|
if (!haveWork())
|
|
{
|
|
calcWorkPriority();
|
|
if (!mFetcher->mDebugPause)
|
|
{
|
|
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
|
|
addWork(0, work_priority);
|
|
}
|
|
}
|
|
else if (mDesiredDiscard < discard)
|
|
{
|
|
prioritize = true;
|
|
}
|
|
mDesiredDiscard = discard;
|
|
mDesiredSize = size;
|
|
}
|
|
else if (size > mDesiredSize)
|
|
{
|
|
mDesiredSize = size;
|
|
prioritize = true;
|
|
}
|
|
if ((prioritize && mState == INIT) || mState == DONE)
|
|
{
|
|
mState = INIT;
|
|
U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
|
|
setPriority(work_priority);
|
|
}
|
|
}
|
|
|
|
void LLTextureFetchWorker::setImagePriority(F32 priority)
|
|
{
|
|
// llassert_always(priority >= 0 && priority <= LLViewerImage::maxDecodePriority());
|
|
F32 delta = fabs(priority - mImagePriority);
|
|
if (delta > (mImagePriority * .05f) || mState == DONE)
|
|
{
|
|
mImagePriority = priority;
|
|
calcWorkPriority();
|
|
U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS);
|
|
setPriority(work_priority);
|
|
}
|
|
}
|
|
|
|
void LLTextureFetchWorker::resetFormattedData()
|
|
{
|
|
delete[] mBuffer;
|
|
mBuffer = NULL;
|
|
mBufferSize = 0;
|
|
if (mFormattedImage.notNull())
|
|
{
|
|
mFormattedImage->deleteData();
|
|
}
|
|
mHaveAllData = FALSE;
|
|
}
|
|
|
|
// Called from MAIN thread
|
|
void LLTextureFetchWorker::startWork(S32 param)
|
|
{
|
|
llassert(mImageWorker == NULL);
|
|
llassert(mFormattedImage.isNull());
|
|
}
|
|
|
|
#include "llviewerimagelist.h" // debug
|
|
|
|
// Called from LLWorkerThread::processRequest()
|
|
bool LLTextureFetchWorker::doWork(S32 param)
|
|
{
|
|
LLMutexLock lock(&mWorkMutex);
|
|
|
|
if (mFetcher->mDebugPause)
|
|
{
|
|
return false; // debug: don't do any work
|
|
}
|
|
if (mID == mFetcher->mDebugID)
|
|
{
|
|
mFetcher->mDebugCount++; // for setting breakpoints
|
|
}
|
|
|
|
if (mState != DONE)
|
|
{
|
|
mFetchTimer.reset();
|
|
}
|
|
|
|
if (mState == INIT)
|
|
{
|
|
mRequestedDiscard = -1;
|
|
mLoadedDiscard = -1;
|
|
mDecodedDiscard = -1;
|
|
mRequestedSize = 0;
|
|
mFileSize = 0;
|
|
mCachedSize = 0;
|
|
mLoaded = FALSE;
|
|
mSentRequest = UNSENT;
|
|
mDecoded = FALSE;
|
|
mWritten = FALSE;
|
|
delete[] mBuffer;
|
|
mBuffer = NULL;
|
|
mBufferSize = 0;
|
|
mHaveAllData = FALSE;
|
|
clearPackets(); // TODO: Shouldn't be necessary
|
|
mCacheReadHandle = LLTextureCache::nullHandle();
|
|
mCacheWriteHandle = LLTextureCache::nullHandle();
|
|
mURL.clear();
|
|
mState = LOAD_FROM_TEXTURE_CACHE;
|
|
// fall through
|
|
}
|
|
|
|
if (mState == LOAD_FROM_TEXTURE_CACHE)
|
|
{
|
|
if (mCacheReadHandle == LLTextureCache::nullHandle())
|
|
{
|
|
U32 cache_priority = mWorkPriority;
|
|
S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
|
|
S32 size = mDesiredSize - offset;
|
|
if (size <= 0)
|
|
{
|
|
mState = CACHE_POST;
|
|
return false;
|
|
}
|
|
mFileSize = 0;
|
|
mLoaded = FALSE;
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
|
|
CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
|
|
mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority,
|
|
offset, size, responder);
|
|
}
|
|
|
|
if (mLoaded)
|
|
{
|
|
// Make sure request is complete. *TODO: make this auto-complete
|
|
if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false))
|
|
{
|
|
mCacheReadHandle = LLTextureCache::nullHandle();
|
|
mState = CACHE_POST;
|
|
// fall through
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mState == CACHE_POST)
|
|
{
|
|
mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
|
|
// Successfully loaded
|
|
if ((mCachedSize >= mDesiredSize) || mHaveAllData)
|
|
{
|
|
// we have enough data, decode it
|
|
llassert_always(mFormattedImage->getDataSize() > 0);
|
|
mState = DECODE_IMAGE;
|
|
// fall through
|
|
}
|
|
else
|
|
{
|
|
// need more data
|
|
mState = LOAD_FROM_NETWORK;
|
|
// fall through
|
|
}
|
|
}
|
|
|
|
if (mState == LOAD_FROM_NETWORK)
|
|
{
|
|
if (mSentRequest == UNSENT)
|
|
{
|
|
if (mFormattedImage.isNull())
|
|
{
|
|
mFormattedImage = new LLImageJ2C;
|
|
}
|
|
// Add this to the network queue and sit here.
|
|
// LLTextureFetch::update() will send off a request which will change our state
|
|
S32 data_size = mFormattedImage->getDataSize();
|
|
if (data_size > 0)
|
|
{
|
|
// Only used for simulator requests
|
|
mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1;
|
|
if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size)
|
|
{
|
|
llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl;
|
|
removeFromCache();
|
|
resetFormattedData();
|
|
clearPackets();
|
|
}
|
|
else
|
|
{
|
|
mLastPacket = mFirstPacket-1;
|
|
mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1;
|
|
}
|
|
}
|
|
mRequestedSize = mDesiredSize;
|
|
mRequestedDiscard = mDesiredDiscard;
|
|
mSentRequest = QUEUED;
|
|
mFetcher->lockQueue();
|
|
mFetcher->addToNetworkQueue(this);
|
|
mFetcher->unlockQueue();
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (mState == LOAD_FROM_SIMULATOR)
|
|
{
|
|
if (processSimulatorPackets())
|
|
{
|
|
mFetcher->lockQueue();
|
|
mFetcher->removeFromNetworkQueue(this);
|
|
mFetcher->unlockQueue();
|
|
if (!mFormattedImage->getDataSize())
|
|
{
|
|
// processSimulatorPackets() failed
|
|
llwarns << "processSimulatorPackets() failed to load buffer" << llendl;
|
|
return true; // failed
|
|
}
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
mState = DECODE_IMAGE;
|
|
}
|
|
else
|
|
{
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
if (mState == LOAD_FROM_HTTP_GET_URL)
|
|
{
|
|
if (!mSentRequest)
|
|
{
|
|
mSentRequest = TRUE;
|
|
mLoaded = FALSE;
|
|
std::string url;
|
|
LLViewerRegion* region = gAgent.getRegion();
|
|
if (region)
|
|
{
|
|
url = region->getCapability("RequestTextureDownload");
|
|
}
|
|
if (!url.empty())
|
|
{
|
|
LLSD sd;
|
|
sd = mID.asString();
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
LLHTTPClient::post(url, sd, new URLResponder(mFetcher, mID));
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
llwarns << mID << ": HTTP get url failed, requesting from simulator" << llendl;
|
|
mSentRequest = FALSE;
|
|
mState = LOAD_FROM_SIMULATOR;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mLoaded)
|
|
{
|
|
if (!mURL.empty())
|
|
{
|
|
mState = LOAD_FROM_HTTP_GET_DATA;
|
|
mSentRequest = FALSE; // reset
|
|
mLoaded = FALSE; // reset
|
|
}
|
|
else
|
|
{
|
|
llwarns << mID << ": HTTP get url is empty, requesting from simulator" << llendl;
|
|
mSentRequest = FALSE;
|
|
mState = LOAD_FROM_SIMULATOR;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// fall through
|
|
}
|
|
|
|
if (mState == LOAD_FROM_HTTP_GET_DATA)
|
|
{
|
|
if (!mSentRequest)
|
|
{
|
|
mSentRequest = TRUE;
|
|
S32 cur_size = mFormattedImage->getDataSize(); // amount of data we already have
|
|
mRequestedSize = mDesiredSize;
|
|
mRequestedDiscard = mDesiredDiscard;
|
|
#if 1 // *TODO: LLCurl::getByteRange is broken (ignores range)
|
|
cur_size = 0;
|
|
mFormattedImage->deleteData();
|
|
#endif
|
|
mRequestedSize -= cur_size;
|
|
// F32 priority = mImagePriority / (F32)LLViewerImage::maxDecodePriority(); // 0-1
|
|
S32 offset = cur_size;
|
|
mBufferSize = cur_size; // This will get modified by callbackHttpGet()
|
|
std::string url;
|
|
if (mURL.empty())
|
|
{
|
|
//url = "http://asset.agni/0000002f-38ae-0e17-8e72-712e58964e9c.texture";
|
|
std::stringstream urlstr;
|
|
urlstr << "http://asset.agni/" << mID.asString() << ".texture";
|
|
url = urlstr.str();
|
|
}
|
|
else
|
|
{
|
|
url = mURL;
|
|
}
|
|
mLoaded = FALSE;
|
|
// llinfos << "HTTP GET: " << mID << " Offset: " << offset << " Bytes: " << mRequestedSize << llendl;
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
LLCurl::getByteRange(url, offset, mRequestedSize,
|
|
new HTTPGetResponder(mFetcher, mID)); // *TODO: use mWorkPriority
|
|
return false; // not done
|
|
}
|
|
|
|
if (mLoaded)
|
|
{
|
|
S32 cur_size = mFormattedImage->getDataSize();
|
|
if (mRequestedSize < 0)
|
|
{
|
|
llwarns << "http get failed for: " << mID << llendl;
|
|
if (cur_size == 0)
|
|
{
|
|
resetFormattedData();
|
|
return true; // failed
|
|
}
|
|
else
|
|
{
|
|
mState = DECODE_IMAGE;
|
|
return false; // use what we have
|
|
}
|
|
}
|
|
llassert_always(mBufferSize == cur_size + mRequestedSize);
|
|
if (mHaveAllData)
|
|
{
|
|
mFileSize = mBufferSize;
|
|
}
|
|
U8* buffer = new U8[mBufferSize];
|
|
if (cur_size > 0)
|
|
{
|
|
memcpy(buffer, mFormattedImage->getData(), cur_size);
|
|
}
|
|
memcpy(buffer + cur_size, mBuffer, mRequestedSize); // append
|
|
// NOTE: setData releases current data and owns new data (buffer)
|
|
mFormattedImage->setData(buffer, mBufferSize);
|
|
// delete temp data
|
|
delete[] mBuffer; // Note: not 'buffer' (assigned in setData())
|
|
mBuffer = NULL;
|
|
mBufferSize = 0;
|
|
mLoadedDiscard = mRequestedDiscard;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
mState = DECODE_IMAGE;
|
|
return false;
|
|
}
|
|
|
|
// NOTE: Priority gets updated when the http get completes (in callbackHTTPGet())
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (mState == DECODE_IMAGE)
|
|
{
|
|
llassert_always(mFormattedImage->getDataSize() > 0);
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
|
|
mRawImage = NULL;
|
|
mAuxImage = NULL;
|
|
llassert_always(mImageWorker == NULL);
|
|
llassert_always(mFormattedImage.notNull());
|
|
S32 discard = mHaveAllData ? 0 : mLoadedDiscard;
|
|
U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority;
|
|
mDecoded = FALSE;
|
|
mState = DECODE_IMAGE_UPDATE;
|
|
mImageWorker = new LLImageWorker(mFormattedImage, image_priority, discard, new DecodeResponder(mFetcher, mID, this));
|
|
// fall though (need to call requestDecodedData() to start work)
|
|
}
|
|
|
|
if (mState == DECODE_IMAGE_UPDATE)
|
|
{
|
|
if (decodeImage())
|
|
{
|
|
if (mDecodedDiscard < 0)
|
|
{
|
|
if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0)
|
|
{
|
|
// Cache file should be deleted, try again
|
|
llwarns << mID << ": Decode of cached file failed (removed), retrying" << llendl;
|
|
mFormattedImage = NULL;
|
|
++mRetryAttempt;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
mState = INIT;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
llwarns << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << llendl;
|
|
mState = DONE; // failed
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
mState = WRITE_TO_CACHE;
|
|
}
|
|
// fall through
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mState == WRITE_TO_CACHE)
|
|
{
|
|
if (mInLocalCache || !mFileSize || mSentRequest == UNSENT)
|
|
{
|
|
// If we're in a local cache or we didn't actually receive any new data, skip
|
|
mState = DONE;
|
|
return false;
|
|
}
|
|
S32 datasize = mFormattedImage->getDataSize();
|
|
llassert_always(datasize);
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
|
|
U32 cache_priority = mWorkPriority;
|
|
mWritten = FALSE;
|
|
mState = WAIT_ON_WRITE;
|
|
CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
|
|
mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
|
|
mFormattedImage->getData(), datasize,
|
|
mFileSize, responder);
|
|
// fall through
|
|
}
|
|
|
|
if (mState == WAIT_ON_WRITE)
|
|
{
|
|
if (writeToCacheComplete())
|
|
{
|
|
mState = DONE;
|
|
// fall through
|
|
}
|
|
else
|
|
{
|
|
if (mDesiredDiscard < mDecodedDiscard)
|
|
{
|
|
// We're waiting for this write to complete before we can receive more data
|
|
// (we can't touch mFormattedImage until the write completes)
|
|
// Prioritize the write
|
|
mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mState == DONE)
|
|
{
|
|
if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard)
|
|
{
|
|
// More data was requested, return to INIT
|
|
mState = INIT;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Called from MAIN thread
|
|
void LLTextureFetchWorker::endWork(S32 param, bool aborted)
|
|
{
|
|
if (mImageWorker)
|
|
{
|
|
mImageWorker->scheduleDelete();
|
|
mImageWorker = NULL;
|
|
}
|
|
mFormattedImage = NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// virtual
|
|
void LLTextureFetchWorker::finishWork(S32 param, bool completed)
|
|
{
|
|
// The following are required in case the work was aborted
|
|
if (mCacheReadHandle != LLTextureCache::nullHandle())
|
|
{
|
|
mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
|
|
mCacheReadHandle = LLTextureCache::nullHandle();
|
|
}
|
|
if (mCacheWriteHandle != LLTextureCache::nullHandle())
|
|
{
|
|
mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
|
|
mCacheWriteHandle = LLTextureCache::nullHandle();
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
bool LLTextureFetchWorker::deleteOK()
|
|
{
|
|
bool delete_ok = true;
|
|
// Allow any pending reads or writes to complete
|
|
if (mCacheReadHandle != LLTextureCache::nullHandle())
|
|
{
|
|
if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
|
|
{
|
|
mCacheReadHandle = LLTextureCache::nullHandle();
|
|
}
|
|
else
|
|
{
|
|
delete_ok = false;
|
|
}
|
|
}
|
|
if (mCacheWriteHandle != LLTextureCache::nullHandle())
|
|
{
|
|
if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
|
|
{
|
|
mCacheWriteHandle = LLTextureCache::nullHandle();
|
|
}
|
|
else
|
|
{
|
|
delete_ok = false;
|
|
}
|
|
}
|
|
|
|
if ((haveWork() &&
|
|
// not ok to delete from these states
|
|
((mState >= LOAD_FROM_HTTP_GET_URL && mState <= LOAD_FROM_HTTP_GET_DATA) ||
|
|
(mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
|
|
{
|
|
delete_ok = false;
|
|
}
|
|
|
|
return delete_ok;
|
|
}
|
|
|
|
|
|
void LLTextureFetchWorker::removeFromCache()
|
|
{
|
|
if (!mInLocalCache)
|
|
{
|
|
mFetcher->mTextureCache->removeFromCache(mID);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LLTextureFetchWorker::processSimulatorPackets()
|
|
{
|
|
if (mLastPacket >= mFirstPacket)
|
|
{
|
|
S32 buffer_size = mFormattedImage->getDataSize();
|
|
for (S32 i = mFirstPacket; i<=mLastPacket; i++)
|
|
{
|
|
buffer_size += mPackets[i]->mSize;
|
|
}
|
|
bool have_all_data = mLastPacket >= mTotalPackets-1;
|
|
llassert_always(mRequestedSize > 0);
|
|
if (buffer_size >= mRequestedSize || have_all_data)
|
|
{
|
|
/// We have enough (or all) data
|
|
if (have_all_data)
|
|
{
|
|
mHaveAllData = TRUE;
|
|
}
|
|
S32 cur_size = mFormattedImage->getDataSize();
|
|
if (buffer_size > cur_size)
|
|
{
|
|
/// We have new data
|
|
U8* buffer = new U8[buffer_size];
|
|
S32 offset = 0;
|
|
if (cur_size > 0 && mFirstPacket > 0)
|
|
{
|
|
memcpy(buffer, mFormattedImage->getData(), cur_size);
|
|
offset = cur_size;
|
|
}
|
|
for (S32 i=mFirstPacket; i<=mLastPacket; i++)
|
|
{
|
|
memcpy(buffer + offset, mPackets[i]->mData, mPackets[i]->mSize);
|
|
offset += mPackets[i]->mSize;
|
|
}
|
|
// NOTE: setData releases current data
|
|
mFormattedImage->setData(buffer, buffer_size);
|
|
}
|
|
mLoadedDiscard = mRequestedDiscard;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLTextureFetchWorker::callbackURLReceived(const LLSD& data, bool success)
|
|
{
|
|
#if 0
|
|
LLMutexLock lock(&mWorkMutex);
|
|
if (!mSentRequest || mState != LOAD_FROM_HTTP_GET_URL)
|
|
{
|
|
llwarns << "callbackURLReceived for unrequested fetch worker, req="
|
|
<< mSentRequest << " state= " << mState << llendl;
|
|
return;
|
|
}
|
|
if (success)
|
|
{
|
|
mURL = data.asString();
|
|
}
|
|
mLoaded = TRUE;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLTextureFetchWorker::callbackHttpGet(U8* data, S32 data_size, bool last_block)
|
|
{
|
|
#if 0
|
|
LLMutexLock lock(&mWorkMutex);
|
|
if (!mSentRequest || mState != LOAD_FROM_HTTP_GET_DATA)
|
|
{
|
|
llwarns << "callbackHttpGet for unrequested fetch worker, req="
|
|
<< mSentRequest << " state= " << mState << llendl;
|
|
return;
|
|
}
|
|
// llinfos << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << llendl;
|
|
if (mLoaded)
|
|
{
|
|
llwarns << "Duplicate callback for " << mID.asString() << llendl;
|
|
return; // ignore duplicate callback
|
|
}
|
|
if (data_size >= 0)
|
|
{
|
|
if (data_size > 0)
|
|
{
|
|
mBuffer = new U8[data_size];
|
|
// *TODO: set the formatted image data here
|
|
memcpy(mBuffer, data, data_size);
|
|
mBufferSize += data_size;
|
|
if (data_size < mRequestedSize || last_block == true)
|
|
{
|
|
mHaveAllData = TRUE;
|
|
}
|
|
else if (data_size > mRequestedSize)
|
|
{
|
|
// *TODO: This will happen until we fix LLCurl::getByteRange()
|
|
llinfos << "HUH?" << llendl;
|
|
mHaveAllData = TRUE;
|
|
mFormattedImage->deleteData();
|
|
mBufferSize = data_size;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We requested data but received none (and no error),
|
|
// so presumably we have all of it
|
|
mHaveAllData = TRUE;
|
|
}
|
|
mRequestedSize = data_size;
|
|
}
|
|
else
|
|
{
|
|
mRequestedSize = -1; // error
|
|
}
|
|
mLoaded = TRUE;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image,
|
|
S32 imagesize, BOOL islocal)
|
|
{
|
|
LLMutexLock lock(&mWorkMutex);
|
|
if (mState != LOAD_FROM_TEXTURE_CACHE)
|
|
{
|
|
llwarns << "Read callback for " << mID << " with state = " << mState << llendl;
|
|
return;
|
|
}
|
|
if (success)
|
|
{
|
|
llassert_always(imagesize > 0);
|
|
mFileSize = imagesize;
|
|
mFormattedImage = image;
|
|
mImageCodec = image->getCodec();
|
|
mInLocalCache = islocal;
|
|
if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize)
|
|
{
|
|
mHaveAllData = TRUE;
|
|
}
|
|
}
|
|
mLoaded = TRUE;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
}
|
|
|
|
void LLTextureFetchWorker::callbackCacheWrite(bool success)
|
|
{
|
|
LLMutexLock lock(&mWorkMutex);
|
|
if (mState != WAIT_ON_WRITE)
|
|
{
|
|
llwarns << "Write callback for " << mID << " with state = " << mState << llendl;
|
|
return;
|
|
}
|
|
mWritten = TRUE;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLTextureFetchWorker::callbackDecoded(bool success)
|
|
{
|
|
if (mState != DECODE_IMAGE_UPDATE)
|
|
{
|
|
llwarns << "Decode callback for " << mID << " with state = " << mState << llendl;
|
|
return;
|
|
}
|
|
// llinfos << mID << " : DECODE COMPLETE " << llendl;
|
|
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LLTextureFetchWorker::decodeImage()
|
|
{
|
|
llassert_always(mImageWorker);
|
|
bool res = true;
|
|
if (mRawImage.isNull())
|
|
{
|
|
res = false;
|
|
if (mImageWorker->requestDecodedData(mRawImage, -1))
|
|
{
|
|
res = true;
|
|
// llinfos << mID << " : BASE DECODE FINISHED" << llendl;
|
|
}
|
|
}
|
|
if (res &&
|
|
(mRawImage.notNull() && mRawImage->getDataSize() > 0) &&
|
|
(mNeedsAux && mAuxImage.isNull()))
|
|
{
|
|
res = false;
|
|
if (mImageWorker->requestDecodedAuxData(mAuxImage, 4, -1))
|
|
{
|
|
res = true;
|
|
// llinfos << mID << " : AUX DECODE FINISHED" << llendl;
|
|
}
|
|
}
|
|
if (res)
|
|
{
|
|
if ((mRawImage.notNull() && mRawImage->getDataSize() > 0) &&
|
|
(!mNeedsAux || (mAuxImage.notNull() && mAuxImage->getDataSize() > 0)))
|
|
{
|
|
mDecodedDiscard = mFormattedImage->getDiscardLevel();
|
|
// llinfos << mID << " : DECODE FINISHED. DISCARD: " << mDecodedDiscard << llendl;
|
|
}
|
|
else
|
|
{
|
|
llwarns << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << llendl;
|
|
removeFromCache();
|
|
}
|
|
mImageWorker->scheduleDelete();
|
|
mImageWorker = NULL;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LLTextureFetchWorker::writeToCacheComplete()
|
|
{
|
|
// Complete write to cache
|
|
if (mCacheWriteHandle != LLTextureCache::nullHandle())
|
|
{
|
|
if (!mWritten)
|
|
{
|
|
return false;
|
|
}
|
|
if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
|
|
{
|
|
mCacheWriteHandle = LLTextureCache::nullHandle();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// public
|
|
|
|
LLTextureFetch::LLTextureFetch(LLTextureCache* cache, bool threaded)
|
|
: LLWorkerThread("TextureFetch", threaded),
|
|
mDebugCount(0),
|
|
mDebugPause(FALSE),
|
|
mQueueMutex(getAPRPool()),
|
|
mTextureCache(cache)
|
|
{
|
|
}
|
|
|
|
LLTextureFetch::~LLTextureFetch()
|
|
{
|
|
// ~LLQueuedThread() called here
|
|
}
|
|
|
|
bool LLTextureFetch::createRequest(const LLUUID& id, const LLHost& host, F32 priority,
|
|
S32 w, S32 h, S32 c, S32 discard, bool needs_aux)
|
|
{
|
|
if (mDebugPause)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LLTextureFetchWorker* worker = NULL;
|
|
LLMutexLock lock(&mQueueMutex);
|
|
map_t::iterator iter = mRequestMap.find(id);
|
|
if (iter != mRequestMap.end())
|
|
{
|
|
worker = iter->second;
|
|
if (worker->mHost != host)
|
|
{
|
|
llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
|
|
<< host << " != " << worker->mHost << llendl;
|
|
removeRequest(worker, true);
|
|
worker = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
// If the requester knows the dimentions of the image,
|
|
// this will calculate how much data we need without having to parse the header
|
|
S32 desired_size;
|
|
if (w*h*c > 0)
|
|
{
|
|
desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, discard);
|
|
}
|
|
else
|
|
{
|
|
desired_size = FIRST_PACKET_SIZE;
|
|
discard = MAX_DISCARD_LEVEL;
|
|
}
|
|
if (worker)
|
|
{
|
|
if (worker->wasAborted())
|
|
{
|
|
return false; // need to wait for previous aborted request to complete
|
|
}
|
|
worker->lockWorkData();
|
|
worker->setImagePriority(priority);
|
|
worker->setDesiredDiscard(discard, desired_size);
|
|
worker->unlockWorkData();
|
|
if (!worker->haveWork())
|
|
{
|
|
worker->mState = LLTextureFetchWorker::INIT;
|
|
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
worker = new LLTextureFetchWorker(this, id, host, priority, discard, desired_size);
|
|
mRequestMap[id] = worker;
|
|
}
|
|
worker->mActiveCount++;
|
|
worker->mNeedsAux = needs_aux;
|
|
// llinfos << "REQUESTED: " << id << " Discard: " << discard << llendl;
|
|
return true;
|
|
}
|
|
|
|
void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
|
|
{
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
if (worker)
|
|
{
|
|
removeRequest(worker, cancel);
|
|
}
|
|
}
|
|
|
|
// protected
|
|
|
|
// call lockQueue() first!
|
|
void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
|
|
{
|
|
if (mRequestMap.find(worker->mID) != mRequestMap.end())
|
|
{
|
|
// only add to the queue if in the request map
|
|
// i.e. a delete has not been requested
|
|
mNetworkQueue.insert(worker->mID);
|
|
}
|
|
for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
|
|
iter1 != mCancelQueue.end(); ++iter1)
|
|
{
|
|
iter1->second.erase(worker->mID);
|
|
}
|
|
}
|
|
|
|
// call lockQueue() first!
|
|
void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker)
|
|
{
|
|
mNetworkQueue.erase(worker->mID);
|
|
}
|
|
|
|
// call lockQueue() first!
|
|
void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel)
|
|
{
|
|
mRequestMap.erase(worker->mID);
|
|
size_t erased = mNetworkQueue.erase(worker->mID);
|
|
if (cancel && erased > 0)
|
|
{
|
|
mCancelQueue[worker->mHost].insert(worker->mID);
|
|
}
|
|
worker->scheduleDelete();
|
|
}
|
|
|
|
// call lockQueue() first!
|
|
LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
|
|
{
|
|
LLTextureFetchWorker* res = NULL;
|
|
map_t::iterator iter = mRequestMap.find(id);
|
|
if (iter != mRequestMap.end())
|
|
{
|
|
res = iter->second;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
|
|
LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux)
|
|
{
|
|
bool res = false;
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
if (worker)
|
|
{
|
|
if (worker->wasAborted())
|
|
{
|
|
res = true;
|
|
}
|
|
else if (!worker->haveWork())
|
|
{
|
|
// Should only happen if we set mDebugPause...
|
|
if (!mDebugPause)
|
|
{
|
|
// llwarns << "Adding work for inactive worker: " << id << llendl;
|
|
worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
|
|
}
|
|
}
|
|
else if (worker->checkWork())
|
|
{
|
|
discard_level = worker->mDecodedDiscard;
|
|
raw = worker->mRawImage; worker->mRawImage = NULL;
|
|
aux = worker->mAuxImage; worker->mAuxImage = NULL;
|
|
res = true;
|
|
}
|
|
else
|
|
{
|
|
worker->lockWorkData();
|
|
if ((worker->mDecodedDiscard >= 0) &&
|
|
(worker->mDecodedDiscard < discard_level || discard_level < 0) &&
|
|
(worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE))
|
|
{
|
|
// Not finished, but data is ready
|
|
discard_level = worker->mDecodedDiscard;
|
|
if (worker->mRawImage) raw = worker->mRawImage;
|
|
if (worker->mAuxImage) aux = worker->mAuxImage;
|
|
}
|
|
worker->unlockWorkData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = true;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
|
|
{
|
|
bool res = false;
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
if (worker)
|
|
{
|
|
worker->lockWorkData();
|
|
worker->setImagePriority(priority);
|
|
worker->unlockWorkData();
|
|
res = true;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//virtual
|
|
S32 LLTextureFetch::update(U32 max_time_ms)
|
|
{
|
|
S32 res;
|
|
res = LLWorkerThread::update(max_time_ms);
|
|
|
|
const F32 REQUEST_TIME = 1.f;
|
|
|
|
// Periodically, gather the list of textures that need data from the network
|
|
// And send the requests out to the simulators
|
|
if (mNetworkTimer.getElapsedTimeF32() >= REQUEST_TIME)
|
|
{
|
|
mNetworkTimer.reset();
|
|
sendRequestListToSimulators();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLTextureFetch::sendRequestListToSimulators()
|
|
{
|
|
const S32 IMAGES_PER_REQUEST = 50;
|
|
const F32 LAZY_FLUSH_TIMEOUT = 15.f; // 10.0f // temp
|
|
const F32 MIN_REQUEST_TIME = 1.0f;
|
|
const F32 MIN_DELTA_PRIORITY = 1000.f;
|
|
|
|
LLMutexLock lock(&mQueueMutex);
|
|
|
|
// Send requests
|
|
typedef std::set<LLTextureFetchWorker*,LLTextureFetchWorker::Compare> request_list_t;
|
|
typedef std::map< LLHost, request_list_t > work_request_map_t;
|
|
work_request_map_t requests;
|
|
for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
|
|
{
|
|
queue_t::iterator curiter = iter++;
|
|
LLTextureFetchWorker* req = getWorker(*curiter);
|
|
if (!req)
|
|
{
|
|
mNetworkQueue.erase(curiter);
|
|
continue; // paranoia
|
|
}
|
|
if (req->mID == mDebugID)
|
|
{
|
|
mDebugCount++; // for setting breakpoints
|
|
}
|
|
if (req->mTotalPackets > 0 && req->mLastPacket >= req->mTotalPackets-1)
|
|
{
|
|
// We have all the packets... make sure this is high priority
|
|
// req->setPriority(LLWorkerThread::PRIORITY_HIGH | req->mWorkPriority);
|
|
continue;
|
|
}
|
|
F32 elapsed = req->mRequestedTimer.getElapsedTimeF32();
|
|
F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority);
|
|
if ((req->mSimRequestedDiscard != req->mDesiredDiscard) ||
|
|
(delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) ||
|
|
(elapsed >= LAZY_FLUSH_TIMEOUT))
|
|
{
|
|
requests[req->mHost].insert(req);
|
|
}
|
|
}
|
|
|
|
std::string http_url;
|
|
#if 0
|
|
if (gSavedSettings.getBOOL("ImagePipelineUseHTTP"))
|
|
{
|
|
LLViewerRegion* region = gAgent.getRegion();
|
|
if (region)
|
|
{
|
|
http_url = region->getCapability("RequestTextureDownload");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (work_request_map_t::iterator iter1 = requests.begin();
|
|
iter1 != requests.end(); ++iter1)
|
|
{
|
|
bool use_http = http_url.empty() ? false : true;
|
|
LLHost host = iter1->first;
|
|
// invalid host = use agent host
|
|
if (host == LLHost::invalid)
|
|
{
|
|
host = gAgent.getRegionHost();
|
|
}
|
|
else
|
|
{
|
|
use_http = false;
|
|
}
|
|
|
|
if (use_http)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
S32 request_count = 0;
|
|
for (request_list_t::iterator iter2 = iter1->second.begin();
|
|
iter2 != iter1->second.end(); ++iter2)
|
|
{
|
|
LLTextureFetchWorker* req = *iter2;
|
|
req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
|
|
if (0 == request_count)
|
|
{
|
|
gMessageSystem->newMessageFast(_PREHASH_RequestImage);
|
|
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
|
|
gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
|
|
gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
|
|
}
|
|
S32 packet = req->mLastPacket + 1;
|
|
gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
|
|
gMessageSystem->addUUIDFast(_PREHASH_Image, req->mID);
|
|
gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, (S8)req->mSimRequestedDiscard);
|
|
gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, req->mImagePriority);
|
|
gMessageSystem->addU32Fast(_PREHASH_Packet, packet);
|
|
gMessageSystem->addU8Fast(_PREHASH_Type, req->mType);
|
|
// llinfos << "IMAGE REQUEST: " << req->mID << " Discard: " << req->mDesiredDiscard
|
|
// << " Packet: " << packet << " Priority: " << req->mImagePriority << llendl;
|
|
|
|
req->lockWorkData();
|
|
req->mSimRequestedDiscard = req->mDesiredDiscard;
|
|
req->mRequestedPriority = req->mImagePriority;
|
|
req->mRequestedTimer.reset();
|
|
req->unlockWorkData();
|
|
request_count++;
|
|
if (request_count >= IMAGES_PER_REQUEST)
|
|
{
|
|
// llinfos << "REQUESTING " << request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
|
|
gMessageSystem->sendSemiReliable(host, NULL, NULL);
|
|
request_count = 0;
|
|
}
|
|
}
|
|
if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
|
|
{
|
|
// llinfos << "REQUESTING " << request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
|
|
gMessageSystem->sendSemiReliable(host, NULL, NULL);
|
|
request_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send cancelations
|
|
if (!mCancelQueue.empty())
|
|
{
|
|
for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
|
|
iter1 != mCancelQueue.end(); ++iter1)
|
|
{
|
|
LLHost host = iter1->first;
|
|
if (host == LLHost::invalid)
|
|
{
|
|
host = gAgent.getRegionHost();
|
|
}
|
|
S32 request_count = 0;
|
|
for (queue_t::iterator iter2 = iter1->second.begin();
|
|
iter2 != iter1->second.end(); ++iter2)
|
|
{
|
|
if (0 == request_count)
|
|
{
|
|
gMessageSystem->newMessageFast(_PREHASH_RequestImage);
|
|
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
|
|
gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
|
|
gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
|
|
}
|
|
gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
|
|
gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2);
|
|
gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1);
|
|
gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0);
|
|
gMessageSystem->addU32Fast(_PREHASH_Packet, 0);
|
|
gMessageSystem->addU8Fast(_PREHASH_Type, 0);
|
|
// llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl;
|
|
|
|
request_count++;
|
|
if (request_count >= IMAGES_PER_REQUEST)
|
|
{
|
|
gMessageSystem->sendSemiReliable(host, NULL, NULL);
|
|
request_count = 0;
|
|
}
|
|
}
|
|
if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
|
|
{
|
|
gMessageSystem->sendSemiReliable(host, NULL, NULL);
|
|
}
|
|
}
|
|
mCancelQueue.clear();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
|
|
{
|
|
mRequestedTimer.reset();
|
|
if (index >= mTotalPackets)
|
|
{
|
|
// llwarns << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << llendl;
|
|
return false;
|
|
}
|
|
if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE)
|
|
{
|
|
// llwarns << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << llendl;
|
|
return false;
|
|
}
|
|
|
|
if (index >= (S32)mPackets.size())
|
|
{
|
|
mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers
|
|
}
|
|
else if (mPackets[index] != NULL)
|
|
{
|
|
// llwarns << "Received duplicate packet: " << index << " for image: " << mID << llendl;
|
|
return false;
|
|
}
|
|
|
|
mPackets[index] = new PacketData(data, size);
|
|
while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL)
|
|
{
|
|
++mLastPacket;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes,
|
|
U16 data_size, U8* data)
|
|
{
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
bool res = true;
|
|
|
|
++mPacketCount;
|
|
|
|
if (!worker)
|
|
{
|
|
// llwarns << "Received header for non active worker: " << id << llendl;
|
|
res = false;
|
|
}
|
|
else if (worker->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK ||
|
|
worker->mSentRequest != LLTextureFetchWorker::SENT_SIM)
|
|
{
|
|
// llwarns << "receiveImageHeader for worker: " << id
|
|
// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState]
|
|
// << " sent: " << worker->mSentRequest << llendl;
|
|
res = false;
|
|
}
|
|
else if (worker->mLastPacket != -1)
|
|
{
|
|
// check to see if we've gotten this packet before
|
|
// llwarns << "Received duplicate header for: " << id << llendl;
|
|
res = false;
|
|
}
|
|
else if (!data_size)
|
|
{
|
|
llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
|
|
res = false;
|
|
}
|
|
if (!res)
|
|
{
|
|
++mBadPacketCount;
|
|
mCancelQueue[host].insert(id);
|
|
return false;
|
|
}
|
|
|
|
worker->lockWorkData();
|
|
|
|
// Copy header data into image object
|
|
worker->mImageCodec = codec;
|
|
worker->mTotalPackets = packets;
|
|
worker->mFileSize = (S32)totalbytes;
|
|
llassert_always(totalbytes > 0);
|
|
llassert_always(data_size == FIRST_PACKET_SIZE || data_size == worker->mFileSize);
|
|
res = worker->insertPacket(0, data, data_size);
|
|
worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
|
|
worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
|
|
worker->unlockWorkData();
|
|
return res;
|
|
}
|
|
|
|
bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data)
|
|
{
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
bool res = true;
|
|
|
|
++mPacketCount;
|
|
|
|
if (!worker)
|
|
{
|
|
// llwarns << "Received packet " << packet_num << " for non active worker: " << id << llendl;
|
|
res = false;
|
|
}
|
|
else if (worker->mLastPacket == -1)
|
|
{
|
|
// llwarns << "Received packet " << packet_num << " before header for: " << id << llendl;
|
|
res = false;
|
|
}
|
|
else if (!data_size)
|
|
{
|
|
llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
|
|
res = false;
|
|
}
|
|
if (!res)
|
|
{
|
|
++mBadPacketCount;
|
|
mCancelQueue[host].insert(id);
|
|
return false;
|
|
}
|
|
|
|
worker->lockWorkData();
|
|
|
|
res = worker->insertPacket(packet_num, data, data_size);
|
|
|
|
if ((worker->mState == LLTextureFetchWorker::LOAD_FROM_SIMULATOR) ||
|
|
(worker->mState == LLTextureFetchWorker::LOAD_FROM_NETWORK))
|
|
{
|
|
worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
|
|
worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
|
|
}
|
|
else
|
|
{
|
|
// llwarns << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
|
|
// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << llendl;
|
|
removeFromNetworkQueue(worker); // failsafe
|
|
mCancelQueue[host].insert(id);
|
|
}
|
|
|
|
worker->unlockWorkData();
|
|
|
|
return res;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p,
|
|
U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p)
|
|
{
|
|
S32 state = LLTextureFetchWorker::INVALID;
|
|
F32 data_progress = 0.0f;
|
|
F32 requested_priority = 0.0f;
|
|
F32 fetch_dtime = 999999.f;
|
|
F32 request_dtime = 999999.f;
|
|
U32 fetch_priority = 0;
|
|
|
|
LLMutexLock lock(&mQueueMutex);
|
|
LLTextureFetchWorker* worker = getWorker(id);
|
|
if (worker && worker->haveWork())
|
|
{
|
|
worker->lockWorkData();
|
|
state = worker->mState;
|
|
fetch_dtime = worker->mFetchTimer.getElapsedTimeF32();
|
|
request_dtime = worker->mRequestedTimer.getElapsedTimeF32();
|
|
if (worker->mFileSize > 0)
|
|
{
|
|
if (state == LLTextureFetchWorker::LOAD_FROM_SIMULATOR)
|
|
{
|
|
S32 data_size = FIRST_PACKET_SIZE + (worker->mLastPacket-1) * MAX_IMG_PACKET_SIZE;
|
|
data_size = llmax(data_size, 0);
|
|
data_progress = (F32)data_size / (F32)worker->mFileSize;
|
|
}
|
|
else if (worker->mFormattedImage.notNull())
|
|
{
|
|
data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize;
|
|
}
|
|
}
|
|
if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::LOAD_FROM_HTTP_GET_DATA)
|
|
{
|
|
requested_priority = worker->mRequestedPriority;
|
|
}
|
|
else
|
|
{
|
|
requested_priority = worker->mImagePriority;
|
|
}
|
|
fetch_priority = worker->getPriority();
|
|
worker->unlockWorkData();
|
|
}
|
|
data_progress_p = data_progress;
|
|
requested_priority_p = requested_priority;
|
|
fetch_priority_p = fetch_priority;
|
|
fetch_dtime_p = fetch_dtime;
|
|
request_dtime_p = request_dtime;
|
|
return state;
|
|
}
|
|
|
|
void LLTextureFetch::dump()
|
|
{
|
|
llinfos << "LLTextureFetch REQUESTS:" << llendl;
|
|
for (request_queue_t::iterator iter = mRequestQueue.begin();
|
|
iter != mRequestQueue.end(); ++iter)
|
|
{
|
|
LLQueuedThread::QueuedRequest* qreq = *iter;
|
|
LLWorkerThread::WorkRequest* wreq = (LLWorkerThread::WorkRequest*)qreq;
|
|
LLTextureFetchWorker* worker = (LLTextureFetchWorker*)wreq->getWorkerClass();
|
|
llinfos << " ID: " << worker->mID
|
|
<< " PRI: " << llformat("0x%08x",wreq->getPriority())
|
|
<< " STATE: " << worker->sStateDescs[worker->mState]
|
|
<< llendl;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|