Sync light state, bound shader, and various gl context states similarly to render matrices. Texture handles now refcounted, as multiple viewer textures could ref the same handle (cubemaps do this) Clean up gl extension loading a bit. Not necessary, but only look for ARB variants if not included in current core version. Removed unused extensions. Use core shader api if supported, else use ARB. (FN signatures are identical. Just doing some pointer substitution to ARB if not core.) Attempt at improving VBO update batching. Subdata updates better batched to gether per-frame. There's probably other stuff I forgot that is in this changeset, too. Todo: Fix lightstate assertion when toggling fullscreen with shaders off.
1857 lines
57 KiB
C++
1857 lines
57 KiB
C++
/**
|
|
* @file llviewertexturelist.cpp
|
|
* @brief Object for managing the list of images within a region
|
|
*
|
|
* $LicenseInfo:firstyear=2000&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2000-2010, 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://secondlife.com/developers/opensource/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://secondlife.com/developers/opensource/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 <sys/stat.h>
|
|
|
|
#include "llviewertexturelist.h"
|
|
|
|
#include "imageids.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 "llvfs.h"
|
|
#include "llvfile.h"
|
|
#include "llvfsthread.h"
|
|
#include "llxmltree.h"
|
|
#include "message.h"
|
|
|
|
#include "lltexturecache.h"
|
|
#include "lltexturefetch.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llviewertexture.h"
|
|
#include "llviewermedia.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewerstats.h"
|
|
#include "pipeline.h"
|
|
#include "llappviewer.h"
|
|
#include "llxuiparser.h"
|
|
#include "llagent.h"
|
|
#include "llviewerdisplay.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llprogressview.h"
|
|
#include "llflexibleobject.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL;
|
|
|
|
U32Bits LLViewerTextureList::sTextureBits(0);
|
|
U32 LLViewerTextureList::sTexturePackets = 0;
|
|
S32 LLViewerTextureList::sNumImages = 0;
|
|
|
|
LLViewerTextureList gTextureList;
|
|
static LLTrace::BlockTimerStatHandle FTM_PROCESS_IMAGES("Process Images");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
LLViewerTextureList::LLViewerTextureList()
|
|
: mForceResetTextureStats(FALSE),
|
|
mUpdateStats(FALSE),
|
|
mMaxResidentTexMemInMegaBytes(0),
|
|
mMaxTotalTextureMemInMegaBytes(0),
|
|
mInitialized(FALSE)
|
|
{
|
|
}
|
|
|
|
void LLViewerTextureList::init()
|
|
{
|
|
mInitialized = TRUE ;
|
|
sNumImages = 0;
|
|
mUpdateStats = TRUE;
|
|
mMaxResidentTexMemInMegaBytes = (U32Bytes)0;
|
|
mMaxTotalTextureMemInMegaBytes = (U32Bytes)0;
|
|
if (gNoRender)
|
|
{
|
|
// Don't initialize GL stuff if we're not rendering.
|
|
return;
|
|
}
|
|
|
|
// Update how much texture RAM we're allowed to use.
|
|
updateMaxResidentTexMem(S32Megabytes(0)); // 0 = use current
|
|
|
|
doPreloadImages();
|
|
}
|
|
|
|
|
|
void LLViewerTextureList::doPreloadImages()
|
|
{
|
|
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();
|
|
|
|
LLViewerFetchedTexture::sFlatNormalImagep = LLViewerTextureManager::getFetchedTextureFromFile("flatnormal.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_BUMP);
|
|
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);
|
|
|
|
// prefetch specific UUIDs
|
|
LLViewerTextureManager::getFetchedTexture(IMG_SHOT);
|
|
LLViewerTextureManager::getFetchedTexture(IMG_SMOKE_POOF);
|
|
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::getFetchedTexture(DEFAULT_WATER_NORMAL, FTT_DEFAULT, 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,LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"));
|
|
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,LLUUID("e97cf410-8e61-7005-ec06-629eba4cd1fb"));
|
|
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,LLUUID("38b86f85-2575-52a9-a531-23108d8da837"));
|
|
if (image)
|
|
{
|
|
image->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
mImagePreloads.insert(image);
|
|
}
|
|
//Hideous hack to set filtering and address modes without messing with our texture classes.
|
|
{
|
|
LLPointer<LLUIImage> temp_image = image_list->getUIImage("checkerboard.tga", LLViewerFetchedTexture::BOOST_UI);
|
|
if(temp_image.notNull() && temp_image->getImage().notNull())
|
|
{
|
|
LLViewerTexture *tex = (LLViewerTexture*)temp_image->getImage().get();
|
|
tex->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
tex->setFilteringOption(LLTexUnit::TFO_POINT);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static std::string get_texture_list_name()
|
|
{
|
|
return gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "texture_list_" + gSavedSettings.getString("LoginLocation") + ".xml");
|
|
}
|
|
|
|
void LLViewerTextureList::doPrefetchImages()
|
|
{
|
|
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()
|
|
{
|
|
// clear out preloads
|
|
mImagePreloads.clear();
|
|
|
|
// Write out list of currently loaded textures for precaching on startup
|
|
typedef std::set<std::pair<S32,LLViewerFetchedTexture*> > image_area_list_t;
|
|
image_area_list_t image_area_list;
|
|
for (image_priority_list_t::iterator iter = mImageList.begin();
|
|
iter != mImageList.end(); ++iter)
|
|
{
|
|
LLViewerFetchedTexture* image = *iter;
|
|
if (!image->hasGLTexture() ||
|
|
!image->getUseDiscard() ||
|
|
image->needsAux() ||
|
|
image->getTargetHost() != LLHost::invalid ||
|
|
!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->getLindenUserDir(true).empty())
|
|
{
|
|
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, get_texture_list_name());
|
|
llofstream file;
|
|
file.open(filename);
|
|
LLSDSerialize::toPrettyXML(imagelist, file);
|
|
}
|
|
|
|
//
|
|
// Clean up "loaded" callbacks.
|
|
//
|
|
mCallbackList.clear();
|
|
|
|
// Flush all of the references
|
|
mLoadingStreamList.clear();
|
|
mCreateTextureList.clear();
|
|
|
|
mUUIDMap.clear();
|
|
|
|
mImageList.clear();
|
|
|
|
mInitialized = FALSE ; //prevent loading textures again.
|
|
}
|
|
|
|
void LLViewerTextureList::dump()
|
|
{
|
|
LL_INFOS() << "LLViewerTextureList::dump()" << LL_ENDL;
|
|
for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture* image = *it;
|
|
|
|
LL_INFOS() << "priority " << image->getDecodePriority()
|
|
<< " 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(BOOL save_state)
|
|
{
|
|
clearFetchingRequests();
|
|
LLImageGL::destroyGL(save_state);
|
|
}
|
|
|
|
void LLViewerTextureList::restoreGL()
|
|
{
|
|
llassert_always(mInitialized) ;
|
|
LLImageGL::restoreGL();
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
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;
|
|
return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI);
|
|
}
|
|
|
|
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)
|
|
{
|
|
|
|
if(!mInitialized)
|
|
{
|
|
return NULL ;
|
|
}
|
|
if (gNoRender)
|
|
{
|
|
// Never mind that this ignores image_set_id;
|
|
// getImage() will handle that later.
|
|
return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI);
|
|
}
|
|
|
|
// generate UUID based on hash of filename
|
|
LLUUID new_id;
|
|
if (force_id.notNull())
|
|
{
|
|
new_id = force_id;
|
|
}
|
|
else
|
|
{
|
|
new_id.generate(url);
|
|
}
|
|
|
|
LLPointer<LLViewerFetchedTexture> imagep = findImage(new_id);
|
|
|
|
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);
|
|
|
|
if (boost_priority != 0)
|
|
{
|
|
if (boost_priority == LLViewerFetchedTexture::BOOST_UI ||
|
|
boost_priority == LLViewerFetchedTexture::BOOST_ICON)
|
|
{
|
|
imagep->dontDiscard();
|
|
}
|
|
imagep->setBoostLevel(boost_priority);
|
|
}
|
|
}
|
|
|
|
imagep->setGLTextureCreated(true);
|
|
|
|
return imagep;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
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 == NULL) || image_id.isNull())
|
|
{
|
|
return (LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI));
|
|
}
|
|
|
|
LLPointer<LLViewerFetchedTexture> imagep = findImage(image_id);
|
|
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)
|
|
{
|
|
LLPointer<LLViewerFetchedTexture> 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);
|
|
|
|
if (boost_priority != 0)
|
|
{
|
|
if (boost_priority == LLViewerFetchedTexture::BOOST_UI ||
|
|
boost_priority == LLViewerFetchedTexture::BOOST_ICON)
|
|
{
|
|
imagep->dontDiscard();
|
|
}
|
|
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() ;
|
|
}
|
|
|
|
return imagep ;
|
|
}
|
|
|
|
LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLUUID &image_id)
|
|
{
|
|
uuid_map_t::iterator iter = mUUIDMap.find(image_id);
|
|
if(iter == mUUIDMap.end())
|
|
return NULL;
|
|
return iter->second;
|
|
}
|
|
|
|
void LLViewerTextureList::addImageToList(LLViewerFetchedTexture *image)
|
|
{
|
|
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 != true)
|
|
{
|
|
LL_WARNS() << "Error happens when insert image " << image->getID() << " into mImageList!" << LL_ENDL ;
|
|
}
|
|
image->setInImageList(TRUE) ;
|
|
}
|
|
}
|
|
|
|
void LLViewerTextureList::removeImageFromList(LLViewerFetchedTexture *image)
|
|
{
|
|
assert_main_thread();
|
|
llassert_always(mInitialized) ;
|
|
llassert(image);
|
|
|
|
S32 count = 0;
|
|
if (image->isInImageList())
|
|
{
|
|
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(image->getID());
|
|
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) ;
|
|
if(count != 0)
|
|
{ // it was in the list already?
|
|
LL_WARNS() << "Image " << image->getID()
|
|
<< " had mInImageList false but mImageList.erase() returned " << count
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
image->setInImageList(FALSE) ;
|
|
}
|
|
|
|
void LLViewerTextureList::addImage(LLViewerFetchedTexture *new_image)
|
|
{
|
|
if (!new_image)
|
|
{
|
|
LL_WARNS() << "No image to add to image list" << LL_ENDL;
|
|
return;
|
|
}
|
|
LLUUID image_id = new_image->getID();
|
|
|
|
LLViewerFetchedTexture *image = findImage(image_id);
|
|
if (image)
|
|
{
|
|
LL_INFOS() << "Image with ID " << image_id << " already in list" << LL_ENDL;
|
|
}
|
|
sNumImages++;
|
|
|
|
addImageToList(new_image);
|
|
mUUIDMap[image_id] = new_image;
|
|
}
|
|
|
|
|
|
void LLViewerTextureList::deleteImage(LLViewerFetchedTexture *image)
|
|
{
|
|
if( image)
|
|
{
|
|
if (image->hasCallbacks())
|
|
{
|
|
mCallbackList.erase(image);
|
|
}
|
|
|
|
llverify(mUUIDMap.erase(image->getID()) == 1);
|
|
sNumImages--;
|
|
removeImageFromList(image);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LLViewerTextureList::dirtyImage(LLViewerFetchedTexture *image)
|
|
{
|
|
mDirtyTextureList.insert(image);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_MARK_DIRTY("Dirty Images");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_UPDATE_PRIORITIES("Prioritize");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_CALLBACKS("Callbacks");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_FETCH("Fetch");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_CREATE("Create");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_STATS("Stats");
|
|
static LLTrace::BlockTimerStatHandle FTM_IMAGE_MEDIA("Media");
|
|
|
|
void LLViewerTextureList::updateImages(F32 max_time)
|
|
{
|
|
static BOOL cleared = FALSE;
|
|
//Singu note: Don't clear vbos on local tp until some serious geom rebuild bugs are stomped out.
|
|
//Currently it causes prims to fail to rebuild when they should be popping back into visiblity.
|
|
//static const LLCachedControl<bool> hide_tp_screen("AscentDisableTeleportScreens",false);
|
|
|
|
//Can't check gTeleportDisplay due to a process_teleport_local(), which sets it to true for local teleports... so:
|
|
// Do this case if IS teleporting but NOT local teleporting, AND either the TP screen is set to appear OR we just entered the sim (TELEPORT_START_ARRIVAL)
|
|
LLAgent::ETeleportState state = gAgent.getTeleportState();
|
|
if(state != LLAgent::TELEPORT_NONE && state != LLAgent::TELEPORT_LOCAL && state != LLAgent::TELEPORT_PENDING &&
|
|
(/*!hide_tp_screen ||*/ state == LLAgent::TELEPORT_START_ARRIVAL || state == LLAgent::TELEPORT_ARRIVING))
|
|
{
|
|
if(!cleared)
|
|
{
|
|
//LL_INFOS() << "flushing upon teleport." << LL_ENDL;
|
|
clearFetchingRequests();
|
|
//gPipeline.clearRebuildGroups() really doesn't belong here... but since it is here, do a few other needed things too.
|
|
gPipeline.clearRebuildGroups();
|
|
gPipeline.resetVertexBuffers();
|
|
LLVolumeImplFlexible::resetTimers();
|
|
cleared = TRUE;
|
|
}
|
|
return;
|
|
}
|
|
cleared = FALSE;
|
|
|
|
S64 global_raw_memory;
|
|
{
|
|
global_raw_memory = *AIAccess<S64>(LLImageRaw::sGlobalRawMemory);
|
|
}
|
|
LLViewerStats::getInstance()->mNumImagesStat.addValue(sNumImages);
|
|
LLViewerStats::getInstance()->mNumRawImagesStat.addValue(LLImageRaw::sRawImageCount);
|
|
LLViewerStats::getInstance()->mGLTexMemStat.addValue((F32)LLImageGL::sGlobalTextureMemory.valueInUnits<LLUnits::Megabytes>());
|
|
LLViewerStats::getInstance()->mGLBoundMemStat.addValue((F32)LLImageGL::sBoundTextureMemory.valueInUnits<LLUnits::Megabytes>());
|
|
LLViewerStats::getInstance()->mRawMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(global_raw_memory));
|
|
LLViewerStats::getInstance()->mFormattedMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageFormatted::sGlobalFormattedMemory));
|
|
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_UPDATE_PRIORITIES);
|
|
updateImagesDecodePriorities();
|
|
}
|
|
|
|
F32 total_max_time = max_time;
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_FETCH);
|
|
max_time -= updateImagesFetchTextures(max_time);
|
|
}
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_CREATE);
|
|
max_time = llmax(max_time, total_max_time*.50f); // at least 50% of max_time
|
|
max_time -= updateImagesCreateTextures(max_time);
|
|
}
|
|
|
|
if (!mDirtyTextureList.empty())
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_MARK_DIRTY);
|
|
gPipeline.dirtyPoolObjectTextures(mDirtyTextureList);
|
|
mDirtyTextureList.clear();
|
|
}
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_CALLBACKS);
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_STATS);
|
|
updateImagesUpdateStats();
|
|
}
|
|
}
|
|
|
|
void LLViewerTextureList::clearFetchingRequests()
|
|
{
|
|
if (LLAppViewer::getTextureFetch()->getNumRequests() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLAppViewer::getTextureFetch()->deleteAllRequests();
|
|
|
|
for (image_priority_list_t::iterator iter = mImageList.begin();
|
|
iter != mImageList.end(); ++iter)
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter;
|
|
imagep->forceToDeleteRequest() ;
|
|
}
|
|
}
|
|
|
|
void LLViewerTextureList::updateImagesDecodePriorities()
|
|
{
|
|
// Update the decode priority for N images each frame
|
|
{
|
|
F32 lazy_flush_timeout = 30.f; // stop decoding
|
|
F32 max_inactive_time = 20.f; // actually delete
|
|
S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, 1 for local reference
|
|
|
|
//reset imagep->getLastReferencedTimer() when screen is showing the progress view to avoid removing pre-fetched textures too soon.
|
|
bool reset_timer = gViewerWindow->getProgressView()->getVisible();
|
|
|
|
static const S32 MAX_PRIO_UPDATES = gSavedSettings.getS32("TextureFetchUpdatePriorities"); // default: 32
|
|
const size_t max_update_count = llmin((S32) (MAX_PRIO_UPDATES*MAX_PRIO_UPDATES*gFrameIntervalSeconds) + 1, MAX_PRIO_UPDATES);
|
|
S32 update_counter = llmin(max_update_count, mUUIDMap.size());
|
|
uuid_map_t::iterator iter = mUUIDMap.upper_bound(mLastUpdateUUID);
|
|
while ((update_counter-- > 0) && !mUUIDMap.empty())
|
|
{
|
|
if (iter == mUUIDMap.end())
|
|
{
|
|
iter = mUUIDMap.begin();
|
|
}
|
|
mLastUpdateUUID = iter->first;
|
|
LLPointer<LLViewerFetchedTexture> imagep = iter->second;
|
|
++iter; // safe to incrament now
|
|
|
|
//
|
|
// Flush formatted images using a lazy flush
|
|
//
|
|
S32 num_refs = imagep->getNumRefs();
|
|
if (num_refs == min_refs)
|
|
{
|
|
if(reset_timer)
|
|
{
|
|
imagep->getLastReferencedTimer()->reset();
|
|
}
|
|
else if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > lazy_flush_timeout)
|
|
{
|
|
// Remove the unused image from the image list
|
|
deleteImage(imagep);
|
|
imagep = NULL; // should destroy the image
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if(imagep->hasSavedRawImage())
|
|
{
|
|
if(imagep->getElapsedLastReferencedSavedRawImageTime() > max_inactive_time)
|
|
{
|
|
imagep->destroySavedRawImage() ;
|
|
}
|
|
}
|
|
|
|
if(imagep->isDeleted())
|
|
{
|
|
continue ;
|
|
}
|
|
else if(imagep->isDeletionCandidate())
|
|
{
|
|
imagep->destroyTexture() ;
|
|
continue ;
|
|
}
|
|
else if(imagep->isInactive())
|
|
{
|
|
if(reset_timer)
|
|
{
|
|
imagep->getLastReferencedTimer()->reset();
|
|
}
|
|
else if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > max_inactive_time)
|
|
{
|
|
imagep->setDeletionCandidate() ;
|
|
}
|
|
continue ;
|
|
}
|
|
else
|
|
{
|
|
imagep->getLastReferencedTimer()->reset();
|
|
|
|
//reset texture state.
|
|
imagep->setInactive() ;
|
|
}
|
|
}
|
|
|
|
if (!imagep->isInImageList())
|
|
{
|
|
continue;
|
|
}
|
|
imagep->processTextureStats();
|
|
F32 old_priority = imagep->getDecodePriority();
|
|
F32 old_priority_test = llmax(old_priority, 0.0f);
|
|
F32 decode_priority = imagep->calcDecodePriority();
|
|
F32 decode_priority_test = llmax(decode_priority, 0.0f);
|
|
// Ignore < 20% difference
|
|
if ((decode_priority_test < old_priority_test * .8f) ||
|
|
(decode_priority_test > old_priority_test * 1.25f))
|
|
{
|
|
removeImageFromList(imagep);
|
|
imagep->setDecodePriority(decode_priority);
|
|
addImageToList(imagep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
static U8 get_image_type(LLViewerFetchedTexture* imagep, LLHost target_host)
|
|
{
|
|
// Having a target host implies this is a baked image. I don't
|
|
// believe that boost level has been set at this point. JC
|
|
U8 type_from_host = (target_host.isOk()
|
|
? LLImageBase::TYPE_AVATAR_BAKE
|
|
: LLImageBase::TYPE_NORMAL);
|
|
S32 boost_level = imagep->getBoostLevel();
|
|
U8 type_from_boost = ( (boost_level == LLViewerFetchedTexture::BOOST_AVATAR_BAKED
|
|
|| boost_level == LLViewerFetchedTexture::BOOST_AVATAR_BAKED_SELF)
|
|
? LLImageBase::TYPE_AVATAR_BAKE
|
|
: LLImageBase::TYPE_NORMAL);
|
|
if (type_from_host == LLImageBase::TYPE_NORMAL
|
|
&& type_from_boost == LLImageBase::TYPE_AVATAR_BAKE)
|
|
{
|
|
LL_WARNS() << "TAT: get_image_type() type_from_host doesn't match type_from_boost"
|
|
<< " host " << target_host
|
|
<< " boost " << imagep->getBoostLevel()
|
|
<< " imageid " << imagep->getID()
|
|
<< LL_ENDL;
|
|
imagep->dump();
|
|
}
|
|
return type_from_host;
|
|
}
|
|
*/
|
|
|
|
F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time)
|
|
{
|
|
if (gNoRender || 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).
|
|
//
|
|
LL_RECORD_BLOCK_TIME(FTM_IMAGE_CREATE);
|
|
|
|
LLTimer create_timer;
|
|
image_list_t::iterator enditer = mCreateTextureList.begin();
|
|
for (image_list_t::iterator iter = mCreateTextureList.begin();
|
|
iter != mCreateTextureList.end();)
|
|
{
|
|
image_list_t::iterator curiter = iter++;
|
|
enditer = iter;
|
|
LLViewerFetchedTexture *imagep = *curiter;
|
|
imagep->createTexture();
|
|
if (create_timer.getElapsedTimeF32() > max_time)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
mCreateTextureList.erase(mCreateTextureList.begin(), enditer);
|
|
return create_timer.getElapsedTimeF32();
|
|
}
|
|
|
|
void LLViewerTextureList::forceImmediateUpdate(LLViewerFetchedTexture* imagep)
|
|
{
|
|
if(!imagep)
|
|
{
|
|
return ;
|
|
}
|
|
if(imagep->isInImageList())
|
|
{
|
|
removeImageFromList(imagep);
|
|
}
|
|
|
|
imagep->processTextureStats();
|
|
F32 decode_priority = LLViewerFetchedTexture::maxDecodePriority() ;
|
|
imagep->setDecodePriority(decode_priority);
|
|
addImageToList(imagep);
|
|
|
|
return ;
|
|
}
|
|
|
|
F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time)
|
|
{
|
|
LLTimer image_op_timer;
|
|
|
|
// Update fetch for N images each frame
|
|
static const S32 MAX_HIGH_PRIO_COUNT = gSavedSettings.getS32("TextureFetchUpdateHighPriority"); // default: 32
|
|
static const S32 MAX_UPDATE_COUNT = gSavedSettings.getS32("TextureFetchUpdateMaxMediumPriority"); // default: 256
|
|
static const S32 MIN_UPDATE_COUNT = gSavedSettings.getS32("TextureFetchUpdateMinMediumPriority"); // default: 32
|
|
static const F32 MIN_PRIORITY_THRESHOLD = gSavedSettings.getF32("TextureFetchUpdatePriorityThreshold"); // default: 0.0
|
|
static const bool SKIP_LOW_PRIO = gSavedSettings.getBOOL("TextureFetchUpdateSkipLowPriority"); // default: false
|
|
|
|
size_t max_priority_count = llmin((S32) (MAX_HIGH_PRIO_COUNT*MAX_HIGH_PRIO_COUNT*gFrameIntervalSeconds)+1, MAX_HIGH_PRIO_COUNT);
|
|
max_priority_count = llmin(max_priority_count, mImageList.size());
|
|
|
|
size_t total_update_count = mUUIDMap.size();
|
|
size_t max_update_count = llmin((S32) (MAX_UPDATE_COUNT*MAX_UPDATE_COUNT*gFrameIntervalSeconds)+1, MAX_UPDATE_COUNT);
|
|
max_update_count = llmin(max_update_count, total_update_count);
|
|
|
|
// MAX_HIGH_PRIO_COUNT high priority entries
|
|
typedef std::vector<LLViewerFetchedTexture*> entries_list_t;
|
|
entries_list_t entries;
|
|
size_t update_counter = max_priority_count;
|
|
image_priority_list_t::iterator iter1 = mImageList.begin();
|
|
while(update_counter > 0)
|
|
{
|
|
entries.push_back(*iter1);
|
|
|
|
++iter1;
|
|
update_counter--;
|
|
}
|
|
|
|
// MAX_UPDATE_COUNT cycled entries
|
|
update_counter = max_update_count;
|
|
if(update_counter > 0)
|
|
{
|
|
uuid_map_t::iterator iter2 = mUUIDMap.upper_bound(mLastFetchUUID);
|
|
while ((update_counter > 0) && (total_update_count > 0))
|
|
{
|
|
if (iter2 == mUUIDMap.end())
|
|
{
|
|
iter2 = mUUIDMap.begin();
|
|
}
|
|
LLViewerFetchedTexture* imagep = iter2->second;
|
|
// Skip the textures where there's really nothing to do so to give some times to others. Also skip the texture if it's already in the high prio set.
|
|
if (!SKIP_LOW_PRIO || (SKIP_LOW_PRIO && ((imagep->getDecodePriority() > MIN_PRIORITY_THRESHOLD) || imagep->hasFetcher())))
|
|
{
|
|
entries.push_back(imagep);
|
|
update_counter--;
|
|
}
|
|
|
|
iter2++;
|
|
total_update_count--;
|
|
}
|
|
}
|
|
|
|
S32 fetch_count = 0;
|
|
size_t min_update_count = llmin(MIN_UPDATE_COUNT,(S32)(entries.size()-max_priority_count));
|
|
S32 min_count = max_priority_count + min_update_count;
|
|
for (entries_list_t::iterator iter3 = entries.begin();
|
|
iter3 != entries.end(); )
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter3++;
|
|
fetch_count += (imagep->updateFetch() ? 1 : 0);
|
|
if (min_count <= (S32)min_update_count)
|
|
{
|
|
mLastFetchUUID = imagep->getID();
|
|
}
|
|
if ((min_count-- <= 0) && (image_op_timer.getElapsedTimeF32() > max_time))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return image_op_timer.getElapsedTimeF32();
|
|
}
|
|
|
|
void LLViewerTextureList::updateImagesUpdateStats()
|
|
{
|
|
if (mUpdateStats && mForceResetTextureStats)
|
|
{
|
|
for (image_priority_list_t::iterator iter = mImageList.begin();
|
|
iter != mImageList.end(); )
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter++;
|
|
imagep->resetTextureStats();
|
|
}
|
|
mUpdateStats = FALSE;
|
|
mForceResetTextureStats = FALSE;
|
|
}
|
|
}
|
|
|
|
void LLViewerTextureList::decodeAllImages(F32 max_time)
|
|
{
|
|
LLTimer timer;
|
|
if(gNoRender) return;
|
|
|
|
// Update texture stats and priorities
|
|
std::vector<LLPointer<LLViewerFetchedTexture> > image_list;
|
|
for (image_priority_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<LLPointer<LLViewerFetchedTexture> >::iterator iter = image_list.begin();
|
|
iter != image_list.end(); ++iter)
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter;
|
|
imagep->processTextureStats();
|
|
F32 decode_priority = imagep->calcDecodePriority();
|
|
imagep->setDecodePriority(decode_priority);
|
|
addImageToList(imagep);
|
|
}
|
|
image_list.clear();
|
|
|
|
// Update fetch (decode)
|
|
for (image_priority_list_t::iterator iter = mImageList.begin();
|
|
iter != mImageList.end(); )
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter++;
|
|
imagep->updateFetch();
|
|
}
|
|
// Run threads
|
|
S32 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 (fetch_pending == 0 || timer.getElapsedTimeF32() > max_time)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Update fetch again
|
|
for (image_priority_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(const std::string& filename,
|
|
const std::string& out_filename,
|
|
const U8 codec)
|
|
{
|
|
// Load the image
|
|
LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec);
|
|
if (image.isNull())
|
|
{
|
|
LLImage::setLastError("Couldn't open the image to be uploaded.");
|
|
return FALSE;
|
|
}
|
|
if (!image->load(filename))
|
|
{
|
|
LLImage::setLastError("Couldn't load the image to be uploaded.");
|
|
return FALSE;
|
|
}
|
|
// Decompress or expand it in a raw image structure
|
|
LLPointer<LLImageRaw> raw_image = new LLImageRaw;
|
|
if (!image->decode(raw_image, 0.0f))
|
|
{
|
|
LLImage::setLastError("Couldn't decode the image to be uploaded.");
|
|
return FALSE;
|
|
}
|
|
// Check the image constraints
|
|
if ((image->getComponents() != 3) && (image->getComponents() != 4))
|
|
{
|
|
LLImage::setLastError("Image files with less than 3 or more than 4 components are not supported.");
|
|
return FALSE;
|
|
}
|
|
// Convert to j2c (JPEG2000) and save the file locally
|
|
LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image);
|
|
if (compressedImage.isNull())
|
|
{
|
|
LLImage::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))
|
|
{
|
|
LLImage::setLastError("Couldn't create the jpeg2000 image for upload.");
|
|
LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL;
|
|
return FALSE;
|
|
}
|
|
return verifyUploadFile(out_filename, codec);
|
|
}
|
|
|
|
static bool positive_power_of_two(int dim)
|
|
{
|
|
return dim > 0 && !(dim & (dim - 1));
|
|
}
|
|
|
|
BOOL LLViewerTextureList::verifyUploadFile(const std::string& out_filename, const U8 codec)
|
|
{
|
|
// Test to see if the encode and save worked
|
|
LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C;
|
|
if (!integrity_test->loadAndValidate( out_filename ))
|
|
{
|
|
LLImage::setLastError(std::string("The ") + ((codec == IMG_CODEC_J2C) ? "" : "created ") + "jpeg2000 image is corrupt: " + LLImage::getLastError());
|
|
LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL;
|
|
return FALSE;
|
|
}
|
|
if (codec == IMG_CODEC_J2C)
|
|
{
|
|
if (integrity_test->getComponents() < 3 || integrity_test->getComponents() > 4)
|
|
{
|
|
LLImage::setLastError("Image files with less than 3 or more than 4 components are not supported.");
|
|
return FALSE;
|
|
}
|
|
else if (!positive_power_of_two(integrity_test->getWidth()) ||
|
|
!positive_power_of_two(integrity_test->getHeight()))
|
|
{
|
|
LLImage::setLastError("The width or height is not a (positive) power of two.");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// note: modifies the argument raw_image!!!!
|
|
LLPointer<LLImageJ2C> LLViewerTextureList::convertToUploadFile(LLPointer<LLImageRaw> raw_image)
|
|
{
|
|
raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT);
|
|
LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C();
|
|
compressedImage->setRate(0.f);
|
|
|
|
if (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;
|
|
}
|
|
|
|
// Returns min setting for TextureMemory (in MB)
|
|
S32Megabytes LLViewerTextureList::getMinVideoRamSetting()
|
|
{
|
|
S32Megabytes system_ram = gSysMemory.getPhysicalMemoryClamped();
|
|
//min texture mem sets to 64M if total physical mem is more than 1.5GB
|
|
return (system_ram > S32Megabytes(1500)) ? S32Megabytes(64) : gMinVideoRam ;
|
|
}
|
|
|
|
//static
|
|
// Returns max setting for TextureMemory (in MB)
|
|
S32Megabytes LLViewerTextureList::getMaxVideoRamSetting(bool get_recommended, float mem_multiplier)
|
|
{
|
|
#if LL_LINUX
|
|
if (gGLManager.mIsIntel && gGLManager.mGLVersion >= 3.f && !gGLManager.mVRAM)
|
|
{
|
|
gGLManager.mVRAM = 512;
|
|
}
|
|
#endif
|
|
S32Megabytes max_texmem;
|
|
if (gGLManager.mVRAM != 0)
|
|
{
|
|
// Treat any card with < 32 MB (shudder) as having 32 MB
|
|
// - it's going to be swapping constantly regardless
|
|
S32Megabytes max_vram(gGLManager.mVRAM);
|
|
max_vram = llmax(max_vram, getMinVideoRamSetting());
|
|
max_texmem = max_vram;
|
|
if (!get_recommended)
|
|
max_texmem *= 2;
|
|
}
|
|
else
|
|
{
|
|
if (!get_recommended)
|
|
{
|
|
max_texmem = (S32Megabytes)512;
|
|
}
|
|
else if (gSavedSettings.getBOOL("NoHardwareProbe")) //did not do hardware detection at startup
|
|
{
|
|
max_texmem = (S32Megabytes)512;
|
|
}
|
|
else
|
|
{
|
|
max_texmem = (S32Megabytes)128;
|
|
}
|
|
|
|
LL_WARNS() << "VRAM amount not detected, defaulting to " << max_texmem << " MB" << LL_ENDL;
|
|
}
|
|
|
|
S32Megabytes system_ram = gSysMemory.getPhysicalMemoryClamped(); // In MB
|
|
//LL_INFOS() << "*** DETECTED " << system_ram << " MB of system memory." << LL_ENDL;
|
|
if (get_recommended)
|
|
max_texmem = llmin(max_texmem, system_ram/2);
|
|
else
|
|
max_texmem = llmin(max_texmem, system_ram);
|
|
|
|
// limit the texture memory to a multiple of the default if we've found some cards to behave poorly otherwise
|
|
max_texmem = llmin(max_texmem, (S32Megabytes) (mem_multiplier * max_texmem));
|
|
|
|
max_texmem = llclamp(max_texmem, getMinVideoRamSetting(), gMaxVideoRam);
|
|
|
|
return max_texmem;
|
|
}
|
|
|
|
const S32Megabytes VIDEO_CARD_FRAMEBUFFER_MEM(12);
|
|
const S32Megabytes MIN_MEM_FOR_NON_TEXTURE(512);
|
|
void LLViewerTextureList::updateMaxResidentTexMem(S32Megabytes mem)
|
|
{
|
|
// Initialize the image pipeline VRAM settings
|
|
S32Megabytes cur_mem(gSavedSettings.getS32("TextureMemory"));
|
|
F32 mem_multiplier = gSavedSettings.getF32("RenderTextureMemoryMultiple");
|
|
S32Megabytes default_mem = getMaxVideoRamSetting(true, mem_multiplier); // recommended default
|
|
if (mem == (S32Bytes)0)
|
|
{
|
|
mem = cur_mem > (S32Bytes)0 ? cur_mem : default_mem;
|
|
}
|
|
else if (mem < (S32Bytes)0)
|
|
{
|
|
mem = default_mem;
|
|
}
|
|
|
|
mem = llclamp(mem, getMinVideoRamSetting(), getMaxVideoRamSetting(false, mem_multiplier));
|
|
if (mem != cur_mem)
|
|
{
|
|
gSavedSettings.setS32("TextureMemory", mem.value());
|
|
return; //listener will re-enter this function
|
|
}
|
|
|
|
// TODO: set available resident texture mem based on use by other subsystems
|
|
// currently max(12MB, VRAM/4) assumed...
|
|
|
|
S32Megabytes vb_mem = mem;
|
|
S32Megabytes fb_mem = llmax(VIDEO_CARD_FRAMEBUFFER_MEM, vb_mem/4);
|
|
mMaxResidentTexMemInMegaBytes = (vb_mem - fb_mem) ; //in MB
|
|
|
|
mMaxTotalTextureMemInMegaBytes = mMaxResidentTexMemInMegaBytes * 2;
|
|
if (mMaxResidentTexMemInMegaBytes > (S32Megabytes)640)
|
|
{
|
|
mMaxTotalTextureMemInMegaBytes -= (mMaxResidentTexMemInMegaBytes / 4);
|
|
}
|
|
|
|
//system mem
|
|
S32Megabytes system_ram = gSysMemory.getPhysicalMemoryClamped();
|
|
|
|
//minimum memory reserved for non-texture use.
|
|
//if system_raw >= 1GB, reserve at least 512MB for non-texture use;
|
|
//otherwise reserve half of the system_ram for non-texture use.
|
|
S32Megabytes min_non_texture_mem = llmin(system_ram / 2, MIN_MEM_FOR_NON_TEXTURE) ;
|
|
|
|
if (mMaxTotalTextureMemInMegaBytes > system_ram - min_non_texture_mem)
|
|
{
|
|
mMaxTotalTextureMemInMegaBytes = system_ram - min_non_texture_mem ;
|
|
}
|
|
|
|
LL_INFOS() << "Total Video Memory set to: " << vb_mem << " MB" << LL_ENDL;
|
|
LL_INFOS() << "Available Texture Memory set to: " << (vb_mem - fb_mem) << " MB" << LL_ENDL;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// static
|
|
void LLViewerTextureList::receiveImageHeader(LLMessageSystem *msg, void **user_data)
|
|
{
|
|
static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ;
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_PROCESS_IMAGES);
|
|
|
|
// Receive image header, copy into image object and decompresses
|
|
// if this is a one-packet image.
|
|
|
|
LLUUID id;
|
|
|
|
char ip_string[256];
|
|
u32_to_ip_string(msg->getSenderIP(),ip_string);
|
|
|
|
U32Bytes received_size ;
|
|
if (msg->getReceiveCompressedSize())
|
|
{
|
|
received_size = (U32Bytes)msg->getReceiveCompressedSize() ;
|
|
}
|
|
else
|
|
{
|
|
received_size = (U32Bytes)msg->getReceiveSize() ;
|
|
}
|
|
// Only used for statistics and texture console.
|
|
gTextureList.sTextureBits += received_size;
|
|
gTextureList.sTexturePackets++;
|
|
|
|
U8 codec;
|
|
U16 packets;
|
|
U32 totalbytes;
|
|
msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, id);
|
|
msg->getU8Fast(_PREHASH_ImageID, _PREHASH_Codec, codec);
|
|
msg->getU16Fast(_PREHASH_ImageID, _PREHASH_Packets, packets);
|
|
msg->getU32Fast(_PREHASH_ImageID, _PREHASH_Size, totalbytes);
|
|
|
|
S32 data_size = msg->getSizeFast(_PREHASH_ImageData, _PREHASH_Data);
|
|
if (!data_size)
|
|
{
|
|
return;
|
|
}
|
|
if (data_size < 0)
|
|
{
|
|
// msg->getSizeFast() is probably trying to tell us there
|
|
// was an error.
|
|
LL_ERRS() << "image header chunk size was negative: "
|
|
<< data_size << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// this buffer gets saved off in the packet list
|
|
U8 *data = new U8[data_size];
|
|
msg->getBinaryDataFast(_PREHASH_ImageData, _PREHASH_Data, data, data_size);
|
|
|
|
LLViewerFetchedTexture *image = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
|
|
if (!image)
|
|
{
|
|
delete [] data;
|
|
return;
|
|
}
|
|
if(log_texture_traffic)
|
|
{
|
|
gTotalTextureBytesPerBoostLevel[image->getBoostLevel()] += received_size ;
|
|
}
|
|
|
|
//image->getLastPacketTimer()->reset();
|
|
bool res = LLAppViewer::getTextureFetch()->receiveImageHeader(msg->getSender(), id, codec, packets, totalbytes, data_size, data);
|
|
if (!res)
|
|
{
|
|
delete[] data;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLViewerTextureList::receiveImagePacket(LLMessageSystem *msg, void **user_data)
|
|
{
|
|
static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ;
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_PROCESS_IMAGES);
|
|
|
|
// Receives image packet, copy into image object,
|
|
// checks if all packets received, decompresses if so.
|
|
|
|
LLUUID id;
|
|
U16 packet_num;
|
|
|
|
char ip_string[256];
|
|
u32_to_ip_string(msg->getSenderIP(),ip_string);
|
|
|
|
U32Bytes received_size ;
|
|
if (msg->getReceiveCompressedSize())
|
|
{
|
|
received_size = (U32Bytes)msg->getReceiveCompressedSize() ;
|
|
}
|
|
else
|
|
{
|
|
received_size = (U32Bytes)msg->getReceiveSize() ;
|
|
}
|
|
gTextureList.sTextureBits += received_size;
|
|
gTextureList.sTexturePackets++;
|
|
|
|
//llprintline("Start decode, image header...");
|
|
msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, id);
|
|
msg->getU16Fast(_PREHASH_ImageID, _PREHASH_Packet, packet_num);
|
|
S32 data_size = msg->getSizeFast(_PREHASH_ImageData, _PREHASH_Data);
|
|
|
|
if (!data_size)
|
|
{
|
|
return;
|
|
}
|
|
if (data_size < 0)
|
|
{
|
|
// msg->getSizeFast() is probably trying to tell us there
|
|
// was an error.
|
|
LL_ERRS() << "image data chunk size was negative: "
|
|
<< data_size << LL_ENDL;
|
|
return;
|
|
}
|
|
if (data_size > MTUBYTES)
|
|
{
|
|
LL_ERRS() << "image data chunk too large: " << data_size << " bytes" << LL_ENDL;
|
|
return;
|
|
}
|
|
U8 *data = new U8[data_size];
|
|
msg->getBinaryDataFast(_PREHASH_ImageData, _PREHASH_Data, data, data_size);
|
|
|
|
LLViewerFetchedTexture *image = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
|
|
if (!image)
|
|
{
|
|
delete [] data;
|
|
return;
|
|
}
|
|
if(log_texture_traffic)
|
|
{
|
|
gTotalTextureBytesPerBoostLevel[image->getBoostLevel()] += received_size ;
|
|
}
|
|
|
|
//image->getLastPacketTimer()->reset();
|
|
bool res = LLAppViewer::getTextureFetch()->receiveImagePacket(msg->getSender(), id, packet_num, data_size, data);
|
|
if (!res)
|
|
{
|
|
delete[] data;
|
|
}
|
|
}
|
|
|
|
|
|
// We've been that the asset server does not contain the requested image id.
|
|
// static
|
|
void LLViewerTextureList::processImageNotInDatabase(LLMessageSystem *msg,void **user_data)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_PROCESS_IMAGES);
|
|
LLUUID image_id;
|
|
msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id);
|
|
|
|
LLViewerFetchedTexture* image = gTextureList.findImage( image_id );
|
|
if( image )
|
|
{
|
|
LL_WARNS() << "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)
|
|
{
|
|
// 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)
|
|
{
|
|
// 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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
if (!imagep) return NULL;
|
|
|
|
imagep->setAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
//don't compress UI images
|
|
imagep->getGLTexture()->setAllowCompression(false);
|
|
|
|
//all UI images are non-deletable
|
|
imagep->setNoDelete();
|
|
|
|
LLUIImagePtr new_imagep = new LLUIImage(name, imagep);
|
|
new_imagep->setScaleStyle(scale_style);
|
|
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)
|
|
{
|
|
// 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 )
|
|
{
|
|
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<LLUIImage::EScaleStyle> : public TypeValuesHelper<LLUIImage::EScaleStyle>
|
|
{
|
|
static void declareValues()
|
|
{
|
|
declare("scale_inner", LLUIImage::SCALE_INNER);
|
|
declare("scale_outer", LLUIImage::SCALE_OUTER);
|
|
}
|
|
};
|
|
}
|
|
|
|
struct UIImageDeclaration : public LLInitParam::Block<UIImageDeclaration>
|
|
{
|
|
Mandatory<std::string> name;
|
|
Optional<std::string> file_name;
|
|
Optional<bool> preload;
|
|
Optional<LLRect> scale;
|
|
Optional<LLRect> clip;
|
|
Optional<bool> use_mips;
|
|
Optional<LLUIImage::EScaleStyle> 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<UIImageDeclarations>
|
|
{
|
|
Mandatory<S32> version;
|
|
Multiple<UIImageDeclaration> textures;
|
|
|
|
UIImageDeclarations()
|
|
: version("version"),
|
|
textures("texture")
|
|
{}
|
|
};
|
|
|
|
bool LLUIImageList::initFromFile()
|
|
{
|
|
// 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<std::string> textures_paths =
|
|
gDirUtilp->findSkinnedFilenames(LLDir::TEXTURES, "textures.xml", LLDir::ALL_SKINS);
|
|
std::vector<std::string>::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<std::string, UIImageDeclaration> merged_declarations;
|
|
for (LLInitParam::ParamIterator<UIImageDeclaration>::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<std::string, UIImageDeclaration>::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 (cur_pass == PASS_DECODE_NOW && !gSavedSettings.getBOOL("NoPreload"))
|
|
{
|
|
gTextureList.decodeAllImages(10.f); // decode preloaded images
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|