This is accessed by different threads. Other globals like it might also be accessed by different threads, this has to be investigated.
1756 lines
49 KiB
C++
1756 lines
49 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"
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL;
|
|
|
|
U32 LLViewerTextureList::sTextureBits = 0;
|
|
U32 LLViewerTextureList::sTexturePackets = 0;
|
|
S32 LLViewerTextureList::sNumImages = 0;
|
|
LLStat LLViewerTextureList::sNumImagesStat(32, TRUE);
|
|
LLStat LLViewerTextureList::sNumRawImagesStat(32, TRUE);
|
|
LLStat LLViewerTextureList::sGLTexMemStat(32, TRUE);
|
|
LLStat LLViewerTextureList::sGLBoundMemStat(32, TRUE);
|
|
LLStat LLViewerTextureList::sRawMemStat(32, TRUE);
|
|
LLStat LLViewerTextureList::sFormattedMemStat(32, TRUE);
|
|
|
|
LLViewerTextureList gTextureList;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
LLViewerTextureList::LLViewerTextureList()
|
|
: mForceResetTextureStats(FALSE),
|
|
mUpdateStats(FALSE),
|
|
mMaxResidentTexMemInMegaBytes(0),
|
|
mMaxTotalTextureMemInMegaBytes(0),
|
|
mInitialized(FALSE)
|
|
{
|
|
}
|
|
|
|
void LLViewerTextureList::init()
|
|
{
|
|
mInitialized = TRUE ;
|
|
sNumImages = 0;
|
|
mUpdateStats = TRUE;
|
|
mMaxResidentTexMemInMegaBytes = 0;
|
|
mMaxTotalTextureMemInMegaBytes = 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(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", MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI);
|
|
|
|
// Set the "white" image
|
|
LLViewerFetchedTexture::sWhiteImagep = LLViewerTextureManager::getFetchedTextureFromFile("white.tga", MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI);
|
|
|
|
LLUIImageList* image_list = LLUIImageList::getInstance();
|
|
|
|
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, TRUE);
|
|
LLViewerTextureManager::getFetchedTexture(IMG_SMOKE_POOF, TRUE);
|
|
LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI);
|
|
if (image)
|
|
{
|
|
image->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
mImagePreloads.insert(image);
|
|
}
|
|
image = LLViewerTextureManager::getFetchedTextureFromFile("noentrylines.j2c"/*"world/NoEntryLines.png"*/, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI);
|
|
if (image)
|
|
{
|
|
image->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
mImagePreloads.insert(image);
|
|
}
|
|
image = LLViewerTextureManager::getFetchedTextureFromFile("noentrypasslines.j2c"/*"world/NoEntryPassLines.png"*/, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI);
|
|
if (image)
|
|
{
|
|
image->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
mImagePreloads.insert(image);
|
|
}
|
|
image = LLViewerTextureManager::getFetchedTexture(DEFAULT_WATER_NORMAL, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI);
|
|
if (image)
|
|
{
|
|
image->setAddressMode(LLTexUnit::TAM_WRAP);
|
|
mImagePreloads.insert(image);
|
|
}
|
|
image = LLViewerTextureManager::getFetchedTextureFromFile("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903.j2c"/*"transparent.j2c"*/, 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);
|
|
}
|
|
|
|
}
|
|
|
|
static std::string get_texture_list_name()
|
|
{
|
|
//return std::string("texture_list_") + gSavedSettings.getString("LoginLocation") + ".xml";
|
|
bool login_last = gSavedSettings.getBOOL("LoginLastLocation");
|
|
return std::string("texture_list_") + (login_last?"last":"home") + ".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 = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, get_texture_list_name());
|
|
llifstream file;
|
|
file.open(filename);
|
|
if (file.is_open())
|
|
{
|
|
LLSDSerialize::fromXML(imagelist, file);
|
|
}
|
|
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, MIPMAP_TRUE, LLViewerTexture::BOOST_NONE, texture_type);
|
|
if (image)
|
|
{
|
|
image->addTextureStats((F32)pixel_area);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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)
|
|
{
|
|
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()
|
|
{
|
|
llinfos << "LLViewerTextureList::dump()" << llendl;
|
|
for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture* image = *it;
|
|
|
|
llinfos << "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"
|
|
<< llendl;
|
|
}
|
|
}
|
|
|
|
void LLViewerTextureList::destroyGL(BOOL save_state)
|
|
{
|
|
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,
|
|
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())
|
|
{
|
|
llwarns << "Failed to find local image file: " << filename << llendl;
|
|
return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, TRUE, LLViewerTexture::BOOST_UI);
|
|
}
|
|
|
|
std::string url = "file://" + full_path;
|
|
|
|
return getImageFromUrl(url, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id);
|
|
}
|
|
|
|
LLViewerFetchedTexture* LLViewerTextureList::getImageFromUrl(const std::string& url,
|
|
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, TRUE, LLViewerTexture::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())
|
|
{
|
|
switch(texture_type)
|
|
{
|
|
case LLViewerTexture::FETCHED_TEXTURE:
|
|
imagep = new LLViewerFetchedTexture(url, new_id, usemipmaps);
|
|
break ;
|
|
case LLViewerTexture::LOD_TEXTURE:
|
|
imagep = new LLViewerLODTexture(url, new_id, usemipmaps);
|
|
break ;
|
|
default:
|
|
llerrs << "Invalid texture type " << texture_type << llendl ;
|
|
}
|
|
|
|
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,
|
|
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, TRUE, LLViewerTexture::BOOST_UI));
|
|
}
|
|
|
|
LLPointer<LLViewerFetchedTexture> imagep = findImage(image_id);
|
|
|
|
if (imagep.isNull())
|
|
{
|
|
imagep = createImage(image_id, 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,
|
|
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, request_from_host, usemipmaps);
|
|
break ;
|
|
case LLViewerTexture::LOD_TEXTURE:
|
|
imagep = new LLViewerLODTexture(image_id, request_from_host, usemipmaps);
|
|
break ;
|
|
default:
|
|
llerrs << "Invalid texture type " << texture_type << llendl ;
|
|
}
|
|
|
|
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)
|
|
{
|
|
llassert(image);
|
|
if (image->isInImageList())
|
|
{
|
|
llerrs << "LLViewerTextureList::addImageToList - Image already in list" << llendl;
|
|
}
|
|
if((mImageList.insert(image)).second != true)
|
|
{
|
|
llerrs << "Error happens when insert image to mImageList!" << llendl ;
|
|
}
|
|
|
|
image->setInImageList(TRUE) ;
|
|
}
|
|
|
|
void LLViewerTextureList::removeImageFromList(LLViewerFetchedTexture *image)
|
|
{
|
|
llassert(image);
|
|
if (!image->isInImageList())
|
|
{
|
|
llinfos << "RefCount: " << image->getNumRefs() << llendl ;
|
|
uuid_map_t::iterator iter = mUUIDMap.find(image->getID());
|
|
if(iter == mUUIDMap.end() || iter->second != image)
|
|
{
|
|
llinfos << "Image is not in mUUIDMap!" << llendl ;
|
|
}
|
|
llerrs << "LLViewerTextureList::removeImageFromList - Image not in list" << llendl;
|
|
}
|
|
if(mImageList.erase(image) != 1)
|
|
{
|
|
llerrs << "Error happens when remove image from mImageList!" << llendl ;
|
|
}
|
|
|
|
image->setInImageList(FALSE) ;
|
|
}
|
|
|
|
void LLViewerTextureList::addImage(LLViewerFetchedTexture *new_image)
|
|
{
|
|
if (!new_image)
|
|
{
|
|
llwarning("No image to add to image list", 0);
|
|
return;
|
|
}
|
|
LLUUID image_id = new_image->getID();
|
|
|
|
LLViewerFetchedTexture *image = findImage(image_id);
|
|
if (image)
|
|
{
|
|
llwarns << "Image with ID " << image_id << " already in list" << llendl;
|
|
}
|
|
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 LLFastTimer::DeclareTimer FTM_IMAGE_MARK_DIRTY("Dirty Images");
|
|
|
|
void LLViewerTextureList::updateImages(F32 max_time)
|
|
{
|
|
LLAppViewer::getTextureFetch()->setTextureBandwidth(LLViewerStats::getInstance()->mTextureKBitStat.getMeanPerSec());
|
|
|
|
S32 global_raw_memory;
|
|
{
|
|
global_raw_memory = *AIAccess<S32>(LLImageRaw::sGlobalRawMemory);
|
|
}
|
|
sNumImagesStat.addValue(sNumImages);
|
|
sNumRawImagesStat.addValue(LLImageRaw::sRawImageCount);
|
|
sGLTexMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageGL::sGlobalTextureMemoryInBytes));
|
|
sGLBoundMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageGL::sBoundTextureMemoryInBytes));
|
|
sRawMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(global_raw_memory));
|
|
sFormattedMemStat.addValue((F32)BYTES_TO_MEGA_BYTES(LLImageFormatted::sGlobalFormattedMemory));
|
|
|
|
updateImagesDecodePriorities();
|
|
|
|
F32 total_max_time = max_time;
|
|
max_time -= updateImagesFetchTextures(max_time);
|
|
|
|
max_time = llmax(max_time, total_max_time*.50f); // at least 50% of max_time
|
|
max_time -= updateImagesCreateTextures(max_time);
|
|
|
|
if (!mDirtyTextureList.empty())
|
|
{
|
|
LLFastTimer t(LLFastTimer::FTM_IMAGE_MARK_DIRTY);
|
|
gPipeline.dirtyPoolObjectTextures(mDirtyTextureList);
|
|
mDirtyTextureList.clear();
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
//Required for old media system
|
|
if (!gNoRender && !gGLManager.mIsDisabled)
|
|
{
|
|
LLViewerMedia::updateMedia();
|
|
}
|
|
updateImagesUpdateStats();
|
|
}
|
|
|
|
void LLViewerTextureList::updateImagesDecodePriorities()
|
|
{
|
|
// Update the decode priority for N images each frame
|
|
{
|
|
const size_t max_update_count = llmin((S32) (1024*gFrameIntervalSeconds) + 1, 32); //target 1024 textures per second
|
|
S32 update_counter = llmin(max_update_count, mUUIDMap.size()/10);
|
|
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
|
|
//
|
|
const F32 LAZY_FLUSH_TIMEOUT = 30.f; // stop decoding
|
|
const F32 MAX_INACTIVE_TIME = 50.f; // actually delete
|
|
S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, 1 for local reference
|
|
if (imagep->hasCallbacks())
|
|
{
|
|
min_refs++; // Add an extra reference if we're on the loaded callback list
|
|
}
|
|
S32 num_refs = imagep->getNumRefs();
|
|
if (num_refs == min_refs)
|
|
{
|
|
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 (imagep->getLastReferencedTimer()->getElapsedTimeF32() > MAX_INACTIVE_TIME)
|
|
{
|
|
imagep->setDeletionCandidate() ;
|
|
}
|
|
continue ;
|
|
}
|
|
else
|
|
{
|
|
imagep->getLastReferencedTimer()->reset();
|
|
|
|
//reset texture state.
|
|
imagep->setInactive() ;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
update_counter--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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)
|
|
{
|
|
llwarns << "TAT: get_image_type() type_from_host doesn't match type_from_boost"
|
|
<< " host " << target_host
|
|
<< " boost " << imagep->getBoostLevel()
|
|
<< " imageid " << imagep->getID()
|
|
<< llendl;
|
|
imagep->dump();
|
|
}
|
|
return type_from_host;
|
|
}
|
|
*/
|
|
//static LLFastTimer::DeclareTimer FTM_IMAGE_CREATE("Create Images");
|
|
|
|
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).
|
|
//
|
|
LLFastTimer t(LLFastTimer::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);
|
|
mImageList.insert(imagep);
|
|
imagep->setInImageList(TRUE) ;
|
|
|
|
return ;
|
|
}
|
|
|
|
F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time)
|
|
{
|
|
LLTimer image_op_timer;
|
|
|
|
// Update the decode priority for N images each frame
|
|
// Make a list with 32 high priority entries + 256 cycled entries
|
|
const size_t max_priority_count = llmin((S32) (256*10.f*gFrameIntervalSeconds)+1, 32);
|
|
const size_t max_update_count = llmin((S32) (1024*10.f*gFrameIntervalSeconds)+1, 256);
|
|
|
|
// 32 high priority entries
|
|
typedef std::vector<LLViewerFetchedTexture*> entries_list_t;
|
|
entries_list_t entries;
|
|
size_t update_counter = llmin(max_priority_count, mImageList.size());
|
|
image_priority_list_t::iterator iter1 = mImageList.begin();
|
|
while(update_counter > 0)
|
|
{
|
|
entries.push_back(*iter1);
|
|
|
|
++iter1;
|
|
update_counter--;
|
|
}
|
|
|
|
// 256 cycled entries
|
|
update_counter = llmin(max_update_count, mUUIDMap.size());
|
|
if(update_counter > 0)
|
|
{
|
|
uuid_map_t::iterator iter2 = mUUIDMap.upper_bound(mLastFetchUUID);
|
|
uuid_map_t::iterator iter2p = iter2;
|
|
while(update_counter > 0)
|
|
{
|
|
if (iter2 == mUUIDMap.end())
|
|
{
|
|
iter2 = mUUIDMap.begin();
|
|
}
|
|
entries.push_back(iter2->second);
|
|
iter2p = iter2++;
|
|
update_counter--;
|
|
}
|
|
|
|
mLastFetchUUID = iter2p->first;
|
|
}
|
|
|
|
S32 fetch_count = 0;
|
|
S32 min_count = max_priority_count + max_update_count/4;
|
|
for (entries_list_t::iterator iter3 = entries.begin();
|
|
iter3 != entries.end(); )
|
|
{
|
|
LLViewerFetchedTexture* imagep = *iter3++;
|
|
|
|
bool fetching = imagep->updateFetch();
|
|
if (fetching)
|
|
{
|
|
fetch_count++;
|
|
}
|
|
if (min_count <= 0 && image_op_timer.getElapsedTimeF32() > max_time)
|
|
{
|
|
break;
|
|
}
|
|
min_count--;
|
|
}
|
|
//if (fetch_count == 0)
|
|
//{
|
|
// gDebugTimers[0].pause();
|
|
//}
|
|
//else
|
|
//{
|
|
// gDebugTimers[0].unpause();
|
|
//}
|
|
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) ;
|
|
}
|
|
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);
|
|
mImageList.insert(imagep);
|
|
imagep->setInImageList(TRUE) ;
|
|
}
|
|
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)
|
|
{
|
|
// First, load the image.
|
|
LLPointer<LLImageRaw> raw_image = new LLImageRaw;
|
|
|
|
switch (codec)
|
|
{
|
|
case IMG_CODEC_BMP:
|
|
{
|
|
LLPointer<LLImageBMP> bmp_image = new LLImageBMP;
|
|
|
|
if (!bmp_image->load(filename))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!bmp_image->decode(raw_image, 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
case IMG_CODEC_TGA:
|
|
{
|
|
LLPointer<LLImageTGA> tga_image = new LLImageTGA;
|
|
|
|
if (!tga_image->load(filename))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!tga_image->decode(raw_image))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( (tga_image->getComponents() != 3) &&
|
|
(tga_image->getComponents() != 4) )
|
|
{
|
|
tga_image->setLastError( "Image files with less than 3 or more than 4 components are not supported." );
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
case IMG_CODEC_JPEG:
|
|
{
|
|
LLPointer<LLImageJPEG> jpeg_image = new LLImageJPEG;
|
|
|
|
if (!jpeg_image->load(filename))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!jpeg_image->decode(raw_image, 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
case IMG_CODEC_PNG:
|
|
{
|
|
LLPointer<LLImagePNG> png_image = new LLImagePNG;
|
|
|
|
if (!png_image->load(filename))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!png_image->decode(raw_image, 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image);
|
|
|
|
if( !compressedImage->save(out_filename) )
|
|
{
|
|
llinfos << "Couldn't create output file " << out_filename << llendl;
|
|
return FALSE;
|
|
}
|
|
|
|
// test to see if the encode and save worked.
|
|
LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C;
|
|
if( !integrity_test->loadAndValidate( out_filename ) )
|
|
{
|
|
llinfos << "Image: " << out_filename << " is corrupt." << llendl;
|
|
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);
|
|
|
|
compressedImage->encode(raw_image, 0.0f);
|
|
|
|
return compressedImage;
|
|
}
|
|
|
|
// Returns min setting for TextureMemory (in MB)
|
|
S32 LLViewerTextureList::getMinVideoRamSetting()
|
|
{
|
|
S32 system_ram = (S32)BYTES_TO_MEGA_BYTES(gSysMemory.getPhysicalMemoryClamped());
|
|
if (system_ram > 2000)
|
|
return 128;
|
|
else if (system_ram > 1000)
|
|
return 64;
|
|
else
|
|
return MIN_VIDEO_RAM_IN_MEGA_BYTES;
|
|
}
|
|
|
|
//static
|
|
// Returns max setting for TextureMemory (in MB)
|
|
S32 LLViewerTextureList::getMaxVideoRamSetting(bool get_recommended)
|
|
{
|
|
S32 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
|
|
S32 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 = 128;
|
|
else
|
|
max_texmem = 512;
|
|
llwarns << "VRAM amount not detected, defaulting to " << max_texmem << " MB" << llendl;
|
|
}
|
|
|
|
S32 system_ram = (S32)BYTES_TO_MEGA_BYTES(gSysMemory.getPhysicalMemoryClamped()); // In MB
|
|
//llinfos << "*** DETECTED " << system_ram << " MB of system memory." << llendl;
|
|
if (get_recommended)
|
|
max_texmem = llmin(max_texmem, (S32)(system_ram/2));
|
|
else
|
|
max_texmem = llmin(max_texmem, (S32)(system_ram));
|
|
|
|
max_texmem = llclamp(max_texmem, getMinVideoRamSetting(), MAX_VIDEO_RAM_IN_MEGA_BYTES);
|
|
|
|
return max_texmem;
|
|
}
|
|
|
|
const S32 VIDEO_CARD_FRAMEBUFFER_MEM = 12; // MB
|
|
const S32 MIN_MEM_FOR_NON_TEXTURE = 512 ; //MB
|
|
void LLViewerTextureList::updateMaxResidentTexMem(S32 mem)
|
|
{
|
|
// Initialize the image pipeline VRAM settings
|
|
S32 cur_mem = gSavedSettings.getS32("TextureMemory");
|
|
F32 mem_multiplier = gSavedSettings.getF32("RenderTextureMemoryMultiple");
|
|
S32 default_mem = getMaxVideoRamSetting(true); // recommended default
|
|
if (mem == 0)
|
|
{
|
|
mem = cur_mem > 0 ? cur_mem : default_mem;
|
|
}
|
|
else if (mem < 0)
|
|
{
|
|
mem = default_mem;
|
|
}
|
|
|
|
// limit the texture memory to a multiple of the default if we've found some cards to behave poorly otherwise
|
|
mem = llmin(mem, (S32) (mem_multiplier * (F32) default_mem));
|
|
|
|
mem = llclamp(mem, getMinVideoRamSetting(), getMaxVideoRamSetting());
|
|
if (mem != cur_mem)
|
|
{
|
|
gSavedSettings.setS32("TextureMemory", mem);
|
|
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...
|
|
|
|
S32 vb_mem = mem;
|
|
S32 fb_mem = llmax(VIDEO_CARD_FRAMEBUFFER_MEM, vb_mem/4);
|
|
mMaxResidentTexMemInMegaBytes = (vb_mem - fb_mem) ; //in MB
|
|
|
|
mMaxTotalTextureMemInMegaBytes = mMaxResidentTexMemInMegaBytes * 2;
|
|
if (mMaxResidentTexMemInMegaBytes > 640)
|
|
{
|
|
mMaxTotalTextureMemInMegaBytes -= (mMaxResidentTexMemInMegaBytes >> 2);
|
|
}
|
|
|
|
//system mem
|
|
S32 system_ram = (S32)BYTES_TO_MEGA_BYTES(gSysMemory.getPhysicalMemoryClamped()); // In MB
|
|
|
|
//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.
|
|
S32 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 ;
|
|
}
|
|
|
|
llinfos << "Total Video Memory set to: " << vb_mem << " MB" << llendl;
|
|
llinfos << "Available Texture Memory set to: " << (vb_mem - fb_mem) << " MB" << llendl;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// static
|
|
void LLViewerTextureList::receiveImageHeader(LLMessageSystem *msg, void **user_data)
|
|
{
|
|
static LLCachedControl<bool> log_texture_traffic("LogTextureNetworkTraffic",false) ;
|
|
|
|
LLFastTimer t(LLFastTimer::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);
|
|
|
|
U32 received_size ;
|
|
if (msg->getReceiveCompressedSize())
|
|
{
|
|
received_size = msg->getReceiveCompressedSize() ;
|
|
}
|
|
else
|
|
{
|
|
received_size = msg->getReceiveSize() ;
|
|
}
|
|
gTextureList.sTextureBits += received_size * 8;
|
|
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.
|
|
llerrs << "image header chunk size was negative: "
|
|
<< data_size << llendl;
|
|
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, TRUE, LLViewerTexture::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("LogTextureNetworkTraffic",FALSE) ;
|
|
|
|
LLMemType mt1(LLMemType::MTYPE_APPFMTIMAGE);
|
|
LLFastTimer t(LLFastTimer::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);
|
|
|
|
U32 received_size ;
|
|
if (msg->getReceiveCompressedSize())
|
|
{
|
|
received_size = msg->getReceiveCompressedSize() ;
|
|
}
|
|
else
|
|
{
|
|
received_size = msg->getReceiveSize() ;
|
|
}
|
|
gTextureList.sTextureBits += received_size * 8;
|
|
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.
|
|
llerrs << "image data chunk size was negative: "
|
|
<< data_size << llendl;
|
|
return;
|
|
}
|
|
if (data_size > MTUBYTES)
|
|
{
|
|
llerrs << "image data chunk too large: " << data_size << " bytes" << llendl;
|
|
return;
|
|
}
|
|
U8 *data = new U8[data_size];
|
|
msg->getBinaryDataFast(_PREHASH_ImageData, _PREHASH_Data, data, data_size);
|
|
|
|
LLViewerFetchedTexture *image = LLViewerTextureManager::getFetchedTexture(id, TRUE, LLViewerTexture::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)
|
|
{
|
|
LLFastTimer t(LLFastTimer::FTM_PROCESS_IMAGES);
|
|
LLUUID image_id;
|
|
msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id);
|
|
|
|
LLViewerFetchedTexture* image = gTextureList.findImage( image_id );
|
|
if( image )
|
|
{
|
|
image->setIsMissingAsset();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//static
|
|
const U32 SIXTEEN_MEG = 0x1000000;
|
|
S32 LLViewerTextureList::calcMaxTextureRAM()
|
|
{
|
|
// Decide the maximum amount of RAM we should allow the user to allocate to texture cache
|
|
LLMemoryInfo memory_info;
|
|
U32 available_memory = memory_info.getPhysicalMemoryClamped();
|
|
|
|
clamp_rescale((F32)available_memory,
|
|
(F32)(SIXTEEN_MEG * 16),
|
|
(F32)U32_MAX,
|
|
(F32)(SIXTEEN_MEG * 4),
|
|
(F32)(U32_MAX >> 1));
|
|
return available_memory;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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;
|
|
return loadUIImageByID(image_id, use_mips, scale_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;
|
|
return loadUIImageByName(image_name, image_name, use_mips, scale_rect, (LLViewerTexture::EBoostLevel)priority);
|
|
}
|
|
|
|
LLUIImagePtr LLUIImageList::loadUIImageByName(const std::string& name, const std::string& filename,
|
|
BOOL use_mips, const LLRect& scale_rect, LLViewerTexture::EBoostLevel boost_priority )
|
|
{
|
|
if (boost_priority == LLViewerTexture::BOOST_NONE)
|
|
{
|
|
boost_priority = LLViewerTexture::BOOST_UI;
|
|
}
|
|
LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTextureFromFile(filename, MIPMAP_NO, boost_priority);
|
|
return loadUIImage(imagep, name, use_mips, scale_rect);
|
|
}
|
|
|
|
LLUIImagePtr LLUIImageList::loadUIImageByID(const LLUUID& id,
|
|
BOOL use_mips, const LLRect& scale_rect, LLViewerTexture::EBoostLevel boost_priority)
|
|
{
|
|
if (boost_priority == LLViewerTexture::BOOST_NONE)
|
|
{
|
|
boost_priority = LLViewerTexture::BOOST_UI;
|
|
}
|
|
LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTexture(id, MIPMAP_NO, boost_priority);
|
|
return loadUIImage(imagep, id.asString(), use_mips, scale_rect);
|
|
}
|
|
|
|
LLUIImagePtr LLUIImageList::loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, BOOL use_mips, const LLRect& scale_rect)
|
|
{
|
|
if (!imagep) return NULL;
|
|
|
|
imagep->setAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
//all UI images are non-deletable
|
|
imagep->setNoDelete();
|
|
|
|
LLUIImagePtr new_imagep = new LLUIImage(name, imagep);
|
|
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() == LLViewerTexture::BOOST_UI)
|
|
{
|
|
LLUIImageLoadData* datap = new LLUIImageLoadData;
|
|
datap->mImageName = name;
|
|
datap->mImageScaleRegion = scale_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)
|
|
{
|
|
// look for existing image
|
|
uuid_ui_image_map_t::iterator found_it = mUIImages.find(name);
|
|
if (found_it != mUIImages.end())
|
|
{
|
|
// image already loaded!
|
|
llerrs << "UI Image " << name << " already loaded." << llendl;
|
|
}
|
|
|
|
return loadUIImageByName(name, filename, use_mips, scale_rect);
|
|
}
|
|
|
|
//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;
|
|
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 clip_x = (F32)src_vi->getOriginalWidth() / (F32)src_vi->getFullWidth();
|
|
F32 clip_y = (F32)src_vi->getOriginalHeight() / (F32)src_vi->getFullHeight();
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*struct UIImageDeclaration : public LLInitParam::Block<UIImageDeclaration>
|
|
{
|
|
Mandatory<std::string> name;
|
|
Optional<std::string> file_name;
|
|
Optional<bool> preload;
|
|
Optional<LLRect> scale;
|
|
Optional<bool> use_mips;
|
|
|
|
UIImageDeclaration()
|
|
: name("name"),
|
|
file_name("file_name"),
|
|
preload("preload", false),
|
|
scale("scale"),
|
|
use_mips("use_mips", false)
|
|
{}
|
|
};
|
|
|
|
struct UIImageDeclarations : public LLInitParam::Block<UIImageDeclarations>
|
|
{
|
|
Mandatory<S32> version;
|
|
Multiple<UIImageDeclaration> textures;
|
|
|
|
UIImageDeclarations()
|
|
: version("version"),
|
|
textures("texture")
|
|
{}
|
|
};
|
|
|
|
bool LLUIImageList::initFromFile()
|
|
{
|
|
// construct path to canonical textures.xml in default skin dir
|
|
std::string base_file_path = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "default", "textures", "textures.xml");
|
|
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLXMLNode::parseFile(base_file_path, root, NULL))
|
|
{
|
|
llwarns << "Unable to parse UI image list file " << base_file_path << llendl;
|
|
return false;
|
|
}
|
|
if (!root->hasAttribute("version"))
|
|
{
|
|
llwarns << "No valid version number in UI image list file " << base_file_path << llendl;
|
|
return false;
|
|
}
|
|
|
|
UIImageDeclarations images;
|
|
LLXUIParser parser;
|
|
parser.readXUI(root, images, base_file_path);
|
|
|
|
// add components defined in current skin
|
|
std::string skin_update_path = gDirUtilp->getSkinDir()
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures"
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures.xml";
|
|
LLXMLNodePtr update_root;
|
|
if (skin_update_path != base_file_path
|
|
&& LLXMLNode::parseFile(skin_update_path, update_root, NULL))
|
|
{
|
|
parser.readXUI(update_root, images, skin_update_path);
|
|
}
|
|
|
|
// add components defined in user override of current skin
|
|
skin_update_path = gDirUtilp->getUserSkinDir()
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures"
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures.xml";
|
|
if (skin_update_path != base_file_path
|
|
&& LLXMLNode::parseFile(skin_update_path, update_root, NULL))
|
|
{
|
|
parser.readXUI(update_root, images, skin_update_path);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (cur_pass == PASS_DECODE_NOW && !gSavedSettings.getBOOL("NoPreload"))
|
|
{
|
|
gTextureList.decodeAllImages(10.f); // decode preloaded images
|
|
}
|
|
}
|
|
return true;
|
|
}*/
|
|
bool LLUIImageList::initFromFile()
|
|
{
|
|
// construct path to canonical textures.xml in default skin dir
|
|
std::string base_file_path = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "default", "textures", "textures.xml");
|
|
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLXMLNode::parseFile(base_file_path, root, NULL))
|
|
{
|
|
llwarns << "Unable to parse UI image list file " << base_file_path << llendl;
|
|
return false;
|
|
}
|
|
|
|
if (!root->hasAttribute("version"))
|
|
{
|
|
llwarns << "No valid version number in UI image list file " << base_file_path << llendl;
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::string> paths;
|
|
// path to current selected skin
|
|
paths.push_back(gDirUtilp->getSkinDir()
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures"
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures.xml");
|
|
// path to user overrides on current skin
|
|
paths.push_back(gDirUtilp->getUserSkinDir()
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures"
|
|
+ gDirUtilp->getDirDelimiter()
|
|
+ "textures.xml");
|
|
|
|
// apply skinned xml files incrementally
|
|
for(std::vector<std::string>::iterator path_it = paths.begin();
|
|
path_it != paths.end();
|
|
++path_it)
|
|
{
|
|
// don't reapply base file to itself
|
|
if (!path_it->empty() && (*path_it) != base_file_path)
|
|
{
|
|
LLXMLNodePtr update_root;
|
|
if (LLXMLNode::parseFile(*path_it, update_root, NULL))
|
|
{
|
|
LLXMLNode::updateNode(root, update_root);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum
|
|
{
|
|
PASS_DECODE_NOW,
|
|
PASS_DECODE_LATER,
|
|
NUM_PASSES
|
|
};
|
|
|
|
for (S32 pass = PASS_DECODE_NOW; pass < NUM_PASSES; pass++)
|
|
{
|
|
LLXMLNodePtr child_nodep = root->getFirstChild();
|
|
while(child_nodep.notNull())
|
|
{
|
|
std::string image_name;
|
|
child_nodep->getAttributeString("name", image_name);
|
|
|
|
std::string file_name = image_name;
|
|
LLRect scale_rect;
|
|
BOOL use_mip_maps = FALSE;
|
|
|
|
BOOL preload = FALSE;
|
|
child_nodep->getAttributeBOOL("preload", preload);
|
|
|
|
// load high priority textures on first pass (to kick off decode)
|
|
if (preload)
|
|
{
|
|
if (pass == PASS_DECODE_LATER)
|
|
{
|
|
child_nodep = child_nodep->getNextSibling();
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pass == PASS_DECODE_NOW)
|
|
{
|
|
child_nodep = child_nodep->getNextSibling();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
child_nodep->getAttributeString("file_name", file_name);
|
|
child_nodep->getAttributeBOOL("use_mips", use_mip_maps);
|
|
|
|
child_nodep->getAttributeS32("scale_left", scale_rect.mLeft);
|
|
child_nodep->getAttributeS32("scale_right", scale_rect.mRight);
|
|
child_nodep->getAttributeS32("scale_bottom", scale_rect.mBottom);
|
|
child_nodep->getAttributeS32("scale_top", scale_rect.mTop);
|
|
|
|
preloadUIImage(image_name, file_name, use_mip_maps, scale_rect);
|
|
|
|
child_nodep = child_nodep->getNextSibling();
|
|
}
|
|
|
|
if (pass == PASS_DECODE_NOW && !gSavedSettings.getBOOL("NoPreload"))
|
|
{
|
|
gTextureList.decodeAllImages(10.f); // decode preloaded images
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|