/** * @file lllocalbitmaps.cpp * @author Vaalith Jinn * @brief Local Bitmaps source * * $LicenseInfo:firstyear=2011&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2011, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ /* precompiled headers */ #include "llviewerprecompiledheaders.h" /* own header */ #include "lllocalbitmaps.h" /* boost: will not compile unless equivalent is undef'd, beware. */ #ifdef equivalent #undef equivalent #endif #include /* image compression headers. */ #include "llimagebmp.h" #include "llimagetga.h" #include "llimagejpeg.h" #include "llimagepng.h" /* time headers */ #include #include /* misc headers */ #include "llscrolllistctrl.h" #include "llfilepicker.h" #include "llviewertexturelist.h" #include "llviewerobjectlist.h" #include "llviewerobject.h" #include "llface.h" #include "llvoavatarself.h" #include "llwearable.h" #include "llagentwearables.h" #include "lltexlayerparams.h" #include "llvovolume.h" /*=======================================*/ /* Formal declarations, constants, etc. */ /*=======================================*/ std::list LLLocalBitmapMgr::sBitmapList; LLLocalBitmapTimer LLLocalBitmapMgr::sTimer; bool LLLocalBitmapMgr::sNeedsRebake; static const F32 LL_LOCAL_TIMER_HEARTBEAT = 3.0; static const BOOL LL_LOCAL_USE_MIPMAPS = true; static const S32 LL_LOCAL_DISCARD_LEVEL = 0; static const U32 LL_LOCAL_TEXLAYER_FOR_IDX = 0; static const bool LL_LOCAL_SLAM_FOR_DEBUG = true; static const bool LL_LOCAL_REPLACE_ON_DEL = true; static const S32 LL_LOCAL_UPDATE_RETRIES = 5; /*=======================================*/ /* LLLocalBitmap: unit class */ /*=======================================*/ LLLocalBitmap::LLLocalBitmap(std::string filename) : mFilename(filename) , mShortName(gDirUtilp->getBaseFileName(filename, true)) , mValid(false) , mLastModified() , mLinkStatus(LS_ON) , mUpdateRetries(LL_LOCAL_UPDATE_RETRIES) { mTrackingID.generate(); /* extension */ std::string temp_exten = gDirUtilp->getExtension(mFilename); if (temp_exten == "bmp") { mExtension = ET_IMG_BMP; } else if (temp_exten == "tga") { mExtension = ET_IMG_TGA; } else if (temp_exten == "jpg" || temp_exten == "jpeg") { mExtension = ET_IMG_JPG; } else if (temp_exten == "png") { mExtension = ET_IMG_PNG; } else { llwarns << "File of no valid extension given, local bitmap creation aborted." << "\n" << "Filename: " << mFilename << llendl; return; // no valid extension. } /* next phase of unit creation is nearly the same as an update cycle. true means the unit's update is running for the first time so it will not check for current usage nor will it attempt to replace the old, non existent image */ mValid = updateSelf(true); } LLLocalBitmap::~LLLocalBitmap() { // replace IDs with defaults, if set to do so. if(LL_LOCAL_REPLACE_ON_DEL) { replaceIDs(mWorldID, IMG_DEFAULT); LLLocalBitmapMgr::doRebake(); } // delete self from gimagelist LLViewerFetchedTexture* image = gTextureList.findImage(mWorldID); gTextureList.deleteImage(image); if (image) { image->unref(); } } /* accessors */ std::string LLLocalBitmap::getFilename() { return mFilename; } std::string LLLocalBitmap::getShortName() { return mShortName; } LLUUID LLLocalBitmap::getTrackingID() { return mTrackingID; } LLUUID LLLocalBitmap::getWorldID() { return mWorldID; } bool LLLocalBitmap::getValid() { return mValid; } /* update functions */ bool LLLocalBitmap::updateSelf(bool first_update) { bool updated = false; if (mLinkStatus == LS_ON) { // verifying that the file exists if (gDirUtilp->fileExists(mFilename)) { // verifying that the file has indeed been modified const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); LLSD new_last_modified = asctime(localtime(&temp_time)); if (mLastModified.asString() != new_last_modified.asString()) { /* loading the image file and decoding it, here is a critical point which, if fails, invalidates the whole update (or unit creation) process. */ LLPointer raw_image = new LLImageRaw(); if (decodeBitmap(raw_image)) { // decode is successful, we can safely proceed. LLUUID old_id = LLUUID::null; if (!first_update && !mWorldID.isNull()) { old_id = mWorldID; } mWorldID.generate(); mLastModified = new_last_modified; LLPointer texture = new LLViewerFetchedTexture ("file://"+mFilename, mWorldID, LL_LOCAL_USE_MIPMAPS); texture->createGLTexture(LL_LOCAL_DISCARD_LEVEL, raw_image); texture->setCachedRawImage(LL_LOCAL_DISCARD_LEVEL, raw_image); texture->ref(); gTextureList.addImage(texture); if (!first_update) { // seek out everything old_id uses and replace it with mWorldID replaceIDs(old_id, mWorldID); // remove old_id from gimagelist LLViewerFetchedTexture* image = gTextureList.findImage(old_id); gTextureList.deleteImage(image); image->unref(); } mUpdateRetries = LL_LOCAL_UPDATE_RETRIES; updated = true; } // if decoding failed, we get here and it will attempt to decode it in the next cycles // until mUpdateRetries runs out. this is done because some software lock the bitmap while writing to it else { if (mUpdateRetries) { mUpdateRetries--; } else { llwarns << "During the update process the following file was found" << "\n" << "but could not be opened or decoded for " << LL_LOCAL_UPDATE_RETRIES << " attempts." << "\n" << "Filename: " << mFilename << "\n" << "Disabling further update attempts for this file." << llendl; mLinkStatus = LS_BROKEN; } } } } // end if file exists else { llwarns << "During the update process, the following file was not found." << "\n" << "Filename: " << mFilename << "\n" << "Disabling further update attempts for this file." << llendl; mLinkStatus = LS_BROKEN; } } return updated; } bool LLLocalBitmap::decodeBitmap(LLPointer rawimg) { bool decode_successful = false; switch (mExtension) { case ET_IMG_BMP: { LLPointer bmp_image = new LLImageBMP; if (bmp_image->load(mFilename) && bmp_image->decode(rawimg, 0.0f)) { rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); decode_successful = true; } break; } case ET_IMG_TGA: { LLPointer tga_image = new LLImageTGA; if ((tga_image->load(mFilename) && tga_image->decode(rawimg)) && ((tga_image->getComponents() == 3) || (tga_image->getComponents() == 4))) { rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); decode_successful = true; } break; } case ET_IMG_JPG: { LLPointer jpeg_image = new LLImageJPEG; if (jpeg_image->load(mFilename) && jpeg_image->decode(rawimg, 0.0f)) { rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); decode_successful = true; } break; } case ET_IMG_PNG: { LLPointer png_image = new LLImagePNG; if (png_image->load(mFilename) && png_image->decode(rawimg, 0.0f)) { rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); decode_successful = true; } break; } default: { // separating this into -several- llwarns calls because in the extremely unlikely case that this happens // accessing mFilename and any other object properties might very well crash the viewer. // getting here should be impossible, or there's been a pretty serious bug. llwarns << "During a decode attempt, the following local bitmap had no properly assigned extension." << llendl; llwarns << "Filename: " << mFilename << llendl; llwarns << "Disabling further update attempts for this file." << llendl; mLinkStatus = LS_BROKEN; } } return decode_successful; } void LLLocalBitmap::replaceIDs(LLUUID old_id, LLUUID new_id) { // checking for misuse. if (old_id == new_id) { llinfos << "An attempt was made to replace a texture with itself. (matching UUIDs)" << "\n" << "Texture UUID: " << old_id.asString() << llendl; return; } updateUserPrims(old_id, new_id); updateUserSculpts(old_id, new_id); // isn't there supposed to be an IMG_DEFAULT_SCULPT or something? // default safeguard image for layers if( new_id == IMG_DEFAULT ) { new_id = IMG_DEFAULT_AVATAR; } /* It doesn't actually update all of those, it merely checks if any of them contain the referenced ID and if so, updates. */ updateUserLayers(old_id, new_id, LLWearableType::WT_ALPHA); updateUserLayers(old_id, new_id, LLWearableType::WT_EYES); updateUserLayers(old_id, new_id, LLWearableType::WT_GLOVES); updateUserLayers(old_id, new_id, LLWearableType::WT_JACKET); updateUserLayers(old_id, new_id, LLWearableType::WT_PANTS); updateUserLayers(old_id, new_id, LLWearableType::WT_SHIRT); updateUserLayers(old_id, new_id, LLWearableType::WT_SHOES); updateUserLayers(old_id, new_id, LLWearableType::WT_SKIN); updateUserLayers(old_id, new_id, LLWearableType::WT_SKIRT); updateUserLayers(old_id, new_id, LLWearableType::WT_SOCKS); updateUserLayers(old_id, new_id, LLWearableType::WT_TATTOO); updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERPANTS); updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERSHIRT); } // this function sorts the faces from a getFaceList[getNumFaces] into a list of objects // in order to prevent multiple sendTEUpdate calls per object during updateUserPrims std::vector LLLocalBitmap::prepUpdateObjects(LLUUID old_id) { std::vector obj_list; LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id); for(U32 face_iterator = 0; face_iterator < old_texture->getNumFaces(); face_iterator++) { // getting an object from a face LLFace* face_to_object = (*old_texture->getFaceList())[face_iterator]; if(face_to_object) { LLViewerObject* affected_object = face_to_object->getViewerObject(); if(affected_object) { // we have an object, we'll take it's UUID and compare it to // whatever we already have in the returnable object list. // if there is a match - we do not add (to prevent duplicates) LLUUID mainlist_obj_id = affected_object->getID(); bool add_object = true; // begin looking for duplicates std::vector::iterator objlist_iter = obj_list.begin(); for(; (objlist_iter != obj_list.end()) && add_object; objlist_iter++) { LLViewerObject* obj = *objlist_iter; if (obj->getID() == mainlist_obj_id) { add_object = false; // duplicate found. } } // end looking for duplicates if(add_object) { obj_list.push_back(affected_object); } } } } // end of face-iterating for() return obj_list; } void LLLocalBitmap::updateUserPrims(LLUUID old_id, LLUUID new_id) { std::vector objectlist = prepUpdateObjects(old_id); for(std::vector::iterator object_iterator = objectlist.begin(); object_iterator != objectlist.end(); object_iterator++) { LLViewerObject* object = *object_iterator; if(object) { bool update_obj = false; S32 num_faces = object->getNumFaces(); for (U8 face_iter = 0; face_iter < num_faces; face_iter++) { if (object->mDrawable) { LLFace* face = object->mDrawable->getFace(face_iter); if (face && face->getTexture() && face->getTexture()->getID() == old_id) { object->setTEImage(face_iter, LLViewerTextureManager::getFetchedTexture (new_id, TRUE, LLViewerTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); update_obj = true; } } } if (update_obj) { object->sendTEUpdate(); } } } } void LLLocalBitmap::updateUserSculpts(LLUUID old_id, LLUUID new_id) { LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id); for(U32 volume_iter = 0; volume_iter < old_texture->getNumVolumes(); volume_iter++) { LLVOVolume* volume_to_object = (*old_texture->getVolumeList())[volume_iter]; LLViewerObject* object = (LLViewerObject*)volume_to_object; if(object) { if (object->isSculpted() && object->getVolume() && object->getVolume()->getParams().getSculptID() == old_id) { LLSculptParams* old_params = (LLSculptParams*)object->getParameterEntry(LLNetworkData::PARAMS_SCULPT); LLSculptParams new_params(*old_params); new_params.setSculptTexture(new_id); object->setParameterEntry(LLNetworkData::PARAMS_SCULPT, new_params, TRUE); } } } } void LLLocalBitmap::updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type) { U32 count = gAgentWearables.getWearableCount(type); for(U32 wearable_iter = 0; wearable_iter < count; wearable_iter++) { LLWearable* wearable = gAgentWearables.getWearable(type, wearable_iter); if (wearable) { std::vector texture_list = wearable->getLocalTextureListSeq(); for(std::vector::iterator texture_iter = texture_list.begin(); texture_iter != texture_list.end(); texture_iter++) { LLLocalTextureObject* lto = *texture_iter; if (lto && lto->getID() == old_id) { U32 local_texlayer_index = 0; /* can't keep that as static const, gives errors, so i'm leaving this var here */ LLVOAvatarDefines::EBakedTextureIndex baked_texind = lto->getTexLayer(local_texlayer_index)->getTexLayerSet()->getBakedTexIndex(); LLVOAvatarDefines::ETextureIndex reg_texind = getTexIndex(type, baked_texind); if (reg_texind != LLVOAvatarDefines::TEX_NUM_INDICES) { U32 index = gAgentWearables.getWearableIndex(wearable); gAgentAvatarp->setLocalTexture(reg_texind, gTextureList.getImage(new_id), FALSE, index); gAgentAvatarp->wearableUpdated(type, FALSE); /* telling the manager to rebake once update cycle is fully done */ LLLocalBitmapMgr::setNeedsRebake(); } } } } } } LLVOAvatarDefines::ETextureIndex LLLocalBitmap::getTexIndex( LLWearableType::EType type, LLVOAvatarDefines::EBakedTextureIndex baked_texind) { LLVOAvatarDefines::ETextureIndex result = LLVOAvatarDefines::TEX_NUM_INDICES; // using as a default/fail return. switch(type) { case LLWearableType::WT_ALPHA: { switch(baked_texind) { case LLVOAvatarDefines::BAKED_EYES: { result = LLVOAvatarDefines::TEX_EYES_ALPHA; break; } case LLVOAvatarDefines::BAKED_HAIR: { result = LLVOAvatarDefines::TEX_HAIR_ALPHA; break; } case LLVOAvatarDefines::BAKED_HEAD: { result = LLVOAvatarDefines::TEX_HEAD_ALPHA; break; } case LLVOAvatarDefines::BAKED_LOWER: { result = LLVOAvatarDefines::TEX_LOWER_ALPHA; break; } case LLVOAvatarDefines::BAKED_UPPER: { result = LLVOAvatarDefines::TEX_UPPER_ALPHA; break; } default: { break; } } break; } case LLWearableType::WT_EYES: { if (baked_texind == LLVOAvatarDefines::BAKED_EYES) { result = LLVOAvatarDefines::TEX_EYES_IRIS; } break; } case LLWearableType::WT_GLOVES: { if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) { result = LLVOAvatarDefines::TEX_UPPER_GLOVES; } break; } case LLWearableType::WT_JACKET: { if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) { result = LLVOAvatarDefines::TEX_LOWER_JACKET; } else if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) { result = LLVOAvatarDefines::TEX_UPPER_JACKET; } break; } case LLWearableType::WT_PANTS: { if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) { result = LLVOAvatarDefines::TEX_LOWER_PANTS; } break; } case LLWearableType::WT_SHIRT: { if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) { result = LLVOAvatarDefines::TEX_UPPER_SHIRT; } break; } case LLWearableType::WT_SHOES: { if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) { result = LLVOAvatarDefines::TEX_LOWER_SHOES; } break; } case LLWearableType::WT_SKIN: { switch(baked_texind) { case LLVOAvatarDefines::BAKED_HEAD: { result = LLVOAvatarDefines::TEX_HEAD_BODYPAINT; break; } case LLVOAvatarDefines::BAKED_LOWER: { result = LLVOAvatarDefines::TEX_LOWER_BODYPAINT; break; } case LLVOAvatarDefines::BAKED_UPPER: { result = LLVOAvatarDefines::TEX_UPPER_BODYPAINT; break; } default: { break; } } break; } case LLWearableType::WT_SKIRT: { if (baked_texind == LLVOAvatarDefines::BAKED_SKIRT) { result = LLVOAvatarDefines::TEX_SKIRT; } break; } case LLWearableType::WT_SOCKS: { if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) { result = LLVOAvatarDefines::TEX_LOWER_SOCKS; } break; } case LLWearableType::WT_TATTOO: { switch(baked_texind) { case LLVOAvatarDefines::BAKED_HEAD: { result = LLVOAvatarDefines::TEX_HEAD_TATTOO; break; } case LLVOAvatarDefines::BAKED_LOWER: { result = LLVOAvatarDefines::TEX_LOWER_TATTOO; break; } case LLVOAvatarDefines::BAKED_UPPER: { result = LLVOAvatarDefines::TEX_UPPER_TATTOO; break; } default: { break; } } break; } case LLWearableType::WT_UNDERPANTS: { if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) { result = LLVOAvatarDefines::TEX_LOWER_UNDERPANTS; } break; } case LLWearableType::WT_UNDERSHIRT: { if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) { result = LLVOAvatarDefines::TEX_UPPER_UNDERSHIRT; } break; } default: { llwarns << "Unknown wearable type: " << (int)type << "\n" << "Baked Texture Index: " << (int)baked_texind << "\n" << "Filename: " << mFilename << "\n" << "TrackingID: " << mTrackingID << "\n" << "InworldID: " << mWorldID << llendl; } } return result; } /*=======================================*/ /* LLLocalBitmapTimer: timer class */ /*=======================================*/ LLLocalBitmapTimer::LLLocalBitmapTimer() : LLEventTimer(LL_LOCAL_TIMER_HEARTBEAT) { } LLLocalBitmapTimer::~LLLocalBitmapTimer() { } void LLLocalBitmapTimer::startTimer() { mEventTimer.start(); } void LLLocalBitmapTimer::stopTimer() { mEventTimer.stop(); } bool LLLocalBitmapTimer::isRunning() { return mEventTimer.getStarted(); } BOOL LLLocalBitmapTimer::tick() { LLLocalBitmapMgr::doUpdates(); return FALSE; } /*=======================================*/ /* LLLocalBitmapMgr: manager class */ /*=======================================*/ LLLocalBitmapMgr::LLLocalBitmapMgr() { // The class is all made of static members, should i even bother instantiating? } LLLocalBitmapMgr::~LLLocalBitmapMgr() { } bool LLLocalBitmapMgr::addUnit() { bool add_successful = false; LLFilePicker& picker = LLFilePicker::instance(); if (picker.getMultipleOpenFiles(LLFilePicker::FFLOAD_IMAGE)) { sTimer.stopTimer(); std::string filename = picker.getFirstFile(); while(!filename.empty()) { LLLocalBitmap* unit = new LLLocalBitmap(filename); if (unit->getValid()) { sBitmapList.push_back(unit); add_successful = true; } else { delete unit; unit = NULL; } filename = picker.getNextFile(); } sTimer.startTimer(); } return add_successful; } void LLLocalBitmapMgr::delUnit(LLUUID tracking_id) { if (!sBitmapList.empty()) { std::vector to_delete; for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) { /* finding which ones we want deleted and making a separate list */ LLLocalBitmap* unit = *iter; if (unit->getTrackingID() == tracking_id) { to_delete.push_back(unit); } } for(std::vector::iterator del_iter = to_delete.begin(); del_iter != to_delete.end(); del_iter++) { /* iterating over a temporary list, hence preserving the iterator validity while deleting. */ LLLocalBitmap* unit = *del_iter; sBitmapList.remove(unit); delete unit; unit = NULL; } } } LLUUID LLLocalBitmapMgr::getWorldID(LLUUID tracking_id) { LLUUID world_id = LLUUID::null; for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) { LLLocalBitmap* unit = *iter; if (unit->getTrackingID() == tracking_id) { world_id = unit->getWorldID(); } } return world_id; } std::string LLLocalBitmapMgr::getFilename(LLUUID tracking_id) { std::string filename = ""; for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) { LLLocalBitmap* unit = *iter; if (unit->getTrackingID() == tracking_id) { filename = unit->getFilename(); } } return filename; } void LLLocalBitmapMgr::feedScrollList(LLScrollListCtrl* ctrl) { if (ctrl) { ctrl->clearRows(); if (!sBitmapList.empty()) { for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) { LLSD element; element["columns"][0]["column"] = "unit_name"; element["columns"][0]["type"] = "text"; element["columns"][0]["value"] = (*iter)->getShortName(); element["columns"][1]["column"] = "unit_id_HIDDEN"; element["columns"][1]["type"] = "text"; element["columns"][1]["value"] = (*iter)->getTrackingID(); ctrl->addElement(element); } } } } void LLLocalBitmapMgr::doUpdates() { // preventing theoretical overlap in cases with huge number of loaded images. sTimer.stopTimer(); sNeedsRebake = false; for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) { (*iter)->updateSelf(); } doRebake(); sTimer.startTimer(); } void LLLocalBitmapMgr::setNeedsRebake() { sNeedsRebake = true; } void LLLocalBitmapMgr::doRebake() { /* separated that from doUpdates to insure a rebake can be called separately during deletion */ if (sNeedsRebake) { gAgentAvatarp->forceBakeAllTextures(LL_LOCAL_SLAM_FOR_DEBUG); sNeedsRebake = false; } }