/** * @file lltexlayer.cpp * @brief A texture layer. Used for avatars. * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "lltexlayer.h" #include "llavatarappearance.h" #include "llcrc.h" #include "llimagej2c.h" #include "llimagetga.h" #include "lldir.h" #include "lltexlayerparams.h" #include "lltexturemanagerbridge.h" #include "lllocaltextureobject.h" #include "../llui/llui.h" #include "llwearable.h" #include "llwearabledata.h" #include "llvertexbuffer.h" #include "llviewervisualparam.h" #include "llfasttimer.h" //#include "../tools/imdebug/imdebug.h" using namespace LLAvatarAppearanceDefines; // runway consolidate extern std::string self_av_string(); class LLTexLayerInfo { friend class LLTexLayer; friend class LLTexLayerTemplate; friend class LLTexLayerInterface; public: LLTexLayerInfo(); ~LLTexLayerInfo(); bool parseXml(LLXmlTreeNode* node); bool createVisualParams(LLAvatarAppearance *appearance); bool isUserSettable() { return mLocalTexture != -1; } S32 getLocalTexture() const { return mLocalTexture; } bool getOnlyAlpha() const { return mUseLocalTextureAlphaOnly; } std::string getName() const { return mName; } private: std::string mName; bool mWriteAllChannels; // Don't use masking. Just write RGBA into buffer, LLTexLayerInterface::ERenderPass mRenderPass; std::string mGlobalColor; LLColor4 mFixedColor; S32 mLocalTexture; std::string mStaticImageFileName; bool mStaticImageIsMask; bool mUseLocalTextureAlphaOnly; // Ignore RGB channels from the input texture. Use alpha as a mask bool mIsVisibilityMask; typedef std::vector< std::pair< std::string,bool > > morph_name_list_t; morph_name_list_t mMorphNameList; param_color_info_list_t mParamColorInfoList; param_alpha_info_list_t mParamAlphaInfoList; }; //----------------------------------------------------------------------------- // LLTexLayerSetBuffer // The composite image that a LLViewerTexLayerSet writes to. Each LLViewerTexLayerSet has one. //----------------------------------------------------------------------------- LLTexLayerSetBuffer::LLTexLayerSetBuffer(LLTexLayerSet* const owner) : mTexLayerSet(owner) { } LLTexLayerSetBuffer::~LLTexLayerSetBuffer() { } void LLTexLayerSetBuffer::pushProjection() const { gGL.matrixMode(LLRender::MM_PROJECTION); gGL.pushMatrix(); gGL.loadIdentity(); gGL.ortho(0.0f, getCompositeWidth(), 0.0f, getCompositeHeight(), -1.0f, 1.0f); gGL.matrixMode(LLRender::MM_MODELVIEW); gGL.pushMatrix(); gGL.loadIdentity(); } void LLTexLayerSetBuffer::popProjection() const { gGL.matrixMode(LLRender::MM_PROJECTION); gGL.popMatrix(); gGL.matrixMode(LLRender::MM_MODELVIEW); gGL.popMatrix(); } // virtual void LLTexLayerSetBuffer::preRenderTexLayerSet() { // Set up an ortho projection pushProjection(); } // virtual void LLTexLayerSetBuffer::postRenderTexLayerSet(bool success) { popProjection(); } bool LLTexLayerSetBuffer::renderTexLayerSet(LLRenderTarget* bound_target) { // Default color mask for tex layer render gGL.setColorMask(true, true); bool success = true; gAlphaMaskProgram.bind(); gAlphaMaskProgram.setMinimumAlpha(0.004f); LLVertexBuffer::unbind(); // Composite the color data LLGLSUIDefault gls_ui; success &= mTexLayerSet->render( getCompositeOriginX(), getCompositeOriginY(), getCompositeWidth(), getCompositeHeight(), bound_target ); gGL.flush(); midRenderTexLayerSet(success); gAlphaMaskProgram.unbind(); LLVertexBuffer::unbind(); // reset GL state gGL.setColorMask(true, true); gGL.setSceneBlendType(LLRender::BT_ALPHA); return success; } //----------------------------------------------------------------------------- // LLTexLayerSetInfo // An ordered set of texture layers that get composited into a single texture. //----------------------------------------------------------------------------- LLTexLayerSetInfo::LLTexLayerSetInfo() : mBodyRegion( "" ), mWidth( 512 ), mHeight( 512 ), mClearAlpha( true ) { } LLTexLayerSetInfo::~LLTexLayerSetInfo( ) { std::for_each(mLayerInfoList.begin(), mLayerInfoList.end(), DeletePointer()); mLayerInfoList.clear(); } bool LLTexLayerSetInfo::parseXml(LLXmlTreeNode* node) { llassert( node->hasName( "layer_set" ) ); if( !node->hasName( "layer_set" ) ) { return false; } // body_region static LLStdStringHandle body_region_string = LLXmlTree::addAttributeString("body_region"); if( !node->getFastAttributeString( body_region_string, mBodyRegion ) ) { LL_WARNS() << " is missing body_region attribute" << LL_ENDL; return false; } // width, height static LLStdStringHandle width_string = LLXmlTree::addAttributeString("width"); if( !node->getFastAttributeS32( width_string, mWidth ) ) { return false; } static LLStdStringHandle height_string = LLXmlTree::addAttributeString("height"); if( !node->getFastAttributeS32( height_string, mHeight ) ) { return false; } // Optional alpha component to apply after all compositing is complete. static LLStdStringHandle alpha_tga_file_string = LLXmlTree::addAttributeString("alpha_tga_file"); node->getFastAttributeString( alpha_tga_file_string, mStaticAlphaFileName ); static LLStdStringHandle clear_alpha_string = LLXmlTree::addAttributeString("clear_alpha"); node->getFastAttributeBOOL( clear_alpha_string, mClearAlpha ); // for (LLXmlTreeNode* child = node->getChildByName( "layer" ); child; child = node->getNextNamedChild()) { LLTexLayerInfo* info = new LLTexLayerInfo(); if( !info->parseXml( child )) { delete info; return false; } mLayerInfoList.push_back( info ); } return true; } // creates visual params without generating layersets or layers void LLTexLayerSetInfo::createVisualParams(LLAvatarAppearance *appearance) { //layer_info_list_t mLayerInfoList; for (LLTexLayerInfo* layer_info : mLayerInfoList) { layer_info->createVisualParams(appearance); } } //----------------------------------------------------------------------------- // LLTexLayerSet // An ordered set of texture layers that get composited into a single texture. //----------------------------------------------------------------------------- bool LLTexLayerSet::sHasCaches = false; LLTexLayerSet::LLTexLayerSet(LLAvatarAppearance* const appearance) : mAvatarAppearance( appearance ), mIsVisible( true ), mBakedTexIndex(LLAvatarAppearanceDefines::BAKED_HEAD), mInfo( NULL ) { } // virtual LLTexLayerSet::~LLTexLayerSet() { deleteCaches(); std::for_each(mLayerList.begin(), mLayerList.end(), DeletePointer()); mLayerList.clear(); std::for_each(mMaskLayerList.begin(), mMaskLayerList.end(), DeletePointer()); mMaskLayerList.clear(); } //----------------------------------------------------------------------------- // setInfo //----------------------------------------------------------------------------- bool LLTexLayerSet::setInfo(const LLTexLayerSetInfo *info) { llassert(mInfo == NULL); mInfo = info; //mID = info->mID; // No ID mLayerList.reserve(info->mLayerInfoList.size()); for (LLTexLayerInfo* layer_info : info->mLayerInfoList) { LLTexLayerInterface *layer = NULL; if (layer_info->isUserSettable()) { layer = new LLTexLayerTemplate( this, getAvatarAppearance() ); } else { layer = new LLTexLayer(this); } // this is the first time this layer (of either type) is being created - make sure you add the parameters to the avatar appearance if (!layer->setInfo(layer_info, NULL)) { mInfo = NULL; return false; } if (!layer->isVisibilityMask()) { mLayerList.push_back( layer ); } else { mMaskLayerList.push_back(layer); } } requestUpdate(); stop_glerror(); return true; } #if 0 // obsolete //----------------------------------------------------------------------------- // parseData //----------------------------------------------------------------------------- bool LLTexLayerSet::parseData(LLXmlTreeNode* node) { LLTexLayerSetInfo *info = new LLTexLayerSetInfo; if (!info->parseXml(node)) { delete info; return false; } if (!setInfo(info)) { delete info; return false; } return true; } #endif void LLTexLayerSet::deleteCaches() { for(LLTexLayerInterface* layer : mLayerList) { layer->deleteCaches(); } for (LLTexLayerInterface* layer : mMaskLayerList) { layer->deleteCaches(); } } bool LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target ) { bool success = true; mIsVisible = true; if (mMaskLayerList.size() > 0) { for (LLTexLayerInterface* layer : mMaskLayerList) { if (layer->isInvisibleAlphaMask()) { mIsVisible = false; } } } LLGLSUIDefault gls_ui; LLGLDepthTest gls_depth(GL_FALSE, GL_FALSE); gGL.setColorMask(true, true); // clear buffer area to ensure we don't pick up UI elements { gGL.flush(); gAlphaMaskProgram.setMinimumAlpha(0.0f); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, 1.f ); gl_rect_2d_simple( width, height ); gGL.flush(); gAlphaMaskProgram.setMinimumAlpha(0.004f); } if (mIsVisible) { // composite color layers for(LLTexLayerInterface* layer : mLayerList) { if (layer->getRenderPass() == LLTexLayer::RP_COLOR) { gGL.flush(); success &= layer->render(x, y, width, height, bound_target); gGL.flush(); } } renderAlphaMaskTextures(x, y, width, height, bound_target, false); stop_glerror(); } else { gGL.flush(); gGL.setSceneBlendType(LLRender::BT_REPLACE); gAlphaMaskProgram.setMinimumAlpha(0.f); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, 0.f ); gl_rect_2d_simple( width, height ); gGL.setSceneBlendType(LLRender::BT_ALPHA); gGL.flush(); gAlphaMaskProgram.setMinimumAlpha(0.004f); } return success; } bool LLTexLayerSet::isBodyRegion(const std::string& region) const { return mInfo->mBodyRegion == region; } const std::string LLTexLayerSet::getBodyRegionName() const { return mInfo->mBodyRegion; } void LLTexLayerSet::destroyComposite() { if( mComposite ) { mComposite = NULL; } } LLTexLayerSetBuffer* LLTexLayerSet::getComposite() { if (!mComposite) { createComposite(); } return mComposite; } const LLTexLayerSetBuffer* LLTexLayerSet::getComposite() const { return mComposite; } void LLTexLayerSet::gatherMorphMaskAlpha(U8 *data, S32 origin_x, S32 origin_y, S32 width, S32 height, LLRenderTarget* bound_target) { LL_PROFILE_ZONE_SCOPED; memset(data, 255, width * height); for(LLTexLayerInterface* layer : mLayerList) { layer->gatherAlphaMasks(data, origin_x, origin_y, width, height, bound_target); } // Set alpha back to that of our alpha masks. renderAlphaMaskTextures(origin_x, origin_y, width, height, bound_target, true); } void LLTexLayerSet::renderAlphaMaskTextures(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target, bool forceClear) { LL_PROFILE_ZONE_SCOPED; const LLTexLayerSetInfo *info = getInfo(); gGL.setColorMask(false, true); gGL.setSceneBlendType(LLRender::BT_REPLACE); // (Optionally) replace alpha with a single component image from a tga file. if (!info->mStaticAlphaFileName.empty()) { gGL.flush(); { LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture(info->mStaticAlphaFileName, true); if( tex ) { LLGLSUIDefault gls_ui; gGL.getTexUnit(0)->bind(tex); gl_rect_2d_simple_tex( width, height ); } } gGL.flush(); } else if (forceClear || info->mClearAlpha || (mMaskLayerList.size() > 0)) { // Set the alpha channel to one (clean up after previous blending) gGL.flush(); gAlphaMaskProgram.setMinimumAlpha(0.f); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, 1.f ); gl_rect_2d_simple( width, height ); gGL.flush(); gAlphaMaskProgram.setMinimumAlpha(0.004f); } // (Optional) Mask out part of the baked texture with alpha masks // will still have an effect even if mClearAlpha is set or the alpha component was replaced if (mMaskLayerList.size() > 0) { gGL.setSceneBlendType(LLRender::BT_MULT_ALPHA); for (LLTexLayerInterface* layer : mMaskLayerList) { gGL.flush(); layer->blendAlphaTexture(x,y,width, height); gGL.flush(); } } gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.setColorMask(true, true); gGL.setSceneBlendType(LLRender::BT_ALPHA); } void LLTexLayerSet::applyMorphMask(const U8* tex_data, S32 width, S32 height, S32 num_components) { mAvatarAppearance->applyMorphMask(tex_data, width, height, num_components, mBakedTexIndex); } bool LLTexLayerSet::isMorphValid() const { for(const LLTexLayerInterface* layer : mLayerList) { if (layer && !layer->isMorphValid()) { return false; } } return true; } void LLTexLayerSet::invalidateMorphMasks() { for(LLTexLayerInterface* layer : mLayerList) { if (layer) { layer->invalidateMorphMasks(); } } } //----------------------------------------------------------------------------- // LLTexLayerInfo //----------------------------------------------------------------------------- LLTexLayerInfo::LLTexLayerInfo() : mWriteAllChannels( false ), mRenderPass(LLTexLayer::RP_COLOR), mFixedColor( 0.f, 0.f, 0.f, 0.f ), mLocalTexture( -1 ), mStaticImageIsMask( false ), mUseLocalTextureAlphaOnly(false), mIsVisibilityMask(false) { } LLTexLayerInfo::~LLTexLayerInfo( ) { std::for_each(mParamColorInfoList.begin(), mParamColorInfoList.end(), DeletePointer()); mParamColorInfoList.clear(); std::for_each(mParamAlphaInfoList.begin(), mParamAlphaInfoList.end(), DeletePointer()); mParamAlphaInfoList.clear(); } bool LLTexLayerInfo::parseXml(LLXmlTreeNode* node) { llassert( node->hasName( "layer" ) ); // name attribute static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); if( !node->getFastAttributeString( name_string, mName ) ) { return false; } static LLStdStringHandle write_all_channels_string = LLXmlTree::addAttributeString("write_all_channels"); node->getFastAttributeBOOL( write_all_channels_string, mWriteAllChannels ); std::string render_pass_name; static LLStdStringHandle render_pass_string = LLXmlTree::addAttributeString("render_pass"); if( node->getFastAttributeString( render_pass_string, render_pass_name ) ) { if( render_pass_name == "bump" ) { mRenderPass = LLTexLayer::RP_BUMP; } } // Note: layers can have either a "global_color" attrib, a "fixed_color" attrib, or a child. // global color attribute (optional) static LLStdStringHandle global_color_string = LLXmlTree::addAttributeString("global_color"); node->getFastAttributeString( global_color_string, mGlobalColor ); // Visibility mask (optional) bool is_visibility; static LLStdStringHandle visibility_mask_string = LLXmlTree::addAttributeString("visibility_mask"); if (node->getFastAttributeBOOL(visibility_mask_string, is_visibility)) { mIsVisibilityMask = is_visibility; } // color attribute (optional) LLColor4U color4u; static LLStdStringHandle fixed_color_string = LLXmlTree::addAttributeString("fixed_color"); if( node->getFastAttributeColor4U( fixed_color_string, color4u ) ) { mFixedColor.setVec( color4u ); } // optional sub-element for (LLXmlTreeNode* texture_node = node->getChildByName( "texture" ); texture_node; texture_node = node->getNextNamedChild()) { std::string local_texture_name; static LLStdStringHandle tga_file_string = LLXmlTree::addAttributeString("tga_file"); static LLStdStringHandle local_texture_string = LLXmlTree::addAttributeString("local_texture"); static LLStdStringHandle file_is_mask_string = LLXmlTree::addAttributeString("file_is_mask"); static LLStdStringHandle local_texture_alpha_only_string = LLXmlTree::addAttributeString("local_texture_alpha_only"); if( texture_node->getFastAttributeString( tga_file_string, mStaticImageFileName ) ) { texture_node->getFastAttributeBOOL( file_is_mask_string, mStaticImageIsMask ); } else if (texture_node->getFastAttributeString(local_texture_string, local_texture_name)) { texture_node->getFastAttributeBOOL( local_texture_alpha_only_string, mUseLocalTextureAlphaOnly ); /* if ("upper_shirt" == local_texture_name) mLocalTexture = TEX_UPPER_SHIRT; */ mLocalTexture = TEX_NUM_INDICES; for (const LLAvatarAppearanceDictionary::Textures::value_type& dict_pair : LLAvatarAppearance::getDictionary()->getTextures()) { const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = dict_pair.second; if (local_texture_name == texture_dict->mName) { mLocalTexture = dict_pair.first; break; } } if (mLocalTexture == TEX_NUM_INDICES) { LL_WARNS() << " element has invalid local_texture attribute: " << mName << " " << local_texture_name << LL_ENDL; return false; } } else { LL_WARNS() << " element is missing a required attribute. " << mName << LL_ENDL; return false; } } for (LLXmlTreeNode* maskNode = node->getChildByName( "morph_mask" ); maskNode; maskNode = node->getNextNamedChild()) { std::string morph_name; static LLStdStringHandle morph_name_string = LLXmlTree::addAttributeString("morph_name"); if (maskNode->getFastAttributeString(morph_name_string, morph_name)) { bool invert = false; static LLStdStringHandle invert_string = LLXmlTree::addAttributeString("invert"); maskNode->getFastAttributeBOOL(invert_string, invert); mMorphNameList.push_back(std::pair(morph_name,invert)); } } // optional sub-element (color or alpha params) for (LLXmlTreeNode* child = node->getChildByName( "param" ); child; child = node->getNextNamedChild()) { if( child->getChildByName( "param_color" ) ) { // LLTexLayerParamColorInfo* info = new LLTexLayerParamColorInfo(); if (!info->parseXml(child)) { delete info; return false; } mParamColorInfoList.push_back(info); } else if( child->getChildByName( "param_alpha" ) ) { // LLTexLayerParamAlphaInfo* info = new LLTexLayerParamAlphaInfo( ); if (!info->parseXml(child)) { delete info; return false; } mParamAlphaInfoList.push_back(info); } } return true; } bool LLTexLayerInfo::createVisualParams(LLAvatarAppearance *appearance) { bool success = true; for (LLTexLayerParamColorInfo* color_info : mParamColorInfoList) { LLTexLayerParamColor* param_color = new LLTexLayerParamColor(appearance); if (!param_color->setInfo(color_info, true)) { LL_WARNS() << "NULL TexLayer Color Param could not be added to visual param list. Deleting." << LL_ENDL; delete param_color; success = false; } } for (LLTexLayerParamAlphaInfo* alpha_info : mParamAlphaInfoList) { LLTexLayerParamAlpha* param_alpha = new LLTexLayerParamAlpha(appearance); if (!param_alpha->setInfo(alpha_info, true)) { LL_WARNS() << "NULL TexLayer Alpha Param could not be added to visual param list. Deleting." << LL_ENDL; delete param_alpha; success = false; } } return success; } LLTexLayerInterface::LLTexLayerInterface(LLTexLayerSet* const layer_set): mTexLayerSet( layer_set ), mMorphMasksValid( false ), mInfo(NULL), mHasMorph(false) { } LLTexLayerInterface::LLTexLayerInterface(const LLTexLayerInterface &layer, LLWearable *wearable): mTexLayerSet( layer.mTexLayerSet ), mInfo(NULL) { // don't add visual params for cloned layers setInfo(layer.getInfo(), wearable); mHasMorph = layer.mHasMorph; } bool LLTexLayerInterface::setInfo(const LLTexLayerInfo *info, LLWearable* wearable ) // This sets mInfo and calls initialization functions { // setInfo should only be called once. Code is not robust enough to handle redefinition of a texlayer. // Not a critical warning, but could be useful for debugging later issues. -Nyx if (mInfo != NULL) { LL_WARNS() << "mInfo != NULL" << LL_ENDL; } mInfo = info; //mID = info->mID; // No ID mParamColorList.reserve(mInfo->mParamColorInfoList.size()); for (LLTexLayerParamColorInfo* color_info : mInfo->mParamColorInfoList) { LLTexLayerParamColor* param_color; if (!wearable) { param_color = new LLTexLayerParamColor(this); if (!param_color->setInfo(color_info, true)) { mInfo = NULL; return false; } } else { param_color = (LLTexLayerParamColor*)wearable->getVisualParam(color_info->getID()); if (!param_color) { mInfo = NULL; return false; } } mParamColorList.push_back( param_color ); } mParamAlphaList.reserve(mInfo->mParamAlphaInfoList.size()); for (LLTexLayerParamAlphaInfo* alpha_info : mInfo->mParamAlphaInfoList) { LLTexLayerParamAlpha* param_alpha; if (!wearable) { param_alpha = new LLTexLayerParamAlpha( this ); if (!param_alpha->setInfo(alpha_info, true)) { mInfo = NULL; return false; } } else { param_alpha = (LLTexLayerParamAlpha*) wearable->getVisualParam(alpha_info->getID()); if (!param_alpha) { mInfo = NULL; return false; } } mParamAlphaList.push_back( param_alpha ); } return true; } /*virtual*/ void LLTexLayerInterface::requestUpdate() { mTexLayerSet->requestUpdate(); } const std::string& LLTexLayerInterface::getName() const { return mInfo->mName; } ETextureIndex LLTexLayerInterface::getLocalTextureIndex() const { return (ETextureIndex) mInfo->mLocalTexture; } LLWearableType::EType LLTexLayerInterface::getWearableType() const { ETextureIndex te = getLocalTextureIndex(); if (TEX_INVALID == te) { LLWearableType::EType type = LLWearableType::WT_INVALID; for (LLTexLayerParamColor* param : mParamColorList) { if (param) { LLWearableType::EType new_type = (LLWearableType::EType)param->getWearableType(); if (new_type != LLWearableType::WT_INVALID && new_type != type) { if (type != LLWearableType::WT_INVALID) { return LLWearableType::WT_INVALID; } type = new_type; } } } for (LLTexLayerParamAlpha* param : mParamAlphaList) { if (param) { LLWearableType::EType new_type = (LLWearableType::EType)param->getWearableType(); if (new_type != LLWearableType::WT_INVALID && new_type != type) { if (type != LLWearableType::WT_INVALID) { return LLWearableType::WT_INVALID; } type = new_type; } } } return type; } return LLAvatarAppearance::getDictionary()->getTEWearableType(te); } LLTexLayerInterface::ERenderPass LLTexLayerInterface::getRenderPass() const { return mInfo->mRenderPass; } const std::string& LLTexLayerInterface::getGlobalColor() const { return mInfo->mGlobalColor; } bool LLTexLayerInterface::isVisibilityMask() const { return mInfo->mIsVisibilityMask; } void LLTexLayerInterface::invalidateMorphMasks() { mMorphMasksValid = false; } LLViewerVisualParam* LLTexLayerInterface::getVisualParamPtr(S32 index) const { LLViewerVisualParam *result = NULL; for (LLTexLayerParamColor* param : mParamColorList) { if (param->getID() == index) { result = param; } } for (LLTexLayerParamAlpha* param : mParamAlphaList) { if (param->getID() == index) { result = param; } } return result; } //----------------------------------------------------------------------------- // LLTexLayer // A single texture layer, consisting of: // * color, consisting of either // * one or more color parameters (weighted colors) // * a reference to a global color // * a fixed color with non-zero alpha // * opaque white (the default) // * (optional) a texture defined by either // * a GUID // * a texture entry index (TE) // * (optional) one or more alpha parameters (weighted alpha textures) //----------------------------------------------------------------------------- LLTexLayer::LLTexLayer(LLTexLayerSet* const layer_set) : LLTexLayerInterface( layer_set ), mLocalTextureObject(NULL) { } LLTexLayer::LLTexLayer(const LLTexLayer &layer, LLWearable *wearable) : LLTexLayerInterface( layer, wearable ), mLocalTextureObject(NULL) { } LLTexLayer::LLTexLayer(const LLTexLayerTemplate &layer_template, LLLocalTextureObject *lto, LLWearable *wearable) : LLTexLayerInterface( layer_template, wearable ), mLocalTextureObject(lto) { } LLTexLayer::~LLTexLayer() { // mParamAlphaList and mParamColorList are LLViewerVisualParam's and get // deleted with ~LLCharacter() //std::for_each(mParamAlphaList.begin(), mParamAlphaList.end(), DeletePointer()); //std::for_each(mParamColorList.begin(), mParamColorList.end(), DeletePointer()); for (alpha_cache_t::value_type& alpha_pair : mAlphaCache) { U8* alpha_data = alpha_pair.second; ll_aligned_free_32(alpha_data); } } void LLTexLayer::asLLSD(LLSD& sd) const { // *TODO: Finish sd["id"] = getUUID(); } //----------------------------------------------------------------------------- // setInfo //----------------------------------------------------------------------------- bool LLTexLayer::setInfo(const LLTexLayerInfo* info, LLWearable* wearable ) { return LLTexLayerInterface::setInfo(info, wearable); } //static void LLTexLayer::calculateTexLayerColor(const param_color_list_t ¶m_list, LLColor4 &net_color) { for (const LLTexLayerParamColor* param : param_list) { LLColor4 param_net = param->getNetColor(); const LLTexLayerParamColorInfo *info = (LLTexLayerParamColorInfo *)param->getInfo(); switch(info->getOperation()) { case LLTexLayerParamColor::OP_ADD: net_color += param_net; break; case LLTexLayerParamColor::OP_MULTIPLY: net_color = net_color * param_net; break; case LLTexLayerParamColor::OP_BLEND: net_color = lerp(net_color, param_net, param->getWeight()); break; default: llassert(0); break; } } net_color.clamp(); } /*virtual*/ void LLTexLayer::deleteCaches() { // Only need to delete caches for alpha params. Color params don't hold extra memory for (LLTexLayerParamAlpha* param : mParamAlphaList) { param->deleteCaches(); } } bool LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target) { // *TODO: Is this correct? //gPipeline.disableLights(); stop_glerror(); LLColor4 net_color; bool color_specified = findNetColor(&net_color); if (mTexLayerSet->getAvatarAppearance()->mIsDummy) { color_specified = true; net_color = LLAvatarAppearance::getDummyColor(); } bool success = true; // If you can't see the layer, don't render it. if( is_approx_zero( net_color.mV[VALPHA] ) ) { return success; } bool alpha_mask_specified = false; param_alpha_list_t::const_iterator iter = mParamAlphaList.begin(); if( iter != mParamAlphaList.end() ) { // If we have alpha masks, but we're skipping all of them, skip the whole layer. // However, we can't do this optimization if we have morph masks that need updating. /* if (!mHasMorph) { bool skip_layer = true; while( iter != mParamAlphaList.end() ) { const LLTexLayerParamAlpha* param = *iter; if( !param->getSkip() ) { skip_layer = false; break; } iter++; } if( skip_layer ) { return success; } }//*/ const bool force_render = true; renderMorphMasks(x, y, width, height, net_color, bound_target, force_render); alpha_mask_specified = true; gGL.flush(); gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ONE_MINUS_DEST_ALPHA); } gGL.color4fv( net_color.mV); if( getInfo()->mWriteAllChannels ) { gGL.flush(); gGL.setSceneBlendType(LLRender::BT_REPLACE); } if( (getInfo()->mLocalTexture != -1) && !getInfo()->mUseLocalTextureAlphaOnly ) { { LLGLTexture* tex = NULL; if (mLocalTextureObject && mLocalTextureObject->getImage()) { tex = mLocalTextureObject->getImage(); if (mLocalTextureObject->getID() == IMG_DEFAULT_AVATAR) { tex = NULL; } } else { LL_INFOS() << "lto not defined or image not defined: " << getInfo()->getLocalTexture() << " lto: " << mLocalTextureObject << LL_ENDL; } // if( mTexLayerSet->getAvatarAppearance()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &image_gl ) ) { if( tex ) { bool no_alpha_test = getInfo()->mWriteAllChannels; if (no_alpha_test) { gAlphaMaskProgram.setMinimumAlpha(0.f); } LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode(); gGL.getTexUnit(0)->bind(tex, true); gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->setTextureAddressMode(old_mode); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); if (no_alpha_test) { gAlphaMaskProgram.setMinimumAlpha(0.004f); } } } // else // { // success = false; // } } } if( !getInfo()->mStaticImageFileName.empty() ) { { LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask); if( tex ) { gGL.getTexUnit(0)->bind(tex, true); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } else { success = false; } } } if(((-1 == getInfo()->mLocalTexture) || getInfo()->mUseLocalTextureAlphaOnly) && getInfo()->mStaticImageFileName.empty() && color_specified ) { gAlphaMaskProgram.setMinimumAlpha(0.000f); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4fv( net_color.mV ); gl_rect_2d_simple( width, height ); gAlphaMaskProgram.setMinimumAlpha(0.004f); } if( alpha_mask_specified || getInfo()->mWriteAllChannels ) { // Restore standard blend func value gGL.flush(); gGL.setSceneBlendType(LLRender::BT_ALPHA); stop_glerror(); } if( !success ) { LL_INFOS() << "LLTexLayer::render() partial: " << getInfo()->mName << LL_ENDL; } return success; } const U8* LLTexLayer::getAlphaData() const { LLCRC alpha_mask_crc; const LLUUID& uuid = getUUID(); alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES); for (const LLTexLayerParamAlpha* param : mParamAlphaList) { // MULTI-WEARABLE: verify visual parameters used here F32 param_weight = param->getWeight(); alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32)); } U32 cache_index = alpha_mask_crc.getCRC(); alpha_cache_t::const_iterator iter2 = mAlphaCache.find(cache_index); return (iter2 == mAlphaCache.end()) ? 0 : iter2->second; } bool LLTexLayer::findNetColor(LLColor4* net_color) const { // Color is either: // * one or more color parameters (weighted colors) (which may make use of a global color or fixed color) // * a reference to a global color // * a fixed color with non-zero alpha // * opaque white (the default) if( !mParamColorList.empty() ) { if( !getGlobalColor().empty() ) { net_color->setVec( mTexLayerSet->getAvatarAppearance()->getGlobalColor( getInfo()->mGlobalColor ) ); } else if (getInfo()->mFixedColor.mV[VALPHA]) { net_color->setVec( getInfo()->mFixedColor ); } else { net_color->setVec( 0.f, 0.f, 0.f, 0.f ); } calculateTexLayerColor(mParamColorList, *net_color); return true; } if( !getGlobalColor().empty() ) { net_color->setVec( mTexLayerSet->getAvatarAppearance()->getGlobalColor( getGlobalColor() ) ); return true; } if( getInfo()->mFixedColor.mV[VALPHA] ) { net_color->setVec( getInfo()->mFixedColor ); return true; } net_color->setToWhite(); return false; // No need to draw a separate colored polygon } bool LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height) { bool success = true; gGL.flush(); if( !getInfo()->mStaticImageFileName.empty() ) { LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask ); if( tex ) { gAlphaMaskProgram.setMinimumAlpha(0.f); gGL.getTexUnit(0)->bind(tex, true); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gAlphaMaskProgram.setMinimumAlpha(0.004f); } else { success = false; } } else { if (getInfo()->mLocalTexture >=0 && getInfo()->mLocalTexture < TEX_NUM_INDICES) { LLGLTexture* tex = mLocalTextureObject->getImage(); if (tex) { gAlphaMaskProgram.setMinimumAlpha(0.f); gGL.getTexUnit(0)->bind(tex); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gAlphaMaskProgram.setMinimumAlpha(0.004f); } } } return success; } /*virtual*/ void LLTexLayer::gatherAlphaMasks(U8 *data, S32 originX, S32 originY, S32 width, S32 height, LLRenderTarget* bound_target) { addAlphaMask(data, originX, originY, width, height, bound_target); } void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLColor4 &layer_color, LLRenderTarget* bound_target, bool force_render) { if (!force_render && !hasMorph()) { LL_DEBUGS() << "skipping renderMorphMasks for " << getUUID() << LL_ENDL; return; } LL_PROFILE_ZONE_SCOPED; bool success = true; llassert( !mParamAlphaList.empty() ); gAlphaMaskProgram.setMinimumAlpha(0.f); gGL.setColorMask(false, true); LLTexLayerParamAlpha* first_param = *mParamAlphaList.begin(); // Note: if the first param is a mulitply, multiply against the current buffer's alpha if( !first_param || !first_param->getMultiplyBlend() ) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); // Clear the alpha gGL.flush(); gGL.setSceneBlendType(LLRender::BT_REPLACE); gGL.color4f( 0.f, 0.f, 0.f, 0.f ); gl_rect_2d_simple( width, height ); } // Accumulate alphas gGL.color4f( 1.f, 1.f, 1.f, 1.f ); for (LLTexLayerParamAlpha* param : mParamAlphaList) { success &= param->render( x, y, width, height ); if (!success && !force_render) { LL_DEBUGS() << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL; return; } } // Approximates a min() function gGL.flush(); gGL.setSceneBlendType(LLRender::BT_MULT_ALPHA); // Accumulate the alpha component of the texture if( getInfo()->mLocalTexture != -1 ) { LLGLTexture* tex = mLocalTextureObject->getImage(); if( tex && (tex->getComponents() == 4) ) { LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode(); gGL.getTexUnit(0)->bind(tex, true); gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->setTextureAddressMode(old_mode); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } } if( !getInfo()->mStaticImageFileName.empty() && getInfo()->mStaticImageIsMask ) { LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask); if( tex ) { if( (tex->getComponents() == 4) || (tex->getComponents() == 1) ) { gGL.getTexUnit(0)->bind(tex, true); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } else { LL_WARNS() << "Skipping rendering of " << getInfo()->mStaticImageFileName << "; expected 1 or 4 components." << LL_ENDL; } } } // Draw a rectangle with the layer color to multiply the alpha by that color's alpha. // Note: we're still using gGL.blendFunc( GL_DST_ALPHA, GL_ZERO ); if ( !is_approx_equal(layer_color.mV[VALPHA], 1.f) ) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4fv(layer_color.mV); gl_rect_2d_simple( width, height ); } gAlphaMaskProgram.setMinimumAlpha(0.004f); LLGLSUIDefault gls_ui; gGL.setColorMask(true, true); if (hasMorph() && success) { LLCRC alpha_mask_crc; const LLUUID& uuid = getUUID(); alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES); for (const LLTexLayerParamAlpha* param : mParamAlphaList) { F32 param_weight = param->getWeight(); alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32)); } U32 cache_index = alpha_mask_crc.getCRC(); U8* alpha_data = NULL; // We believe we need to generate morph masks, do not assume that the cached version is accurate. // We can get bad morph masks during login, on minimize, and occasional gl errors. // We should only be doing this when we believe something has changed with respect to the user's appearance. { LL_DEBUGS("Avatar") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL; // clear out a slot if we have filled our cache S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1; while ((S32)mAlphaCache.size() >= max_cache_entries) { alpha_cache_t::iterator iter2 = mAlphaCache.begin(); // arbitrarily grab the first entry alpha_data = iter2->second; ll_aligned_free_32(alpha_data); mAlphaCache.erase(iter2); } // GPUs tend to be very uptight about memory alignment as the DMA used to convey // said data to the card works better when well-aligned so plain old default-aligned heap mem is a no-no //new U8[width * height]; size_t bytes_per_pixel = 1; // unsigned byte alpha channel only... size_t row_size = (width + 3) & ~0x3; // OpenGL 4-byte row align (even for things < 4 bpp...) size_t pixels = (row_size * height); size_t mem_size = pixels * bytes_per_pixel; alpha_data = (U8*)ll_aligned_malloc_32(mem_size); bool skip_readback = LLRender::sNsightDebugSupport; // nSight doesn't support use of glReadPixels if (!skip_readback) { if (gGLManager.mIsIntel) { // work-around for broken intel drivers which cannot do glReadPixels on an RGBA FBO // returning only the alpha portion without locking up downstream U8* temp = (U8*)ll_aligned_malloc_32(mem_size << 2); // allocate same size, but RGBA if (bound_target) { gGL.getTexUnit(0)->bind(bound_target); } else { gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, 0); } #if GL_VERSION_1_1 glGetTexImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGBA, GL_UNSIGNED_BYTE, temp); #endif U8* alpha_cursor = alpha_data; U8* pixel = temp; for (int i = 0; i < pixels; i++) { *alpha_cursor++ = pixel[3]; pixel += 4; } gGL.getTexUnit(0)->disable(); ll_aligned_free_32(temp); } else { // platforms with working drivers... // We just want GL_ALPHA, but that isn't supported in OGL core profile 4. static const size_t TEMP_BYTES_PER_PIXEL = 4; U8* temp_data = (U8*)ll_aligned_malloc_32(mem_size * TEMP_BYTES_PER_PIXEL); glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_data); for (size_t pixel = 0; pixel < pixels; pixel++) { alpha_data[pixel] = temp_data[(pixel * TEMP_BYTES_PER_PIXEL) + 3]; } ll_aligned_free_32(temp_data); } } else { ll_aligned_free_32(alpha_data); alpha_data = nullptr; } mAlphaCache[cache_index] = alpha_data; } getTexLayerSet()->getAvatarAppearance()->dirtyMesh(); mMorphMasksValid = true; getTexLayerSet()->applyMorphMask(alpha_data, width, height, 1); } } void LLTexLayer::addAlphaMask(U8 *data, S32 originX, S32 originY, S32 width, S32 height, LLRenderTarget* bound_target) { LL_PROFILE_ZONE_SCOPED; S32 size = width * height; const U8* alphaData = getAlphaData(); if (!alphaData && hasAlphaParams()) { LLColor4 net_color; findNetColor( &net_color ); // TODO: eliminate need for layer morph mask valid flag invalidateMorphMasks(); const bool force_render = false; renderMorphMasks(originX, originY, width, height, net_color, bound_target, force_render); alphaData = getAlphaData(); } if (alphaData) { for( S32 i = 0; i < size; i++ ) { U8 curAlpha = data[i]; U16 resultAlpha = curAlpha; resultAlpha *= ( ((U16)alphaData[i]) + 1); resultAlpha = resultAlpha >> 8; data[i] = (U8)resultAlpha; } } } /*virtual*/ bool LLTexLayer::isInvisibleAlphaMask() const { if (mLocalTextureObject) { if (mLocalTextureObject->getID() == IMG_INVISIBLE) { return true; } } return false; } LLUUID LLTexLayer::getUUID() const { LLUUID uuid; if( getInfo()->mLocalTexture != -1 ) { LLGLTexture* tex = mLocalTextureObject->getImage(); if (tex) { uuid = mLocalTextureObject->getID(); } } if( !getInfo()->mStaticImageFileName.empty() ) { LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask); if( tex ) { uuid = tex->getID(); } } return uuid; } //----------------------------------------------------------------------------- // LLTexLayerTemplate // A single texture layer, consisting of: // * color, consisting of either // * one or more color parameters (weighted colors) // * a reference to a global color // * a fixed color with non-zero alpha // * opaque white (the default) // * (optional) a texture defined by either // * a GUID // * a texture entry index (TE) // * (optional) one or more alpha parameters (weighted alpha textures) //----------------------------------------------------------------------------- LLTexLayerTemplate::LLTexLayerTemplate(LLTexLayerSet* layer_set, LLAvatarAppearance* const appearance) : LLTexLayerInterface(layer_set), mAvatarAppearance( appearance ) { } LLTexLayerTemplate::LLTexLayerTemplate(const LLTexLayerTemplate &layer) : LLTexLayerInterface(layer), mAvatarAppearance(layer.getAvatarAppearance()) { } LLTexLayerTemplate::~LLTexLayerTemplate() { } //----------------------------------------------------------------------------- // setInfo //----------------------------------------------------------------------------- /*virtual*/ bool LLTexLayerTemplate::setInfo(const LLTexLayerInfo* info, LLWearable* wearable ) { return LLTexLayerInterface::setInfo(info, wearable); } U32 LLTexLayerTemplate::updateWearableCache() const { mWearableCache.clear(); LLWearableType::EType wearable_type = getWearableType(); if (LLWearableType::WT_INVALID == wearable_type) { //this isn't a cloneable layer return 0; } U32 num_wearables = getAvatarAppearance()->getWearableData()->getWearableCount(wearable_type); U32 added = 0; for (U32 i = 0; i < num_wearables; i++) { LLWearable* wearable = getAvatarAppearance()->getWearableData()->getWearable(wearable_type, i); if (!wearable) { continue; } mWearableCache.push_back(wearable); added++; } return added; } LLTexLayer* LLTexLayerTemplate::getLayer(U32 i) const { if (mWearableCache.size() <= i) { return NULL; } LLWearable *wearable = mWearableCache[i]; LLLocalTextureObject *lto = NULL; LLTexLayer *layer = NULL; if (wearable) { lto = wearable->getLocalTextureObject(mInfo->mLocalTexture); } if (lto) { layer = lto->getTexLayer(getName()); } return layer; } /*virtual*/ bool LLTexLayerTemplate::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target) { if(!mInfo) { return false ; } bool success = true; updateWearableCache(); for (LLWearable* wearable : mWearableCache) { LLLocalTextureObject *lto = NULL; LLTexLayer *layer = NULL; if (wearable) { lto = wearable->getLocalTextureObject(mInfo->mLocalTexture); } if (lto) { layer = lto->getTexLayer(getName()); } if (layer) { wearable->writeToAvatar(mAvatarAppearance); layer->setLTO(lto); success &= layer->render(x, y, width, height, bound_target); } } return success; } /*virtual*/ bool LLTexLayerTemplate::blendAlphaTexture( S32 x, S32 y, S32 width, S32 height) // Multiplies a single alpha texture against the frame buffer { bool success = true; U32 num_wearables = updateWearableCache(); for (U32 i = 0; i < num_wearables; i++) { LLTexLayer *layer = getLayer(i); if (layer) { success &= layer->blendAlphaTexture(x,y,width,height); } } return success; } /*virtual*/ void LLTexLayerTemplate::gatherAlphaMasks(U8 *data, S32 originX, S32 originY, S32 width, S32 height, LLRenderTarget* bound_target) { U32 num_wearables = updateWearableCache(); U32 i = num_wearables - 1; // For rendering morph masks, we only want to use the top wearable LLTexLayer *layer = getLayer(i); if (layer) { layer->addAlphaMask(data, originX, originY, width, height, bound_target); } } /*virtual*/ void LLTexLayerTemplate::setHasMorph(bool newval) { mHasMorph = newval; U32 num_wearables = updateWearableCache(); for (U32 i = 0; i < num_wearables; i++) { LLTexLayer *layer = getLayer(i); if (layer) { layer->setHasMorph(newval); } } } /*virtual*/ void LLTexLayerTemplate::deleteCaches() { U32 num_wearables = updateWearableCache(); for (U32 i = 0; i < num_wearables; i++) { LLTexLayer *layer = getLayer(i); if (layer) { layer->deleteCaches(); } } } /*virtual*/ bool LLTexLayerTemplate::isInvisibleAlphaMask() const { U32 num_wearables = updateWearableCache(); for (U32 i = 0; i < num_wearables; i++) { LLTexLayer *layer = getLayer(i); if (layer) { if (layer->isInvisibleAlphaMask()) { return true; } } } return false; } //----------------------------------------------------------------------------- // finds a specific layer based on a passed in name //----------------------------------------------------------------------------- LLTexLayerInterface* LLTexLayerSet::findLayerByName(const std::string& name) { for (LLTexLayerInterface* layer : mLayerList) { if (layer->getName() == name) { return layer; } } for (LLTexLayerInterface* layer : mMaskLayerList) { if (layer->getName() == name) { return layer; } } return NULL; } void LLTexLayerSet::cloneTemplates(LLLocalTextureObject *lto, LLAvatarAppearanceDefines::ETextureIndex tex_index, LLWearable *wearable) { // initialize all texlayers with this texture type for this LTO for(LLTexLayerInterface* layer : mLayerList) { LLTexLayerTemplate* layer_template = (LLTexLayerTemplate*)layer; if (layer_template->getInfo()->getLocalTexture() == (S32)tex_index) { lto->addTexLayer(layer_template, wearable); } } for(LLTexLayerInterface* layer : mMaskLayerList) { LLTexLayerTemplate* layer_template = (LLTexLayerTemplate*)layer; if (layer_template->getInfo()->getLocalTexture() == (S32)tex_index) { lto->addTexLayer(layer_template, wearable); } } } //----------------------------------------------------------------------------- // LLTexLayerStaticImageList //----------------------------------------------------------------------------- LLTexLayerStaticImageList::LLTexLayerStaticImageList() : mGLBytes(0), mTGABytes(0), mImageNames(16384) { } LLTexLayerStaticImageList::~LLTexLayerStaticImageList() { deleteCachedImages(); } void LLTexLayerStaticImageList::dumpByteCount() const { LL_INFOS() << "Avatar Static Textures " << "KB GL:" << (mGLBytes / 1024) << "KB TGA:" << (mTGABytes / 1024) << "KB" << LL_ENDL; } void LLTexLayerStaticImageList::deleteCachedImages() { if( mGLBytes || mTGABytes ) { LL_INFOS() << "Clearing Static Textures " << "KB GL:" << (mGLBytes / 1024) << "KB TGA:" << (mTGABytes / 1024) << "KB" << LL_ENDL; //mStaticImageLists uses LLPointers, clear() will cause deletion mStaticImageListTGA.clear(); mStaticImageList.clear(); mGLBytes = 0; mTGABytes = 0; } } // Note: in general, for a given image image we'll call either getImageTga() or getTexture(). // We call getImageTga() if the image is used as an alpha gradient. // Otherwise, we call getTexture() // Returns an LLImageTGA that contains the encoded data from a tga file named file_name. // Caches the result to speed identical subsequent requests. LLImageTGA* LLTexLayerStaticImageList::getImageTGA(const std::string& file_name) { LL_PROFILE_ZONE_SCOPED; const char *namekey = mImageNames.addString(file_name); image_tga_map_t::const_iterator iter = mStaticImageListTGA.find(namekey); if( iter != mStaticImageListTGA.end() ) { return iter->second; } else { std::string path; path = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,file_name); LLPointer image_tga = new LLImageTGA( path ); if( image_tga->getDataSize() > 0 ) { mStaticImageListTGA[ namekey ] = image_tga; mTGABytes += image_tga->getDataSize(); return image_tga; } else { return NULL; } } } // Returns a GL Image (without a backing ImageRaw) that contains the decoded data from a tga file named file_name. // Caches the result to speed identical subsequent requests. LLGLTexture* LLTexLayerStaticImageList::getTexture(const std::string& file_name, bool is_mask) { LL_PROFILE_ZONE_SCOPED; LLPointer tex; const char *namekey = mImageNames.addString(file_name); texture_map_t::const_iterator iter = mStaticImageList.find(namekey); if( iter != mStaticImageList.end() ) { tex = iter->second; } else { llassert(gTextureManagerBridgep); tex = gTextureManagerBridgep->getLocalTexture( false ); LLPointer image_raw = new LLImageRaw; if( loadImageRaw( file_name, image_raw ) ) { if( (image_raw->getComponents() == 1) && is_mask ) { // Convert grayscale alpha masks from single channel into RGBA. // Fill RGB with black to allow fixed function gl calls // to match shader implementation. LLPointer alpha_image_raw = image_raw; image_raw = new LLImageRaw(image_raw->getWidth(), image_raw->getHeight(), 4); image_raw->copyUnscaledAlphaMask(alpha_image_raw, LLColor4U::black); } tex->createGLTexture(0, image_raw, 0, true, LLGLTexture::LOCAL); gGL.getTexUnit(0)->bind(tex); tex->setAddressMode(LLTexUnit::TAM_CLAMP); mStaticImageList [ namekey ] = tex; mGLBytes += (S32)tex->getWidth() * tex->getHeight() * tex->getComponents(); } else { tex = NULL; } } return tex; } // Reads a .tga file, decodes it, and puts the decoded data in image_raw. // Returns true if successful. bool LLTexLayerStaticImageList::loadImageRaw(const std::string& file_name, LLImageRaw* image_raw) { LL_PROFILE_ZONE_SCOPED; bool success = false; std::string path; path = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,file_name); LLPointer image_tga = new LLImageTGA( path ); if( image_tga->getDataSize() > 0 ) { // Copy data from tga to raw. success = image_tga->decode( image_raw ); } return success; }