/** * @file llviewertexturelist.cpp * @brief Object for managing the list of images within a region * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include #include "llviewertexturelist.h" #include "llgl.h" // fot gathering stats from GL #include "llimagegl.h" #include "llimagebmp.h" #include "llimagej2c.h" #include "llimagetga.h" #include "llimagejpeg.h" #include "llimagepng.h" #include "llimageworker.h" #include "llsdserialize.h" #include "llsys.h" #include "llfilesystem.h" #include "llxmltree.h" #include "message.h" #include "lldrawpoolbump.h" // to init bumpmap images #include "lltexturecache.h" #include "lltexturefetch.h" #include "llviewercontrol.h" #include "llviewertexture.h" #include "llviewermedia.h" #include "llviewernetwork.h" #include "llviewerregion.h" #include "llviewerstats.h" #include "pipeline.h" #include "llappviewer.h" #include "llxuiparser.h" #include "lltracerecording.h" #include "llviewerdisplay.h" #include "llviewerwindow.h" #include "llprogressview.h" //////////////////////////////////////////////////////////////////////////// void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; S32 LLViewerTextureList::sNumImages = 0; LLViewerTextureList gTextureList; extern LLGLSLShader gCopyProgram; ETexListType get_element_type(S32 priority) { return (priority == LLViewerFetchedTexture::BOOST_ICON || priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) ? TEX_LIST_SCALE : TEX_LIST_STANDARD; } /////////////////////////////////////////////////////////////////////////////// LLTextureKey::LLTextureKey() : textureId(LLUUID::null), textureType(TEX_LIST_STANDARD) { } LLTextureKey::LLTextureKey(LLUUID id, ETexListType tex_type) : textureId(id), textureType(tex_type) { } /////////////////////////////////////////////////////////////////////////////// LLViewerTextureList::LLViewerTextureList() : mForceResetTextureStats(false), mInitialized(false) { } void LLViewerTextureList::init() { mInitialized = true ; sNumImages = 0; doPreloadImages(); } void LLViewerTextureList::doPreloadImages() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LL_DEBUGS("ViewerImages") << "Preloading images..." << LL_ENDL; llassert_always(mInitialized) ; llassert_always(mImageList.empty()) ; llassert_always(mUUIDMap.empty()) ; // Set the "missing asset" image LLViewerFetchedTexture::sMissingAssetImagep = LLViewerTextureManager::getFetchedTextureFromFile("missing_asset.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); // Set the "white" image LLViewerFetchedTexture::sWhiteImagep = LLViewerTextureManager::getFetchedTextureFromFile("white.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); LLUIImageList* image_list = LLUIImageList::getInstance(); // Set default particle texture LLViewerFetchedTexture::sDefaultParticleImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); // Set the default flat normal map // BLANK_OBJECT_NORMAL has a version on dataserver, but it has compression artifacts LLViewerFetchedTexture::sFlatNormalImagep = LLViewerTextureManager::getFetchedTextureFromFile("flatnormal.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_BUMP, LLViewerTexture::FETCHED_TEXTURE, 0, 0, BLANK_OBJECT_NORMAL); // PBR: irradiance LLViewerFetchedTexture::sDefaultIrradiancePBRp = LLViewerTextureManager::getFetchedTextureFromFile("default_irradiance.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); image_list->initFromFile(); // turn off clamping and bilinear filtering for uv picking images //LLViewerFetchedTexture* uv_test = preloadUIImage("uv_test1.tga", LLUUID::null, false); //uv_test->setClamp(false, false); //uv_test->setMipFilterNearest(true, true); //uv_test = preloadUIImage("uv_test2.tga", LLUUID::null, false); //uv_test->setClamp(false, false); //uv_test->setMipFilterNearest(true, true); LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); if (image) { image->setAddressMode(LLTexUnit::TAM_WRAP); mImagePreloads.insert(image); } image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); if (image) { image->setAddressMode(LLTexUnit::TAM_WRAP); mImagePreloads.insert(image); } image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryPassLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); if (image) { image->setAddressMode(LLTexUnit::TAM_WRAP); mImagePreloads.insert(image); } image = LLViewerTextureManager::getFetchedTextureFromFile("transparent.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, 0, 0, IMG_TRANSPARENT); if (image) { image->setAddressMode(LLTexUnit::TAM_WRAP); mImagePreloads.insert(image); } #if GL_VERSION_1_1 image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD); #else image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, GL_ALPHA8_EXT, GL_ALPHA, IMG_ALPHA_GRAD); #endif if (image) { image->setAddressMode(LLTexUnit::TAM_CLAMP); mImagePreloads.insert(image); } #if GL_VERSION_1_1 image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD_2D); #else image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, GL_ALPHA8_EXT, GL_ALPHA, IMG_ALPHA_GRAD_2D); #endif if (image) { image->setAddressMode(LLTexUnit::TAM_CLAMP); mImagePreloads.insert(image); } } static std::string get_texture_list_name() { if (LLGridManager::getInstance()->isInProductionGrid()) { return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + ".xml"); } else { const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); const std::string& grid_id_lower = utf8str_tolower(grid_id_str); return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + "." + grid_id_lower + ".xml"); } } void LLViewerTextureList::doPrefetchImages() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // todo: do not load without getViewerAssetUrl() // either fail login without caps or provide this // in some other way, textures won't load otherwise LLViewerFetchedTexture *imagep = findImage(DEFAULT_WATER_NORMAL, TEX_LIST_STANDARD); if (!imagep) { // add it to mImagePreloads only once imagep = LLViewerTextureManager::getFetchedTexture(DEFAULT_WATER_NORMAL, FTT_DEFAULT, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); if (imagep) { imagep->setAddressMode(LLTexUnit::TAM_WRAP); mImagePreloads.insert(imagep); } } LLViewerTextureManager::getFetchedTexture(IMG_SHOT); LLViewerTextureManager::getFetchedTexture(IMG_SMOKE_POOF); LLViewerFetchedTexture::sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); LLViewerFetchedTexture::sSmokeImagep->setNoDelete(); LLStandardBumpmap::addstandard(); if (LLAppViewer::instance()->getPurgeCache()) { // cache was purged, no point return; } // Pre-fetch textures from last logout LLSD imagelist; std::string filename = get_texture_list_name(); llifstream file; file.open(filename.c_str()); if (file.is_open()) { if ( ! LLSDSerialize::fromXML(imagelist, file) ) { file.close(); LL_WARNS() << "XML parse error reading texture list '" << filename << "'" << LL_ENDL; LL_WARNS() << "Removing invalid texture list '" << filename << "'" << LL_ENDL; LLFile::remove(filename); return; } file.close(); } S32 texture_count = 0; for (LLSD::array_iterator iter = imagelist.beginArray(); iter != imagelist.endArray(); ++iter) { LLSD imagesd = *iter; LLUUID uuid = imagesd["uuid"]; S32 pixel_area = imagesd["area"]; S32 texture_type = imagesd["type"]; if(LLViewerTexture::FETCHED_TEXTURE == texture_type || LLViewerTexture::LOD_TEXTURE == texture_type) { LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTexture(uuid, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, texture_type); if (image) { texture_count += 1; image->addTextureStats((F32)pixel_area); } } } LL_DEBUGS() << "fetched " << texture_count << " images from " << filename << LL_ENDL; } /////////////////////////////////////////////////////////////////////////////// LLViewerTextureList::~LLViewerTextureList() { } void LLViewerTextureList::shutdown() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // clear out preloads mImagePreloads.clear(); // Write out list of currently loaded textures for precaching on startup typedef std::set > image_area_list_t; image_area_list_t image_area_list; for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ++iter) { LLViewerFetchedTexture* image = *iter; if (!image->hasGLTexture() || !image->getUseDiscard() || image->needsAux() || !image->getTargetHost().isInvalid() || !image->getUrl().empty() ) { continue; // avoid UI, baked, and other special images } if(!image->getBoundRecently()) { continue ; } S32 desired = image->getDesiredDiscardLevel(); if (desired >= 0 && desired < MAX_DISCARD_LEVEL) { S32 pixel_area = image->getWidth(desired) * image->getHeight(desired); image_area_list.insert(std::make_pair(pixel_area, image)); } } LLSD imagelist; const S32 max_count = 1000; S32 count = 0; S32 image_type ; for (image_area_list_t::reverse_iterator riter = image_area_list.rbegin(); riter != image_area_list.rend(); ++riter) { LLViewerFetchedTexture* image = riter->second; image_type = (S32)image->getType() ; imagelist[count]["area"] = riter->first; imagelist[count]["uuid"] = image->getID(); imagelist[count]["type"] = image_type; if (++count >= max_count) break; } if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "").empty()) { std::string filename = get_texture_list_name(); llofstream file; file.open(filename.c_str()); LL_DEBUGS() << "saving " << imagelist.size() << " image list entries" << LL_ENDL; LLSDSerialize::toPrettyXML(imagelist, file); } // // Clean up "loaded" callbacks. // mCallbackList.clear(); // Flush all of the references while (!mCreateTextureList.empty()) { mCreateTextureList.front()->mCreatePending = false; mCreateTextureList.pop(); } mFastCacheList.clear(); mUUIDMap.clear(); mImageList.clear(); mInitialized = false ; //prevent loading textures again. } void LLViewerTextureList::dump() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LL_INFOS() << "LLViewerTextureList::dump()" << LL_ENDL; for (image_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it) { LLViewerFetchedTexture* image = *it; LL_INFOS() << "priority " << image->getMaxVirtualSize() << " boost " << image->getBoostLevel() << " size " << image->getWidth() << "x" << image->getHeight() << " discard " << image->getDiscardLevel() << " desired " << image->getDesiredDiscardLevel() << " http://asset.siva.lindenlab.com/" << image->getID() << ".texture" << LL_ENDL; } } void LLViewerTextureList::destroyGL() { LLImageGL::destroyGL(); } /* Vertical tab container button image IDs Seem to not decode when running app in debug. const LLUUID BAD_IMG_ONE("1097dcb3-aef9-8152-f471-431d840ea89e"); const LLUUID BAD_IMG_TWO("bea77041-5835-1661-f298-47e2d32b7a70"); */ /////////////////////////////////////////////////////////////////////////////// LLViewerFetchedTexture* LLViewerTextureList::getImageFromFile(const std::string& filename, FTType f_type, bool usemipmaps, LLViewerTexture::EBoostLevel boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, const LLUUID& force_id) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LL_PROFILE_ZONE_TEXT(filename.c_str(), filename.size()); if(!mInitialized) { return NULL ; } std::string full_path = gDirUtilp->findSkinnedFilename("textures", filename); if (full_path.empty()) { LL_WARNS() << "Failed to find local image file: " << filename << LL_ENDL; LLViewerTexture::EBoostLevel priority = LLGLTexture::BOOST_UI; return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, priority); } std::string url = "file://" + full_path; return getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); } LLViewerFetchedTexture* LLViewerTextureList::getImageFromUrl(const std::string& url, FTType f_type, bool usemipmaps, LLViewerTexture::EBoostLevel boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, const LLUUID& force_id) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if(!mInitialized) { return NULL ; } // generate UUID based on hash of filename LLUUID new_id; if (force_id.notNull()) { new_id = force_id; } else { new_id.generate(url); } LLPointer imagep = findImage(new_id, get_element_type(boost_priority)); if (!imagep.isNull()) { LLViewerFetchedTexture *texture = imagep.get(); if (texture->getUrl().empty()) { LL_WARNS() << "Requested texture " << new_id << " already exists but does not have a URL" << LL_ENDL; } else if (texture->getUrl() != url) { // This is not an error as long as the images really match - // e.g. could be two avatars wearing the same outfit. LL_DEBUGS("Avatar") << "Requested texture " << new_id << " already exists with a different url, requested: " << url << " current: " << texture->getUrl() << LL_ENDL; } } if (imagep.isNull()) { switch(texture_type) { case LLViewerTexture::FETCHED_TEXTURE: imagep = new LLViewerFetchedTexture(url, f_type, new_id, usemipmaps); break ; case LLViewerTexture::LOD_TEXTURE: imagep = new LLViewerLODTexture(url, f_type, new_id, usemipmaps); break ; default: LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; } if (internal_format && primary_format) { imagep->setExplicitFormat(internal_format, primary_format); } addImage(imagep, get_element_type(boost_priority)); if (boost_priority != 0) { if (boost_priority == LLViewerFetchedTexture::BOOST_UI) { imagep->dontDiscard(); } if (boost_priority == LLViewerFetchedTexture::BOOST_ICON || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) { // Agent and group Icons are downloadable content, nothing manages // icon deletion yet, so they should not persist imagep->dontDiscard(); imagep->forceActive(); } imagep->setBoostLevel(boost_priority); } } imagep->setGLTextureCreated(true); return imagep; } LLImageRaw* LLViewerTextureList::getRawImageFromMemory(const U8* data, U32 size, std::string_view mimetype) { LLPointer image = LLImageFormatted::loadFromMemory(data, size, mimetype); if (image) { LLImageRaw* raw_image = new LLImageRaw(); image->decode(raw_image, 0.f); return raw_image; } else { return nullptr; } } LLViewerFetchedTexture* LLViewerTextureList::getImageFromMemory(const U8* data, U32 size, std::string_view mimetype) { LLPointer raw_image = getRawImageFromMemory(data, size, mimetype); if (raw_image.notNull()) { LLViewerFetchedTexture* imagep = new LLViewerFetchedTexture(raw_image, FTT_LOCAL_FILE, true); addImage(imagep, TEX_LIST_STANDARD); imagep->dontDiscard(); imagep->setBoostLevel(LLViewerFetchedTexture::BOOST_PREVIEW); return imagep; } else { return nullptr; } } LLViewerFetchedTexture* LLViewerTextureList::getImage(const LLUUID &image_id, FTType f_type, bool usemipmaps, LLViewerTexture::EBoostLevel boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, LLHost request_from_host) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if(!mInitialized) { return NULL ; } // Return the image with ID image_id // If the image is not found, creates new image and // enqueues a request for transmission if (image_id.isNull()) { return (LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, LLGLTexture::BOOST_UI)); } LLPointer imagep = findImage(image_id, get_element_type(boost_priority)); if (!imagep.isNull()) { LLViewerFetchedTexture *texture = imagep.get(); if (request_from_host.isOk() && !texture->getTargetHost().isOk()) { LL_WARNS() << "Requested texture " << image_id << " already exists but does not have a host" << LL_ENDL; } else if (request_from_host.isOk() && texture->getTargetHost().isOk() && request_from_host != texture->getTargetHost()) { LL_WARNS() << "Requested texture " << image_id << " already exists with a different target host, requested: " << request_from_host << " current: " << texture->getTargetHost() << LL_ENDL; } if (f_type != FTT_DEFAULT && imagep->getFTType() != f_type) { LL_WARNS() << "FTType mismatch: requested " << f_type << " image has " << imagep->getFTType() << LL_ENDL; } } if (imagep.isNull()) { imagep = createImage(image_id, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host) ; } imagep->setGLTextureCreated(true); return imagep; } //when this function is called, there is no such texture in the gTextureList with image_id. LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id, FTType f_type, bool usemipmaps, LLViewerTexture::EBoostLevel boost_priority, S8 texture_type, LLGLint internal_format, LLGLenum primary_format, LLHost request_from_host) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLPointer imagep ; switch(texture_type) { case LLViewerTexture::FETCHED_TEXTURE: imagep = new LLViewerFetchedTexture(image_id, f_type, request_from_host, usemipmaps); break ; case LLViewerTexture::LOD_TEXTURE: imagep = new LLViewerLODTexture(image_id, f_type, request_from_host, usemipmaps); break ; default: LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; } if (internal_format && primary_format) { imagep->setExplicitFormat(internal_format, primary_format); } addImage(imagep, get_element_type(boost_priority)); if (boost_priority != 0) { if (boost_priority == LLViewerFetchedTexture::BOOST_UI) { imagep->dontDiscard(); } if (boost_priority == LLViewerFetchedTexture::BOOST_ICON || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) { // Agent and group Icons are downloadable content, nothing manages // icon deletion yet, so they should not persist. imagep->dontDiscard(); imagep->forceActive(); } imagep->setBoostLevel(boost_priority); } else { //by default, the texture can not be removed from memory even if it is not used. //here turn this off //if this texture should be set to NO_DELETE, call setNoDelete() afterwards. imagep->forceActive() ; } mFastCacheList.insert(imagep); imagep->setInFastCacheList(true); return imagep ; } void LLViewerTextureList::findTexturesByID(const LLUUID &image_id, std::vector &output) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLTextureKey search_key(image_id, TEX_LIST_STANDARD); uuid_map_t::iterator iter = mUUIDMap.lower_bound(search_key); while (iter != mUUIDMap.end() && iter->first.textureId == image_id) { output.push_back(iter->second); iter++; } } LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLTextureKey &search_key) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; uuid_map_t::iterator iter = mUUIDMap.find(search_key); if (iter == mUUIDMap.end()) return NULL; return iter->second; } LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLUUID &image_id, ETexListType tex_type) { return findImage(LLTextureKey(image_id, tex_type)); } void LLViewerTextureList::addImageToList(LLViewerFetchedTexture *image) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; assert_main_thread(); llassert_always(mInitialized) ; llassert(image); if (image->isInImageList()) { // Flag is already set? LL_WARNS() << "LLViewerTextureList::addImageToList - image " << image->getID() << " already in list" << LL_ENDL; } else { if (!(mImageList.insert(image)).second) { LL_WARNS() << "Error happens when insert image " << image->getID() << " into mImageList!" << LL_ENDL ; } image->setInImageList(true); } } void LLViewerTextureList::removeImageFromList(LLViewerFetchedTexture *image) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; assert_main_thread(); llassert_always(mInitialized) ; llassert(image); size_t count = 0; if (image->isInImageList()) { image->setInImageList(false); count = mImageList.erase(image) ; if(count != 1) { LL_INFOS() << "Image " << image->getID() << " had mInImageList set but mImageList.erase() returned " << count << LL_ENDL; } } else { // Something is wrong, image is expected in list or callers should check first LL_INFOS() << "Calling removeImageFromList() for " << image->getID() << " but doesn't have mInImageList set" << " ref count is " << image->getNumRefs() << LL_ENDL; uuid_map_t::iterator iter = mUUIDMap.find(LLTextureKey(image->getID(), (ETexListType)image->getTextureListType())); if(iter == mUUIDMap.end()) { LL_INFOS() << "Image " << image->getID() << " is also not in mUUIDMap!" << LL_ENDL ; } else if (iter->second != image) { LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap but with different pointer" << LL_ENDL ; } else { LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap with same pointer" << LL_ENDL ; } count = mImageList.erase(image) ; llassert(count != 0); if(count != 0) { // it was in the list already? LL_WARNS() << "Image " << image->getID() << " had mInImageList false but mImageList.erase() returned " << count << LL_ENDL; } } } void LLViewerTextureList::addImage(LLViewerFetchedTexture *new_image, ETexListType tex_type) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (!new_image) { return; } //llassert(new_image); LLUUID image_id = new_image->getID(); LLTextureKey key(image_id, tex_type); LLViewerFetchedTexture *image = findImage(key); if (image) { LL_INFOS() << "Image with ID " << image_id << " already in list" << LL_ENDL; } sNumImages++; addImageToList(new_image); mUUIDMap[key] = new_image; new_image->setTextureListType(tex_type); } void LLViewerTextureList::deleteImage(LLViewerFetchedTexture *image) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if( image) { if (image->hasCallbacks()) { mCallbackList.erase(image); } LLTextureKey key(image->getID(), (ETexListType)image->getTextureListType()); llverify(mUUIDMap.erase(key) == 1); sNumImages--; removeImageFromList(image); } } /////////////////////////////////////////////////////////////////////////////// void LLViewerTextureList::updateImages(F32 max_time) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; static bool cleared = false; if(gTeleportDisplay) { if(!cleared) { clearFetchingRequests(); gPipeline.clearRebuildGroups(); cleared = true; } return; } cleared = false; LLAppViewer::getTextureFetch()->setTextureBandwidth((F32)LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED).value()); { using namespace LLStatViewer; sample(NUM_IMAGES, sNumImages); sample(NUM_RAW_IMAGES, LLImageRaw::sRawImageCount); sample(FORMATTED_MEM, F64Bytes(LLImageFormatted::sGlobalFormattedMemory)); } // make sure each call below gets at least its "fair share" of time F32 min_time = max_time * 0.33f; F32 remaining_time = max_time; //loading from fast cache remaining_time -= updateImagesLoadingFastCache(remaining_time); remaining_time = llmax(remaining_time, min_time); //dispatch to texture fetch threads remaining_time -= updateImagesFetchTextures(remaining_time); remaining_time = llmax(remaining_time, min_time); //handle results from decode threads updateImagesCreateTextures(remaining_time); bool didone = false; for (image_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) { //trigger loaded callbacks on local textures immediately LLViewerFetchedTexture* image = *iter++; if (!image->getUrl().empty()) { // Do stuff to handle callbacks, update priorities, etc. didone = image->doLoadedCallbacks(); } else if (!didone) { // Do stuff to handle callbacks, update priorities, etc. didone = image->doLoadedCallbacks(); } } updateImagesUpdateStats(); } void LLViewerTextureList::clearFetchingRequests() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (LLAppViewer::getTextureFetch()->getNumRequests() == 0) { return; } LLAppViewer::getTextureFetch()->deleteAllRequests(); for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ++iter) { LLViewerFetchedTexture* imagep = *iter; imagep->forceToDeleteRequest() ; } } extern bool gCubeSnapshot; void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep, bool flush_images) { llassert(!gCubeSnapshot); if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { static LLCachedControl bias_distance_scale(gSavedSettings, "TextureBiasDistanceScale", 1.f); static LLCachedControl texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.04f); static LLCachedControl texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); F32 max_vsize = 0.f; bool on_screen = false; U32 face_count = 0; F32 bias = (F32) llroundf(powf(4, LLViewerTexture::sDesiredDiscardBias - 1.f)); LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { for (S32 fi = 0; fi < imagep->getNumFaces(i); ++fi) { LLFace* face = (*(imagep->getFaceList(i)))[fi]; if (face && face->getViewerObject()) { ++face_count; F32 radius; F32 cos_angle_to_view_dir; static LLCachedControl bias_unimportant_threshold(gSavedSettings, "TextureBiasUnimportantFactor", 0.25f); if ((gFrameCount - face->mLastTextureUpdate) > 10) { // only call calcPixelArea at most once every 10 frames for a given face // this helps eliminate redundant calls to calcPixelArea for faces that have multiple textures // assigned to them, such as is the case with GLTF materials or Blinn-Phong materials face->mInFrustum = face->calcPixelArea(cos_angle_to_view_dir, radius); face->mLastTextureUpdate = gFrameCount; } F32 vsize = face->getPixelArea(); on_screen = face->mInFrustum; // Scale desired texture resolution higher or lower depending on texture scale // // Minimum usage examples: a 1024x1024 texture with aplhabet, runing string // shows one letter at a time // // Maximum usage examples: huge chunk of terrain repeats texture // TODO: make this work with the GLTF texture transforms S32 te_offset = face->getTEOffset(); // offset is -1 if not inited LLViewerObject* objp = face->getViewerObject(); const LLTextureEntry* te = (te_offset < 0 || te_offset >= objp->getNumTEs()) ? nullptr : objp->getTE(te_offset); F32 min_scale = te ? llmin(fabsf(te->getScaleS()), fabsf(te->getScaleT())) : 1.f; min_scale = llclamp(min_scale * min_scale, texture_scale_min(), texture_scale_max()); vsize /= min_scale; // apply bias to offscreen faces all the time, but only to onscreen faces when bias is large if (!face->mInFrustum || LLViewerTexture::sDesiredDiscardBias > 2.f) { vsize /= bias; } max_vsize = llmax(max_vsize, vsize); } } } if (face_count > 1024) { // this texture is used in so many places we should just boost it and not bother checking its vsize // this is especially important because the above is not time sliced and can hit multiple ms for a single texture imagep->setBoostLevel(LLViewerFetchedTexture::BOOST_HIGH); } if (imagep->getType() == LLViewerTexture::LOD_TEXTURE && imagep->getBoostLevel() == LLViewerTexture::BOOST_NONE) { // conditionally reset max virtual size for unboosted LOD_TEXTURES // this is an alternative to decaying mMaxVirtualSize over time // that keeps textures from continously downrezzing and uprezzing in the background if (LLViewerTexture::sDesiredDiscardBias > 1.5f || (!on_screen && LLViewerTexture::sDesiredDiscardBias > 1.f)) { imagep->mMaxVirtualSize = 0.f; } } imagep->addTextureStats(max_vsize); } #if 0 imagep->setDebugText(llformat("%d/%d - %d/%d -- %d/%d", (S32)sqrtf(max_vsize), (S32)sqrtf(imagep->mMaxVirtualSize), imagep->getDiscardLevel(), imagep->getDesiredDiscardLevel(), imagep->getWidth(), imagep->getFullWidth())); #endif // make sure to addTextureStats for any spotlights that are using this texture for (S32 vi = 0; vi < imagep->getNumVolumes(LLRender::LIGHT_TEX); ++vi) { LLVOVolume* volume = (*imagep->getVolumeList(LLRender::LIGHT_TEX))[vi]; volume->updateSpotLightPriority(); } F32 max_inactive_time = 20.f; // inactive time before deleting saved raw image S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, and 1 for "entries" in updateImagesFetchTextures F32 lazy_flush_timeout = 30.f; // delete unused images after 30 seconds // // Flush formatted images using a lazy flush // S32 num_refs = imagep->getNumRefs(); if (num_refs <= min_refs && flush_images) { if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > lazy_flush_timeout) { // Remove the unused image from the image list deleteImage(imagep); return; } } else { // still referenced outside of image list, reset timer imagep->getLastReferencedTimer()->reset(); if (imagep->hasSavedRawImage()) { if (imagep->getElapsedLastReferencedSavedRawImageTime() > max_inactive_time) { imagep->destroySavedRawImage(); } } if (imagep->isDeleted()) { return; } } if (!imagep->isInImageList()) { return; } if (imagep->isInFastCacheList()) { return; //wait for loading from the fast cache. } imagep->processTextureStats(); } F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (gGLManager.mIsDisabled) return 0.0f; // // Create GL textures for all textures that need them (images which have been // decoded, but haven't been pushed into GL). // LLTimer create_timer; while (!mCreateTextureList.empty()) { LLViewerFetchedTexture* imagep = mCreateTextureList.front(); llassert(imagep->mCreatePending); imagep->createTexture(); imagep->postCreateTexture(); imagep->mCreatePending = false; mCreateTextureList.pop(); if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel()) { LL_WARNS_ONCE("Texture") << "Texture will be downscaled immediately after loading." << LL_ENDL; imagep->scaleDown(); } if (create_timer.getElapsedTimeF32() > max_time) { break; } } if (!mDownScaleQueue.empty() && gPipeline.mDownResMap.isComplete()) { LLGLDisable blend(GL_BLEND); gGL.setColorMask(true, true); // just in case we downres textures, bind downresmap and copy program gPipeline.mDownResMap.bindTarget(); gCopyProgram.bind(); gPipeline.mScreenTriangleVB->setBuffer(); // give time to downscaling first -- if mDownScaleQueue is not empty, we're running out of memory and need // to free up memory by discarding off screen textures quickly // do at least 5 and make sure we don't get too far behind even if it violates // the time limit. If we don't downscale quickly the viewer will hit swap and may // freeze. S32 min_count = (S32)mCreateTextureList.size() / 20 + 5; create_timer.reset(); while (!mDownScaleQueue.empty()) { LLViewerFetchedTexture* image = mDownScaleQueue.front(); llassert(image->mDownScalePending); LLImageGL* img = image->getGLTexture(); if (img && img->getHasGLTexture()) { img->scaleDown(image->getDesiredDiscardLevel()); } image->mDownScalePending = false; mDownScaleQueue.pop(); if (create_timer.getElapsedTimeF32() > max_time && --min_count <= 0) { break; } } gCopyProgram.unbind(); gPipeline.mDownResMap.flush(); } return create_timer.getElapsedTimeF32(); } F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (gGLManager.mIsDisabled) return 0.0f; if(mFastCacheList.empty()) { return 0.f; } // // loading texture raw data from the fast cache directly. // LLTimer timer; image_list_t::iterator enditer = mFastCacheList.begin(); for (image_list_t::iterator iter = mFastCacheList.begin(); iter != mFastCacheList.end();) { image_list_t::iterator curiter = iter++; enditer = iter; LLViewerFetchedTexture *imagep = *curiter; imagep->loadFromFastCache(); } mFastCacheList.erase(mFastCacheList.begin(), enditer); return timer.getElapsedTimeF32(); } void LLViewerTextureList::forceImmediateUpdate(LLViewerFetchedTexture* imagep) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if(!imagep || gCubeSnapshot) { return ; } imagep->processTextureStats(); return ; } F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; typedef std::vector > entries_list_t; entries_list_t entries; // update N textures at beginning of mImageList U32 update_count = 0; static const S32 MIN_UPDATE_COUNT = gSavedSettings.getS32("TextureFetchUpdateMinCount"); // default: 32 // NOTE: a texture may be deleted as a side effect of some of these updates // Deletion rules check ref count, so be careful not to hold any LLPointer references to the textures here other than the one in entries. //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); if (LLViewerTexture::sDesiredDiscardBias > 1.f) { // we are over memory target, update more agresively update_count = (S32)(update_count * LLViewerTexture::sDesiredDiscardBias); } update_count = llmin(update_count, (U32) mUUIDMap.size()); { // copy entries out of UUID map to avoid iterator invalidation from deletion inside updateImageDecodeProiroty or updateFetch below LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vtluift - copy"); // copy entries out of UUID map for updating entries.reserve(update_count); uuid_map_t::iterator iter = mUUIDMap.upper_bound(mLastUpdateKey); while (update_count-- > 0) { if (iter == mUUIDMap.end()) { iter = mUUIDMap.begin(); } if (iter->second->getGLTexture()) { entries.push_back(iter->second); } ++iter; } } LLTimer timer; for (auto& imagep : entries) { mLastUpdateKey = LLTextureKey(imagep->getID(), (ETexListType)imagep->getTextureListType()); if (imagep->getNumRefs() > 1) // make sure this image hasn't been deleted before attempting to update (may happen as a side effect of some other image updating) { updateImageDecodePriority(imagep); imagep->updateFetch(); } if (timer.getElapsedTimeF32() > max_time) { break; } } return timer.getElapsedTimeF32(); } void LLViewerTextureList::updateImagesUpdateStats() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (mForceResetTextureStats) { for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerFetchedTexture* imagep = *iter++; imagep->resetTextureStats(); } mForceResetTextureStats = false; } } void LLViewerTextureList::decodeAllImages(F32 max_time) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLTimer timer; //loading from fast cache updateImagesLoadingFastCache(max_time); // Update texture stats and priorities std::vector > image_list; for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerFetchedTexture* imagep = *iter++; image_list.push_back(imagep); imagep->setInImageList(false) ; } llassert_always(image_list.size() == mImageList.size()) ; mImageList.clear(); for (std::vector >::iterator iter = image_list.begin(); iter != image_list.end(); ++iter) { LLViewerFetchedTexture* imagep = *iter; imagep->processTextureStats(); addImageToList(imagep); } image_list.clear(); // Update fetch (decode) for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerFetchedTexture* imagep = *iter++; imagep->updateFetch(); } std::shared_ptr main_queue = LLImageGLThread::sEnabledTextures ? LL::WorkQueue::getInstance("mainloop") : NULL; // Run threads size_t fetch_pending = 0; while (1) { LLAppViewer::instance()->getTextureCache()->update(1); // unpauses the texture cache thread LLAppViewer::instance()->getImageDecodeThread()->update(1); // unpauses the image thread fetch_pending = LLAppViewer::instance()->getTextureFetch()->update(1); // unpauses the texture fetch thread if (LLImageGLThread::sEnabledTextures) { main_queue->runFor(std::chrono::milliseconds(1)); fetch_pending += main_queue->size(); } if (fetch_pending == 0 || timer.getElapsedTimeF32() > max_time) { break; } } // Update fetch again for (image_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerFetchedTexture* imagep = *iter++; imagep->updateFetch(); } max_time -= timer.getElapsedTimeF32(); max_time = llmax(max_time, .001f); F32 create_time = updateImagesCreateTextures(max_time); LL_DEBUGS("ViewerImages") << "decodeAllImages() took " << timer.getElapsedTimeF32() << " seconds. " << " fetch_pending " << fetch_pending << " create_time " << create_time << LL_ENDL; } bool LLViewerTextureList::createUploadFile(LLPointer raw_image, const std::string& out_filename, const S32 max_image_dimentions, const S32 min_image_dimentions) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLImageDataSharedLock lock(raw_image); // make a copy, since convertToUploadFile scales raw image LLPointer scale_image = new LLImageRaw( raw_image->getData(), raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents()); LLPointer compressedImage = LLViewerTextureList::convertToUploadFile(scale_image, max_image_dimentions); if (compressedImage->getWidth() < min_image_dimentions || compressedImage->getHeight() < min_image_dimentions) { std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", min_image_dimentions, min_image_dimentions, compressedImage->getWidth(), compressedImage->getHeight()); compressedImage->setLastError(reason); return false; } if (compressedImage.isNull()) { compressedImage->setLastError("Couldn't convert the image to jpeg2000."); LL_INFOS() << "Couldn't convert to j2c, file : " << out_filename << LL_ENDL; return false; } if (!compressedImage->save(out_filename)) { compressedImage->setLastError("Couldn't create the jpeg2000 image for upload."); LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; return false; } return true; } bool LLViewerTextureList::createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec, const S32 max_image_dimentions, const S32 min_image_dimentions, bool force_square) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; try { // Load the image LLPointer image = LLImageFormatted::createFromType(codec); if (image.isNull()) { LL_WARNS() << "Couldn't open the image to be uploaded." << LL_ENDL; return false; } if (!image->load(filename)) { image->setLastError("Couldn't load the image to be uploaded."); return false; } // Decompress or expand it in a raw image structure LLPointer raw_image = new LLImageRaw; if (!image->decode(raw_image, 0.0f)) { image->setLastError("Couldn't decode the image to be uploaded."); return false; } // Check the image constraints if ((image->getComponents() != 3) && (image->getComponents() != 4)) { image->setLastError("Image files with less than 3 or more than 4 components are not supported."); return false; } if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) { std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", min_image_dimentions, min_image_dimentions, image->getWidth(), image->getHeight()); image->setLastError(reason); return false; } // Convert to j2c (JPEG2000) and save the file locally LLPointer compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); if (compressedImage.isNull()) { image->setLastError("Couldn't convert the image to jpeg2000."); LL_INFOS() << "Couldn't convert to j2c, file : " << filename << LL_ENDL; return false; } if (!compressedImage->save(out_filename)) { image->setLastError("Couldn't create the jpeg2000 image for upload."); LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; return false; } // Test to see if the encode and save worked LLPointer integrity_test = new LLImageJ2C; if (!integrity_test->loadAndValidate( out_filename )) { image->setLastError("The created jpeg2000 image is corrupt."); LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL; return false; } } catch (...) { LOG_UNHANDLED_EXCEPTION(""); return false; } return true; } // note: modifies the argument raw_image!!!! LLPointer LLViewerTextureList::convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions, bool force_square, bool force_lossless) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLImageDataLock lock(raw_image); if (force_square) { S32 biggest_side = llmax(raw_image->getWidth(), raw_image->getHeight()); S32 square_size = raw_image->biasedDimToPowerOfTwo(biggest_side, max_image_dimentions); raw_image->scale(square_size, square_size); } else { raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); } LLPointer compressedImage = new LLImageJ2C(); if (force_lossless || (gSavedSettings.getBOOL("LosslessJ2CUpload") && (raw_image->getWidth() * raw_image->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF))) { compressedImage->setReversible(true); } if (gSavedSettings.getBOOL("Jpeg2000AdvancedCompression")) { // This test option will create jpeg2000 images with precincts for each level, RPCL ordering // and PLT markers. The block size is also optionally modifiable. // Note: the images hence created are compatible with older versions of the viewer. // Read the blocks and precincts size settings S32 block_size = gSavedSettings.getS32("Jpeg2000BlocksSize"); S32 precinct_size = gSavedSettings.getS32("Jpeg2000PrecinctsSize"); LL_INFOS() << "Advanced JPEG2000 Compression: precinct = " << precinct_size << ", block = " << block_size << LL_ENDL; compressedImage->initEncode(*raw_image, block_size, precinct_size, 0); } if (!compressedImage->encode(raw_image, 0.0f)) { LL_INFOS() << "convertToUploadFile : encode returns with error!!" << LL_ENDL; // Clear up the pointer so we don't leak that one compressedImage = NULL; } return compressedImage; } /////////////////////////////////////////////////////////////////////////////// // We've been that the asset server does not contain the requested image id. // static void LLViewerTextureList::processImageNotInDatabase(LLMessageSystem *msg,void **user_data) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLUUID image_id; msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id); LLViewerFetchedTexture* image = gTextureList.findImage( image_id, TEX_LIST_STANDARD); if( image ) { LL_WARNS() << "Image not in db" << LL_ENDL; image->setIsMissingAsset(); } image = gTextureList.findImage(image_id, TEX_LIST_SCALE); if (image) { LL_WARNS() << "Icon not in db" << LL_ENDL; image->setIsMissingAsset(); } } /////////////////////////////////////////////////////////////////////////////// // explicitly cleanup resources, as this is a singleton class with process // lifetime so ability to perform std::map operations in destructor is not // guaranteed. void LLUIImageList::cleanUp() { mUIImages.clear(); mUITextureList.clear() ; } LLUIImagePtr LLUIImageList::getUIImageByID(const LLUUID& image_id, S32 priority) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // use id as image name std::string image_name = image_id.asString(); // look for existing image uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); if (found_it != mUIImages.end()) { return found_it->second; } const bool use_mips = false; const LLRect scale_rect = LLRect::null; const LLRect clip_rect = LLRect::null; return loadUIImageByID(image_id, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); } LLUIImagePtr LLUIImageList::getUIImage(const std::string& image_name, S32 priority) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // look for existing image uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); if (found_it != mUIImages.end()) { return found_it->second; } const bool use_mips = false; const LLRect scale_rect = LLRect::null; const LLRect clip_rect = LLRect::null; return loadUIImageByName(image_name, image_name, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); } LLUIImagePtr LLUIImageList::loadUIImageByName(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, LLUIImage::EScaleStyle scale_style) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (boost_priority == LLGLTexture::BOOST_NONE) { boost_priority = LLGLTexture::BOOST_UI; } LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTextureFromFile(filename, FTT_LOCAL_FILE, MIPMAP_NO, boost_priority); return loadUIImage(imagep, name, use_mips, scale_rect, clip_rect, scale_style); } LLUIImagePtr LLUIImageList::loadUIImageByID(const LLUUID& id, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, LLUIImage::EScaleStyle scale_style) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (boost_priority == LLGLTexture::BOOST_NONE) { boost_priority = LLGLTexture::BOOST_UI; } LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, MIPMAP_NO, boost_priority); return loadUIImage(imagep, id.asString(), use_mips, scale_rect, clip_rect, scale_style); } LLUIImagePtr LLUIImageList::loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle scale_style) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if (!imagep) return NULL; imagep->setAddressMode(LLTexUnit::TAM_CLAMP); //don't compress UI images imagep->getGLTexture()->setAllowCompression(false); LLUIImagePtr new_imagep = new LLUIImage(name, imagep); new_imagep->setScaleStyle(scale_style); if (imagep->getBoostLevel() != LLGLTexture::BOOST_ICON && imagep->getBoostLevel() != LLGLTexture::BOOST_THUMBNAIL && imagep->getBoostLevel() != LLGLTexture::BOOST_PREVIEW) { // Don't add downloadable content into this list // all UI images are non-deletable and list does not support deletion imagep->setNoDelete(); mUIImages.insert(std::make_pair(name, new_imagep)); mUITextureList.push_back(imagep); } //Note: //Some other textures such as ICON also through this flow to be fetched. //But only UI textures need to set this callback. if(imagep->getBoostLevel() == LLGLTexture::BOOST_UI) { LLUIImageLoadData* datap = new LLUIImageLoadData; datap->mImageName = name; datap->mImageScaleRegion = scale_rect; datap->mImageClipRegion = clip_rect; imagep->setLoadedCallback(onUIImageLoaded, 0, false, false, datap, NULL); } return new_imagep; } LLUIImagePtr LLUIImageList::preloadUIImage(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle scale_style) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // look for existing image uuid_ui_image_map_t::iterator found_it = mUIImages.find(name); if (found_it != mUIImages.end()) { // image already loaded! LL_ERRS() << "UI Image " << name << " already loaded." << LL_ENDL; } return loadUIImageByName(name, filename, use_mips, scale_rect, clip_rect, LLGLTexture::BOOST_UI, scale_style); } //static void LLUIImageList::onUIImageLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* user_data ) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; if(!success || !user_data) { return; } LLUIImageLoadData* image_datap = (LLUIImageLoadData*)user_data; std::string ui_image_name = image_datap->mImageName; LLRect scale_rect = image_datap->mImageScaleRegion; LLRect clip_rect = image_datap->mImageClipRegion; if (final) { delete image_datap; } LLUIImageList* instance = getInstance(); uuid_ui_image_map_t::iterator found_it = instance->mUIImages.find(ui_image_name); if (found_it != instance->mUIImages.end()) { LLUIImagePtr imagep = found_it->second; // for images grabbed from local files, apply clipping rectangle to restore original dimensions // from power-of-2 gl image if (success && imagep.notNull() && src_vi && (src_vi->getUrl().compare(0, 7, "file://")==0)) { F32 full_width = (F32)src_vi->getFullWidth(); F32 full_height = (F32)src_vi->getFullHeight(); F32 clip_x = (F32)src_vi->getOriginalWidth() / full_width; F32 clip_y = (F32)src_vi->getOriginalHeight() / full_height; if (clip_rect != LLRect::null) { imagep->setClipRegion(LLRectf(llclamp((F32)clip_rect.mLeft / full_width, 0.f, 1.f), llclamp((F32)clip_rect.mTop / full_height, 0.f, 1.f), llclamp((F32)clip_rect.mRight / full_width, 0.f, 1.f), llclamp((F32)clip_rect.mBottom / full_height, 0.f, 1.f))); } else { imagep->setClipRegion(LLRectf(0.f, clip_y, clip_x, 0.f)); } if (scale_rect != LLRect::null) { imagep->setScaleRegion( LLRectf(llclamp((F32)scale_rect.mLeft / (F32)imagep->getWidth(), 0.f, 1.f), llclamp((F32)scale_rect.mTop / (F32)imagep->getHeight(), 0.f, 1.f), llclamp((F32)scale_rect.mRight / (F32)imagep->getWidth(), 0.f, 1.f), llclamp((F32)scale_rect.mBottom / (F32)imagep->getHeight(), 0.f, 1.f))); } imagep->onImageLoaded(); } } } namespace LLInitParam { template<> struct TypeValues : public TypeValuesHelper { static void declareValues() { declare("scale_inner", LLUIImage::SCALE_INNER); declare("scale_outer", LLUIImage::SCALE_OUTER); } }; } struct UIImageDeclaration : public LLInitParam::Block { Mandatory name; Optional file_name; Optional preload; Optional scale; Optional clip; Optional use_mips; Optional scale_type; UIImageDeclaration() : name("name"), file_name("file_name"), preload("preload", false), scale("scale"), clip("clip"), use_mips("use_mips", false), scale_type("scale_type", LLUIImage::SCALE_INNER) {} }; struct UIImageDeclarations : public LLInitParam::Block { Mandatory version; Multiple textures; UIImageDeclarations() : version("version"), textures("texture") {} }; bool LLUIImageList::initFromFile() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // Look for textures.xml in all the right places. Pass // constraint=LLDir::ALL_SKINS because we want to overlay textures.xml // from all the skins directories. std::vector textures_paths = gDirUtilp->findSkinnedFilenames(LLDir::TEXTURES, "textures.xml", LLDir::ALL_SKINS); std::vector::const_iterator pi(textures_paths.begin()), pend(textures_paths.end()); if (pi == pend) { LL_WARNS() << "No textures.xml found in skins directories" << LL_ENDL; return false; } // The first (most generic) file gets special validations LLXMLNodePtr root; if (!LLXMLNode::parseFile(*pi, root, NULL)) { LL_WARNS() << "Unable to parse UI image list file " << *pi << LL_ENDL; return false; } if (!root->hasAttribute("version")) { LL_WARNS() << "No valid version number in UI image list file " << *pi << LL_ENDL; return false; } UIImageDeclarations images; LLXUIParser parser; parser.readXUI(root, images, *pi); // add components defined in the rest of the skin paths while (++pi != pend) { LLXMLNodePtr update_root; if (LLXMLNode::parseFile(*pi, update_root, NULL)) { parser.readXUI(update_root, images, *pi); } } if (!images.validateBlock()) return false; std::map merged_declarations; for (LLInitParam::ParamIterator::const_iterator image_it = images.textures.begin(); image_it != images.textures.end(); ++image_it) { merged_declarations[image_it->name].overwriteFrom(*image_it); } enum e_decode_pass { PASS_DECODE_NOW, PASS_DECODE_LATER, NUM_PASSES }; for (S32 cur_pass = PASS_DECODE_NOW; cur_pass < NUM_PASSES; cur_pass++) { for (std::map::const_iterator image_it = merged_declarations.begin(); image_it != merged_declarations.end(); ++image_it) { const UIImageDeclaration& image = image_it->second; std::string file_name = image.file_name.isProvided() ? image.file_name() : image.name(); // load high priority textures on first pass (to kick off decode) enum e_decode_pass decode_pass = image.preload ? PASS_DECODE_NOW : PASS_DECODE_LATER; if (decode_pass != cur_pass) { continue; } preloadUIImage(image.name, file_name, image.use_mips, image.scale, image.clip, image.scale_type); } if (!gSavedSettings.getBOOL("NoPreload")) { if (cur_pass == PASS_DECODE_NOW) { // init fetching and decoding of preloaded images gTextureList.decodeAllImages(9.f); } else { // decodeAllImages needs two passes to refresh stats and priorities on second pass gTextureList.decodeAllImages(1.f); } } } return true; }