diff options
Diffstat (limited to 'indra/newview/lltexturefetch.cpp')
-rw-r--r-- | indra/newview/lltexturefetch.cpp | 1951 |
1 files changed, 1475 insertions, 476 deletions
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 75777024da..db747c60fc 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1,6 +1,6 @@ /** - * @file llviewerimage.cpp - * @brief Object which handles a received image (and associated texture(s)) + * @file lltexturecache.cpp + * @brief Object which fetches textures from the cache and/or network * * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. * $License$ @@ -12,96 +12,283 @@ #include "lltexturefetch.h" -#include "llworkerthread.h" +#include "llcurl.h" +#include "llhttpclient.h" #include "llimage.h" -#include "llvfs.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 LLTextureFetchImpl; - -public: - // From LLWorkerClass - static void initClass(bool threaded, bool runalways); - static void updateClass(); - static void cleanupClass(); - - // New methods - static LLTextureFetchWorker* getWorker(const LLUUID& id, const LLHost& host, - F32 mPriority, S32 discard, - BOOL needs_aux = FALSE); - static LLTextureFetchWorker* getActiveWorker(const LLUUID& id); + friend class LLTextureFetch; private: - static void sendRequestListToSimulators(); + 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*/ 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(const LLUUID& id, const LLHost& host, F32 mPriority, S32 discard); + 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) + /*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); - void insertPacket(S32 index, U8* data, S32 size); + void setDesiredDiscard(S32 discard, S32 size); + bool insertPacket(S32 index, U8* data, S32 size); void clearPackets(); U32 calcWorkPriority(); - bool startVFSLoad(LLVFS* vfs, LLAssetType::EType asset_type); - bool loadFromVFS(); + void removeFromCache(); bool processSimulatorPackets(); void startDecode(); bool decodeImage(); - + bool writeToCacheComplete(); + void lockWorkData() { mWorkMutex.lock(); } void unlockWorkData() { mWorkMutex.unlock(); } - - static void lockQueue() { sDataMutex->lock(); } - static void unlockQueue() { sDataMutex->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 { - INIT = 1, - LOAD_FROM_CACHE, + // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack) + INVALID = 0, + INIT, + LOAD_FROM_TEXTURE_CACHE, + CACHE_POST, + LOAD_FROM_NETWORK, LOAD_FROM_SIMULATOR, - WRITE_TO_VFS, + LOAD_FROM_HTTP_GET_URL, + LOAD_FROM_HTTP_GET_DATA, DECODE_IMAGE, DECODE_IMAGE_UPDATE, + WRITE_TO_CACHE, + WAIT_ON_WRITE, DONE }; + static const char* sStateDescs[]; e_state mState; + LLTextureFetch* mFetcher; + LLImageWorker* mImageWorker; LLPointer<LLImageFormatted> mFormattedImage; LLPointer<LLImageRaw> mRawImage; LLPointer<LLImageRaw> mAuxImage; LLUUID mID; LLHost mHost; - F32 mPriority; + F32 mImagePriority; + U32 mWorkPriority; + F32 mRequestedPriority; S32 mDesiredDiscard; + S32 mSimRequestedDiscard; S32 mRequestedDiscard; + S32 mLoadedDiscard; S32 mDecodedDiscard; LLFrameTimer mRequestedTimer; LLFrameTimer mIdleTimer; - LLVFSThread::handle_t mFileHandle; + LLTextureCache::handle_t mCacheReadHandle; + LLTextureCache::handle_t mCacheWriteHandle; U8* mBuffer; S32 mBufferSize; S32 mRequestedSize; + S32 mDesiredSize; + S32 mFileSize; + S32 mCachedSize; BOOL mLoaded; BOOL mRequested; BOOL mDecoded; + BOOL mWritten; BOOL mNeedsAux; + BOOL mHaveAllData; + BOOL mUseHTTPGet; + BOOL mInLocalCache; + S32 mRetryAttempt; + std::string mURL; S32 mActiveCount; // Work Data @@ -110,638 +297,1450 @@ private: { PacketData(U8* data, S32 size) { mData = data; mSize = size; } ~PacketData() { clearData(); } - void clearData() { delete mData; mData = NULL; } + void clearData() { delete[] mData; mData = NULL; } U8* mData; U32 mSize; }; std::vector<PacketData*> mPackets; + S32 mFirstPacket; S32 mLastPacket; - S32 mTotalPackets; - S32 mTotalBytes; - - // Class variables (statics) - - static LLWorkerThread* sWorkerThread; - static LLMutex* sDataMutex; + U16 mTotalPackets; + U8 mImageCodec; + LLFrameTimer mFetchTimer; // debug +}; - // Map of all requests by UUID - typedef std::map<LLUUID,LLTextureFetchWorker*> map_t; - static map_t sRequests; +//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", +}; - // Set of requests that require network data - typedef std::set<LLTextureFetchWorker*> queue_t ; - static queue_t sNetworkQueue; +// called from MAIN THREAD - static LLFrameTimer sNetworkTimer; -}; +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), + mRequested(FALSE), + mDecoded(FALSE), + mWritten(FALSE), + mNeedsAux(FALSE), + mHaveAllData(FALSE), + mUseHTTPGet(FALSE), + mInLocalCache(FALSE), + mRetryAttempt(0), + mActiveCount(0), + mWorkMutex(fetcher->getWorkerAPRPool()), + mFirstPacket(0), + mLastPacket(-1), + mTotalPackets(0), + mImageCodec(IMG_CODEC_INVALID) +{ + calcWorkPriority(); + if ((gSavedSettings.getBOOL("ImagePipelineUseHTTP")) && + (host == LLHost::invalid)) + { + mUseHTTPGet = TRUE; + } + if (!mFetcher->mDebugPause) + { + U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH; + addWork(0, work_priority ); + } + setDesiredDiscard(discard, size); +} -//statics -LLTextureFetchWorker::map_t LLTextureFetchWorker::sRequests; -LLTextureFetchWorker::queue_t LLTextureFetchWorker::sNetworkQueue; -LLFrameTimer LLTextureFetchWorker::sNetworkTimer; -LLWorkerThread* LLTextureFetchWorker::sWorkerThread = NULL; -LLMutex* LLTextureFetchWorker::sDataMutex = NULL; +LLTextureFetchWorker::~LLTextureFetchWorker() +{ + 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(); +} -// called from MAIN THREAD -//static -void LLTextureFetchWorker::initClass(bool threaded, bool runalways) +void LLTextureFetchWorker::clearPackets() { - sWorkerThread = new LLWorkerThread(threaded, runalways); - sDataMutex = new LLMutex(sWorkerThread->getAPRPool()); + for_each(mPackets.begin(), mPackets.end(), DeletePointer()); + mPackets.clear(); + mTotalPackets = 0; + mLastPacket = -1; + mFirstPacket = 0; } -// called from MAIN THREAD -//static -void LLTextureFetchWorker::updateClass() +U32 LLTextureFetchWorker::calcWorkPriority() { - const F32 REQUEST_TIME = 1.f; - const F32 MIN_IDLE_TIME = 1.f * 60.f; // 1 minute - const F32 MAX_IDLE_TIME = 5.f * 60.f; // 5 minutes - const S32 MIN_IDLE_COUNT = 16; // always keep last 16 idle requests - const F32 MAX_IDLE_COUNT = 1024; // max number of idle requests + F32 priority_scale = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerImage::maxDecodePriority(); + mWorkPriority = (U32)(mImagePriority * priority_scale); + return mWorkPriority; +} - // Periodically, gather the list of textures that need data from the network - // And send the requests out to the simulators - if (sNetworkTimer.getElapsedTimeF32() >= REQUEST_TIME) +void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size) +{ + if (mDesiredDiscard != discard) { - sNetworkTimer.reset(); - sendRequestListToSimulators(); + if (!haveWork()) + { + calcWorkPriority(); + if (!mFetcher->mDebugPause) + { + U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH; + addWork(0, work_priority); + } + } + else if (mDesiredDiscard < discard) + { + U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH; + setPriority(work_priority); + } + mDesiredDiscard = discard; + mDesiredSize = size; } - // Remove any old requests (releasing their raw data) - typedef std::pair<F32, LLTextureFetchWorker*> idle_pair; - typedef std::set<idle_pair, compare_pair_greater<F32,LLTextureFetchWorker*> > idle_set; - idle_set remove_set; - for (map_t::iterator iter = sRequests.begin(); iter != sRequests.end(); ++iter) + else if (size > mDesiredSize) { - LLTextureFetchWorker* worker = iter->second; - if (worker->mActiveCount > 0) - continue; - if (worker->haveWork()) - continue; - F32 idletime = worker->mIdleTimer.getElapsedTimeF32(); - if (idletime < MIN_IDLE_TIME) - continue; - remove_set.insert(std::make_pair(idletime, worker)); + mDesiredSize = size; + U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH; + setPriority(work_priority); } - S32 num_left = remove_set.size(); - for (idle_set::iterator iter = remove_set.begin(); iter != remove_set.end(); ++iter) +} + +void LLTextureFetchWorker::setImagePriority(F32 priority) +{ + F32 delta = fabs(priority - mImagePriority); + if (delta > (mImagePriority * .05f)) // 5% { - if (num_left <= MIN_IDLE_COUNT) - break; - if (iter->first < MAX_IDLE_TIME && - num_left < MAX_IDLE_COUNT) - break; - num_left--; + mImagePriority = priority; + calcWorkPriority(); + U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS); + setPriority(work_priority); } } -// called from MAIN THREAD -//static -void LLTextureFetchWorker::sendRequestListToSimulators() +void LLTextureFetchWorker::resetFormattedData() { - const F32 LAZY_FLUSH_TIMEOUT = 60.f; - const S32 IMAGES_PER_REQUEST = 50; - // Send requests - typedef std::map< LLHost, std::vector<LLTextureFetchWorker*> > work_request_map_t; - work_request_map_t requests; - for (queue_t::iterator iter = sNetworkQueue.begin(); iter != sNetworkQueue.end(); ++iter) + 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); + + e_state old_state = mState; + mFetchTimer.reset(); + + if (mFetcher->mDebugPause) + { + return false; // debug: don't do any work + } + if (mID == mFetcher->mDebugID) + { + mFetcher->mDebugCount++; // for setting breakpoints + } + + if (mState == INIT) { - LLTextureFetchWorker* req = *iter; - if (req->haveWork()) + mRequestedDiscard = -1; + mLoadedDiscard = -1; + mDecodedDiscard = -1; + mRequestedSize = 0; + mFileSize = 0; + mCachedSize = 0; + mLoaded = FALSE; + mRequested = FALSE; + mDecoded = FALSE; + mWritten = FALSE; + delete[] mBuffer; + mBuffer = NULL; + mBufferSize = 0; + mHaveAllData = FALSE; + clearPackets(); + 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()) { - continue; // busy + 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 ((req->mRequestedDiscard == req->mDesiredDiscard) && - (req->mRequestedTimer.getElapsedTimeF32() < LAZY_FLUSH_TIMEOUT)) + + if (mLoaded) { - continue; + // 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; } - req->mRequestedDiscard = req->mDesiredDiscard; - req->mRequestedTimer.reset(); - requests[req->mHost].push_back(req); } - for (work_request_map_t::iterator iter1 = requests.begin(); - iter1 != requests.end(); ++iter1) + + if (mState == CACHE_POST) { - LLHost host = iter1->first; - // invalid host = load from static VFS - if (host != LLHost::invalid) + mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; + // Successfully loaded + if ((mCachedSize >= mDesiredSize) || mHaveAllData) { - S32 request_count = 0; - for (std::vector<LLTextureFetchWorker*>::iterator iter2 = iter1->second.begin(); - iter2 != iter1->second.end(); ++iter2) + // 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 (mFormattedImage.isNull()) + { + mFormattedImage = new LLImageJ2C; + } + mState = mUseHTTPGet ? LOAD_FROM_HTTP_GET_URL : LOAD_FROM_SIMULATOR; + return false; + } + + if (mState == LOAD_FROM_SIMULATOR) + { + if (!mRequested) + { + S32 data_size = mFormattedImage->getDataSize(); + if (data_size > 0) { - LLTextureFetchWorker* req = *iter2; - if (0 == request_count) + mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1; + if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size) { - gMessageSystem->newMessageFast(_PREHASH_RequestImage); + llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl; + removeFromCache(); + resetFormattedData(); + clearPackets(); } - S32 packet = req->mLastPacket + 1; - gMessageSystem->nextBlockFast(_PREHASH_RequestImage); - gMessageSystem->addUUIDFast(_PREHASH_Image, req->mID); - gMessageSystem->addS32Fast(_PREHASH_DiscardLevel, req->mDesiredDiscard); - gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, req->mPriority); - gMessageSystem->addU32Fast(_PREHASH_Packet, packet); + else + { + mLastPacket = mFirstPacket-1; + mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1; + } + } + mRequested = TRUE; + mRequestedSize = mDesiredSize; + mRequestedDiscard = mDesiredDiscard; + mFetcher->lockQueue(); + mFetcher->addToNetworkQueue(this); + mFetcher->unlockQueue(); + } + 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 (mState == LOAD_FROM_HTTP_GET_URL) + { + if (!mRequested) + { + mRequested = TRUE; + mLoaded = FALSE; + std::string url; + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + url = region->getCapability("RequestTextureDownload"); + } + if (!url.empty()) + { + LLSD sd; + sd = mID.asString(); + LLHTTPClient::post(url, sd, new URLResponder(mFetcher, mID)); +//*TODO:uncomment setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + return false; + } + else + { + llwarns << mID << ": HTTP get url failed, requesting from simulator" << llendl; + mRequested = FALSE; + mState = LOAD_FROM_SIMULATOR; + return false; + } + } + else + { + if (mLoaded) + { + if (!mURL.empty()) + { + mState = LOAD_FROM_HTTP_GET_DATA; + mRequested = FALSE; // reset + mLoaded = FALSE; // reset + } + else + { + llwarns << mID << ": HTTP get url is empty, requesting from simulator" << llendl; + mRequested = FALSE; + mState = LOAD_FROM_SIMULATOR; + return false; + } + } + } + // fall through + } + + if (mState == LOAD_FROM_HTTP_GET_DATA) + { + if (!mRequested) + { + mRequested = 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; + LLCurl::getByteRange(url, offset, mRequestedSize, + new HTTPGetResponder(mFetcher, mID)); // *TODO: use mWorkPriority +//*TODO:uncomment setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + return false; // not done + } - request_count++; - if (request_count >= IMAGES_PER_REQUEST) + if (mLoaded) + { + S32 cur_size = mFormattedImage->getDataSize(); + if (mRequestedSize < 0) + { + llwarns << "http get failed for: " << mID << llendl; + if (cur_size == 0) { - gMessageSystem->sendSemiReliable(host, NULL, NULL); - request_count = 0; + resetFormattedData(); + return true; // failed + } + else + { + mState = DECODE_IMAGE; + return false; // use what we have } } - if (request_count >= IMAGES_PER_REQUEST) + llassert(mBufferSize == cur_size + mRequestedSize); + if (mHaveAllData) { - gMessageSystem->sendSemiReliable(host, NULL, NULL); + mFileSize = mBufferSize; + } + if (mRequestedSize > 0) + { + 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; + } + else + { + llassert_always(cur_size); } + 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; } -} -//static -LLTextureFetchWorker* LLTextureFetchWorker::getWorker(const LLUUID& id, - const LLHost& host, - F32 priority, - S32 discard, - BOOL needs_aux) -{ - LLTextureFetchWorker* res; - lockQueue(); - map_t::iterator iter = sRequests.find(id); - if (iter != sRequests.end()) + if (mState == DECODE_IMAGE) { - res = iter->second; - if (res->mHost != host) + llassert_always(mFormattedImage->getDataSize() > 0); + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it + startDecode(); + mState = DECODE_IMAGE_UPDATE; + // fall though (need to call requestDecodedData() to start work) + } + + if (mState == DECODE_IMAGE_UPDATE) + { + if (decodeImage()) { - llerrs << "LLTextureFetchWorker::getWorker called with multiple hosts" << llendl; + 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; } - res->setImagePriority(priority); - res->setDesiredDiscard(discard); - } - else + + if (mState == WRITE_TO_CACHE) { - res = new LLTextureFetchWorker(id, host, priority, discard); + if (mInLocalCache || !mFileSize || !mRequested) + { + // 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; + CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID); + mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority, + mFormattedImage->getData(), datasize, + mFileSize, responder); + mState = WAIT_ON_WRITE; + // 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; + } } - unlockQueue(); - res->mActiveCount++; - res->mNeedsAux = needs_aux; - return res; -} -LLTextureFetchWorker* LLTextureFetchWorker::getActiveWorker(const LLUUID& id) -{ - LLTextureFetchWorker* res = NULL; - lockQueue(); - map_t::iterator iter = sRequests.find(id); - if (iter != sRequests.end()) + if (mState == DONE) { - res = iter->second; + if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard) + { + // More data was requested, return to INIT + mState = INIT; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + return false; + } + if (old_state != DONE) + { + mIdleTimer.reset(); + } + return true; } - unlockQueue(); - return res; + + return false; } -LLTextureFetchWorker::LLTextureFetchWorker(const LLUUID& id, // Image UUID - const LLHost& host, // Simulator host - F32 priority, // Priority - S32 discard) // Desired discard level - : LLWorkerClass(sWorkerThread, "TextureFetch"), - mState(INIT), - mID(id), - mHost(host), - mPriority(priority), - mDesiredDiscard(discard), - mRequestedDiscard(-1), - mDecodedDiscard(-1), - mFileHandle(LLVFSThread::nullHandle()), - mBuffer(NULL), - mBufferSize(0), - mRequestedSize(0), - mLoaded(FALSE), - mRequested(FALSE), - mDecoded(FALSE), - mActiveCount(0), - mWorkMutex(sWorkerThread->getAPRPool()), - mLastPacket(-1), - mTotalPackets(0), - mTotalBytes(0) +// Called from MAIN thread +void LLTextureFetchWorker::endWork(S32 param, bool aborted) { - lockQueue(); - sRequests[mID] = this; - unlockQueue(); - addWork(0, calcWorkPriority()); + if (mImageWorker) + { + mImageWorker->scheduleDelete(); + mImageWorker = NULL; + } + mFormattedImage = NULL; } -LLTextureFetchWorker::~LLTextureFetchWorker() +////////////////////////////////////////////////////////////////////////////// + +// virtual +void LLTextureFetchWorker::finishWork(S32 param, bool completed) { - lockQueue(); - mFormattedImage = NULL; - map_t::iterator iter = sRequests.find(mID); - if (iter != sRequests.end() && iter->second == this) + // The following are required in case the work was aborted + if (mCacheReadHandle != LLTextureCache::nullHandle()) { - sRequests.erase(iter); + mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); + mCacheReadHandle = LLTextureCache::nullHandle(); + } + if (mCacheWriteHandle != LLTextureCache::nullHandle()) + { + mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true); + mCacheWriteHandle = LLTextureCache::nullHandle(); } - sNetworkQueue.erase(this); - unlockQueue(); - clearPackets(); } -void LLTextureFetchWorker::clearPackets() +// virtual +bool LLTextureFetchWorker::deleteOK() { - lockWorkData(); - for_each(mPackets.begin(), mPackets.end(), DeletePointer()); - mPackets.clear(); - unlockWorkData(); + 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; + } + } + // Don't delete while waiting for network requests *TODO: Need LLCurl::abort() + // Don't delete while waiting on writes + if ((mState >= LLTextureFetchWorker::LOAD_FROM_HTTP_GET_URL && + mState <= LLTextureFetchWorker::LOAD_FROM_HTTP_GET_DATA) || + (mState >= LLTextureFetchWorker::WRITE_TO_CACHE && + mState <= LLTextureFetchWorker::WAIT_ON_WRITE)) + { + delete_ok = false; + } + return delete_ok; } -U32 LLTextureFetchWorker::calcWorkPriority() + +void LLTextureFetchWorker::removeFromCache() { - F32 priority_scale = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerImage::maxDecodePriority(); - U32 priority = (U32)(mPriority * priority_scale); - return LLWorkerThread::PRIORITY_NORMAL | priority; + if (!mInLocalCache) + { + mFetcher->mTextureCache->removeFromCache(mID); + } } -void LLTextureFetchWorker::setDesiredDiscard(S32 discard) + +////////////////////////////////////////////////////////////////////////////// + +bool LLTextureFetchWorker::processSimulatorPackets() { - if (mDesiredDiscard != discard) + if (mLastPacket >= mFirstPacket) { - mDesiredDiscard = discard; - if (!haveWork()) + S32 buffer_size = mFormattedImage->getDataSize(); + for (S32 i = mFirstPacket; i<=mLastPacket; i++) { - addWork(0, calcWorkPriority()); + 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::setImagePriority(F32 priority) +////////////////////////////////////////////////////////////////////////////// + +void LLTextureFetchWorker::callbackURLReceived(const LLSD& data, bool success) { - if (priority != mPriority) + LLMutexLock lock(&mWorkMutex); + if (success) { - mPriority = priority; - setPriority(calcWorkPriority()); + mURL = data.asString(); } + mLoaded = TRUE; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); } -void LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) +////////////////////////////////////////////////////////////////////////////// + +void LLTextureFetchWorker::callbackHttpGet(U8* data, S32 data_size, bool last_block) { - PacketData* packet = new PacketData(data, size); - - lockWorkData(); - if (index >= (S32)mPackets.size()) + LLMutexLock lock(&mWorkMutex); + llassert_always(mRequested); +// llinfos << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << llendl; + if (mLoaded) { - mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers + llwarns << "Duplicate callback for " << mID.asString() << llendl; + return; // ignore duplicate callback } - if (mPackets[index] != NULL) + if (data_size >= 0) { - llwarns << "LLTextureFetchWorker::insertPacket called for duplicate packet: " << index << llendl; + 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; } - mPackets[index] = packet; - while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL) + else { - ++mLastPacket; + mRequestedSize = -1; // error } - unlockWorkData(); + mLoaded = TRUE; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); } -// Called from LLWorkerThread::processRequest() -bool LLTextureFetchWorker::doWork(S32 param) +////////////////////////////////////////////////////////////////////////////// + +void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image, + S32 imagesize, BOOL islocal) { - switch(mState) - { - case INIT: - { - // fall through - } - case LOAD_FROM_CACHE: - { - // Load any existing data from the cache - if (mFileHandle == LLVFSThread::nullHandle()) - { - bool res = startVFSLoad(gVFS, LLAssetType::AT_TEXTURE); - if (!res) res = startVFSLoad(gStaticVFS, LLAssetType::AT_TEXTURE_TGA); - if (!res) res = startVFSLoad(gStaticVFS, LLAssetType::AT_TEXTURE); - if (!res) - { - // Didn't load from VFS - mFormattedImage = new LLImageJ2C; - mState = LOAD_FROM_SIMULATOR; - } - } - if (mFileHandle != LLVFSThread::nullHandle()) - { - if (!loadFromVFS()) - { - return false; // not done - } - if (!mLoaded) - { - llwarns << "Load from VFS failed on: " << mID << llendl; - return true; - } - bool res = mFormattedImage->setData(mBuffer, mBufferSize); - if (!res) - { - llwarns << "loadLocalImage() - setData() failed" << llendl; - mFormattedImage->deleteData(); - return true; - } - // Successfully loaded - if (mFormattedImage->getDiscardLevel() <= mRequestedDiscard) - { - // we have enough data, decode it - mState = DECODE_IMAGE; - mRequestedSize = mBufferSize; - } - else - { - // need more data - mState = LOAD_FROM_SIMULATOR; - mRequestedSize = mFormattedImage->calcDataSize(mRequestedDiscard); - } - } - return false; - } - case LOAD_FROM_SIMULATOR: - { - if (!mRequested) - { - lockQueue(); - sNetworkQueue.insert(this); - unlockQueue(); - mRequested = TRUE; - } - if (processSimulatorPackets()) - { - mState = WRITE_TO_VFS; - } - return false; - } - case WRITE_TO_VFS: - { - mState = DECODE_IMAGE; - // fall through - } - case DECODE_IMAGE: - { - startDecode(); - mState = DECODE_IMAGE_UPDATE; - // fall through - } - case DECODE_IMAGE_UPDATE: - { - if (decodeImage()) - { - mState = DONE; - } - return false; - } - case DONE: - { - // Do any cleanup here - // Destroy the formatted image, we don't need it any more (raw image is still valid) - mFormattedImage = NULL; - mIdleTimer.reset(); - return true; - } - default: - { - llerrs << "LLTextureFetchWorker::doWork() has illegal state" << llendl; - return true; - } + LLMutexLock lock(&mWorkMutex); + 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); } -// Called from MAIN thread -void LLTextureFetchWorker::startWork(S32 param) +void LLTextureFetchWorker::callbackCacheWrite(bool success) { + LLMutexLock lock(&mWorkMutex); + mWritten = TRUE; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); } -void LLTextureFetchWorker::endWork(S32 param, bool aborted) +////////////////////////////////////////////////////////////////////////////// + +void LLTextureFetchWorker::callbackDecoded(bool success) { +// llinfos << mID << " : DECODE COMPLETE " << llendl; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); } ////////////////////////////////////////////////////////////////////////////// -bool LLTextureFetchWorker::startVFSLoad(LLVFS* vfs, LLAssetType::EType asset_type) +void LLTextureFetchWorker::startDecode() { - // Start load from VFS if it's there - if (vfs->getExists(mID, asset_type)) + mRawImage = NULL; + mAuxImage = NULL; + llassert_always(mImageWorker == NULL); + llassert_always(mFormattedImage.notNull()); + S32 discard = mHaveAllData ? 0 : mLoadedDiscard; + U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority; +// llinfos << mID << " : DECODE STARTED : " << discard +// << " Pri: " << priority +// << " Components:" << (S32)mFormattedImage->getComponents() << llendl; + mImageWorker = new LLImageWorker(mFormattedImage, image_priority, discard, new DecodeResponder(mFetcher, mID, this)); +} + +bool LLTextureFetchWorker::decodeImage() +{ + llassert_always(mImageWorker); + bool res = true; + if (mRawImage.isNull()) { - mBufferSize = vfs->getSize(mID, asset_type); - mBuffer = new U8[mBufferSize]; - mFileHandle = LLVFSThread::sLocal->read(vfs, mID, asset_type, mBuffer, 0, mBufferSize); /* Flawfinder: ignore */ - if (mFileHandle == LLVFSThread::nullHandle()) + res = false; + if (mImageWorker->requestDecodedData(mRawImage, -1)) { - llwarns << "loadLocalImage() - vfs read failed in static VFS: " << mID << llendl; - delete mBuffer; - mBuffer = NULL; - return false; + res = true; +// llinfos << mID << " : BASE DECODE FINISHED" << llendl; } - if (asset_type == LLAssetType::AT_TEXTURE_TGA) + } + if (res && + (mRawImage.notNull() && mRawImage->getDataSize() > 0) && + (mNeedsAux && mAuxImage.isNull())) + { + res = false; + if (mImageWorker->requestDecodedAuxData(mAuxImage, 4, -1)) { - mFormattedImage = new LLImageTGA; + res = true; +// llinfos << mID << " : AUX DECODE FINISHED" << llendl; } - else if (asset_type == LLAssetType::AT_TEXTURE) + } + if (res) + { + if ((mRawImage.notNull() && mRawImage->getDataSize() > 0) && + (!mNeedsAux || (mAuxImage.notNull() && mAuxImage->getDataSize() > 0))) { - mFormattedImage = new LLImageJ2C; + mDecodedDiscard = mFormattedImage->getDiscardLevel(); +// llinfos << mID << " : DECODE FINISHED. DISCARD: " << mDecodedDiscard << llendl; } else { - llerrs << "LLTextureFetchWorker::startVFSLoad called with bad asset type: " << asset_type << llendl; + llwarns << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << llendl; + removeFromCache(); } - return true; + mImageWorker->scheduleDelete(); + mImageWorker = NULL; } - return false; + else + { + U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority; + mImageWorker->setPriority(image_priority); + //llinfos << worker->mID << " : DECODE PRIORITY : " << priority << llendl; + } + return res; } -bool LLTextureFetchWorker::loadFromVFS() +////////////////////////////////////////////////////////////////////////////// + +bool LLTextureFetchWorker::writeToCacheComplete() { - LLMemType mt1(LLMemType::MTYPE_APPFMTIMAGE); + // 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; +} - llassert(mLoaded == FALSE); - - // Check loading status - LLVFSThread::status_t status = LLVFSThread::sLocal->getRequestStatus(mFileHandle); - if (status == LLVFSThread::STATUS_QUEUED || status == LLVFSThread::STATUS_INPROGRESS) + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// public + +LLTextureFetch::LLTextureFetch(LLTextureCache* cache, bool threaded) + : LLWorkerThread("TextureFetch", threaded), + mDebugCount(0), + mDebugPause(0), + mTextureCache(cache), + mQueueMutex(getAPRPool()) +{ +} + +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) +{ + LLTextureFetchWorker* worker = NULL; + LLMutexLock lock(&mQueueMutex); + map_t::iterator iter = mRequestMap.find(id); + if (iter != mRequestMap.end()) { - return false; + worker = iter->second; + if (worker->mHost != host) + { + llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts" << llendl; + removeRequest(worker, false); + worker = NULL; + } } - else if (status == LLVFSThread::STATUS_COMPLETE) + // 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) { - mLoaded = TRUE; - return true; + desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, discard); } else { - llwarns << "loadLocalImage() - vfs read failed" << llendl; - LLVFSThread::Request* req = (LLVFSThread::Request*)LLVFSThread::sLocal->getRequest(mFileHandle); - if (req && mFormattedImage.notNull()) + desired_size = FIRST_PACKET_SIZE; + discard = MAX_DISCARD_LEVEL; + } + if (worker) + { + if (worker->wasAborted()) { - LLVFS* vfs = req->getVFS(); - LLAssetType::EType asset_type = mFormattedImage->getCodec() == IMG_CODEC_TGA ? LLAssetType::AT_TEXTURE_TGA : LLAssetType::AT_TEXTURE; - vfs->removeFile(mID, asset_type); + return false; // need to wait for previous aborted request to complete } - return true; + worker->lockWorkData(); + worker->setImagePriority(priority); + worker->setDesiredDiscard(discard, desired_size); + worker->unlockWorkData(); + } + 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); + } +} -bool LLTextureFetchWorker::processSimulatorPackets() +// 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; - lockWorkData(); - if (mLastPacket >= 0) + LLMutexLock lock(&mQueueMutex); + LLTextureFetchWorker* worker = getWorker(id); + if (worker) { - S32 data_size = 0; - for (S32 i = 0; i<=mLastPacket; i++) + if (worker->wasAborted()) { - data_size += mPackets[i]->mSize; + res = true; } - if (data_size >= mRequestedSize || mLastPacket == mTotalPackets) + else if (!worker->haveWork()) { - /// We have enough (or all) data, copy it into mBuffer - if (mBufferSize < data_size) + // Should only happen if we set mDebugPause... + if (!mDebugPause) { - delete mBuffer; - mBuffer = new U8[data_size]; - mBufferSize = data_size; + worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); } - S32 offset = 0; - for (S32 i = 0; i<=mLastPacket; i++) + } + 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)) { - if (mPackets[i]->mData != NULL) - { - memcpy(mBuffer + offset, mPackets[i]->mData, mPackets[i]->mSize); /* Flawfinder: ignore */ - offset += mPackets[i]->mSize; - } + // Not finished, but data is ready + discard_level = worker->mDecodedDiscard; + if (worker->mRawImage) raw = worker->mRawImage; + if (worker->mAuxImage) aux = worker->mAuxImage; } - res = true; + worker->unlockWorkData(); } } - 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; } ////////////////////////////////////////////////////////////////////////////// -void LLTextureFetchWorker::startDecode() +//virtual +S32 LLTextureFetch::update(U32 max_time_ms) { - mRawImage = NULL; - mAuxImage = NULL; + 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(); + } + +#if 0 // Currently this logic is handled in LLViewer + { + LLMutexLock lock(&mQueueMutex); + const F32 MIN_IDLE_TIME = 1.f * 60.f; // 1 minute + const F32 MAX_IDLE_TIME = 5.f * 60.f; // 5 minutes + const S32 MIN_IDLE_COUNT = 16; // always keep last 16 idle requests + const F32 MAX_IDLE_COUNT = 1024; // max number of idle requests + // Remove any old requests (releasing their raw data) + typedef std::pair<F32, LLTextureFetchWorker*> idle_pair; + typedef std::set<idle_pair, compare_pair_greater<F32,LLTextureFetchWorker*> > idle_set; + idle_set remove_set; + for (map_t::iterator iter = mRequestMap.begin(); iter != mRequestMap.end(); ++iter) + { + LLTextureFetchWorker* worker = iter->second; + if (worker->mActiveCount > 0) + continue; + if (worker->haveWork()) + continue; + F32 idletime = worker->mIdleTimer.getElapsedTimeF32(); + if (idletime < MIN_IDLE_TIME) + continue; + remove_set.insert(std::make_pair(idletime, worker)); + } + S32 num_left = remove_set.size(); + for (idle_set::iterator iter = remove_set.begin(); iter != remove_set.end(); ++iter) + { + if (num_left <= MIN_IDLE_COUNT) + break; + if (iter->first < MAX_IDLE_TIME && + num_left < MAX_IDLE_COUNT) + break; + num_left--; + } + } +#endif + + return res; } -bool LLTextureFetchWorker::decodeImage() +////////////////////////////////////////////////////////////////////////////// + +void LLTextureFetch::sendRequestListToSimulators() { - const F32 MAX_DECODE_TIME = .001f; // 1 ms - if (mRawImage->getDataSize() == 0) + 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(); ++iter) { - if (!mFormattedImage->requestDecodedData(mRawImage, -1, MAX_DECODE_TIME)) + LLTextureFetchWorker* req = getWorker(*iter); + if (req->mID == mDebugID) { - return false; + 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); } - mFormattedImage->releaseDecodedData(); // so that we have the only ref to the raw image } - if (mNeedsAux && mAuxImage->getDataSize() == 0) + for (work_request_map_t::iterator iter1 = requests.begin(); + iter1 != requests.end(); ++iter1) { - if (!mFormattedImage->requestDecodedAuxData(mAuxImage, 4, -1, MAX_DECODE_TIME )) + LLHost host = iter1->first; + // invalid host = use agent host + if (host == LLHost::invalid) { - return false; + host = gAgent.getRegionHost(); + } + + S32 request_count = 0; + for (request_list_t::iterator iter2 = iter1->second.begin(); + iter2 != iter1->second.end(); ++iter2) + { + LLTextureFetchWorker* req = *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()); + } + 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); + U8 type = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL; + gMessageSystem->addU8Fast(_PREHASH_Type, type); +// 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; + break; // only send the top requests + } + } + if (request_count > 0 && request_count < IMAGES_PER_REQUEST) + { +// llinfos << "REQUESTING " << request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl; + gMessageSystem->sendSemiReliable(host, NULL, NULL); } - mFormattedImage->releaseDecodedData(); // so that we have the only ref to the raw image } - mDecodedDiscard = mFormattedImage->getDiscardLevel(); - return true; + + // Send cancelations + if (!mCancelQueue.empty()) + { + for (cancel_queue_t::iterator iter1 = mCancelQueue.begin(); + iter1 != mCancelQueue.end(); ++iter1) + { + LLHost host = iter1->first; + // invalid host = use agent host + 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(); + } } ////////////////////////////////////////////////////////////////////////////// -#if 0 -// static -void LLTextureFetchWorker::receiveImageHeader(LLMessageSystem *msg, void **user_data) +bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) { - LLFastTimer t(LLFastTimer::FTM_PROCESS_IMAGES); + mRequestedTimer.reset(); + if (index >= mTotalPackets) + { + llwarns << "Received Image Packet " << index << " > max: " << mTotalPackets << " Skipping. " << 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 << " Skipping. " << llendl; + return false; + } - // Receive image header, copy into image object and decompresses - // if this is a one-packet image. + if (index >= (S32)mPackets.size()) + { + mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers + } + else if (mPackets[index] != NULL) + { +// llwarns << "LLTextureFetchWorker::insertPacket called for duplicate packet: " << index << llendl; + return false; + } - gImageList.sTextureBits += msg->getReceiveBytes(); - gImageList.sTexturePackets++; + mPackets[index] = new PacketData(data, size); + while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL) + { + ++mLastPacket; + } + return true; +} - LLUUID id; - msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, id); -// LLString ip_string(u32_to_ip_string(msg->getSenderIP())); - - LLTextureFetchWorker* worker = getActiveWorker(id); +bool LLTextureFetch::receiveImageHeader(const LLUUID& id, U8 codec, U16 packets, U32 totalbytes, + U16 data_size, U8* data) +{ + LLMutexLock lock(&mQueueMutex); + LLTextureFetchWorker* worker = getWorker(id); if (!worker) { - llwarns << "receiveImageHeader for non active worker: " << id << llendl; - return; +// llwarns << "receiveImageHeader for non active worker: " << id << llendl; + return false; } - worker->mRequestedTimer.reset(); - // check to see if we've gotten this packet before if (worker->mLastPacket != -1) { - llwarns << "Img: " << id << ":" << " Duplicate Image Header" << llendl; - return; +// llwarns << "Img: " << id << ":" << " Duplicate Image Header" << llendl; + return false; } - // Copy header data into image object worker->lockWorkData(); - msg->getU8Fast(_PREHASH_ImageID, _PREHASH_Codec, image->mDataCodec); - msg->getU16Fast(_PREHASH_ImageID, _PREHASH_Packets, image->mTotalPackets); - msg->getU32Fast(_PREHASH_ImageID, _PREHASH_Size, image->mTotalBytes); - if (0 == image->mTotalPackets) + + // Copy header data into image object + worker->mImageCodec = codec; + worker->mTotalPackets = packets; + worker->mFileSize = (S32)totalbytes; + llassert_always(totalbytes > 0); + bool res = false; + if (data_size) { - llwarns << "Img: " << id << ":" << " Number of packets is 0" << llendl; + llassert(data_size == FIRST_PACKET_SIZE || data_size == worker->mFileSize); + res = worker->insertPacket(0, data, data_size); + worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); } worker->unlockWorkData(); + return res; +} - U16 data_size = msg->getSizeFast(_PREHASH_ImageData, _PREHASH_Data); +bool LLTextureFetch::receiveImagePacket(const LLUUID& id, U16 packet_num, U16 data_size, U8* data) +{ + LLMutexLock lock(&mQueueMutex); + LLTextureFetchWorker* worker = getWorker(id); + if (!worker) + { +// llwarns << "receiveImagePacket " << packet_num << " for non active worker: " << id << llendl; + return false; + } + if (worker->mLastPacket == -1) + { +// llwarns << "Img: " << id << ":" << " Image Packet " << packet_num << " received before header" << llendl; + return false; + } + + bool res = false; if (data_size) { - U8 *data = new U8[data_size]; - msg->getBinaryDataFast(_PREHASH_ImageData, _PREHASH_Data, data, data_size); - worker->insertPacket(0, data, data_size) + worker->lockWorkData(); + res = worker->insertPacket(packet_num, data, data_size); + worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); + worker->unlockWorkData(); } + return res; } -/////////////////////////////////////////////////////////////////////////////// -// static -void LLTextureFetchWorker::receiveImagePacket(LLMessageSystem *msg, void **user_data) +////////////////////////////////////////////////////////////////////////////// + +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) { - LLMemType mt1(LLMemType::MTYPE_APPFMTIMAGE); - LLFastTimer t(LLFastTimer::FTM_PROCESS_IMAGES); + 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; - gImageList.sTextureBits += msg->getReceiveBytes(); - gImageList.sTexturePackets++; - - LLUUID id; - msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, id); -// LLString ip_string(u32_to_ip_string(msg->getSenderIP())); - - U16 packet_num; - msg->getU16Fast(_PREHASH_ImageID, _PREHASH_Packet, packet_num); - - LLTextureFetchWorker* worker = getActiveWorker(id); - if (!worker) + LLMutexLock lock(&mQueueMutex); + LLTextureFetchWorker* worker = getWorker(id); + if (worker && worker->haveWork()) { - llwarns << "receiveImageHeader for non active worker: " << id << llendl; - return; + 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(); } - worker->mRequestedTimer.reset(); + 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; +} - U16 data_size = msg->getSizeFast(_PREHASH_ImageData, _PREHASH_Data); - if (data_size) +void LLTextureFetch::dump() +{ + llinfos << "LLTextureFetch REQUESTS:" << llendl; + for (request_queue_t::iterator iter = mRequestQueue.begin(); + iter != mRequestQueue.end(); ++iter) { - U8 *data = new U8[data_size]; - msg->getBinaryDataFast(_PREHASH_ImageData, _PREHASH_Data, data, data_size); - worker->insertPacket(0, data, data_size) + 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; } } -#endif - ////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// |