/** * @file llviewerimagelist.cpp * @brief Object for managing the list of images within a region * * $LicenseInfo:firstyear=2000&license=viewergpl$ * * Copyright (c) 2000-2007, 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 #include "llviewerimagelist.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 "llmediaengine.h" #include "llsdserialize.h" #include "llsys.h" #include "llvfs.h" #include "llvfile.h" #include "llvfsthread.h" #include "message.h" #include "llagent.h" #include "lltexturecache.h" #include "lltexturefetch.h" #include "llviewercontrol.h" #include "llviewerimage.h" #include "llviewerregion.h" #include "pipeline.h" #include "viewer.h" //////////////////////////////////////////////////////////////////////////// void (*LLViewerImageList::sUUIDCallback)(void **, const LLUUID&) = NULL; U32 LLViewerImageList::sTextureBits = 0; U32 LLViewerImageList::sTexturePackets = 0; const S32 IMAGES_PER_REQUEST = 42; const S32 IMAGES_MIN_UPDATES = 4; // Always update the highest N images each frame const S32 IMAGES_MAX_PACKET_UPDATES = 1; // Only send N packets of IMAGES_PER_REQUEST in a frame const F32 RESEND_IMAGE_REQUEST_TIME = 15.f; // seconds LLViewerImageList gImageList; S32 LLViewerImageList::sNumImages = 0; LLStat LLViewerImageList::sNumImagesStat(32, TRUE); LLStat LLViewerImageList::sNumRawImagesStat(32, TRUE); LLStat LLViewerImageList::sGLTexMemStat(32, TRUE); LLStat LLViewerImageList::sGLBoundMemStat(32, TRUE); LLStat LLViewerImageList::sRawMemStat(32, TRUE); LLStat LLViewerImageList::sFormattedMemStat(32, TRUE); /////////////////////////////////////////////////////////////////////////////// LLViewerImageList::LLViewerImageList() : LLImageProviderInterface(), mForceResetTextureStats(FALSE), mUpdateStats(FALSE), mMaxResidentTexMem(0), mVideoMemorySetting(0), mMovieImageHasMips(FALSE) { } void LLViewerImageList::init() { sNumImages = 0; mMaxResidentTexMem = 0; mVideoMemorySetting = 0; if (gNoRender) { // Don't initialize GL stuff if we're not rendering. return; } mUpdateStats = TRUE; // Update how much texture RAM we're allowed to use. updateMaxResidentTexMem(); mMovieImageHasMips = FALSE; doPreloadImages(); } void LLViewerImageList::doPreloadImages() { llinfos << "Preloading images..." << llendl; // Set the "missing asset" image LLViewerImage::sMissingAssetImagep = preloadImage("missing_asset.tga" , LLUUID::null, TRUE); // Set the "white" image LLViewerImage::sWhiteImagep = preloadImage("white.tga", LLUUID::null, TRUE); // Speeds up startup by 4-5 seconds. JC if (!gPreloadImages) return; // Images listed here are immediately decoded, before the login screen. // Since this slows down perceived viewer startup time, only include // images here for buttons/checkboxes/etc. that are immediately visible. preloadImage("button_disabled_32x128.tga", LLUUID::null, FALSE); preloadImage("button_enabled_32x128.tga", LLUUID::null, FALSE); preloadImage("button_enabled_selected_32x128.tga", LLUUID::null, FALSE); preloadImage("checkbox_disabled_false.tga", LLUUID::null, FALSE); preloadImage("checkbox_disabled_true.tga", LLUUID::null, FALSE); preloadImage("checkbox_enabled_false.tga", LLUUID::null, FALSE); preloadImage("checkbox_enabled_true.tga", LLUUID::null, FALSE); preloadImage("close_in_blue.tga", LLUUID::null, FALSE); preloadImage("combobox_arrow.tga", LLUUID::null, FALSE); preloadImage("minimize.tga", LLUUID::null, FALSE); preloadImage("minimize_pressed.tga", LLUUID::null, FALSE); preloadImage("radio_active_false.tga", LLUUID::null, FALSE); preloadImage("radio_active_true.tga", LLUUID::null, FALSE); preloadImage("radio_inactive_false.tga", LLUUID::null, FALSE); preloadImage("radio_inactive_true.tga", LLUUID::null, FALSE); preloadImage("resize_handle_bottom_right_blue.tga", LLUUID::null, FALSE); preloadImage("rounded_square.tga", LLUUID::null, FALSE); preloadImage("rounded_square_soft.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_down_in_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_down_out_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_left_in_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_left_out_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_right_in_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_right_out_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_up_in_blue.tga", LLUUID::null, FALSE); preloadImage("scrollbutton_up_out_blue.tga", LLUUID::null, FALSE); preloadImage("spin_down_in_blue.tga", LLUUID::null, FALSE); preloadImage("spin_down_out_blue.tga", LLUUID::null, FALSE); preloadImage("spin_up_in_blue.tga", LLUUID::null, FALSE); preloadImage("spin_up_out_blue.tga", LLUUID::null, FALSE); preloadImage("square_btn_32x128.tga", LLUUID::null, FALSE); preloadImage("square_btn_selected_32x128.tga", LLUUID::null, FALSE); preloadImage("startup_logo.tga", LLUUID::null, FALSE); // <<<<<<< --- needed? preloadImage("tab_bottom_blue.tga", LLUUID::null, FALSE); preloadImage("tab_bottom_selected_blue.tga", LLUUID::null, FALSE); preloadImage("tab_left.tga", LLUUID::null, FALSE); preloadImage("tab_left_selected.tga", LLUUID::null, FALSE); preloadImage("tab_top_blue.tga", LLUUID::null, FALSE); preloadImage("tab_top_selected_blue.tga", LLUUID::null, FALSE); decodeAllImages(2.f); // decode preloaded images // These images are queued for decode during the login sequence, when // we have a progress bar. preloadImage("active_voice_tab.tga", LLUUID::null, FALSE); preloadImage("button_anim_pause.tga", LLUUID::null, FALSE); preloadImage("button_anim_pause_selected.tga", LLUUID::null, FALSE); preloadImage("button_anim_play.tga", LLUUID::null, FALSE); preloadImage("button_anim_play_selected.tga", LLUUID::null, FALSE); preloadImage("button_anim_stop.tga", LLUUID::null, FALSE); preloadImage("button_anim_stop_selected.tga", LLUUID::null, FALSE); preloadImage("crosshairs.tga", LLUUID::null, FALSE); preloadImage("direction_arrow.tga", LLUUID::null, FALSE); preloadImage("eyes.tga", LLUUID::null, TRUE); preloadImage("foot_shadow.tga", LLUUID::null, TRUE); preloadImage("hair.tga", LLUUID::null, TRUE); preloadImage("icon_for_sale.tga", LLUUID::null, FALSE); preloadImage("icon_popular.tga", LLUUID::null, FALSE); preloadImage("icon_top_pick.tga", LLUUID::null, FALSE); preloadImage("img_shot.tga", IMG_SHOT, TRUE); preloadImage("img_smoke_poof.tga", IMG_SMOKE_POOF, TRUE); preloadImage("inv_folder_animation.tga", LLUUID::null, FALSE); preloadImage("inv_folder_bodypart.tga", LLUUID::null, FALSE); preloadImage("inv_folder_callingcard.tga", LLUUID::null, FALSE); preloadImage("inv_folder_clothing.tga", LLUUID::null, FALSE); preloadImage("inv_folder_gesture.tga", LLUUID::null, FALSE); preloadImage("inv_folder_landmark.tga", LLUUID::null, FALSE); preloadImage("inv_folder_lostandfound.tga", LLUUID::null, FALSE); preloadImage("inv_folder_notecard.tga", LLUUID::null, FALSE); preloadImage("inv_folder_object.tga", LLUUID::null, FALSE); preloadImage("inv_folder_plain_closed.tga", LLUUID::null, FALSE); preloadImage("inv_folder_script.tga", LLUUID::null, FALSE); preloadImage("inv_folder_snapshot.tga", LLUUID::null, FALSE); preloadImage("inv_folder_sound.tga", LLUUID::null, FALSE); preloadImage("inv_folder_texture.tga", LLUUID::null, FALSE); preloadImage("inv_folder_trash.tga", LLUUID::null, FALSE); preloadImage("inv_item_animation.tga", LLUUID::null, FALSE); preloadImage("inv_item_bodypart.tga", LLUUID::null, FALSE); preloadImage("inv_item_callingcard_offline.tga", LLUUID::null, FALSE); preloadImage("inv_item_callingcard_online.tga", LLUUID::null, FALSE); preloadImage("inv_item_eyes.tga", LLUUID::null, FALSE); preloadImage("inv_item_gesture.tga", LLUUID::null, FALSE); preloadImage("inv_item_gloves.tga", LLUUID::null, FALSE); preloadImage("inv_item_hair.tga", LLUUID::null, FALSE); preloadImage("inv_item_jacket.tga", LLUUID::null, FALSE); preloadImage("inv_item_landmark.tga", LLUUID::null, FALSE); preloadImage("inv_item_landmark_visited.tga", LLUUID::null, FALSE); preloadImage("inv_item_notecard.tga", LLUUID::null, FALSE); preloadImage("inv_item_object.tga", LLUUID::null, FALSE); preloadImage("inv_item_object_multi.tga", LLUUID::null, FALSE); preloadImage("inv_item_pants.tga", LLUUID::null, FALSE); preloadImage("inv_item_script.tga", LLUUID::null, FALSE); preloadImage("inv_item_shape.tga", LLUUID::null, FALSE); preloadImage("inv_item_shirt.tga", LLUUID::null, FALSE); preloadImage("inv_item_shoes.tga", LLUUID::null, FALSE); preloadImage("inv_item_skirt.tga", LLUUID::null, FALSE); preloadImage("inv_item_snapshot.tga", LLUUID::null, FALSE); preloadImage("inv_item_socks.tga", LLUUID::null, FALSE); preloadImage("inv_item_sound.tga", LLUUID::null, FALSE); preloadImage("inv_item_texture.tga", LLUUID::null, FALSE); preloadImage("inv_item_underpants.tga", LLUUID::null, FALSE); preloadImage("inv_item_undershirt.tga", LLUUID::null, FALSE); preloadImage("lag_status_critical.tga", LLUUID::null, FALSE); preloadImage("lag_status_good.tga", LLUUID::null, FALSE); preloadImage("lag_status_warning.tga", LLUUID::null, FALSE); preloadImage("legend.tga", LLUUID::null, FALSE); preloadImage("map_avatar_16.tga", LLUUID::null, FALSE); preloadImage("map_avatar_8.tga", LLUUID::null, FALSE); preloadImage("map_avatar_above_8.tga", LLUUID::null, FALSE); preloadImage("map_avatar_below_8.tga", LLUUID::null, FALSE); preloadImage("map_avatar_you_8.tga", LLUUID::null, FALSE); preloadImage("map_event.tga", LLUUID::null, FALSE); preloadImage("map_event_mature.tga", LLUUID::null, FALSE); preloadImage("map_home.tga", LLUUID::null, FALSE); preloadImage("map_infohub.tga", LLUUID::null, FALSE); preloadImage("map_telehub.tga", LLUUID::null, FALSE); preloadImage("map_track_16.tga", LLUUID::null, FALSE); preloadImage("media_icon.tga", LLUUID::null, FALSE); preloadImage("music_icon.tga", LLUUID::null, FALSE); preloadImage("noentrylines.tga", LLUUID::null, TRUE); preloadImage("noentrypasslines.tga", LLUUID::null, TRUE); preloadImage("notify_tip_icon.tga", LLUUID::null, FALSE); preloadImage("notify_caution_icon.tga", LLUUID::null, FALSE); preloadImage("notify_box_icon.tga", LLUUID::null, FALSE); preloadImage("object_cone.tga", LLUUID::null, FALSE); preloadImage("object_cone_active.tga", LLUUID::null, FALSE); preloadImage("object_cube.tga", LLUUID::null, FALSE); preloadImage("object_cube_active.tga", LLUUID::null, FALSE); preloadImage("object_cylinder.tga", LLUUID::null, FALSE); preloadImage("object_cylinder_active.tga", LLUUID::null, FALSE); preloadImage("object_grass.tga", LLUUID::null, FALSE); preloadImage("object_grass_active.tga", LLUUID::null, FALSE); preloadImage("object_hemi_cone.tga", LLUUID::null, FALSE); preloadImage("object_hemi_cone_active.tga", LLUUID::null, FALSE); preloadImage("object_hemi_cylinder.tga", LLUUID::null, FALSE); preloadImage("object_hemi_cylinder_active.tga", LLUUID::null, FALSE); preloadImage("object_hemi_sphere.tga", LLUUID::null, FALSE); preloadImage("object_hemi_sphere_active.tga", LLUUID::null, FALSE); preloadImage("object_prism.tga", LLUUID::null, FALSE); preloadImage("object_prism_active.tga", LLUUID::null, FALSE); preloadImage("object_pyramid.tga", LLUUID::null, FALSE); preloadImage("object_pyramid_active.tga", LLUUID::null, FALSE); preloadImage("object_ring.tga", LLUUID::null, FALSE); preloadImage("object_ring_active.tga", LLUUID::null, FALSE); preloadImage("object_sphere.tga", LLUUID::null, FALSE); preloadImage("object_sphere_active.tga", LLUUID::null, FALSE); preloadImage("object_tetrahedron.tga", LLUUID::null, FALSE); preloadImage("object_tetrahedron_active.tga", LLUUID::null, FALSE); preloadImage("object_torus.tga", LLUUID::null, FALSE); preloadImage("object_torus_active.tga", LLUUID::null, FALSE); preloadImage("object_tree.tga", LLUUID::null, FALSE); preloadImage("object_tree_active.tga", LLUUID::null, FALSE); preloadImage("object_tube.tga", LLUUID::null, FALSE); preloadImage("object_tube_active.tga", LLUUID::null, FALSE); preloadImage("pixiesmall.tga", LLUUID::null, TRUE); // particle systems preloadImage("script_error.tga", LLUUID::null, TRUE); preloadImage("silhouette.tga", LLUUID::null, TRUE); preloadImage("status_build.tga", LLUUID::null, FALSE); preloadImage("status_buy_currency.tga", LLUUID::null, FALSE); preloadImage("status_buy_currency_pressed.tga", LLUUID::null, FALSE); preloadImage("status_buy_land.tga", LLUUID::null, FALSE); preloadImage("status_buy_land_pressed.tga", LLUUID::null, FALSE); preloadImage("status_fly.tga", LLUUID::null, FALSE); preloadImage("status_health.tga", LLUUID::null, FALSE); preloadImage("status_scripts.tga", LLUUID::null, FALSE); preloadImage("tool_dozer.tga", LLUUID::null, FALSE); preloadImage("tool_dozer_active.tga", LLUUID::null, FALSE); preloadImage("tool_zoom.tga", LLUUID::null, FALSE); preloadImage("tool_zoom_active.tga", LLUUID::null, FALSE); preloadImage("volume_icon.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-dot-lvl0.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-dot-lvl1.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-dot-lvl2.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-typing1.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-typing2.tga", LLUUID::null, FALSE); preloadImage("icn_active-speakers-typing3.tga", LLUUID::null, FALSE); preloadImage("icn_voice_ptt-off.tga", LLUUID::null, FALSE); preloadImage("icn_voice_ptt-on.tga", LLUUID::null, FALSE); preloadImage("icn_voice_ptt-on-lvl1.tga", LLUUID::null, FALSE); preloadImage("icn_voice_ptt-on-lvl2.tga", LLUUID::null, FALSE); preloadImage("icn_voice_ptt-on-lvl3.tga", LLUUID::null, FALSE); } static std::string get_texture_list_name() { BOOL login_last = gSavedSettings.getBOOL("LoginLastLocation"); return std::string("texture_list_") + (login_last?"last":"home") + ".xml"; } void LLViewerImageList::doPrefetchImages() { if (gPurgeCache) { // 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.c_str()); 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"]; LLViewerImage* image = getImage(uuid, MIPMAP_TRUE, FALSE); if (image) { image->addTextureStats((F32)pixel_area); } } } /////////////////////////////////////////////////////////////////////////////// LLViewerImageList::~LLViewerImageList() { llassert(mIRCallbackData.empty()); } void LLViewerImageList::shutdown() { // Write out list of currently loaded textures for precaching on startup typedef std::set > image_area_list_t; image_area_list_t image_area_list; for (image_priority_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ++iter) { LLViewerImage* image = *iter; if (!image->getUseDiscard() || image->needsAux() || image->getTargetHost() != LLHost::invalid) { continue; // avoid UI, baked, and other special images } 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; for (image_area_list_t::reverse_iterator riter = image_area_list.rbegin(); riter != image_area_list.rend(); ++riter) { LLViewerImage* image = riter->second; imagelist[count]["area"] = riter->first; imagelist[count]["uuid"] = image->getID(); if (++count >= max_count) break; } if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "").empty()) { std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, get_texture_list_name()); llofstream file; file.open(filename.c_str()); LLSDSerialize::toPrettyXML(imagelist, file); } // // Clean up "loaded" callbacks. // mCallbackList.clear(); mIRCallbackData.clear(); // Clean up preloaded images mPreloadedImages.clear(); // Flush all of the references mLoadingStreamList.clear(); mCreateTextureList.clear(); mUUIDMap.clear(); mImageList.clear(); } void LLViewerImageList::dump() { llinfos << "LLViewerImageList::dump()" << llendl; for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it) { LLViewerImage* 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 LLViewerImageList::destroyGL(BOOL save_state) { LLImageGL::destroyGL(save_state); } void LLViewerImageList::restoreGL() { 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"); */ LLImageGL* LLViewerImageList::getUIImageByID(const LLUUID& image_id, BOOL clamped) { LLViewerImage* imagep = getImage(image_id, MIPMAP_FALSE, TRUE); // force a high resolution decode for all UI images (pulled this from LLTextEditor) // this might not make any difference imagep->setBoostLevel(LLViewerImage::BOOST_UI); LLViewerImage::bindTexture(imagep); imagep->setClamp(clamped, clamped); imagep->unbindTexture(0, GL_TEXTURE_2D); return (LLImageGL*)imagep; } /////////////////////////////////////////////////////////////////////////////// LLViewerImage* LLViewerImageList::preloadImage(const LLString& filename, const LLUUID &image_set_id, BOOL use_mips) { LLViewerImage* image = getImage(filename, image_set_id, use_mips, TRUE); image->dontDiscard(); mPreloadedImages.push_back(image); return image; } /////////////////////////////////////////////////////////////////////////////// LLViewerImage* LLViewerImageList::getImage(const LLString& filename, const LLUUID &image_set_id, BOOL usemipmaps, BOOL level_immediate) { return getImageFromFile(filename, image_set_id, usemipmaps, level_immediate, 0, 0); } LLViewerImage* LLViewerImageList::getImageFromFile(const LLString& filename, const LLUUID &image_set_id, BOOL usemipmaps, BOOL level_immediate, LLGLint internal_format, LLGLenum primary_format) { if (gNoRender) { // Never mind that this ignores image_set_id; // getImage() will handle that later. return getImage(IMG_DEFAULT, TRUE, TRUE); } // Try to load an image from the skins directory. // Fall back to loading from the VFS if not found. // First verify that the image exists in gViewerArt LLUUID image_id = LLUUID( gViewerArt.getString(filename.c_str()) ); if (image_id.isNull()) { llwarns << "Unable to find image " << filename << " in gViewerArt" << llendl; if (image_set_id.notNull()) { // We *know* that missing_asset.tga exists, // but for paranoia's sake and to avoid infinite recursion, check anyway image_id = LLUUID(gViewerArt.getString("missing_asset.tga")); if (image_id.isNull()) { llerrs << "Missing missing_asset.tga!" << llendl; } return getImageFromFile(LLString("missing_asset.tga"), image_set_id, usemipmaps, level_immediate, internal_format, primary_format); } else { return (getImage(IMG_DEFAULT, TRUE, TRUE)); } } // Now that we have verified that filename exists, load it and assign it to // the filename's UUID, or image_set_id if non null. if (image_set_id.notNull()) { image_id = image_set_id; } // Load the image LLViewerImage* imagep = getImageFromUUID(image_id, usemipmaps, level_immediate, internal_format, primary_format, LLHost()); return imagep; } LLViewerImage* LLViewerImageList::getImage(const LLUUID &image_id, BOOL usemipmaps, BOOL level_immediate) { return getImageFromUUID(image_id, usemipmaps, level_immediate, 0, 0, LLHost()); } LLViewerImage* LLViewerImageList::getImageFromUUID(const LLUUID &image_id, BOOL usemipmaps, BOOL level_immediate, LLGLint internal_format, LLGLenum primary_format, LLHost request_from_host) { // 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 (getImage(IMG_DEFAULT, TRUE, TRUE)); } LLPointer imagep = hasImage(image_id); if (imagep.isNull()) { imagep = new LLViewerImage(image_id, usemipmaps); // Might want to request from host other than where the agent is. JC imagep->setTargetHost(request_from_host); if (internal_format && primary_format) { imagep->setExplicitFormat(internal_format, primary_format); } addImage(imagep); if (level_immediate) { imagep->dontDiscard(); imagep->setBoostLevel(LLViewerImage::BOOST_UI); } } return imagep; } LLViewerImage *LLViewerImageList::hasImage(const LLUUID &image_id) { uuid_map_t::iterator iter = mUUIDMap.find(image_id); if(iter == mUUIDMap.end()) return NULL; return iter->second; } void LLViewerImageList::addImageToList(LLViewerImage *image) { llassert(image); if (image->mInImageList) { llerrs << "LLViewerImageList::addImageToList - Image already in list" << llendl; } llverify((mImageList.insert(image)).second == true); image->mInImageList = TRUE; } void LLViewerImageList::removeImageFromList(LLViewerImage *image) { llassert(image); if (!image->mInImageList) { llerrs << "LLViewerImageList::removeImageFromList - Image not in list" << llendl; } llverify(mImageList.erase(image) == 1); image->mInImageList = FALSE; } void LLViewerImageList::addImage(LLViewerImage *new_image) { if (!new_image) { llwarning("No image to add to image list", 0); return; } LLUUID image_id = new_image->getID(); LLViewerImage *image = hasImage(image_id); if (image) { llerrs << "Image with ID " << image_id << " already in list" << llendl; } sNumImages++; addImageToList(new_image); mUUIDMap[image_id] = new_image; } void LLViewerImageList::deleteImage(LLViewerImage *image) { if( image) { if (image->hasCallbacks()) { mCallbackList.erase((LLViewerImage*)image); } llverify(mUUIDMap.erase(image->getID()) == 1); sNumImages--; removeImageFromList(image); } } /////////////////////////////////////////////////////////////////////////////// void LLViewerImageList::updateMovieImage(const LLUUID& uuid, BOOL active) { // IF the media image hasn't changed, do nothing if (mMovieImageUUID == uuid) { return; } // If we have changed media uuid, restore the old one if (!mMovieImageUUID.isNull()) { LLViewerImage* oldImage = getImage( mMovieImageUUID ); if (oldImage) { oldImage->reinit(mMovieImageHasMips); oldImage->mIsMediaTexture = FALSE; } mMovieImageUUID.setNull(); } // If the movie is playing, set the new media image if (active && !uuid.isNull()) { LLViewerImage* viewerImage = getImage( uuid ); if( viewerImage ) { mMovieImageUUID = uuid; // Can't use mipmaps for movies because they don't update the full image mMovieImageHasMips = viewerImage->getUseMipMaps(); viewerImage->reinit(FALSE); viewerImage->mIsMediaTexture = TRUE; } } } //////////////////////////////////////////////////////////////////////////// void LLViewerImageList::dirtyImage(LLViewerImage *image) { mDirtyTextureList.insert(image); } //////////////////////////////////////////////////////////////////////////// void LLViewerImageList::updateImages(F32 max_time) { sNumImagesStat.addValue(sNumImages); sNumRawImagesStat.addValue(LLImageRaw::sRawImageCount); sGLTexMemStat.addValue(LLImageGL::sGlobalTextureMemory/(1024.f*1024.f)); sGLBoundMemStat.addValue(LLImageGL::sBoundTextureMemory/(1024.f*1024.f)); sRawMemStat.addValue(LLImageRaw::sGlobalRawMemory/(1024.f*1024.f)); sFormattedMemStat.addValue(LLImageFormatted::sGlobalFormattedMemory/(1024.f*1024.f)); updateImagesDecodePriorities(); max_time -= updateImagesFetchTextures(max_time); max_time = llmax(max_time, 0.001f); max_time -= updateImagesCreateTextures(max_time); max_time = llmax(max_time, 0.001f); if (!mDirtyTextureList.empty()) { LLFastTimer t(LLFastTimer::FTM_IMAGE_MARK_DIRTY); gPipeline.dirtyPoolObjectTextures(mDirtyTextureList); mDirtyTextureList.clear(); } for (image_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) { LLViewerImage* image = *iter++; // Do stuff to handle callbacks, update priorities, etc. bool res = image->doLoadedCallbacks(); if (res) { break; // only actually do one callback per frame } } updateImagesMediaStreams(); updateImagesUpdateStats(); } void LLViewerImageList::updateImagesDecodePriorities() { // Update the decode priority for N images each frame { const size_t max_update_count = 256; S32 update_counter = llmin(max_update_count, mUUIDMap.size()/10); uuid_map_t::iterator iter = mUUIDMap.upper_bound(mLastUpdateUUID); while(update_counter > 0) { if (iter == mUUIDMap.end()) { iter = mUUIDMap.begin(); } mLastUpdateUUID = iter->first; LLPointer imagep = iter->second; ++iter; // safe to incrament now // // Flush formatted images using a lazy flush // const F32 LAZY_FLUSH_TIMEOUT = 30.f; 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->mLastReferencedTimer.getElapsedTimeF32() > LAZY_FLUSH_TIMEOUT) { // Remove the unused image from the image list deleteImage(imagep); imagep = NULL; // should destroy the image continue; } } else { imagep->mLastReferencedTimer.reset(); } imagep->processTextureStats(); F32 old_priority = imagep->getDecodePriority(); F32 decode_priority = imagep->calcDecodePriority(); // Ignore < 20% difference if ((decode_priority < old_priority * .8f || decode_priority > old_priority * 1.25f)) { removeImageFromList(imagep); imagep->setDecodePriority(decode_priority); addImageToList(imagep); } update_counter--; } } } /* static U8 get_image_type(LLViewerImage* 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 == LLViewerImage::BOOST_AVATAR_BAKED || boost_level == LLViewerImage::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; } */ F32 LLViewerImageList::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; LLViewerImage *imagep = *curiter; imagep->createTexture(); if (create_timer.getElapsedTimeF32() > max_time) { break; } } mCreateTextureList.erase(mCreateTextureList.begin(), enditer); return create_timer.getElapsedTimeF32(); } F32 LLViewerImageList::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 = 32; const size_t max_update_count = 256; // 32 high priority entries std::set entries; size_t update_counter = llmin(max_priority_count, mImageList.size()); image_priority_list_t::iterator iter1 = mImageList.begin(); while(update_counter > 0) { entries.insert(*iter1); ++iter1; update_counter--; } // 256 cycled entries update_counter = llmin(max_update_count, mUUIDMap.size()); uuid_map_t::iterator iter2 = mUUIDMap.upper_bound(mLastFetchUUID); while(update_counter > 0) { if (iter2 == mUUIDMap.end()) { iter2 = mUUIDMap.begin(); } mLastFetchUUID = iter2->first; entries.insert(iter2->second); ++iter2; update_counter--; } S32 min_count = max_priority_count + max_update_count/4; for (std::set::iterator iter3 = entries.begin(); iter3 != entries.end(); ) { LLPointer imagep = *iter3++; imagep->updateFetch(); if (min_count <= 0 && image_op_timer.getElapsedTimeF32() > max_time) { break; } min_count--; } return image_op_timer.getElapsedTimeF32(); } void LLViewerImageList::updateImagesMediaStreams() { if (gNoRender || gGLManager.mIsDisabled) return; // update media stream if required LLMediaEngine* media_engine = LLMediaEngine::getInstance(); if (media_engine) { if ( media_engine->update() ) { LLUUID media_uuid = media_engine->getImageUUID(); updateMovieImage(media_uuid, TRUE); if (!media_uuid.isNull()) { LLViewerImage* viewerImage = getImage( media_uuid ); if( viewerImage ) { LLMediaBase* renderer = media_engine->getMediaRenderer(); if ((renderer->getTextureWidth() != viewerImage->getWidth()) || (renderer->getTextureHeight() != viewerImage->getHeight()) || (renderer->getTextureDepth() != viewerImage->getComponents()) || (viewerImage->getHasGLTexture() == FALSE)) { // destroy existing GL image viewerImage->destroyGLTexture(); // set new size viewerImage->setSize( renderer->getTextureWidth(), renderer->getTextureHeight(), renderer->getTextureDepth() ); LLPointer raw = new LLImageRaw(renderer->getTextureWidth(), renderer->getTextureHeight(), renderer->getTextureDepth()); raw->clear(0x7f,0x7f,0x7f,0xff); viewerImage->createGLTexture(0, raw); } // Set the explicit format the instance wants viewerImage->setExplicitFormat(renderer->getTextureFormatInternal(), renderer->getTextureFormatPrimary(), renderer->getTextureFormatType(), renderer->getTextureFormatSwapBytes()); // This should be redundant, but just in case: viewerImage->setUseMipMaps(FALSE); LLImageRaw* rawImage = media_engine->getImageRaw(); if ( rawImage ) { viewerImage->setSubImage(rawImage, 0, 0, renderer->getMediaWidth(), renderer->getMediaHeight()); } } else { llwarns << "MediaEngine update unable to get viewer image for GL texture" << llendl; } } } else { LLUUID media_uuid = media_engine->getImageUUID(); updateMovieImage(media_uuid, FALSE); } } } void LLViewerImageList::updateImagesUpdateStats() { if (mUpdateStats) { for (image_priority_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerImage* imagep = *iter++; imagep->resetTextureStats(mForceResetTextureStats); } mUpdateStats = FALSE; mForceResetTextureStats = FALSE; } } void LLViewerImageList::decodeAllImages(F32 max_time) { LLTimer timer; if(gNoRender) return; // Update texture stats and priorities std::vector > image_list; for (image_priority_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerImage* imagep = *iter++; image_list.push_back(imagep); imagep->mInImageList = FALSE; } mImageList.clear(); for (std::vector >::iterator iter = image_list.begin(); iter != image_list.end(); ++iter) { LLViewerImage* imagep = *iter; imagep->processTextureStats(); F32 decode_priority = imagep->calcDecodePriority(); imagep->setDecodePriority(decode_priority); mImageList.insert(imagep); imagep->mInImageList = TRUE; } image_list.clear(); // Update fetch (decode) for (image_priority_list_t::iterator iter = mImageList.begin(); iter != mImageList.end(); ) { LLViewerImage* imagep = *iter++; imagep->updateFetch(); } // Run threads S32 fetch_pending = 0; while (1) { gTextureCache->update(1); // unpauses the texture cache thread gImageDecodeThread->update(1); // unpauses the image thread fetch_pending = gTextureFetch->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(); ) { LLViewerImage* imagep = *iter++; imagep->updateFetch(); } max_time -= timer.getElapsedTimeF32(); max_time = llmax(max_time, .01f); F32 create_time = updateImagesCreateTextures(max_time); llinfos << "decodeAllImages() took " << timer.getElapsedTimeF32() << " seconds. " << " fetch_pending " << fetch_pending << " create_time " << create_time << llendl; } BOOL LLViewerImageList::createUploadFile(const LLString& filename, const LLString& out_filename, const U8 codec) { // First, load the image. LLPointer raw_image = new LLImageRaw; switch (codec) { case IMG_CODEC_BMP: { LLPointer bmp_image = new LLImageBMP; if (!bmp_image->load(filename)) { return FALSE; } if (!bmp_image->decode(raw_image)) { return FALSE; } } break; case IMG_CODEC_TGA: { LLPointer 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 jpeg_image = new LLImageJPEG; if (!jpeg_image->load(filename)) { return FALSE; } if (!jpeg_image->decode(raw_image)) { return FALSE; } } break; case IMG_CODEC_PNG: { LLPointer png_image = new LLImagePNG; if (!png_image->load(filename)) { return FALSE; } if (!png_image->decode(raw_image)) { return FALSE; } } break; default: return FALSE; } LLPointer 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 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 LLViewerImageList::convertToUploadFile(LLPointer raw_image) { raw_image->biasedScaleToPowerOfTwo(LLViewerImage::MAX_IMAGE_SIZE_DEFAULT); LLPointer compressedImage = new LLImageJ2C(); compressedImage->setRate(0.f); if (gSavedSettings.getBOOL("LosslessJ2CUpload") && (raw_image->getWidth() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF) && (raw_image->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF)) compressedImage->setReversible(TRUE); compressedImage->encode(raw_image); return compressedImage; } //static S32 LLViewerImageList::getMaxVideoRamSetting(S32 max) { const U32 vram_settings[] = { 16, 32, 64, 128, 256, 512 }; const S32 num_vram_settings = sizeof(vram_settings) / sizeof(vram_settings[0]); U32 max_vram; if (gGLManager.mVRAM != 0) { max_vram = (llmax(gGLManager.mVRAM,16)) << 20; } else { if (max == -2) // max recommended setting { max_vram = 128 << 20; } else { max_vram = 512 << 20; } llwarns << "VRAM amount not detected, defaulting to " << max_vram/(double)(1<<20) << " MB" << llendl; } U32 system_ram = gSysMemory.getPhysicalMemoryClamped(); //llinfos << "*** DETECTED " << system_ram/(double)(1<<20) << " MB of system memory." << llendl; // TomY TESTING DNCI if (max == -2) { max_vram = llmin(max_vram, (U32)(system_ram/2)); // max recommended setting } else { max_vram = llmin(max_vram, (U32)((F32)system_ram/1.5f)); } S32 idx; for (idx=0; idx < num_vram_settings; idx++) { if (idx == max) break; if ((vram_settings[idx] << 20) > max_vram) { idx--; break; } } if( idx == num_vram_settings ) { idx = num_vram_settings - 1; } return idx; } const S32 VIDEO_CARD_MEM_SIZES[6] = { 0x1000000, // 16MB 0x2000000, // 32MB 0x4000000, // 64MB 0x8000000, // 128MB 0x10000000, // 256MB 0x20000000, // 512MB }; const S32 VIDEO_CARD_FRAMEBUFFER_MEM = 0xC00000; // 12MB void LLViewerImageList::updateMaxResidentTexMem(S32 max, U32 fudge) { // Initialize the image pipeline VRAM settings S32 cur_setting = gSavedSettings.getS32("GraphicsCardMemorySetting"); S32 max_setting = getMaxVideoRamSetting(max); if (max >= 0 && max != cur_setting) { S32 default_setting = getMaxVideoRamSetting(-2); // recommended default if (cur_setting >= 0 || max_setting != default_setting) { gSavedSettings.setS32("GraphicsCardMemorySetting", max_setting); return; //listener will reenter this function } cur_setting = max_setting; // max_setting <= max } else if (cur_setting < 0) { S32 default_setting = getMaxVideoRamSetting(-2); // recommended default cur_setting = default_setting; } mVideoMemorySetting = cur_setting; // TODO: set available resident texture mem based on use by other subsystems // currently max(12MB, VRAM/4) assumed... S32 vram_amt = VIDEO_CARD_MEM_SIZES[cur_setting]; S32 fb_mem = llmax(VIDEO_CARD_FRAMEBUFFER_MEM, vram_amt/4); mMaxResidentTexMem = vram_amt - fb_mem - fudge; // llinfos << "Graphics Card memory set to " << (VIDEO_CARD_MEM_SIZES[cur_setting]>>20) // << " MB" << llendl; } /////////////////////////////////////////////////////////////////////////////// // static void LLViewerImageList::receiveImageHeader(LLMessageSystem *msg, void **user_data) { 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); if (msg->getReceiveCompressedSize()) { gImageList.sTextureBits += msg->getReceiveCompressedSize() * 8; } else { gImageList.sTextureBits += msg->getReceiveSize() * 8; } gImageList.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); LLViewerImage *image = gImageList.getImage(id); if (!image) { delete [] data; return; } image->mLastPacketTimer.reset(); bool res = gTextureFetch->receiveImageHeader(msg->getSender(), id, codec, packets, totalbytes, data_size, data); if (!res) { delete[] data; } } // static void LLViewerImageList::receiveImagePacket(LLMessageSystem *msg, void **user_data) { 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); if (msg->getReceiveCompressedSize()) { gImageList.sTextureBits += msg->getReceiveCompressedSize() * 8; } else { gImageList.sTextureBits += msg->getReceiveSize() * 8; } gImageList.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); LLViewerImage *image = gImageList.getImage(id); if (!image) { delete [] data; return; } image->mLastPacketTimer.reset(); bool res = gTextureFetch->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 LLViewerImageList::processImageNotInDatabase(LLMessageSystem *msg,void **user_data) { LLFastTimer t(LLFastTimer::FTM_PROCESS_IMAGES); LLUUID image_id; msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id); LLViewerImage* image = gImageList.hasImage( image_id ); if( image ) { image->setIsMissingAsset(); } } /////////////////////////////////////////////////////////////////////////////// //static const U32 SIXTEEN_MEG = 0x1000000; S32 LLViewerImageList::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; } ///////////////////////////////////////////////////////////////////////////////