/** * @file llviewertexture.cpp * @brief Object which handles a received image (and associated texture(s)) * * $LicenseInfo:firstyear=2000&license=viewergpl$ * * Copyright (c) 2000-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llviewertexture.h" // Library includes #include "imageids.h" #include "llmath.h" #include "llerror.h" #include "llgl.h" #include "llglheaders.h" #include "llhost.h" #include "llimage.h" #include "llimagebmp.h" #include "llimagej2c.h" #include "llimagetga.h" #include "llmemtype.h" #include "llstl.h" #include "llvfile.h" #include "llvfs.h" #include "message.h" #include "lltimer.h" // viewer includes #include "llimagegl.h" #include "lldrawpool.h" #include "lltexturefetch.h" #include "llviewertexturelist.h" #include "llviewercontrol.h" #include "pipeline.h" #include "llappviewer.h" #include "lltextureatlas.h" #include "lltextureatlasmanager.h" #include "lltextureentry.h" #include "llmediaentry.h" #include "llvovolume.h" #include "llviewermedia.h" /////////////////////////////////////////////////////////////////////////////// // statics LLPointer LLViewerTexture::sNullImagep = NULL; LLPointer LLViewerFetchedTexture::sMissingAssetImagep = NULL; LLPointer LLViewerFetchedTexture::sWhiteImagep = NULL; LLPointer LLViewerFetchedTexture::sDefaultImagep = NULL; LLPointer LLViewerFetchedTexture::sSmokeImagep = NULL; LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap ; LLTexturePipelineTester* LLViewerTextureManager::sTesterp = NULL ; S32 LLViewerTexture::sImageCount = 0; S32 LLViewerTexture::sRawCount = 0; S32 LLViewerTexture::sAuxCount = 0; LLTimer LLViewerTexture::sEvaluationTimer; F32 LLViewerTexture::sDesiredDiscardBias = 0.f; F32 LLViewerTexture::sDesiredDiscardScale = 1.1f; S32 LLViewerTexture::sBoundTextureMemoryInBytes = 0; S32 LLViewerTexture::sTotalTextureMemoryInBytes = 0; S32 LLViewerTexture::sMaxBoundTextureMemInMegaBytes = 0; S32 LLViewerTexture::sMaxTotalTextureMemInMegaBytes = 0; S32 LLViewerTexture::sMaxDesiredTextureMemInBytes = 0 ; BOOL LLViewerTexture::sDontLoadVolumeTextures = FALSE; BOOL LLViewerTexture::sUseTextureAtlas = FALSE ; const F32 desired_discard_bias_min = -2.0f; // -max number of levels to improve image quality by const F32 desired_discard_bias_max = 1.5f; // max number of levels to reduce image quality by const F64 log_2 = log(2.0); //---------------------------------------------------------------------------------------------- //namespace: LLViewerTextureAccess //---------------------------------------------------------------------------------------------- LLViewerMediaTexture* LLViewerTextureManager::createMediaTexture(const LLUUID &media_id, BOOL usemipmaps, LLImageGL* gl_image) { return new LLViewerMediaTexture(media_id, usemipmaps, gl_image) ; } LLViewerTexture* LLViewerTextureManager::findTexture(const LLUUID& id) { LLViewerTexture* tex ; //search fetched texture list tex = gTextureList.findImage(id) ; //search media texture list if(!tex) { tex = LLViewerTextureManager::findMediaTexture(id) ; } return tex ; } LLViewerMediaTexture* LLViewerTextureManager::findMediaTexture(const LLUUID &media_id) { return LLViewerMediaTexture::findMediaTexture(media_id) ; } LLViewerMediaTexture* LLViewerTextureManager::getMediaTexture(const LLUUID& id, BOOL usemipmaps, LLImageGL* gl_image) { LLViewerMediaTexture* tex = LLViewerMediaTexture::findMediaTexture(id) ; if(!tex) { tex = LLViewerTextureManager::createMediaTexture(id, usemipmaps, gl_image) ; } tex->initVirtualSize() ; return tex ; } LLViewerFetchedTexture* LLViewerTextureManager::staticCastToFetchedTexture(LLTexture* tex, BOOL report_error) { if(!tex) { return NULL ; } S8 type = tex->getType() ; if(type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) { return static_cast(tex) ; } if(report_error) { llerrs << "not a fetched texture type: " << type << llendl ; } return NULL ; } LLPointer LLViewerTextureManager::getLocalTexture(BOOL usemipmaps, BOOL generate_gl_tex) { LLPointer tex = new LLViewerTexture(usemipmaps) ; if(generate_gl_tex) { tex->generateGLTexture() ; } return tex ; } LLPointer LLViewerTextureManager::getLocalTexture(const LLUUID& id, BOOL usemipmaps, BOOL generate_gl_tex) { LLPointer tex = new LLViewerTexture(id, usemipmaps) ; if(generate_gl_tex) { tex->generateGLTexture() ; } return tex ; } LLPointer LLViewerTextureManager::getLocalTexture(const LLImageRaw* raw, BOOL usemipmaps) { LLPointer tex = new LLViewerTexture(raw, usemipmaps) ; return tex ; } LLPointer LLViewerTextureManager::getLocalTexture(const U32 width, const U32 height, const U8 components, BOOL usemipmaps, BOOL generate_gl_tex) { LLPointer tex = new LLViewerTexture(width, height, components, usemipmaps) ; if(generate_gl_tex) { tex->generateGLTexture() ; } return tex ; } LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture( const LLUUID &image_id, BOOL usemipmaps, S32 boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, LLHost request_from_host) { return gTextureList.getImage(image_id, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host) ; } LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromFile( const std::string& filename, BOOL usemipmaps, S32 boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, const LLUUID& force_id) { return gTextureList.getImageFromFile(filename, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id) ; } LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromHost(const LLUUID& image_id, LLHost host) { return gTextureList.getImageFromHost(image_id, host) ; } void LLViewerTextureManager::init() { LLPointer raw = new LLImageRaw(1,1,3); raw->clear(0x77, 0x77, 0x77, 0xFF); LLViewerTexture::sNullImagep = LLViewerTextureManager::getLocalTexture(raw.get(), TRUE) ; #if 1 LLPointer imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT); LLViewerFetchedTexture::sDefaultImagep = imagep; const S32 dim = 128; LLPointer image_raw = new LLImageRaw(dim,dim,3); U8* data = image_raw->getData(); for (S32 i = 0; i=(dim-border) || j>=(dim-border)) { *data++ = 0xff; *data++ = 0xff; *data++ = 0xff; } else #endif { *data++ = 0x7f; *data++ = 0x7f; *data++ = 0x7f; } } } imagep->createGLTexture(0, image_raw); image_raw = NULL; LLViewerFetchedTexture::sDefaultImagep->dontDiscard(); #else LLViewerFetchedTexture::sDefaultImagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, TRUE, TRUE); #endif LLViewerFetchedTexture::sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE, TRUE, TRUE); LLViewerFetchedTexture::sSmokeImagep->setNoDelete() ; LLViewerTexture::initClass() ; if(LLFastTimer::sMetricLog) { LLViewerTextureManager::sTesterp = new LLTexturePipelineTester() ; } } void LLViewerTextureManager::cleanup() { stop_glerror(); LLImageGL::sDefaultGLTexture = NULL ; LLViewerTexture::sNullImagep = NULL; LLViewerFetchedTexture::sDefaultImagep = NULL; LLViewerFetchedTexture::sSmokeImagep = NULL; LLViewerFetchedTexture::sMissingAssetImagep = NULL; LLViewerFetchedTexture::sWhiteImagep = NULL; LLViewerMediaTexture::cleanup() ; } //---------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------- //start of LLViewerTexture //---------------------------------------------------------------------------------------------- // static void LLViewerTexture::initClass() { LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture() ; } // static void LLViewerTexture::cleanupClass() { } // tuning params const F32 discard_bias_delta = .05f; const F32 discard_delta_time = 0.5f; const S32 min_non_tex_system_mem = (128<<20); // 128 MB // non-const (used externally F32 texmem_lower_bound_scale = 0.85f; F32 texmem_middle_bound_scale = 0.925f; //static void LLViewerTexture::updateClass(const F32 velocity, const F32 angular_velocity) { if(LLViewerTextureManager::sTesterp) { LLViewerTextureManager::sTesterp->update() ; } LLViewerMediaTexture::updateClass() ; sBoundTextureMemoryInBytes = LLImageGL::sBoundTextureMemoryInBytes;//in bytes sTotalTextureMemoryInBytes = LLImageGL::sGlobalTextureMemoryInBytes;//in bytes sMaxBoundTextureMemInMegaBytes = gTextureList.getMaxResidentTexMem();//in MB sMaxTotalTextureMemInMegaBytes = gTextureList.getMaxTotalTextureMem() ;//in MB sMaxDesiredTextureMemInBytes = MEGA_BYTES_TO_BYTES(sMaxTotalTextureMemInMegaBytes) ; //in Bytes, by default and when total used texture memory is small. if (BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) >= sMaxBoundTextureMemInMegaBytes || BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) >= sMaxTotalTextureMemInMegaBytes) { //when texture memory overflows, lower down the threashold to release the textures more aggressively. sMaxDesiredTextureMemInBytes = llmin((S32)(sMaxDesiredTextureMemInBytes * 0.75f) , MEGA_BYTES_TO_BYTES(MAX_VIDEO_RAM_IN_MEGA_BYTES)) ;//512 MB // If we are using more texture memory than we should, // scale up the desired discard level if (sEvaluationTimer.getElapsedTimeF32() > discard_delta_time) { sDesiredDiscardBias += discard_bias_delta; sEvaluationTimer.reset(); } } else if (sDesiredDiscardBias > 0.0f && BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) < sMaxBoundTextureMemInMegaBytes * texmem_lower_bound_scale && BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) < sMaxTotalTextureMemInMegaBytes * texmem_lower_bound_scale) { // If we are using less texture memory than we should, // scale down the desired discard level if (sEvaluationTimer.getElapsedTimeF32() > discard_delta_time) { sDesiredDiscardBias -= discard_bias_delta; sEvaluationTimer.reset(); } } sDesiredDiscardBias = llclamp(sDesiredDiscardBias, desired_discard_bias_min, desired_discard_bias_max); LLViewerTexture::sUseTextureAtlas = gSavedSettings.getBOOL("EnableTextureAtlas") ; } //end of static functions //------------------------------------------------------------------------------------------- const U32 LLViewerTexture::sCurrentFileVersion = 1; LLViewerTexture::LLViewerTexture(BOOL usemipmaps) { init(true); mUseMipMaps = usemipmaps ; mID.generate(); sImageCount++; } LLViewerTexture::LLViewerTexture(const LLUUID& id, BOOL usemipmaps) : mID(id) { init(true); mUseMipMaps = usemipmaps ; sImageCount++; } LLViewerTexture::LLViewerTexture(const U32 width, const U32 height, const U8 components, BOOL usemipmaps) { init(true); mFullWidth = width ; mFullHeight = height ; mUseMipMaps = usemipmaps ; mComponents = components ; mID.generate(); sImageCount++; } LLViewerTexture::LLViewerTexture(const LLImageRaw* raw, BOOL usemipmaps) { init(true); mUseMipMaps = usemipmaps ; mGLTexturep = new LLImageGL(raw, usemipmaps) ; // Create an empty image of the specified size and width mID.generate(); sImageCount++; } LLViewerTexture::~LLViewerTexture() { sImageCount--; } void LLViewerTexture::init(bool firstinit) { mBoostLevel = LLViewerTexture::BOOST_NONE; mFullWidth = 0; mFullHeight = 0; mUseMipMaps = FALSE ; mComponents = 0 ; mTextureState = NO_DELETE ; mDontDiscard = FALSE; mMaxVirtualSize = 0.f; mNeedsResetMaxVirtualSize = FALSE ; mHasParcelMedia = FALSE ; } //virtual S8 LLViewerTexture::getType() const { return LLViewerTexture::LOCAL_TEXTURE ; } void LLViewerTexture::cleanup() { mFaceList.clear() ; if(mGLTexturep) { mGLTexturep->cleanup(); } } // virtual void LLViewerTexture::dump() { if(mGLTexturep) { mGLTexturep->dump(); } llinfos << "LLViewerTexture" << " mID " << mID << llendl; } void LLViewerTexture::setBoostLevel(S32 level) { if(mBoostLevel != level) { mBoostLevel = level ; if(mBoostLevel != LLViewerTexture::BOOST_NONE) { setNoDelete() ; } } } bool LLViewerTexture::bindDefaultImage(S32 stage) const { if (stage < 0) return false; bool res = true; if (LLViewerFetchedTexture::sDefaultImagep.notNull() && (this != LLViewerFetchedTexture::sDefaultImagep.get())) { // use default if we've got it res = gGL.getTexUnit(stage)->bind(LLViewerFetchedTexture::sDefaultImagep); } if (!res && LLViewerTexture::sNullImagep.notNull() && (this != LLViewerTexture::sNullImagep)) { res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sNullImagep) ; } if (!res) { llwarns << "LLViewerTexture::bindDefaultImage failed." << llendl; } stop_glerror(); if(LLViewerTextureManager::sTesterp) { LLViewerTextureManager::sTesterp->updateGrayTextureBinding() ; } return res; } //virtual BOOL LLViewerTexture::isMissingAsset()const { return FALSE; } //virtual void LLViewerTexture::forceImmediateUpdate() { } void LLViewerTexture::addTextureStats(F32 virtual_size) const { if (virtual_size > mMaxVirtualSize) { mMaxVirtualSize = virtual_size; } } void LLViewerTexture::resetTextureStats(BOOL zero) { if (zero) { mMaxVirtualSize = 0.0f; } else { mMaxVirtualSize -= mMaxVirtualSize * .10f; // decay by 5%/update } } //virtual F32 LLViewerTexture::getMaxVirtualSize() { return mMaxVirtualSize ; } //virtual void LLViewerTexture::setKnownDrawSize(S32 width, S32 height) { //nothing here. } //virtual void LLViewerTexture::addFace(LLFace* facep) { mFaceList.push_back(facep) ; } //virtual void LLViewerTexture::removeFace(LLFace* facep) { mFaceList.remove(facep) ; } void LLViewerTexture::forceActive() { mTextureState = ACTIVE ; } void LLViewerTexture::setActive() { if(mTextureState != NO_DELETE) { mTextureState = ACTIVE ; } } //set the texture to stay in memory void LLViewerTexture::setNoDelete() { mTextureState = NO_DELETE ; } void LLViewerTexture::generateGLTexture() { if(mGLTexturep.isNull()) { mGLTexturep = new LLImageGL(mFullWidth, mFullHeight, mComponents, mUseMipMaps) ; } } LLImageGL* LLViewerTexture::getGLTexture() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep ; } BOOL LLViewerTexture::createGLTexture() { if(mGLTexturep.isNull()) { generateGLTexture() ; } return mGLTexturep->createGLTexture() ; } BOOL LLViewerTexture::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename) { llassert_always(mGLTexturep.notNull()) ; BOOL ret = mGLTexturep->createGLTexture(discard_level, imageraw, usename) ; if(ret) { mFullWidth = mGLTexturep->getCurrentWidth() ; mFullHeight = mGLTexturep->getCurrentHeight() ; mComponents = mGLTexturep->getComponents() ; } return ret ; } void LLViewerTexture::setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, BOOL swap_bytes) { llassert_always(mGLTexturep.notNull()) ; mGLTexturep->setExplicitFormat(internal_format, primary_format, type_format, swap_bytes) ; } void LLViewerTexture::setAddressMode(LLTexUnit::eTextureAddressMode mode) { llassert_always(mGLTexturep.notNull()) ; mGLTexturep->setAddressMode(mode) ; } void LLViewerTexture::setFilteringOption(LLTexUnit::eTextureFilterOptions option) { llassert_always(mGLTexturep.notNull()) ; mGLTexturep->setFilteringOption(option) ; } //virtual S32 LLViewerTexture::getWidth(S32 discard_level) const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getWidth(discard_level) ; } //virtual S32 LLViewerTexture::getHeight(S32 discard_level) const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getHeight(discard_level) ; } S32 LLViewerTexture::getMaxDiscardLevel() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getMaxDiscardLevel() ; } S32 LLViewerTexture::getDiscardLevel() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getDiscardLevel() ; } S8 LLViewerTexture::getComponents() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getComponents() ; } LLGLuint LLViewerTexture::getTexName() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getTexName() ; } BOOL LLViewerTexture::hasGLTexture() const { if(mGLTexturep.notNull()) { return mGLTexturep->getHasGLTexture() ; } return FALSE ; } BOOL LLViewerTexture::getBoundRecently() const { if(mGLTexturep.notNull()) { return mGLTexturep->getBoundRecently() ; } return FALSE ; } LLTexUnit::eTextureType LLViewerTexture::getTarget(void) const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getTarget() ; } BOOL LLViewerTexture::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height) { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->setSubImage(imageraw, x_pos, y_pos, width, height) ; } BOOL LLViewerTexture::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height) { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->setSubImage(datap, data_width, data_height, x_pos, y_pos, width, height) ; } void LLViewerTexture::setGLTextureCreated (bool initialized) { llassert_always(mGLTexturep.notNull()) ; mGLTexturep->setGLTextureCreated (initialized) ; } LLTexUnit::eTextureAddressMode LLViewerTexture::getAddressMode(void) const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getAddressMode() ; } S32 LLViewerTexture::getTextureMemory() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->mTextureMemory ; } LLGLenum LLViewerTexture::getPrimaryFormat() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getPrimaryFormat() ; } BOOL LLViewerTexture::getIsAlphaMask() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getIsAlphaMask() ; } BOOL LLViewerTexture::getMask(const LLVector2 &tc) { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getMask(tc) ; } F32 LLViewerTexture::getTimePassedSinceLastBound() { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getTimePassedSinceLastBound() ; } BOOL LLViewerTexture::getMissed() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getMissed() ; } BOOL LLViewerTexture::isValidForSculpt(S32 discard_level, S32 image_width, S32 image_height, S32 ncomponents) { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->isValidForSculpt(discard_level, image_width, image_height, ncomponents) ; } BOOL LLViewerTexture::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->readBackRaw(discard_level, imageraw, compressed_ok) ; } U32 LLViewerTexture::getTexelsInAtlas() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getTexelsInAtlas() ; } U32 LLViewerTexture::getTexelsInGLTexture() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getTexelsInGLTexture() ; } BOOL LLViewerTexture::isGLTextureCreated() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->isGLTextureCreated() ; } S32 LLViewerTexture::getDiscardLevelInAtlas() const { llassert_always(mGLTexturep.notNull()) ; return mGLTexturep->getDiscardLevelInAtlas() ; } void LLViewerTexture::destroyGLTexture() { if(mGLTexturep.notNull() && mGLTexturep->getHasGLTexture()) { mGLTexturep->destroyGLTexture() ; mTextureState = DELETED ; } } //virtual void LLViewerTexture::updateBindStatsForTester() { if(LLViewerTextureManager::sTesterp) { LLViewerTextureManager::sTesterp->updateTextureBindingStats(this) ; } } //---------------------------------------------------------------------------------------------- //end of LLViewerTexture //---------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------- //start of LLViewerFetchedTexture //---------------------------------------------------------------------------------------------- //static F32 LLViewerFetchedTexture::maxDecodePriority() { return 2000000.f; } LLViewerFetchedTexture::LLViewerFetchedTexture(const LLUUID& id, BOOL usemipmaps) : LLViewerTexture(id, usemipmaps) { init(TRUE) ; generateGLTexture() ; } LLViewerFetchedTexture::LLViewerFetchedTexture(const LLImageRaw* raw, BOOL usemipmaps) : LLViewerTexture(raw, usemipmaps) { init(TRUE) ; } LLViewerFetchedTexture::LLViewerFetchedTexture(const std::string& full_path, const LLUUID& id, BOOL usemipmaps) : LLViewerTexture(id, usemipmaps), mLocalFileName(full_path) { init(TRUE) ; generateGLTexture() ; } void LLViewerFetchedTexture::init(bool firstinit) { mOrigWidth = 0; mOrigHeight = 0; mNeedsAux = FALSE; mRequestedDiscardLevel = -1; mRequestedDownloadPriority = 0.f; mFullyLoaded = FALSE; mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; mDecodingAux = FALSE; mKnownDrawWidth = 0; mKnownDrawHeight = 0; mKnownDrawSizeChanged = FALSE ; if (firstinit) { mDecodePriority = 0.f; mInImageList = 0; } // Only set mIsMissingAsset true when we know for certain that the database // does not contain this image. mIsMissingAsset = FALSE; mNeedsCreateTexture = FALSE; mIsRawImageValid = FALSE; mRawDiscardLevel = INVALID_DISCARD_LEVEL; mMinDiscardLevel = 0; mHasFetcher = FALSE; mIsFetching = FALSE; mFetchState = 0; mFetchPriority = 0; mDownloadProgress = 0.f; mFetchDeltaTime = 999999.f; mDecodeFrame = 0; mVisibleFrame = 0; mForSculpt = FALSE ; mIsFetched = FALSE ; } LLViewerFetchedTexture::~LLViewerFetchedTexture() { //*NOTE getTextureFetch can return NULL when Viewer is shutting down. // This is due to LLWearableList is singleton and is destroyed after // LLAppViewer::cleanup() was called. (see ticket EXT-177) if (mHasFetcher && LLAppViewer::getTextureFetch()) { LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); } cleanup(); } //virtual S8 LLViewerFetchedTexture::getType() const { return LLViewerTexture::FETCHED_TEXTURE ; } void LLViewerFetchedTexture::cleanup() { for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { LLLoadedCallbackEntry *entryp = *iter++; // We never finished loading the image. Indicate failure. // Note: this allows mLoadedCallbackUserData to be cleaned up. entryp->mCallback( FALSE, this, NULL, NULL, 0, TRUE, entryp->mUserData ); delete entryp; } mLoadedCallbackList.clear(); mNeedsAux = FALSE; // Clean up image data destroyRawImage(); } void LLViewerFetchedTexture::setForSculpt() { mForSculpt = TRUE ; } BOOL LLViewerFetchedTexture::isDeleted() { return mTextureState == DELETED ; } BOOL LLViewerFetchedTexture::isInactive() { return mTextureState == INACTIVE ; } BOOL LLViewerFetchedTexture::isDeletionCandidate() { return mTextureState == DELETION_CANDIDATE ; } void LLViewerFetchedTexture::setDeletionCandidate() { if(mGLTexturep.notNull() && mGLTexturep->getTexName() && (mTextureState == INACTIVE)) { mTextureState = DELETION_CANDIDATE ; } } //set the texture inactive void LLViewerFetchedTexture::setInactive() { if(mTextureState == ACTIVE && mGLTexturep.notNull() && mGLTexturep->getTexName() && !mGLTexturep->getBoundRecently()) { mTextureState = INACTIVE ; } } // virtual void LLViewerFetchedTexture::dump() { LLViewerTexture::dump(); llinfos << "LLViewerFetchedTexture" << " mIsMissingAsset " << (S32)mIsMissingAsset << " mFullWidth " << mFullWidth << " mFullHeight " << mFullHeight << " mOrigWidth" << mOrigWidth << " mOrigHeight" << mOrigHeight << llendl; } /////////////////////////////////////////////////////////////////////////////// // ONLY called from LLViewerFetchedTextureList void LLViewerFetchedTexture::destroyTexture() { if(LLImageGL::sGlobalTextureMemoryInBytes < sMaxDesiredTextureMemInBytes)//not ready to release unused memory. { return ; } if (mNeedsCreateTexture)//return if in the process of generating a new texture. { return ; } destroyGLTexture() ; mFullyLoaded = FALSE ; } // ONLY called from LLViewerTextureList BOOL LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/) { if (!mNeedsCreateTexture) { destroyRawImage(); return FALSE; } mNeedsCreateTexture = FALSE; if (mRawImage.isNull()) { llerrs << "LLViewerTexture trying to create texture with no Raw Image" << llendl; } // llinfos << llformat("IMAGE Creating (%d) [%d x %d] Bytes: %d ", // mRawDiscardLevel, // mRawImage->getWidth(), mRawImage->getHeight(),mRawImage->getDataSize()) // << mID.getString() << llendl; BOOL res = TRUE; if (!gNoRender) { // store original size only for locally-sourced images if (!mLocalFileName.empty()) { mOrigWidth = mRawImage->getWidth(); mOrigHeight = mRawImage->getHeight(); // leave black border, do not scale image content mRawImage->expandToPowerOfTwo(MAX_IMAGE_SIZE, FALSE); mFullWidth = mRawImage->getWidth(); mFullHeight = mRawImage->getHeight(); } else { mOrigWidth = mFullWidth; mOrigHeight = mFullHeight; } bool size_okay = true; U32 raw_width = mRawImage->getWidth() << mRawDiscardLevel; U32 raw_height = mRawImage->getHeight() << mRawDiscardLevel; if( raw_width > MAX_IMAGE_SIZE || raw_height > MAX_IMAGE_SIZE ) { llinfos << "Width or height is greater than " << MAX_IMAGE_SIZE << ": (" << raw_width << "," << raw_height << ")" << llendl; size_okay = false; } if (!LLImageGL::checkSize(mRawImage->getWidth(), mRawImage->getHeight())) { // A non power-of-two image was uploaded (through a non standard client) llinfos << "Non power of two width or height: (" << mRawImage->getWidth() << "," << mRawImage->getHeight() << ")" << llendl; size_okay = false; } if( !size_okay ) { // An inappropriately-sized image was uploaded (through a non standard client) // We treat these images as missing assets which causes them to // be renderd as 'missing image' and to stop requesting data setIsMissingAsset(); destroyRawImage(); return FALSE; } if(!(res = insertToAtlas())) { res = mGLTexturep->createGLTexture(mRawDiscardLevel, mRawImage, usename); resetFaceAtlas() ; } setActive() ; } // // Iterate through the list of image loading callbacks to see // what sort of data they need. // // *TODO: Fix image callback code BOOL imageraw_callbacks = FALSE; for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { LLLoadedCallbackEntry *entryp = *iter++; if (entryp->mNeedsImageRaw) { imageraw_callbacks = TRUE; break; } } if (!imageraw_callbacks) { mNeedsAux = FALSE; destroyRawImage(); } return res; } // Call with 0,0 to turn this feature off. //virtual void LLViewerFetchedTexture::setKnownDrawSize(S32 width, S32 height) { if(mKnownDrawWidth != width || mKnownDrawHeight != height) { mKnownDrawWidth = width; mKnownDrawHeight = height; mKnownDrawSizeChanged = TRUE ; mFullyLoaded = FALSE ; } addTextureStats((F32)(width * height)); } //virtual void LLViewerFetchedTexture::processTextureStats() { if(mFullyLoaded)//already loaded { return ; } if(!mFullWidth || !mFullHeight) { mDesiredDiscardLevel = getMaxDiscardLevel() ; } else { if(!mKnownDrawWidth || !mKnownDrawHeight || mFullWidth <= mKnownDrawWidth || mFullHeight <= mKnownDrawHeight) { if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) { mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 } else { mDesiredDiscardLevel = 0; } } else if(mKnownDrawSizeChanged)//known draw size is set { mDesiredDiscardLevel = (S8)llmin(log((F32)mFullWidth / mKnownDrawWidth) / log_2, log((F32)mFullHeight / mKnownDrawHeight) / log_2) ; mDesiredDiscardLevel = llclamp(mDesiredDiscardLevel, (S8)0, (S8)getMaxDiscardLevel()) ; } mKnownDrawSizeChanged = FALSE ; if(getDiscardLevel() >= 0 && (getDiscardLevel() <= mDesiredDiscardLevel)) { mFullyLoaded = TRUE ; } } } //texture does not have any data, so we don't know the size of the image, treat it like 32 * 32. F32 LLViewerFetchedTexture::calcDecodePriorityForUnknownTexture(F32 pixel_priority) { F32 desired = (F32)(log(32.0/pixel_priority) / log_2); S32 ddiscard = MAX_DISCARD_LEVEL - (S32)desired + 1; ddiscard = llclamp(ddiscard, 1, 9); return ddiscard*100000.f; } F32 LLViewerFetchedTexture::calcDecodePriority() { #ifndef LL_RELEASE_FOR_DOWNLOAD if (mID == LLAppViewer::getTextureFetch()->mDebugID) { LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints } #endif if(mFullyLoaded)//already loaded for static texture { return -4.0f ; //alreay fetched } if (mNeedsCreateTexture) { return mDecodePriority; // no change while waiting to create } F32 priority; S32 cur_discard = getDiscardLevel(); bool have_all_data = (cur_discard >= 0 && (cur_discard <= mDesiredDiscardLevel)); F32 pixel_priority = fsqrtf(mMaxVirtualSize); const S32 MIN_NOT_VISIBLE_FRAMES = 30; // NOTE: this function is not called every frame mDecodeFrame++; if (pixel_priority > 0.f) { mVisibleFrame = mDecodeFrame; } if (mIsMissingAsset) { priority = 0.0f; } else if (mDesiredDiscardLevel > getMaxDiscardLevel()) { // Don't decode anything we don't need priority = -1.0f; } else if ((mBoostLevel == LLViewerTexture::BOOST_UI || mBoostLevel == LLViewerTexture::BOOST_ICON) && !have_all_data) { priority = 1.f; } else if (pixel_priority <= 0.f && !have_all_data) { // Not on screen but we might want some data if (mBoostLevel > BOOST_HIGH) { // Always want high boosted images priority = 1.f; } else if (mVisibleFrame == 0 || (mDecodeFrame - mVisibleFrame > MIN_NOT_VISIBLE_FRAMES)) { // Don't decode anything that isn't visible unless it's important priority = -2.0f; } else { // Leave the priority as-is return mDecodePriority; } } else if (cur_discard < 0) { priority = calcDecodePriorityForUnknownTexture(pixel_priority) ; } else if ((mMinDiscardLevel > 0) && (cur_discard <= mMinDiscardLevel)) { // larger mips are corrupted priority = -3.0f; } else if (cur_discard <= mDesiredDiscardLevel) { priority = -4.0f; } else { // priority range = 100000-400000 S32 ddiscard = cur_discard - mDesiredDiscardLevel; if (getDontDiscard()) { ddiscard+=2; } else if (mGLTexturep.notNull() && !mGLTexturep->getBoundRecently() && mBoostLevel == 0) { ddiscard-=2; } ddiscard = llclamp(ddiscard, 0, 4); priority = ddiscard*100000.f; } if (priority > 0.0f) { pixel_priority = llclamp(pixel_priority, 0.0f, priority-1.f); // priority range = 100000-900000 if ( mBoostLevel > BOOST_HIGH) { priority = 1000000.f + pixel_priority + 1000.f * mBoostLevel; } else { priority += 0.f + pixel_priority + 1000.f * mBoostLevel; } } return priority; } //============================================================================ void LLViewerFetchedTexture::setDecodePriority(F32 priority) { llassert(!mInImageList); mDecodePriority = priority; } bool LLViewerFetchedTexture::updateFetch() { mFetchState = 0; mFetchPriority = 0; mFetchDeltaTime = 999999.f; mRequestDeltaTime = 999999.f; #ifndef LL_RELEASE_FOR_DOWNLOAD if (mID == LLAppViewer::getTextureFetch()->mDebugID) { LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints } #endif if (mNeedsCreateTexture) { // We may be fetching still (e.g. waiting on write) // but don't check until we've processed the raw data we have return false; } if (mIsMissingAsset) { llassert_always(!mHasFetcher); return false; // skip } if (!mLoadedCallbackList.empty() && mRawImage.notNull()) { return false; // process any raw image data in callbacks before replacing } S32 current_discard = getDiscardLevel() ; S32 desired_discard = getDesiredDiscardLevel(); F32 decode_priority = getDecodePriority(); decode_priority = llmax(decode_priority, 0.0f); if (mIsFetching) { // Sets mRawDiscardLevel, mRawImage, mAuxRawImage S32 fetch_discard = current_discard; if (mRawImage.notNull()) sRawCount--; if (mAuxRawImage.notNull()) sAuxCount--; bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mRawImage, mAuxRawImage); if (mRawImage.notNull()) sRawCount++; if (mAuxRawImage.notNull()) sAuxCount++; if (finished) { mIsFetching = FALSE; } else { mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, mFetchPriority, mFetchDeltaTime, mRequestDeltaTime); } // We may have data ready regardless of whether or not we are finished (e.g. waiting on write) if (mRawImage.notNull()) { if(LLViewerTextureManager::sTesterp) { mIsFetched = TRUE ; LLViewerTextureManager::sTesterp->updateTextureLoadingStats(this, mRawImage, LLAppViewer::getTextureFetch()->isFromLocalCache(mID)) ; } mRawDiscardLevel = fetch_discard; if ((mRawImage->getDataSize() > 0 && mRawDiscardLevel >= 0) && (current_discard < 0 || mRawDiscardLevel < current_discard)) { if (getComponents() != mRawImage->getComponents()) { // We've changed the number of components, so we need to move any // objects using this pool to a different pool. mComponents = mRawImage->getComponents(); mGLTexturep->setComponents(mComponents) ; for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter) { (*iter)->dirtyTexture() ; } } mIsRawImageValid = TRUE; gTextureList.mCreateTextureList.insert(this); mNeedsCreateTexture = TRUE; mFullWidth = mRawImage->getWidth() << mRawDiscardLevel; mFullHeight = mRawImage->getHeight() << mRawDiscardLevel; } else { // Data is ready but we don't need it // (received it already while fetcher was writing to disk) destroyRawImage(); return false; // done } } if (!mIsFetching) { if ((decode_priority > 0) && (mRawDiscardLevel < 0 || mRawDiscardLevel == INVALID_DISCARD_LEVEL)) { // We finished but received no data if (current_discard < 0) { setIsMissingAsset(); desired_discard = -1; } else { llwarns << mID << ": Setting min discard to " << current_discard << llendl; mMinDiscardLevel = current_discard; desired_discard = current_discard; } destroyRawImage(); } else if (mRawImage.isNull()) { // We have data, but our fetch failed to return raw data // *TODO: FIgure out why this is happening and fix it destroyRawImage(); } } else { LLAppViewer::getTextureFetch()->updateRequestPriority(mID, decode_priority); } } bool make_request = true; if (decode_priority <= 0) { make_request = false; } else if (mNeedsCreateTexture || mIsMissingAsset) { make_request = false; } else if (current_discard >= 0 && current_discard <= mMinDiscardLevel) { make_request = false; } else { if (mIsFetching) { if (mRequestedDiscardLevel <= desired_discard) { make_request = false; } } else { if (current_discard >= 0 && current_discard <= desired_discard) { make_request = false; } } } if (make_request) { S32 w=0, h=0, c=0; if (current_discard >= 0) { w = mGLTexturep->getWidth(0); h = mGLTexturep->getHeight(0); c = mComponents; } if (!mDontDiscard) { if (mBoostLevel == 0) { desired_discard = llmax(desired_discard, current_discard-1); } else { desired_discard = llmax(desired_discard, current_discard-2); } } // bypass texturefetch directly by pulling from LLTextureCache bool fetch_request_created = false; if (mLocalFileName.empty()) { fetch_request_created = LLAppViewer::getTextureFetch()->createRequest(getID(), getTargetHost(), decode_priority, w, h, c, desired_discard, needsAux()); } else { fetch_request_created = LLAppViewer::getTextureFetch()->createRequest(mLocalFileName, getID(),getTargetHost(), decode_priority, w, h, c, desired_discard, needsAux()); } if (fetch_request_created) { mHasFetcher = TRUE; mIsFetching = TRUE; mRequestedDiscardLevel = desired_discard; mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, mFetchPriority, mFetchDeltaTime, mRequestDeltaTime); } // if createRequest() failed, we're finishing up a request for this UUID, // wait for it to complete } else if (mHasFetcher && !mIsFetching) { // Only delete requests that haven't receeived any network data for a while const F32 FETCH_IDLE_TIME = 5.f; if (mLastPacketTimer.getElapsedTimeF32() > FETCH_IDLE_TIME) { // llinfos << "Deleting request: " << getID() << " Discard: " << current_discard << " <= min:" << mMinDiscardLevel << " or priority == 0: " << decode_priority << llendl; LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); mHasFetcher = FALSE; } } llassert_always(mRawImage.notNull() || (!mNeedsCreateTexture && !mIsRawImageValid)); return mIsFetching ? true : false; } void LLViewerFetchedTexture::setIsMissingAsset() { llwarns << mLocalFileName << " " << mID << ": Marking image as missing" << llendl; if (mHasFetcher) { LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); mHasFetcher = FALSE; mIsFetching = FALSE; mFetchState = 0; mFetchPriority = 0; } mIsMissingAsset = TRUE; } void LLViewerFetchedTexture::setLoadedCallback( loaded_callback_func loaded_callback, S32 discard_level, BOOL keep_imageraw, BOOL needs_aux, void* userdata) { // // Don't do ANYTHING here, just add it to the global callback list // if (mLoadedCallbackList.empty()) { // Put in list to call this->doLoadedCallbacks() periodically gTextureList.mCallbackList.insert(this); } LLLoadedCallbackEntry* entryp = new LLLoadedCallbackEntry(loaded_callback, discard_level, keep_imageraw, userdata); mLoadedCallbackList.push_back(entryp); mNeedsAux |= needs_aux; if (mNeedsAux && mAuxRawImage.isNull() && getDiscardLevel() >= 0) { // We need aux data, but we've already loaded the image, and it didn't have any llwarns << "No aux data available for callback for image:" << getID() << llendl; } } bool LLViewerFetchedTexture::doLoadedCallbacks() { if (mNeedsCreateTexture) { return false; } bool res = false; if (isMissingAsset()) { for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { LLLoadedCallbackEntry *entryp = *iter++; // We never finished loading the image. Indicate failure. // Note: this allows mLoadedCallbackUserData to be cleaned up. entryp->mCallback(FALSE, this, NULL, NULL, 0, TRUE, entryp->mUserData); delete entryp; } mLoadedCallbackList.clear(); // Remove ourself from the global list of textures with callbacks gTextureList.mCallbackList.erase(this); } S32 gl_discard = getDiscardLevel(); // If we don't have a legit GL image, set it to be lower than the worst discard level if (gl_discard == -1) { gl_discard = MAX_DISCARD_LEVEL + 1; } // // Determine the quality levels of textures that we can provide to callbacks // and whether we need to do decompression/readback to get it // S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard S32 best_raw_discard = gl_discard; // Current GL quality level S32 current_aux_discard = MAX_DISCARD_LEVEL + 1; S32 best_aux_discard = MAX_DISCARD_LEVEL + 1; if (mIsRawImageValid) { // If we have an existing raw image, we have a baseline for the raw and auxiliary quality levels. best_raw_discard = llmin(best_raw_discard, mRawDiscardLevel); best_aux_discard = llmin(best_aux_discard, mRawDiscardLevel); // We always decode the aux when we decode the base raw current_aux_discard = llmin(current_aux_discard, best_aux_discard); } else { // We have no data at all, we need to get it // Do this by forcing the best aux discard to be 0. best_aux_discard = 0; } // // See if any of the callbacks would actually run using the data that we can provide, // and also determine if we need to perform any readbacks or decodes. // bool run_gl_callbacks = false; bool run_raw_callbacks = false; bool need_readback = false; for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { LLLoadedCallbackEntry *entryp = *iter++; if (entryp->mNeedsImageRaw) { if (mNeedsAux) { // // Need raw and auxiliary channels // if (entryp->mLastUsedDiscard > current_aux_discard) { // We have useful data, run the callbacks run_raw_callbacks = true; } } else { if (entryp->mLastUsedDiscard > current_raw_discard) { // We have useful data, just run the callbacks run_raw_callbacks = true; } else if (entryp->mLastUsedDiscard > best_raw_discard) { // We can readback data, and then run the callbacks need_readback = true; run_raw_callbacks = true; } } } else { // Needs just GL if (entryp->mLastUsedDiscard > gl_discard) { // We have enough data, run this callback requiring GL data run_gl_callbacks = true; } } } // // Do a readback if required, OR start off a texture decode // if (need_readback && (getMaxDiscardLevel() > gl_discard)) { // Do a readback to get the GL data into the raw image // We have GL data. destroyRawImage(); readBackRawImage(gl_discard); llassert_always(mRawImage.notNull()); llassert_always(!mNeedsAux || mAuxRawImage.notNull()); } // // Run raw/auxiliary data callbacks // if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= getMaxDiscardLevel())) { // Do callbacks which require raw image data. //llinfos << "doLoadedCallbacks raw for " << getID() << llendl; // Call each party interested in the raw data. for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { callback_list_t::iterator curiter = iter++; LLLoadedCallbackEntry *entryp = *curiter; if (entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > mRawDiscardLevel)) { // If we've loaded all the data there is to load or we've loaded enough // to satisfy the interested party, then this is the last time that // we're going to call them. llassert_always(mRawImage.notNull()); if(mNeedsAux && mAuxRawImage.isNull()) { llwarns << "Raw Image with no Aux Data for callback" << llendl; } BOOL final = mRawDiscardLevel <= entryp->mDesiredDiscard ? TRUE : FALSE; //llinfos << "Running callback for " << getID() << llendl; //llinfos << mRawImage->getWidth() << "x" << mRawImage->getHeight() << llendl; if (final) { //llinfos << "Final!" << llendl; } entryp->mLastUsedDiscard = mRawDiscardLevel; entryp->mCallback(TRUE, this, mRawImage, mAuxRawImage, mRawDiscardLevel, final, entryp->mUserData); if (final) { iter = mLoadedCallbackList.erase(curiter); delete entryp; } res = true; } } } // // Run GL callbacks // if (run_gl_callbacks && (gl_discard <= getMaxDiscardLevel())) { //llinfos << "doLoadedCallbacks GL for " << getID() << llendl; // Call the callbacks interested in GL data. for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); iter != mLoadedCallbackList.end(); ) { callback_list_t::iterator curiter = iter++; LLLoadedCallbackEntry *entryp = *curiter; if (!entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > gl_discard)) { BOOL final = gl_discard <= entryp->mDesiredDiscard ? TRUE : FALSE; entryp->mLastUsedDiscard = gl_discard; entryp->mCallback(TRUE, this, NULL, NULL, gl_discard, final, entryp->mUserData); if (final) { iter = mLoadedCallbackList.erase(curiter); delete entryp; } res = true; } } } // // If we have no callbacks, take us off of the image callback list. // if (mLoadedCallbackList.empty()) { gTextureList.mCallbackList.erase(this); } // Done with any raw image data at this point (will be re-created if we still have callbacks) destroyRawImage(); return res; } //virtual void LLViewerFetchedTexture::forceImmediateUpdate() { //only immediately update a deleted texture which is now being re-used. if(!isDeleted()) { return ; } //if already called forceImmediateUpdate() if(mInImageList && mDecodePriority == LLViewerFetchedTexture::maxDecodePriority()) { return ; } gTextureList.forceImmediateUpdate(this) ; return ; } // Was in LLImageGL LLImageRaw* LLViewerFetchedTexture::readBackRawImage(S8 discard_level) { llassert_always(mGLTexturep.notNull()) ; llassert_always(discard_level >= 0); llassert_always(mComponents > 0); if (mRawImage.notNull()) { llerrs << "called with existing mRawImage" << llendl; mRawImage = NULL; } mRawImage = new LLImageRaw(mGLTexturep->getWidth(discard_level), mGLTexturep->getHeight(discard_level), mComponents); sRawCount++; mRawDiscardLevel = discard_level; mGLTexturep->readBackRaw(mRawDiscardLevel, mRawImage, false); mIsRawImageValid = TRUE; return mRawImage; } void LLViewerFetchedTexture::destroyRawImage() { if (mRawImage.notNull()) sRawCount--; if (mAuxRawImage.notNull()) sAuxCount--; mRawImage = NULL; mAuxRawImage = NULL; mIsRawImageValid = FALSE; mRawDiscardLevel = INVALID_DISCARD_LEVEL; } //---------------------------------------------------------------------------------------------- //atlasing //---------------------------------------------------------------------------------------------- void LLViewerFetchedTexture::resetFaceAtlas() { //Nothing should be done here. } //invalidate all atlas slots for this image. void LLViewerFetchedTexture::invalidateAtlas(BOOL rebuild_geom) { for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter) { if(*iter) { LLFace* facep = (LLFace*)*iter ; facep->removeAtlas() ; if(rebuild_geom && facep->getDrawable() && facep->getDrawable()->getSpatialGroup()) { facep->getDrawable()->getSpatialGroup()->setState(LLSpatialGroup::GEOM_DIRTY); } } } } BOOL LLViewerFetchedTexture::insertToAtlas() { if(!LLViewerTexture::sUseTextureAtlas) { return FALSE ; } if(mFaceList.size() < 1) { return FALSE ; } if(mGLTexturep->getDiscardLevelInAtlas() > 0 && mRawDiscardLevel >= mGLTexturep->getDiscardLevelInAtlas()) { return FALSE ; } if(!LLTextureAtlasManager::getInstance()->canAddToAtlas(mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents(), mGLTexturep->getTexTarget())) { return FALSE ; } BOOL ret = TRUE ;//if ret is set to false, will generate a gl texture for this image. S32 raw_w = mRawImage->getWidth() ; S32 raw_h = mRawImage->getHeight() ; F32 xscale = 1.0f, yscale = 1.0f ; LLPointer slot_infop; LLTextureAtlasSlot* cur_slotp ;//no need to be smart pointer. LLSpatialGroup* groupp ; LLFace* facep; //if the atlas slot pointers for some faces are null, process them later. ll_face_list_t waiting_list ; for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter) { if(*iter) { facep = (LLFace*)*iter ; //face can not use atlas. if(!facep->canUseAtlas()) { if(facep->getAtlasInfo()) { facep->removeAtlas() ; } ret = FALSE ; continue ; } //the atlas slot is updated slot_infop = facep->getAtlasInfo() ; groupp = facep->getDrawable()->getSpatialGroup() ; if(slot_infop) { if(slot_infop->getSpatialGroup() != groupp) { if((cur_slotp = groupp->getCurUpdatingSlot(this))) //switch slot { facep->setAtlasInfo(cur_slotp) ; facep->setAtlasInUse(TRUE) ; continue ; } else //do not forget to update slot_infop->getSpatialGroup(). { LLSpatialGroup* gp = slot_infop->getSpatialGroup() ; gp->setCurUpdatingTime(gFrameCount) ; gp->setCurUpdatingTexture(this) ; gp->setCurUpdatingSlot(slot_infop) ; } } else //same group { if(gFrameCount && slot_infop->getUpdatedTime() == gFrameCount)//slot is just updated { facep->setAtlasInUse(TRUE) ; continue ; } } } else { //if the slot is null, wait to process them later. waiting_list.push_back(facep) ; continue ; } //---------- //insert to atlas if(!slot_infop->getAtlas()->insertSubTexture(mGLTexturep, mRawDiscardLevel, mRawImage, slot_infop->getSlotCol(), slot_infop->getSlotRow())) { //the texture does not qualify to add to atlas, do not bother to try for other faces. //invalidateAtlas(); return FALSE ; } //update texture scale slot_infop->getAtlas()->getTexCoordScale(raw_w, raw_h, xscale, yscale) ; slot_infop->setTexCoordScale(xscale, yscale) ; slot_infop->setValid() ; slot_infop->setUpdatedTime(gFrameCount) ; //update spatial group atlas info groupp->setCurUpdatingTime(gFrameCount) ; groupp->setCurUpdatingTexture(this) ; groupp->setCurUpdatingSlot(slot_infop) ; //make the face to switch to the atlas. facep->setAtlasInUse(TRUE) ; } } //process the waiting_list for(ll_face_list_t::iterator iter = waiting_list.begin(); iter != waiting_list.end(); ++iter) { facep = (LLFace*)*iter ; groupp = facep->getDrawable()->getSpatialGroup() ; //check if this texture already inserted to atlas for this group if((cur_slotp = groupp->getCurUpdatingSlot(this))) { facep->setAtlasInfo(cur_slotp) ; facep->setAtlasInUse(TRUE) ; continue ; } //need to reserve a slot from atlas slot_infop = LLTextureAtlasManager::getInstance()->reserveAtlasSlot(llmax(mFullWidth, mFullHeight), getComponents(), groupp, this) ; facep->setAtlasInfo(slot_infop) ; groupp->setCurUpdatingTime(gFrameCount) ; groupp->setCurUpdatingTexture(this) ; groupp->setCurUpdatingSlot(slot_infop) ; //slot allocation failed. if(!slot_infop || !slot_infop->getAtlas()) { ret = FALSE ; facep->setAtlasInUse(FALSE) ; continue ; } //insert to atlas if(!slot_infop->getAtlas()->insertSubTexture(mGLTexturep, mRawDiscardLevel, mRawImage, slot_infop->getSlotCol(), slot_infop->getSlotRow())) { //the texture does not qualify to add to atlas, do not bother to try for other faces. ret = FALSE ; //invalidateAtlas(); break ; } //update texture scale slot_infop->getAtlas()->getTexCoordScale(raw_w, raw_h, xscale, yscale) ; slot_infop->setTexCoordScale(xscale, yscale) ; slot_infop->setValid() ; slot_infop->setUpdatedTime(gFrameCount) ; //make the face to switch to the atlas. facep->setAtlasInUse(TRUE) ; } return ret ; } //---------------------------------------------------------------------------------------------- //end of LLViewerFetchedTexture //---------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------- //start of LLViewerLODTexture //---------------------------------------------------------------------------------------------- LLViewerLODTexture::LLViewerLODTexture(const LLUUID& id, BOOL usemipmaps) : LLViewerFetchedTexture(id, usemipmaps) { init(TRUE) ; } LLViewerLODTexture::LLViewerLODTexture(const std::string& full_path, const LLUUID& id, BOOL usemipmaps) : LLViewerFetchedTexture(full_path, id, usemipmaps) { init(TRUE) ; } void LLViewerLODTexture::init(bool firstinit) { mTexelsPerImage = 64.f*64.f; mDiscardVirtualSize = 0.f; mCalculatedDiscardLevel = -1.f; } //virtual S8 LLViewerLODTexture::getType() const { return LLViewerTexture::LOD_TEXTURE ; } // This is gauranteed to get called periodically for every texture //virtual void LLViewerLODTexture::processTextureStats() { // Generate the request priority and render priority if (mDontDiscard || !mUseMipMaps) { mDesiredDiscardLevel = 0; if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 } else if (mBoostLevel < LLViewerTexture::BOOST_HIGH && mMaxVirtualSize <= 10.f) { // If the image has not been significantly visible in a while, we don't want it mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1)); } else if (!mFullWidth || !mFullHeight) { mDesiredDiscardLevel = getMaxDiscardLevel() ; } else { //static const F64 log_2 = log(2.0); static const F64 log_4 = log(4.0); S32 fullwidth = llmin(mFullWidth,(S32)MAX_IMAGE_SIZE_DEFAULT); S32 fullheight = llmin(mFullHeight,(S32)MAX_IMAGE_SIZE_DEFAULT); mTexelsPerImage = (F32)fullwidth * fullheight; F32 discard_level = 0.f; // If we know the output width and height, we can force the discard // level to the correct value, and thus not decode more texture // data than we need to. /*if (mBoostLevel == LLViewerTexture::BOOST_UI || mBoostLevel == LLViewerTexture::BOOST_PREVIEW || mBoostLevel == LLViewerTexture::BOOST_AVATAR_SELF) // what about AVATAR_BAKED_SELF? { discard_level = 0; // full res } else*/ if (mKnownDrawWidth && mKnownDrawHeight) { S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; // Use log_4 because we're in square-pixel space, so an image // with twice the width and twice the height will have mTexelsPerImage // 4 * draw_size discard_level = (F32)(log(mTexelsPerImage/draw_texels) / log_4); } else { if ((mCalculatedDiscardLevel >= 0.f) && (llabs(mMaxVirtualSize - mDiscardVirtualSize) < mMaxVirtualSize*.20f)) { // < 20% change in virtual size = no change in desired discard discard_level = mCalculatedDiscardLevel; } else { // Calculate the required scale factor of the image using pixels per texel discard_level = (F32)(log(mTexelsPerImage/mMaxVirtualSize) / log_4); mDiscardVirtualSize = mMaxVirtualSize; mCalculatedDiscardLevel = discard_level; } } if (mBoostLevel < LLViewerTexture::BOOST_HIGH) { static const F32 discard_bias = -.5f; // Must be < 1 or highest discard will never load! discard_level += discard_bias; discard_level += sDesiredDiscardBias; discard_level *= sDesiredDiscardScale; // scale } discard_level = floorf(discard_level); // discard_level -= (gTextureList.mVideoMemorySetting>>1); // more video ram = higher detail F32 min_discard = 0.f; if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) min_discard = 1.f; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL); // Can't go higher than the max discard level mDesiredDiscardLevel = llmin(getMaxDiscardLevel() + 1, (S32)discard_level); // Clamp to min desired discard mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel); // // At this point we've calculated the quality level that we want, // if possible. Now we check to see if we have it, and take the // proper action if we don't. // BOOL increase_discard = FALSE; S32 current_discard = getDiscardLevel(); if ((sDesiredDiscardBias > 0.0f) && (current_discard >= 0 && mDesiredDiscardLevel >= current_discard)) { if ( BYTES_TO_MEGA_BYTES(sBoundTextureMemoryInBytes) > sMaxBoundTextureMemInMegaBytes * texmem_middle_bound_scale) { // Limit the amount of GL memory bound each frame if (mDesiredDiscardLevel > current_discard) { increase_discard = TRUE; } } if ( BYTES_TO_MEGA_BYTES(sTotalTextureMemoryInBytes) > sMaxTotalTextureMemInMegaBytes*texmem_middle_bound_scale) { // Only allow GL to have 2x the video card memory if (!mGLTexturep->getBoundRecently()) { increase_discard = TRUE; } } if (increase_discard) { // llinfos << "DISCARDED: " << mID << " Discard: " << current_discard << llendl; sBoundTextureMemoryInBytes -= mGLTexturep->mTextureMemory; sTotalTextureMemoryInBytes -= mGLTexturep->mTextureMemory; // Increase the discard level (reduce the texture res) S32 new_discard = current_discard+1; mGLTexturep->setDiscardLevel(new_discard); sBoundTextureMemoryInBytes += mGLTexturep->mTextureMemory; sTotalTextureMemoryInBytes += mGLTexturep->mTextureMemory; if(LLViewerTextureManager::sTesterp) { LLViewerTextureManager::sTesterp->setStablizingTime() ; } } } } } //---------------------------------------------------------------------------------------------- //end of LLViewerLODTexture //---------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------- //start of LLViewerMediaTexture //---------------------------------------------------------------------------------------------- //static void LLViewerMediaTexture::updateClass() { static const F32 MAX_INACTIVE_TIME = 30.f ; for(media_map_t::iterator iter = sMediaMap.begin() ; iter != sMediaMap.end(); ) { LLViewerMediaTexture* mediap = iter->second; if(mediap->getNumRefs() == 1) //one reference by sMediaMap { // //Note: delay some time to delete the media textures to stop endlessly creating and immediately removing media texture. // if(mediap->getLastReferencedTimer()->getElapsedTimeF32() > MAX_INACTIVE_TIME) { media_map_t::iterator cur = iter++ ; sMediaMap.erase(cur) ; continue ; } } ++iter ; } } //static void LLViewerMediaTexture::removeMediaImplFromTexture(const LLUUID& media_id) { LLViewerMediaTexture* media_tex = findMediaTexture(media_id) ; if(media_tex) { media_tex->invalidateMediaImpl() ; } } //static void LLViewerMediaTexture::cleanup() { sMediaMap.clear() ; } //static LLViewerMediaTexture* LLViewerMediaTexture::findMediaTexture(const LLUUID& media_id) { media_map_t::iterator iter = sMediaMap.find(media_id); if(iter == sMediaMap.end()) { return NULL; } LLViewerMediaTexture* media_tex = iter->second ; media_tex->setMediaImpl() ; media_tex->getLastReferencedTimer()->reset() ; return media_tex; } LLViewerMediaTexture::LLViewerMediaTexture(const LLUUID& id, BOOL usemipmaps, LLImageGL* gl_image) : LLViewerTexture(id, usemipmaps), mMediaImplp(NULL), mUpdateVirtualSizeTime(0) { sMediaMap.insert(std::make_pair(id, this)); mGLTexturep = gl_image ; if(mGLTexturep.isNull()) { generateGLTexture() ; } mGLTexturep->setNeedsAlphaAndPickMask(FALSE) ; mIsPlaying = FALSE ; setMediaImpl() ; LLViewerTexture* tex = gTextureList.findImage(mID) ; if(tex) //this media is a parcel media for tex. { tex->setParcelMedia(TRUE) ; mParcelTexture = tex ; } } //virtual LLViewerMediaTexture::~LLViewerMediaTexture() { if(mParcelTexture.notNull()) { mParcelTexture->setParcelMedia(FALSE) ; } } void LLViewerMediaTexture::reinit(BOOL usemipmaps /* = TRUE */) { mGLTexturep = NULL ; init(false); mUseMipMaps = usemipmaps ; getLastReferencedTimer()->reset() ; generateGLTexture() ; mGLTexturep->setNeedsAlphaAndPickMask(FALSE) ; } void LLViewerMediaTexture::setUseMipMaps(BOOL mipmap) { mUseMipMaps = mipmap; if(mGLTexturep.notNull()) { mGLTexturep->setUseMipMaps(mipmap) ; } } //virtual S8 LLViewerMediaTexture::getType() const { return LLViewerTexture::MEDIA_TEXTURE ; } void LLViewerMediaTexture::invalidateMediaImpl() { mMediaImplp = NULL ; } void LLViewerMediaTexture::setMediaImpl() { if(!mMediaImplp) { mMediaImplp = LLViewerMedia::getMediaImplFromTextureID(mID) ; } } //return true if all faces to reference to this media texture are found //Note: mMediaFaceList is valid only for the current instant // because it does not check the face validity after the current frame. BOOL LLViewerMediaTexture::findFaces() { mMediaFaceList.clear() ; BOOL ret = TRUE ; //for parcel media if(mParcelTexture.isNull()) { LLViewerTexture* tex = gTextureList.findImage(mID) ; if(tex) { tex->setParcelMedia(TRUE) ; mParcelTexture = tex ; } } if(mParcelTexture.notNull()) { const ll_face_list_t* face_list = mParcelTexture->getFaceList() ; for(ll_face_list_t::const_iterator iter = face_list->begin(); iter != face_list->end(); ++iter) { mMediaFaceList.push_back(*iter) ; } } if(!mMediaImplp) { return TRUE ; } //for media on a face. const std::list< LLVOVolume* >* obj_list = mMediaImplp->getObjectList() ; std::list< LLVOVolume* >::const_iterator iter = obj_list->begin() ; for(; iter != obj_list->end(); ++iter) { LLVOVolume* obj = *iter ; if(obj->mDrawable.isNull()) { ret = FALSE ; continue ; } S32 face_id = -1 ; while((face_id = obj->getFaceIndexWithMediaImpl(mMediaImplp, face_id)) > -1) { LLFace* facep = obj->mDrawable->getFace(face_id) ; if(facep) { mMediaFaceList.push_back(facep) ; } else { ret = FALSE ; } } } return ret ; } void LLViewerMediaTexture::initVirtualSize() { if(mIsPlaying) { return ; } findFaces() ; for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) { addTextureStats((*iter)->getVirtualSize()) ; } } void LLViewerMediaTexture::addMediaToFace(LLFace* facep) { if(!mIsPlaying) { return ; //no need to add the face because the media is not in playing. } switchTexture(facep) ; } void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) { if(!mIsPlaying) { return ; //no need to remove the face because the media is not in playing. } if(!facep) { return ; } mIsPlaying = FALSE ; //set to remove the media from the face. switchTexture(facep) ; mIsPlaying = TRUE ; //set the flag back. if(mFaceList.empty()) //no face referencing to this media { stopPlaying() ; } } //virtual void LLViewerMediaTexture::addFace(LLFace* facep) { LLViewerTexture::addFace(facep) ; const LLTextureEntry* te = facep->getTextureEntry() ; if(te) { LLViewerTexture* tex = gTextureList.findImage(te->getID()) ; if(tex) { mTextureList.push_back(tex) ;//increase the reference number by one for tex to avoid deleting it. return ; } } //check if it is a parcel media if(facep->getTexture() && facep->getTexture() != this && facep->getTexture()->getID() == mID) { mTextureList.push_back(facep->getTexture()) ; //a parcel media. if(mParcelTexture.isNull()) { mParcelTexture = facep->getTexture() ; mParcelTexture->setParcelMedia(TRUE) ; } return ; } llerrs << "The face does not have a valid texture before media texture." << llendl ; } //virtual void LLViewerMediaTexture::removeFace(LLFace* facep) { LLViewerTexture::removeFace(facep) ; const LLTextureEntry* te = facep->getTextureEntry() ; if(te) { LLViewerTexture* tex = gTextureList.findImage(te->getID()) ; if(tex) { for(std::list< LLPointer >::iterator iter = mTextureList.begin(); iter != mTextureList.end(); ++iter) { if(*iter == tex) { mTextureList.erase(iter) ; //decrease the reference number for tex by one. return ; } } // //we have some trouble here: the texture of the face is changed. //we need to find the former texture, and remove it from the list to avoid memory leaking. if(mFaceList.empty()) { mTextureList.clear() ; return ; } S32 end = mFaceList.size() ; std::vector te_list(end) ; S32 i = 0 ; for(ll_face_list_t::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter) { te_list[i++] = (*iter)->getTextureEntry() ;//all textures are in use. } for(std::list< LLPointer >::iterator iter = mTextureList.begin(); iter != mTextureList.end(); ++iter) { for(i = 0 ; i < end ; i++) { if(te_list[i] && te_list[i]->getID() == (*iter)->getID())//the texture is in use. { te_list[i] = NULL ; break ; } } if(i == end) //no hit for this texture, remove it. { mTextureList.erase(iter) ; //decrease the reference number for tex by one. return ; } } } } //check if it is a parcel media for(std::list< LLPointer >::iterator iter = mTextureList.begin(); iter != mTextureList.end(); ++iter) { if((*iter)->getID() == mID) { mTextureList.erase(iter) ; //decrease the reference number for tex by one. return ; } } llerrs << "mTextureList texture reference number is corrupted." << llendl ; } void LLViewerMediaTexture::stopPlaying() { // Don't stop the media impl playing here -- this breaks non-inworld media (login screen, search, and media browser). // if(mMediaImplp) // { // mMediaImplp->stop() ; // } mIsPlaying = FALSE ; } void LLViewerMediaTexture::switchTexture(LLFace* facep) { if(facep) { //check if another media is playing on this face. if(facep->getTexture() && facep->getTexture() != this && facep->getTexture()->getType() == LLViewerTexture::MEDIA_TEXTURE) { if(mID == facep->getTexture()->getID()) //this is a parcel media { return ; //let the prim media win. } } if(mIsPlaying) //old textures switch to the media texture { facep->switchTexture(this) ; } else //switch to old textures. { const LLTextureEntry* te = facep->getTextureEntry() ; if(te) { LLViewerTexture* tex = gTextureList.findImage(te->getID()) ; if(!tex && te->getID() != mID)//try parcel media. { tex = gTextureList.findImage(mID) ; } facep->switchTexture(tex) ; } } } } void LLViewerMediaTexture::setPlaying(BOOL playing) { if(!mMediaImplp) { return ; } if(!playing && !mIsPlaying) { return ; //media is already off } if(playing == mIsPlaying && !mMediaImplp->isUpdated()) { return ; //nothing has changed since last time. } mIsPlaying = playing ; if(mIsPlaying) //is about to play this media { if(findFaces()) { //about to update all faces. mMediaImplp->setUpdated(FALSE) ; } if(mMediaFaceList.empty())//no face pointing to this media { stopPlaying() ; return ; } for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) { switchTexture(*iter) ; } } else //stop playing this media { if(mFaceList.empty()) { return ; } ll_face_list_t::iterator cur ; for(ll_face_list_t::iterator iter = mFaceList.begin(); iter!= mFaceList.end(); ) { cur = iter++ ; switchTexture(*cur) ; //cur could be removed in this function. } } return ; } //virtual F32 LLViewerMediaTexture::getMaxVirtualSize() { if(LLFrameTimer::getFrameCount() == mUpdateVirtualSizeTime) { return mMaxVirtualSize ; } mUpdateVirtualSizeTime = LLFrameTimer::getFrameCount() ; if(mNeedsResetMaxVirtualSize) { mMaxVirtualSize = 0.f ;//reset mNeedsResetMaxVirtualSize = FALSE ; } if(mIsPlaying) //media is playing { if(mFaceList.size() > 0) { for(std::list::iterator iter = mFaceList.begin(); iter != mFaceList.end(); ++iter) { LLFace* facep = *iter ; if(facep->getDrawable()->isRecentlyVisible()) { addTextureStats(facep->getVirtualSize()) ; } } } } else //media is not in playing { findFaces() ; if(!mMediaFaceList.empty()) { for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) { LLFace* facep = *iter ; if(facep->getDrawable()->isRecentlyVisible()) { addTextureStats(facep->getVirtualSize()) ; } } } } mNeedsResetMaxVirtualSize = TRUE ; return mMaxVirtualSize ; } //---------------------------------------------------------------------------------------------- //end of LLViewerMediaTexture //---------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------- //start of LLTexturePipelineTester //---------------------------------------------------------------------------------------------- LLTexturePipelineTester::LLTexturePipelineTester() : LLMetricPerformanceTester("TextureTester", FALSE) { addMetricString("TotalBytesLoaded") ; addMetricString("TotalBytesLoadedFromCache") ; addMetricString("TotalBytesLoadedForLargeImage") ; addMetricString("TotalBytesLoadedForSculpties") ; addMetricString("StartFetchingTime") ; addMetricString("TotalGrayTime") ; addMetricString("TotalStablizingTime") ; addMetricString("StartTimeLoadingSculpties") ; addMetricString("EndTimeLoadingSculpties") ; addMetricString("Time") ; addMetricString("TotalBytesBound") ; addMetricString("TotalBytesBoundForLargeImage") ; addMetricString("PercentageBytesBound") ; mTotalBytesLoaded = 0 ; mTotalBytesLoadedFromCache = 0 ; mTotalBytesLoadedForLargeImage = 0 ; mTotalBytesLoadedForSculpties = 0 ; reset() ; } LLTexturePipelineTester::~LLTexturePipelineTester() { LLViewerTextureManager::sTesterp = NULL ; } void LLTexturePipelineTester::update() { mLastTotalBytesUsed = mTotalBytesUsed ; mLastTotalBytesUsedForLargeImage = mTotalBytesUsedForLargeImage ; mTotalBytesUsed = 0 ; mTotalBytesUsedForLargeImage = 0 ; if(LLAppViewer::getTextureFetch()->getNumRequests() > 0) //fetching list is not empty { if(mPause) { //start a new fetching session reset() ; mStartFetchingTime = LLImageGL::sLastFrameTime ; mPause = FALSE ; } //update total gray time if(mUsingDefaultTexture) { mUsingDefaultTexture = FALSE ; mTotalGrayTime = LLImageGL::sLastFrameTime - mStartFetchingTime ; } //update the stablizing timer. updateStablizingTime() ; outputTestResults() ; } else if(!mPause) { //stop the current fetching session mPause = TRUE ; outputTestResults() ; reset() ; } } void LLTexturePipelineTester::reset() { mPause = TRUE ; mUsingDefaultTexture = FALSE ; mStartStablizingTime = 0.0f ; mEndStablizingTime = 0.0f ; mTotalBytesUsed = 0 ; mTotalBytesUsedForLargeImage = 0 ; mLastTotalBytesUsed = 0 ; mLastTotalBytesUsedForLargeImage = 0 ; mStartFetchingTime = 0.0f ; mTotalGrayTime = 0.0f ; mTotalStablizingTime = 0.0f ; mStartTimeLoadingSculpties = 1.0f ; mEndTimeLoadingSculpties = 0.0f ; } //virtual void LLTexturePipelineTester::outputTestRecord(LLSD *sd) { (*sd)[mCurLabel]["TotalBytesLoaded"] = (LLSD::Integer)mTotalBytesLoaded ; (*sd)[mCurLabel]["TotalBytesLoadedFromCache"] = (LLSD::Integer)mTotalBytesLoadedFromCache ; (*sd)[mCurLabel]["TotalBytesLoadedForLargeImage"] = (LLSD::Integer)mTotalBytesLoadedForLargeImage ; (*sd)[mCurLabel]["TotalBytesLoadedForSculpties"] = (LLSD::Integer)mTotalBytesLoadedForSculpties ; (*sd)[mCurLabel]["StartFetchingTime"] = (LLSD::Real)mStartFetchingTime ; (*sd)[mCurLabel]["TotalGrayTime"] = (LLSD::Real)mTotalGrayTime ; (*sd)[mCurLabel]["TotalStablizingTime"] = (LLSD::Real)mTotalStablizingTime ; (*sd)[mCurLabel]["StartTimeLoadingSculpties"] = (LLSD::Real)mStartTimeLoadingSculpties ; (*sd)[mCurLabel]["EndTimeLoadingSculpties"] = (LLSD::Real)mEndTimeLoadingSculpties ; (*sd)[mCurLabel]["Time"] = LLImageGL::sLastFrameTime ; (*sd)[mCurLabel]["TotalBytesBound"] = (LLSD::Integer)mLastTotalBytesUsed ; (*sd)[mCurLabel]["TotalBytesBoundForLargeImage"] = (LLSD::Integer)mLastTotalBytesUsedForLargeImage ; (*sd)[mCurLabel]["PercentageBytesBound"] = (LLSD::Real)(100.f * mLastTotalBytesUsed / mTotalBytesLoaded) ; } void LLTexturePipelineTester::updateTextureBindingStats(const LLViewerTexture* imagep) { U32 mem_size = (U32)imagep->getTextureMemory() ; mTotalBytesUsed += mem_size ; if(MIN_LARGE_IMAGE_AREA <= (U32)(mem_size / (U32)imagep->getComponents())) { mTotalBytesUsedForLargeImage += mem_size ; } } void LLTexturePipelineTester::updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, BOOL from_cache) { U32 data_size = (U32)raw_imagep->getDataSize() ; mTotalBytesLoaded += data_size ; if(from_cache) { mTotalBytesLoadedFromCache += data_size ; } if(MIN_LARGE_IMAGE_AREA <= (U32)(data_size / (U32)raw_imagep->getComponents())) { mTotalBytesLoadedForLargeImage += data_size ; } if(imagep->isForSculpt()) { mTotalBytesLoadedForSculpties += data_size ; if(mStartTimeLoadingSculpties > mEndTimeLoadingSculpties) { mStartTimeLoadingSculpties = LLImageGL::sLastFrameTime ; } mEndTimeLoadingSculpties = LLImageGL::sLastFrameTime ; } } void LLTexturePipelineTester::updateGrayTextureBinding() { mUsingDefaultTexture = TRUE ; } void LLTexturePipelineTester::setStablizingTime() { if(mStartStablizingTime <= mStartFetchingTime) { mStartStablizingTime = LLImageGL::sLastFrameTime ; } mEndStablizingTime = LLImageGL::sLastFrameTime ; } void LLTexturePipelineTester::updateStablizingTime() { if(mStartStablizingTime > mStartFetchingTime) { F32 t = mEndStablizingTime - mStartStablizingTime ; if(t > 0.0001f && (t - mTotalStablizingTime) < 0.0001f) { //already stablized mTotalStablizingTime = LLImageGL::sLastFrameTime - mStartStablizingTime ; //cancel the timer mStartStablizingTime = 0.f ; mEndStablizingTime = 0.f ; } else { mTotalStablizingTime = t ; } } mTotalStablizingTime = 0.f ; } //virtual void LLTexturePipelineTester::compareTestSessions(std::ofstream* os) { LLTexturePipelineTester::LLTextureTestSession* base_sessionp = dynamic_cast(mBaseSessionp) ; LLTexturePipelineTester::LLTextureTestSession* current_sessionp = dynamic_cast(mCurrentSessionp) ; if(!base_sessionp || !current_sessionp) { llerrs << "type of test session does not match!" << llendl ; } //compare and output the comparison *os << llformat("%s\n", mName.c_str()) ; *os << llformat("AggregateResults\n") ; compareTestResults(os, "TotalFetchingTime", base_sessionp->mTotalFetchingTime, current_sessionp->mTotalFetchingTime) ; compareTestResults(os, "TotalGrayTime", base_sessionp->mTotalGrayTime, current_sessionp->mTotalGrayTime) ; compareTestResults(os, "TotalStablizingTime", base_sessionp->mTotalStablizingTime, current_sessionp->mTotalStablizingTime); compareTestResults(os, "StartTimeLoadingSculpties", base_sessionp->mStartTimeLoadingSculpties, current_sessionp->mStartTimeLoadingSculpties) ; compareTestResults(os, "TotalTimeLoadingSculpties", base_sessionp->mTotalTimeLoadingSculpties, current_sessionp->mTotalTimeLoadingSculpties) ; compareTestResults(os, "TotalBytesLoaded", base_sessionp->mTotalBytesLoaded, current_sessionp->mTotalBytesLoaded) ; compareTestResults(os, "TotalBytesLoadedFromCache", base_sessionp->mTotalBytesLoadedFromCache, current_sessionp->mTotalBytesLoadedFromCache) ; compareTestResults(os, "TotalBytesLoadedForLargeImage", base_sessionp->mTotalBytesLoadedForLargeImage, current_sessionp->mTotalBytesLoadedForLargeImage) ; compareTestResults(os, "TotalBytesLoadedForSculpties", base_sessionp->mTotalBytesLoadedForSculpties, current_sessionp->mTotalBytesLoadedForSculpties) ; *os << llformat("InstantResults\n") ; S32 size = llmin(base_sessionp->mInstantPerformanceListCounter, current_sessionp->mInstantPerformanceListCounter) ; for(S32 i = 0 ; i < size ; i++) { *os << llformat("Time(B-T)-%.4f-%.4f\n", base_sessionp->mInstantPerformanceList[i].mTime, current_sessionp->mInstantPerformanceList[i].mTime) ; compareTestResults(os, "AverageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond, current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ; compareTestResults(os, "AverageBytesUsedForLargeImagePerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond, current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ; compareTestResults(os, "AveragePercentageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond, current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ; } if(size < base_sessionp->mInstantPerformanceListCounter) { for(S32 i = size ; i < base_sessionp->mInstantPerformanceListCounter ; i++) { *os << llformat("Time(B-T)-%.4f- \n", base_sessionp->mInstantPerformanceList[i].mTime) ; *os << llformat(", AverageBytesUsedPerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ; *os << llformat(", AverageBytesUsedForLargeImagePerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ; *os << llformat(", AveragePercentageBytesUsedPerSecond, %.4f, N/A \n", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ; } } else if(size < current_sessionp->mInstantPerformanceListCounter) { for(S32 i = size ; i < current_sessionp->mInstantPerformanceListCounter ; i++) { *os << llformat("Time(B-T)- -%.4f\n", current_sessionp->mInstantPerformanceList[i].mTime) ; *os << llformat(", AverageBytesUsedPerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond) ; *os << llformat(", AverageBytesUsedForLargeImagePerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond) ; *os << llformat(", AveragePercentageBytesUsedPerSecond, N/A, %.4f\n", current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond) ; } } } //virtual LLMetricPerformanceTester::LLTestSession* LLTexturePipelineTester::loadTestSession(LLSD* log) { LLTexturePipelineTester::LLTextureTestSession* sessionp = new LLTexturePipelineTester::LLTextureTestSession() ; if(!sessionp) { return NULL ; } F32 total_fetching_time = 0.f ; F32 total_gray_time = 0.f ; F32 total_stablizing_time = 0.f ; F32 total_loading_sculpties_time = 0.f ; F32 start_fetching_time = -1.f ; F32 start_fetching_sculpties_time = 0.f ; F32 last_time = 0.0f ; S32 frame_count = 0 ; sessionp->mInstantPerformanceListCounter = 0 ; sessionp->mInstantPerformanceList.resize(128) ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0 ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0 ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f ; //load a session BOOL in_log = (*log).has(mCurLabel) ; while(in_log) { LLSD::String label = mCurLabel ; incLabel() ; in_log = (*log).has(mCurLabel) ; if(sessionp->mInstantPerformanceListCounter >= (S32)sessionp->mInstantPerformanceList.size()) { sessionp->mInstantPerformanceList.resize(sessionp->mInstantPerformanceListCounter + 128) ; } //time F32 start_time = (*log)[label]["StartFetchingTime"].asReal() ; F32 cur_time = (*log)[label]["Time"].asReal() ; if(start_time - start_fetching_time > 0.0001f) //fetching has paused for a while { sessionp->mTotalFetchingTime += total_fetching_time ; sessionp->mTotalGrayTime += total_gray_time ; sessionp->mTotalStablizingTime += total_stablizing_time ; sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time ; sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time ; start_fetching_time = start_time ; total_fetching_time = 0.0f ; total_gray_time = 0.f ; total_stablizing_time = 0.f ; total_loading_sculpties_time = 0.f ; } else { total_fetching_time = cur_time - start_time ; total_gray_time = (*log)[label]["TotalGrayTime"].asReal() ; total_stablizing_time = (*log)[label]["TotalStablizingTime"].asReal() ; total_loading_sculpties_time = (*log)[label]["EndTimeLoadingSculpties"].asReal() - (*log)[label]["StartTimeLoadingSculpties"].asReal() ; if(start_fetching_sculpties_time < 0.f && total_loading_sculpties_time > 0.f) { start_fetching_sculpties_time = (*log)[label]["StartTimeLoadingSculpties"].asReal() ; } } //total loaded bytes sessionp->mTotalBytesLoaded = (*log)[label]["TotalBytesLoaded"].asInteger() ; sessionp->mTotalBytesLoadedFromCache = (*log)[label]["TotalBytesLoadedFromCache"].asInteger() ; sessionp->mTotalBytesLoadedForLargeImage = (*log)[label]["TotalBytesLoadedForLargeImage"].asInteger() ; sessionp->mTotalBytesLoadedForSculpties = (*log)[label]["TotalBytesLoadedForSculpties"].asInteger() ; //instant metrics sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond += (*log)[label]["TotalBytesBound"].asInteger() ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond += (*log)[label]["TotalBytesBoundForLargeImage"].asInteger() ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond += (*log)[label]["PercentageBytesBound"].asReal() ; frame_count++ ; if(cur_time - last_time >= 1.0f) { sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond /= frame_count ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond /= frame_count ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond /= frame_count ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = last_time ; frame_count = 0 ; last_time = cur_time ; sessionp->mInstantPerformanceListCounter++ ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0 ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0 ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f ; sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f ; } } sessionp->mTotalFetchingTime += total_fetching_time ; sessionp->mTotalGrayTime += total_gray_time ; sessionp->mTotalStablizingTime += total_stablizing_time ; if(sessionp->mStartTimeLoadingSculpties < 0.f) { sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time ; } sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time ; return sessionp; } LLTexturePipelineTester::LLTextureTestSession::LLTextureTestSession() { reset() ; } LLTexturePipelineTester::LLTextureTestSession::~LLTextureTestSession() { } void LLTexturePipelineTester::LLTextureTestSession::reset() { mTotalFetchingTime = 0.0f ; mTotalGrayTime = 0.0f ; mTotalStablizingTime = 0.0f ; mStartTimeLoadingSculpties = 0.0f ; mTotalTimeLoadingSculpties = 0.0f ; mTotalBytesLoaded = 0 ; mTotalBytesLoadedFromCache = 0 ; mTotalBytesLoadedForLargeImage = 0 ; mTotalBytesLoadedForSculpties = 0 ; mInstantPerformanceListCounter = 0 ; } //---------------------------------------------------------------------------------------------- //end of LLTexturePipelineTester //----------------------------------------------------------------------------------------------