/** * @file lltexlayer.cpp * @brief A texture layer. Used for avatars. * * $LicenseInfo:firstyear=2002&license=viewergpl$ * * Copyright (c) 2002-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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 "imageids.h" #include "llagent.h" #include "llcrc.h" #include "lldir.h" #include "llglheaders.h" #include "llimagebmp.h" #include "llimagej2c.h" #include "llimagetga.h" #include "llpolymorph.h" #include "llquantize.h" #include "lltexlayer.h" #include "llui.h" #include "llvfile.h" #include "llviewerimagelist.h" #include "llviewerimagelist.h" #include "llviewerregion.h" #include "llviewerstats.h" #include "llviewerwindow.h" #include "llvoavatar.h" #include "llxmltree.h" #include "pipeline.h" #include "v4coloru.h" #include "llrender.h" #include "llassetuploadresponders.h" //#include "../tools/imdebug/imdebug.h" using namespace LLVOAvatarDefines; // static S32 LLTexLayerSetBuffer::sGLByteCount = 0; S32 LLTexLayerSetBuffer::sGLBumpByteCount = 0; //----------------------------------------------------------------------------- // LLBakedUploadData() //----------------------------------------------------------------------------- LLBakedUploadData::LLBakedUploadData( LLVOAvatar* avatar, LLTexLayerSet* layerset, LLTexLayerSetBuffer* layerset_buffer, const LLUUID & id ) : mAvatar( avatar ), mLayerSet( layerset ), mLayerSetBuffer( layerset_buffer ), mID(id) { mStartTime = LLFrameTimer::getTotalTime(); // Record starting time for( S32 i = 0; i < WT_COUNT; i++ ) { LLWearable* wearable = gAgent.getWearable( (EWearableType)i); if( wearable ) { mWearableAssets[i] = wearable->getID(); } } } //----------------------------------------------------------------------------- // LLTexLayerSetBuffer // The composite image that a LLTexLayerSet writes to. Each LLTexLayerSet has one. //----------------------------------------------------------------------------- LLTexLayerSetBuffer::LLTexLayerSetBuffer( LLTexLayerSet* owner, S32 width, S32 height, BOOL has_bump ) : // ORDER_LAST => must render these after the hints are created. LLDynamicTexture( width, height, 4, LLDynamicTexture::ORDER_LAST, TRUE ), mNeedsUpdate( TRUE ), mNeedsUpload( FALSE ), mUploadPending( FALSE ), // Not used for any logic here, just to sync sending of updates mTexLayerSet( owner ) { LLTexLayerSetBuffer::sGLByteCount += getSize(); mHasBump = has_bump ; mBumpTex = NULL ; createBumpTexture() ; } LLTexLayerSetBuffer::~LLTexLayerSetBuffer() { LLTexLayerSetBuffer::sGLByteCount -= getSize(); if( mBumpTex.notNull()) { mBumpTex = NULL ; LLImageGL::sGlobalTextureMemory -= mWidth * mHeight * 4; LLTexLayerSetBuffer::sGLBumpByteCount -= mWidth * mHeight * 4; } } //virtual void LLTexLayerSetBuffer::restoreGLTexture() { createBumpTexture() ; LLDynamicTexture::restoreGLTexture() ; } //virtual void LLTexLayerSetBuffer::destroyGLTexture() { if( mBumpTex.notNull() ) { mBumpTex = NULL ; LLImageGL::sGlobalTextureMemory -= mWidth * mHeight * 4; LLTexLayerSetBuffer::sGLBumpByteCount -= mWidth * mHeight * 4; } LLDynamicTexture::destroyGLTexture() ; } void LLTexLayerSetBuffer::createBumpTexture() { if( mHasBump ) { LLGLSUIDefault gls_ui; mBumpTex = new LLImageGL(FALSE) ; if(!mBumpTex->createGLTexture()) { mBumpTex = NULL ; return ; } gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mBumpTex->getTexName()); stop_glerror(); gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_RGBA8, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, NULL); stop_glerror(); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLImageGL::sGlobalTextureMemory += mWidth * mHeight * 4; LLTexLayerSetBuffer::sGLBumpByteCount += mWidth * mHeight * 4; } } // static void LLTexLayerSetBuffer::dumpTotalByteCount() { llinfos << "Composite System GL Buffers: " << (LLTexLayerSetBuffer::sGLByteCount/1024) << "KB" << llendl; llinfos << "Composite System GL Bump Buffers: " << (LLTexLayerSetBuffer::sGLBumpByteCount/1024) << "KB" << llendl; } void LLTexLayerSetBuffer::requestUpdate() { mNeedsUpdate = TRUE; // If we're in the middle of uploading a baked texture, we don't care about it any more. // When it's downloaded, ignore it. mUploadID.setNull(); } void LLTexLayerSetBuffer::requestUpload() { if (!mNeedsUpload) { mNeedsUpload = TRUE; mUploadPending = TRUE; } } void LLTexLayerSetBuffer::cancelUpload() { if (mNeedsUpload) { mNeedsUpload = FALSE; } mUploadPending = FALSE; } void LLTexLayerSetBuffer::pushProjection() { glMatrixMode(GL_PROJECTION); gGL.pushMatrix(); glLoadIdentity(); glOrtho(0.0f, mWidth, 0.0f, mHeight, -1.0f, 1.0f); glMatrixMode(GL_MODELVIEW); gGL.pushMatrix(); glLoadIdentity(); } void LLTexLayerSetBuffer::popProjection() { glMatrixMode(GL_PROJECTION); gGL.popMatrix(); glMatrixMode(GL_MODELVIEW); gGL.popMatrix(); } BOOL LLTexLayerSetBuffer::needsRender() { LLVOAvatar* avatar = mTexLayerSet->getAvatar(); BOOL upload_now = mNeedsUpload && mTexLayerSet->isLocalTextureDataFinal(); BOOL needs_update = gAgent.mNumPendingQueries == 0 && (mNeedsUpdate || upload_now) && !avatar->mAppearanceAnimating; if (needs_update) { BOOL invalid_skirt = avatar->getBakedTE(mTexLayerSet) == TEX_SKIRT_BAKED && !avatar->isWearingWearableType(WT_SKIRT); if (invalid_skirt) { // we were trying to create a skirt texture // but we're no longer wearing a skirt... needs_update = FALSE; cancelUpload(); } else { needs_update &= (avatar->isSelf() || (avatar->isVisible() && !avatar->isCulled())); needs_update &= mTexLayerSet->isLocalTextureDataAvailable(); } } return needs_update; } void LLTexLayerSetBuffer::preRender(BOOL clear_depth) { // Set up an ortho projection pushProjection(); // keep depth buffer, we don't need to clear it LLDynamicTexture::preRender(FALSE); } void LLTexLayerSetBuffer::postRender(BOOL success) { popProjection(); LLDynamicTexture::postRender(success); } BOOL LLTexLayerSetBuffer::render() { U8* baked_bump_data = NULL; // Default color mask for tex layer render gGL.setColorMask(true, true); // do we need to upload, and do we have sufficient data to create an uploadable composite? // When do we upload the texture if gAgent.mNumPendingQueries is non-zero? BOOL upload_now = (gAgent.mNumPendingQueries == 0 && mNeedsUpload && mTexLayerSet->isLocalTextureDataFinal()); BOOL success = TRUE; // Composite bump if( mBumpTex.notNull() ) { // Composite the bump data success &= mTexLayerSet->renderBump( mOrigin.mX, mOrigin.mY, mWidth, mHeight ); stop_glerror(); if (success) { LLGLSUIDefault gls_ui; // read back into texture (this is done externally for the color data) gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mBumpTex->getTexName()); stop_glerror(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mOrigin.mX, mOrigin.mY, mWidth, mHeight); stop_glerror(); // if we need to upload the data, read it back into a buffer if( upload_now ) { baked_bump_data = new U8[ mWidth * mHeight * 4 ]; glReadPixels(mOrigin.mX, mOrigin.mY, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, baked_bump_data ); stop_glerror(); } } } // Composite the color data LLGLSUIDefault gls_ui; success &= mTexLayerSet->render( mOrigin.mX, mOrigin.mY, mWidth, mHeight ); gGL.flush(); if( upload_now ) { if (!success) { delete [] baked_bump_data; llinfos << "Failed attempt to bake " << mTexLayerSet->getBodyRegion() << llendl; mUploadPending = FALSE; } else { readBackAndUpload(baked_bump_data); } } // reset GL state gGL.setColorMask(true, true); gGL.setSceneBlendType(LLRender::BT_ALPHA); // we have valid texture data now mTexture->setGLTextureCreated(true); mNeedsUpdate = FALSE; return success; } bool LLTexLayerSetBuffer::isInitialized(void) const { return mTexture.notNull() && mTexture->isGLTextureCreated(); } BOOL LLTexLayerSetBuffer::updateImmediate() { mNeedsUpdate = TRUE; BOOL result = FALSE; if (needsRender()) { preRender(FALSE); result = render(); postRender(result); } return result; } void LLTexLayerSetBuffer::readBackAndUpload(U8* baked_bump_data) { // pointers for storing data to upload U8* baked_color_data = new U8[ mWidth * mHeight * 4 ]; glReadPixels(mOrigin.mX, mOrigin.mY, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, baked_color_data ); stop_glerror(); llinfos << "Baked " << mTexLayerSet->getBodyRegion() << llendl; LLViewerStats::getInstance()->incStat(LLViewerStats::ST_TEX_BAKES); llassert( gAgent.getAvatarObject() == mTexLayerSet->getAvatar() ); // We won't need our caches since we're baked now. (Techically, we won't // really be baked until this image is sent to the server and the Avatar // Appearance message is received.) mTexLayerSet->deleteCaches(); LLGLSUIDefault gls_ui; LLPointer baked_mask_image = new LLImageRaw(mWidth, mHeight, 1 ); U8* baked_mask_data = baked_mask_image->getData(); mTexLayerSet->gatherAlphaMasks(baked_mask_data, mWidth, mHeight); // imdebug("lum b=8 w=%d h=%d %p", mWidth, mHeight, baked_mask_data); // writes into baked_color_data const char* comment_text = NULL; S32 baked_image_components = mBumpTex.notNull() ? 5 : 4; // red green blue [bump] clothing LLPointer baked_image = new LLImageRaw( mWidth, mHeight, baked_image_components ); U8* baked_image_data = baked_image->getData(); if( mBumpTex.notNull() ) { comment_text = LINDEN_J2C_COMMENT_PREFIX "RGBHM"; // 5 channels: rgb, heightfield/alpha, mask // Hide the alpha for the eyelashes in a corner of the bump map if (mTexLayerSet->getBodyRegion() == "head") { S32 i = 0; for( S32 u = 0; u < mWidth; u++ ) { for( S32 v = 0; v < mHeight; v++ ) { baked_image_data[5*i + 0] = baked_color_data[4*i + 0]; baked_image_data[5*i + 1] = baked_color_data[4*i + 1]; baked_image_data[5*i + 2] = baked_color_data[4*i + 2]; baked_image_data[5*i + 3] = baked_color_data[4*i + 3]; // alpha should be correct for eyelashes. baked_image_data[5*i + 4] = baked_mask_data[i]; i++; } } } else { S32 i = 0; for( S32 u = 0; u < mWidth; u++ ) { for( S32 v = 0; v < mHeight; v++ ) { baked_image_data[5*i + 0] = baked_color_data[4*i + 0]; baked_image_data[5*i + 1] = baked_color_data[4*i + 1]; baked_image_data[5*i + 2] = baked_color_data[4*i + 2]; baked_image_data[5*i + 3] = 255; // reserve for alpha baked_image_data[5*i + 4] = baked_mask_data[i]; i++; } } } } else { if (mTexLayerSet->getBodyRegion() == "skirt" || mTexLayerSet->getBodyRegion() == "hair") { S32 i = 0; for( S32 u = 0; u < mWidth; u++ ) { for( S32 v = 0; v < mHeight; v++ ) { baked_image_data[4*i + 0] = baked_color_data[4*i + 0]; baked_image_data[4*i + 1] = baked_color_data[4*i + 1]; baked_image_data[4*i + 2] = baked_color_data[4*i + 2]; baked_image_data[4*i + 3] = baked_color_data[4*i + 3]; // Use alpha, not bump i++; } } } else { S32 i = 0; for( S32 u = 0; u < mWidth; u++ ) { for( S32 v = 0; v < mHeight; v++ ) { baked_image_data[4*i + 0] = baked_color_data[4*i + 0]; baked_image_data[4*i + 1] = baked_color_data[4*i + 1]; baked_image_data[4*i + 2] = baked_color_data[4*i + 2]; baked_image_data[4*i + 3] = 255; // eyes should have no mask - reserve for alpha i++; } } } } LLPointer compressedImage = new LLImageJ2C; compressedImage->setRate(0.f); LLTransactionID tid; LLAssetID asset_id; tid.generate(); asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); BOOL res = false; if( compressedImage->encode(baked_image, comment_text)) { res = LLVFile::writeFile(compressedImage->getData(), compressedImage->getDataSize(), gVFS, asset_id, LLAssetType::AT_TEXTURE); if (res) { LLPointer integrity_test = new LLImageJ2C; BOOL valid = FALSE; S32 file_size; U8* data = LLVFile::readFile(gVFS, asset_id, LLAssetType::AT_TEXTURE, &file_size); if (data) { valid = integrity_test->validate(data, file_size); // integrity_test will delete 'data' } else { integrity_test->setLastError("Unable to read entire file"); } if( valid ) { // baked_upload_data is owned by the responder and deleted after the request completes LLBakedUploadData* baked_upload_data = new LLBakedUploadData( gAgent.getAvatarObject(), this->mTexLayerSet, this, asset_id ); mUploadID = asset_id; // upload the image std::string url = gAgent.getRegion()->getCapability("UploadBakedTexture"); if(!url.empty() && !LLPipeline::sForceOldBakedUpload) // Toggle the debug setting UploadBakedTexOld to change between the new caps method and old method { llinfos << "Baked texture upload via capability of " << mUploadID << " to " << url << llendl; LLSD body = LLSD::emptyMap(); LLHTTPClient::post(url, body, new LLSendTexLayerResponder(body, mUploadID, LLAssetType::AT_TEXTURE, baked_upload_data)); // Responder will call LLTexLayerSetBuffer::onTextureUploadComplete() } else { llinfos << "Baked texture upload via Asset Store." << llendl; // gAssetStorage->storeAssetData(mTransactionID, LLAssetType::AT_IMAGE_JPEG, &uploadCallback, (void *)this, FALSE); gAssetStorage->storeAssetData(tid, LLAssetType::AT_TEXTURE, LLTexLayerSetBuffer::onTextureUploadComplete, baked_upload_data, TRUE, // temp_file TRUE, // is_priority TRUE); // store_local } mNeedsUpload = FALSE; } else { mUploadPending = FALSE; llinfos << "unable to create baked upload file: corrupted" << llendl; LLVFile file(gVFS, asset_id, LLAssetType::AT_TEXTURE, LLVFile::WRITE); file.remove(); } } } if (!res) { mUploadPending = FALSE; llinfos << "unable to create baked upload file" << llendl; } delete [] baked_color_data; delete [] baked_bump_data; } // static void LLTexLayerSetBuffer::onTextureUploadComplete(const LLUUID& uuid, void* userdata, S32 result, LLExtStat ext_status) // StoreAssetData callback (not fixed) { LLBakedUploadData* baked_upload_data = (LLBakedUploadData*)userdata; LLVOAvatar* avatar = gAgent.getAvatarObject(); if (0 == result && avatar && !avatar->isDead()) { // Sanity check: only the user's avatar should be uploading textures. if( baked_upload_data->mAvatar == avatar ) { // Composite may have changed since the pointer was stored - need to do some checking. LLTexLayerSetBuffer* prev_layerset_buffer = baked_upload_data->mLayerSetBuffer; // Can't just call getComposite() because this will trigger creation if none exists. LLTexLayerSetBuffer* curr_layerset_buffer = baked_upload_data->mLayerSet->hasComposite()?baked_upload_data->mLayerSet->getComposite():NULL; if (prev_layerset_buffer != curr_layerset_buffer) { llinfos << "Baked texture out of date, composite no longer valid, ignored" << llendl; } else { curr_layerset_buffer->mUploadPending = FALSE; if (curr_layerset_buffer->mUploadID.isNull()) { // The upload got canceled, we should be in the process of baking a new texture // so request an upload with the new data curr_layerset_buffer->requestUpload(); } else if( baked_upload_data->mID == curr_layerset_buffer->mUploadID ) { // This is the upload we're currently waiting for. curr_layerset_buffer->mUploadID.setNull(); if( result >= 0 ) { ETextureIndex baked_te = avatar->getBakedTE( curr_layerset_buffer->mTexLayerSet ); U64 now = LLFrameTimer::getTotalTime(); // Record starting time llinfos << "Baked texture upload took " << (S32)((now - baked_upload_data->mStartTime) / 1000) << " ms" << llendl; avatar->setNewBakedTexture( baked_te, uuid ); } else { llinfos << "Baked upload failed. Reason: " << result << llendl; // *FIX: retry upload after n seconds, asset server could be busy } } else { llinfos << "Received baked texture out of date, ignored." << llendl; } avatar->dirtyMesh(); } } } else { // Baked texture failed to upload, but since we didn't set the new baked texture, it means that they'll // try and rebake it at some point in the future (after login?) llwarns << "Baked upload failed" << llendl; } delete baked_upload_data; } void LLTexLayerSetBuffer::bindBumpTexture( U32 stage ) { if( mBumpTex.notNull() ) { gGL.getTexUnit(stage)->bindManual(LLTexUnit::TT_TEXTURE, mBumpTex->getTexName()); gGL.getTexUnit(0)->activate(); if( mLastBindTime != LLImageGL::sLastFrameTime ) { mLastBindTime = LLImageGL::sLastFrameTime; LLImageGL::updateBoundTexMem(mWidth * mHeight * 4); } } else { gGL.getTexUnit(stage)->unbind(LLTexUnit::TT_TEXTURE); gGL.getTexUnit(0)->activate(); } } //----------------------------------------------------------------------------- // LLTexLayerSet // 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()); } 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 ) ) { llwarns << " is missing body_region attribute" << llendl; 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; } //----------------------------------------------------------------------------- // LLTexLayerSet // An ordered set of texture layers that get composited into a single texture. //----------------------------------------------------------------------------- BOOL LLTexLayerSet::sHasCaches = FALSE; LLTexLayerSet::LLTexLayerSet( LLVOAvatar* avatar ) : mComposite( NULL ), mAvatar( avatar ), mUpdatesEnabled( FALSE ), mHasBump( FALSE ), mInfo( NULL ) { } LLTexLayerSet::~LLTexLayerSet() { std::for_each(mLayerList.begin(), mLayerList.end(), DeletePointer()); delete mComposite; } //----------------------------------------------------------------------------- // setInfo //----------------------------------------------------------------------------- BOOL LLTexLayerSet::setInfo(LLTexLayerSetInfo *info) { llassert(mInfo == NULL); mInfo = info; //mID = info->mID; // No ID LLTexLayerSetInfo::layer_info_list_t::iterator iter; mLayerList.reserve(info->mLayerInfoList.size()); for (iter = info->mLayerInfoList.begin(); iter != info->mLayerInfoList.end(); iter++) { LLTexLayer* layer = new LLTexLayer( this ); if (!layer->setInfo(*iter)) { mInfo = NULL; return FALSE; } mLayerList.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( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ ) { LLTexLayer* layer = *iter; layer->deleteCaches(); } } // Returns TRUE if at least one packet of data has been received for each of the textures that this layerset depends on. BOOL LLTexLayerSet::isLocalTextureDataAvailable() { return mAvatar->isLocalTextureDataAvailable( this ); } // Returns TRUE if all of the data for the textures that this layerset depends on have arrived. BOOL LLTexLayerSet::isLocalTextureDataFinal() { return mAvatar->isLocalTextureDataFinal( this ); } BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height ) { BOOL success = TRUE; LLGLSUIDefault gls_ui; LLGLDepthTest gls_depth(GL_FALSE, GL_FALSE); gGL.setColorMask(true, true); // composite color layers for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ ) { LLTexLayer* layer = *iter; if( layer->getRenderPass() == RP_COLOR ) { gGL.flush(); success &= layer->render( x, y, width, height ); gGL.flush(); } } // (Optionally) replace alpha with a single component image from a tga file. if( !getInfo()->mStaticAlphaFileName.empty() ) { LLGLSNoAlphaTest gls_no_alpha_test; gGL.flush(); gGL.setColorMask(false, true); gGL.setSceneBlendType(LLRender::BT_REPLACE); { LLImageGL* image_gl = gTexStaticImageList.getImageGL( getInfo()->mStaticAlphaFileName, TRUE ); if( image_gl ) { LLGLSUIDefault gls_ui; gGL.getTexUnit(0)->bind(image_gl); gGL.getTexUnit(0)->setTextureBlendType( LLTexUnit::TB_REPLACE ); gl_rect_2d_simple_tex( width, height ); } else { success = FALSE; } } gGL.flush(); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT); gGL.setColorMask(true, true); gGL.setSceneBlendType(LLRender::BT_ALPHA); } else if( getInfo()->mClearAlpha ) { // Set the alpha channel to one (clean up after previous blending) LLGLDisable no_alpha(GL_ALPHA_TEST); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, 1.f ); gGL.flush(); gGL.setColorMask(false, true); gl_rect_2d_simple( width, height ); gGL.flush(); gGL.setColorMask(true, true); } stop_glerror(); return success; } BOOL LLTexLayerSet::renderBump( S32 x, S32 y, S32 width, S32 height ) { BOOL success = TRUE; LLGLSUIDefault gls_ui; LLGLDepthTest gls_depth(GL_FALSE, GL_FALSE); //static S32 bump_layer_count = 1; for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ ) { LLTexLayer* layer = *iter; if( layer->getRenderPass() == RP_BUMP ) { success &= layer->render( x, y, width, height ); } } // Set the alpha channel to one (clean up after previous blending) LLGLDisable no_alpha(GL_ALPHA_TEST); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, 1.f ); gGL.setColorMask(false, true); gl_rect_2d_simple( width, height ); gGL.setColorMask(true, true); stop_glerror(); return success; } void LLTexLayerSet::requestUpdate() { if( mUpdatesEnabled ) { createComposite(); mComposite->requestUpdate(); } } void LLTexLayerSet::requestUpload() { createComposite(); mComposite->requestUpload(); } void LLTexLayerSet::cancelUpload() { if(mComposite) { mComposite->cancelUpload(); } } void LLTexLayerSet::createComposite() { if( !mComposite ) { S32 width = mInfo->mWidth; S32 height = mInfo->mHeight; // Composite other avatars at reduced resolution if( !mAvatar->isSelf() ) { width /= 2; height /= 2; } mComposite = new LLTexLayerSetBuffer( this, width, height, mHasBump ); } } void LLTexLayerSet::destroyComposite() { if( mComposite ) { delete mComposite; mComposite = NULL; } } void LLTexLayerSet::setUpdatesEnabled( BOOL b ) { mUpdatesEnabled = b; } void LLTexLayerSet::updateComposite() { createComposite(); mComposite->updateImmediate(); } LLTexLayerSetBuffer* LLTexLayerSet::getComposite() { createComposite(); return mComposite; } void LLTexLayerSet::gatherAlphaMasks(U8 *data, S32 width, S32 height) { S32 size = width * height; memset(data, 255, width * height); for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ ) { LLTexLayer* layer = *iter; U8* alphaData = layer->getAlphaData(); if (!alphaData && layer->hasAlphaParams()) { LLColor4 net_color; layer->findNetColor( &net_color ); layer->invalidateMorphMasks(); layer->renderAlphaMasks(mComposite->getOriginX(), mComposite->getOriginY(), width, height, &net_color); alphaData = layer->getAlphaData(); } if (alphaData) { for( S32 i = 0; i < size; i++ ) { U8 curAlpha = data[i]; U16 resultAlpha = curAlpha; resultAlpha *= (alphaData[i] + 1); resultAlpha = resultAlpha >> 8; data[i] = (U8)resultAlpha; } } } } void LLTexLayerSet::applyMorphMask(U8* tex_data, S32 width, S32 height, S32 num_components) { for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ ) { LLTexLayer* layer = *iter; layer->applyMorphMask(tex_data, width, height, num_components); } } //----------------------------------------------------------------------------- // LLTexLayerInfo //----------------------------------------------------------------------------- LLTexLayerInfo::LLTexLayerInfo( ) : mWriteAllChannels( FALSE ), mRenderPass( RP_COLOR ), mFixedColor( 0.f, 0.f, 0.f, 0.f ), mLocalTexture( -1 ), mStaticImageIsMask( FALSE ), mUseLocalTextureAlphaOnly( FALSE ) { } LLTexLayerInfo::~LLTexLayerInfo( ) { std::for_each(mColorInfoList.begin(), mColorInfoList.end(), DeletePointer()); std::for_each(mAlphaInfoList.begin(), mAlphaInfoList.end(), DeletePointer()); } 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 = 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 ); // 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; 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 ) ) { texture_node->getFastAttributeBOOL( local_texture_alpha_only_string, mUseLocalTextureAlphaOnly ); if( "upper_shirt" == local_texture ) { mLocalTexture = TEX_UPPER_SHIRT; } else if( "upper_bodypaint" == local_texture ) { mLocalTexture = TEX_UPPER_BODYPAINT; } else if( "lower_pants" == local_texture ) { mLocalTexture = TEX_LOWER_PANTS; } else if( "lower_bodypaint" == local_texture ) { mLocalTexture = TEX_LOWER_BODYPAINT; } else if( "lower_shoes" == local_texture ) { mLocalTexture = TEX_LOWER_SHOES; } else if( "head_bodypaint" == local_texture ) { mLocalTexture = TEX_HEAD_BODYPAINT; } else if( "lower_socks" == local_texture ) { mLocalTexture = TEX_LOWER_SOCKS; } else if( "upper_jacket" == local_texture ) { mLocalTexture = TEX_UPPER_JACKET; } else if( "lower_jacket" == local_texture ) { mLocalTexture = TEX_LOWER_JACKET; } else if( "upper_gloves" == local_texture ) { mLocalTexture = TEX_UPPER_GLOVES; } else if( "upper_undershirt" == local_texture ) { mLocalTexture = TEX_UPPER_UNDERSHIRT; } else if( "lower_underpants" == local_texture ) { mLocalTexture = TEX_LOWER_UNDERPANTS; } else if( "eyes_iris" == local_texture ) { mLocalTexture = TEX_EYES_IRIS; } else if( "skirt" == local_texture ) { mLocalTexture = TEX_SKIRT; } else if( "hair_grain" == local_texture ) { mLocalTexture = TEX_HAIR; } else { llwarns << " element has invalid local_texure attribute: " << mName << " " << local_texture << llendl; return FALSE; } } else { llwarns << " element is missing a required attribute. " << mName << llendl; 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" ) ) { // LLTexParamColorInfo* info = new LLTexParamColorInfo( ); if (!info->parseXml(child)) { delete info; return FALSE; } mColorInfoList.push_back( info ); } else if( child->getChildByName( "param_alpha" ) ) { // LLTexLayerParamAlphaInfo* info = new LLTexLayerParamAlphaInfo( ); if (!info->parseXml(child)) { delete info; return FALSE; } mAlphaInfoList.push_back( info ); } } return TRUE; } //----------------------------------------------------------------------------- // 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* layer_set ) : mTexLayerSet( layer_set ), mMorphMasksValid( FALSE ), mStaticImageInvalid( FALSE ), mInfo( NULL ) { } 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::iterator iter = mAlphaCache.begin(); iter != mAlphaCache.end(); iter++ ) { U8* alpha_data = iter->second; delete [] alpha_data; } } //----------------------------------------------------------------------------- // setInfo //----------------------------------------------------------------------------- BOOL LLTexLayer::setInfo(LLTexLayerInfo* info) { llassert(mInfo == NULL); mInfo = info; //mID = info->mID; // No ID if (info->mRenderPass == RP_BUMP) mTexLayerSet->setBump(TRUE); { LLTexLayerInfo::morph_name_list_t::iterator iter; for (iter = mInfo->mMorphNameList.begin(); iter != mInfo->mMorphNameList.end(); iter++) { // *FIX: we assume that the referenced visual param is a // morph target, need a better way of actually looking // this up. LLPolyMorphTarget *morph_param; std::string *name = &(iter->first); morph_param = (LLPolyMorphTarget *)(getTexLayerSet()->getAvatar()->getVisualParam(name->c_str())); if (morph_param) { BOOL invert = iter->second; addMaskedMorph(morph_param, invert); } } } { LLTexLayerInfo::color_info_list_t::iterator iter; mParamColorList.reserve(mInfo->mColorInfoList.size()); for (iter = mInfo->mColorInfoList.begin(); iter != mInfo->mColorInfoList.end(); iter++) { LLTexParamColor* param_color = new LLTexParamColor( this ); if (!param_color->setInfo(*iter)) { mInfo = NULL; return FALSE; } mParamColorList.push_back( param_color ); } } { LLTexLayerInfo::alpha_info_list_t::iterator iter; mParamAlphaList.reserve(mInfo->mAlphaInfoList.size()); for (iter = mInfo->mAlphaInfoList.begin(); iter != mInfo->mAlphaInfoList.end(); iter++) { LLTexLayerParamAlpha* param_alpha = new LLTexLayerParamAlpha( this ); if (!param_alpha->setInfo(*iter)) { mInfo = NULL; return FALSE; } mParamAlphaList.push_back( param_alpha ); } } return TRUE; } #if 0 // obsolete //----------------------------------------------------------------------------- // parseData //----------------------------------------------------------------------------- BOOL LLTexLayer::parseData( LLXmlTreeNode* node ) { LLTexLayerInfo *info = new LLTexLayerInfo; if (!info->parseXml(node)) { delete info; return FALSE; } if (!setInfo(info)) { delete info; return FALSE; } return TRUE; } #endif //----------------------------------------------------------------------------- void LLTexLayer::deleteCaches() { for( alpha_list_t::iterator iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ ) { LLTexLayerParamAlpha* param = *iter; param->deleteCaches(); } mStaticImageRaw = NULL; } BOOL LLTexLayer::render( S32 x, S32 y, S32 width, S32 height ) { LLGLEnable color_mat(GL_COLOR_MATERIAL); gPipeline.disableLights(); BOOL success = TRUE; BOOL color_specified = FALSE; BOOL alpha_mask_specified = FALSE; LLColor4 net_color; color_specified = findNetColor( &net_color ); if (mTexLayerSet->getAvatar()->mIsDummy) { color_specified = true; net_color = LLVOAvatar::getDummyColor(); } // If you can't see the layer, don't render it. if( is_approx_zero( net_color.mV[VW] ) ) { return success; } alpha_list_t::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( mMaskedMorphs.empty() ) { BOOL skip_layer = TRUE; while( iter != mParamAlphaList.end() ) { LLTexLayerParamAlpha* param = *iter; if( !param->getSkip() ) { skip_layer = FALSE; break; } iter++; } if( skip_layer ) { return success; } } renderAlphaMasks( x, y, width, height, &net_color ); 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 ) { { LLImageGL* image_gl = NULL; if( mTexLayerSet->getAvatar()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &image_gl ) ) { if( image_gl ) { LLGLDisable alpha_test(getInfo()->mWriteAllChannels ? GL_ALPHA_TEST : 0); LLTexUnit::eTextureAddressMode old_mode = image_gl->getAddressMode(); gGL.getTexUnit(0)->bind(image_gl); 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); } } else { success = FALSE; } } } if( !getInfo()->mStaticImageFileName.empty() ) { { LLImageGL* image_gl = gTexStaticImageList.getImageGL( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask ); if( image_gl ) { gGL.getTexUnit(0)->bind(image_gl); 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 ) { LLGLDisable no_alpha(GL_ALPHA_TEST); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4fv( net_color.mV); gl_rect_2d_simple( width, height ); } if( alpha_mask_specified || getInfo()->mWriteAllChannels ) { // Restore standard blend func value gGL.flush(); gGL.setSceneBlendType(LLRender::BT_ALPHA); stop_glerror(); } if( !success ) { llinfos << "LLTexLayer::render() partial: " << getInfo()->mName << llendl; } return success; } U8* LLTexLayer::getAlphaData() { LLCRC alpha_mask_crc; const LLUUID& uuid = mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)getInfo()->mLocalTexture); alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES); for( alpha_list_t::iterator iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ ) { LLTexLayerParamAlpha* param = *iter; F32 param_weight = param->getWeight(); alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32)); } U32 cache_index = alpha_mask_crc.getCRC(); alpha_cache_t::iterator iter2 = mAlphaCache.find(cache_index); return (iter2 == mAlphaCache.end()) ? 0 : iter2->second; } BOOL LLTexLayer::findNetColor( LLColor4* net_color ) { // 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->getAvatar()->getGlobalColor( getInfo()->mGlobalColor ) ); } else if( getInfo()->mFixedColor.mV[VW] ) { net_color->setVec( getInfo()->mFixedColor ); } else { net_color->setVec( 0.f, 0.f, 0.f, 0.f ); } for( color_list_t::iterator iter = mParamColorList.begin(); iter != mParamColorList.end(); iter++ ) { LLTexParamColor* param = *iter; LLColor4 param_net = param->getNetColor(); switch( param->getOperation() ) { case OP_ADD: *net_color += param_net; break; case OP_MULTIPLY: net_color->mV[VX] *= param_net.mV[VX]; net_color->mV[VY] *= param_net.mV[VY]; net_color->mV[VZ] *= param_net.mV[VZ]; net_color->mV[VW] *= param_net.mV[VW]; break; case OP_BLEND: net_color->setVec( lerp(*net_color, param_net, param->getWeight()) ); break; default: llassert(0); break; } } return TRUE; } if( !getGlobalColor().empty() ) { net_color->setVec( mTexLayerSet->getAvatar()->getGlobalColor( getGlobalColor() ) ); return TRUE; } if( getInfo()->mFixedColor.mV[VW] ) { net_color->setVec( getInfo()->mFixedColor ); return TRUE; } net_color->setToWhite(); return FALSE; // No need to draw a separate colored polygon } BOOL LLTexLayer::renderAlphaMasks( S32 x, S32 y, S32 width, S32 height, LLColor4* colorp ) { BOOL success = TRUE; llassert( !mParamAlphaList.empty() ); gGL.setColorMask(false, true); alpha_list_t::iterator iter = mParamAlphaList.begin(); LLTexLayerParamAlpha* first_param = *iter; // Note: if the first param is a mulitply, multiply against the current buffer's alpha if( !first_param || !first_param->getMultiplyBlend() ) { LLGLDisable no_alpha(GL_ALPHA_TEST); 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 LLGLSNoAlphaTest gls_no_alpha_test; gGL.color4f( 1.f, 1.f, 1.f, 1.f ); for( iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ ) { LLTexLayerParamAlpha* param = *iter; success &= param->render( x, y, width, height ); } // Approximates a min() function gGL.flush(); gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ZERO); // Accumulate the alpha component of the texture if( getInfo()->mLocalTexture != -1 ) { { LLImageGL* image_gl = NULL; if( mTexLayerSet->getAvatar()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &image_gl ) ) { if( image_gl && (image_gl->getComponents() == 4) ) { LLGLSNoAlphaTest gls_no_alpha_test; LLTexUnit::eTextureAddressMode old_mode = image_gl->getAddressMode(); gGL.getTexUnit(0)->bind(image_gl); 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); } } else { success = FALSE; } } } if( !getInfo()->mStaticImageFileName.empty() ) { { LLImageGL* image_gl = gTexStaticImageList.getImageGL( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask ); if( image_gl ) { if( (image_gl->getComponents() == 4) || ( (image_gl->getComponents() == 1) && getInfo()->mStaticImageIsMask ) ) { LLGLSNoAlphaTest gls_no_alpha_test; gGL.getTexUnit(0)->bind(image_gl); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } } else { success = FALSE; } } } // 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( colorp->mV[VW] != 1.f ) { LLGLDisable no_alpha(GL_ALPHA_TEST); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4fv( colorp->mV ); gl_rect_2d_simple( width, height ); } LLGLSUIDefault gls_ui; gGL.setColorMask(true, true); if (!mMorphMasksValid && !mMaskedMorphs.empty()) { LLCRC alpha_mask_crc; const LLUUID& uuid = mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)getInfo()->mLocalTexture); alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES); for( alpha_list_t::iterator iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ ) { LLTexLayerParamAlpha* param = *iter; F32 param_weight = param->getWeight(); alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32)); } U32 cache_index = alpha_mask_crc.getCRC(); alpha_cache_t::iterator iter2 = mAlphaCache.find(cache_index); U8* alpha_data; if (iter2 != mAlphaCache.end()) { alpha_data = iter2->second; } else { // clear out a slot if we have filled our cache S32 max_cache_entries = getTexLayerSet()->getAvatar()->isSelf() ? 4 : 1; while ((S32)mAlphaCache.size() >= max_cache_entries) { iter2 = mAlphaCache.begin(); // arbitrarily grab the first entry alpha_data = iter2->second; delete [] alpha_data; mAlphaCache.erase(iter2); } alpha_data = new U8[width * height]; mAlphaCache[cache_index] = alpha_data; glReadPixels(x, y, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, alpha_data); } getTexLayerSet()->getAvatar()->dirtyMesh(); mMorphMasksValid = TRUE; for( morph_list_t::iterator iter3 = mMaskedMorphs.begin(); iter3 != mMaskedMorphs.end(); iter3++ ) { LLMaskedMorph* maskedMorph = &(*iter3); maskedMorph->mMorphTarget->applyMask(alpha_data, width, height, 1, maskedMorph->mInvert); } } return success; } void LLTexLayer::applyMorphMask(U8* tex_data, S32 width, S32 height, S32 num_components) { for( morph_list_t::iterator iter = mMaskedMorphs.begin(); iter != mMaskedMorphs.end(); iter++ ) { LLMaskedMorph* maskedMorph = &(*iter); maskedMorph->mMorphTarget->applyMask(tex_data, width, height, num_components, maskedMorph->mInvert); } } // Returns TRUE on success. BOOL LLTexLayer::renderImageRaw( U8* in_data, S32 in_width, S32 in_height, S32 in_components, S32 width, S32 height, BOOL is_mask ) { if (!in_data) { return FALSE; } GLenum format_options[4] = { GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA }; GLenum format = format_options[in_components-1]; if( is_mask ) { llassert( 1 == in_components ); format = GL_ALPHA; } if( (in_width != SCRATCH_TEX_WIDTH) || (in_height != SCRATCH_TEX_HEIGHT) ) { LLGLSNoAlphaTest gls_no_alpha_test; GLenum internal_format_options[4] = { GL_LUMINANCE8, GL_LUMINANCE8_ALPHA8, GL_RGB8, GL_RGBA8 }; GLenum internal_format = internal_format_options[in_components-1]; if( is_mask ) { llassert( 1 == in_components ); internal_format = GL_ALPHA8; } U32 name = 0; LLImageGL::generateTextures(1, &name ); stop_glerror(); gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, name); stop_glerror(); LLImageGL::setManualImage( GL_TEXTURE_2D, 0, internal_format, in_width, in_height, format, GL_UNSIGNED_BYTE, in_data ); stop_glerror(); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLImageGL::deleteTextures(1, &name ); stop_glerror(); } else { LLGLSNoAlphaTest gls_no_alpha_test; if( !mTexLayerSet->getAvatar()->bindScratchTexture(format) ) { return FALSE; } glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, in_width, in_height, format, GL_UNSIGNED_BYTE, in_data ); stop_glerror(); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } return TRUE; } void LLTexLayer::requestUpdate() { mTexLayerSet->requestUpdate(); } void LLTexLayer::addMaskedMorph(LLPolyMorphTarget* morph_target, BOOL invert) { mMaskedMorphs.push_front(LLMaskedMorph(morph_target, invert)); } void LLTexLayer::invalidateMorphMasks() { mMorphMasksValid = FALSE; } //----------------------------------------------------------------------------- // LLTexLayerParamAlphaInfo //----------------------------------------------------------------------------- LLTexLayerParamAlphaInfo::LLTexLayerParamAlphaInfo( ) : mMultiplyBlend( FALSE ), mSkipIfZeroWeight( FALSE ), mDomain( 0.f ) { } BOOL LLTexLayerParamAlphaInfo::parseXml(LLXmlTreeNode* node) { llassert( node->hasName( "param" ) && node->getChildByName( "param_alpha" ) ); if( !LLViewerVisualParamInfo::parseXml(node) ) return FALSE; LLXmlTreeNode* param_alpha_node = node->getChildByName( "param_alpha" ); if( !param_alpha_node ) { return FALSE; } static LLStdStringHandle tga_file_string = LLXmlTree::addAttributeString("tga_file"); if( param_alpha_node->getFastAttributeString( tga_file_string, mStaticImageFileName ) ) { // Don't load the image file until it's actually needed. } // else // { // llwarns << " element is missing tga_file attribute." << llendl; // } static LLStdStringHandle multiply_blend_string = LLXmlTree::addAttributeString("multiply_blend"); param_alpha_node->getFastAttributeBOOL( multiply_blend_string, mMultiplyBlend ); static LLStdStringHandle skip_if_zero_string = LLXmlTree::addAttributeString("skip_if_zero"); param_alpha_node->getFastAttributeBOOL( skip_if_zero_string, mSkipIfZeroWeight ); static LLStdStringHandle domain_string = LLXmlTree::addAttributeString("domain"); param_alpha_node->getFastAttributeF32( domain_string, mDomain ); return TRUE; } //----------------------------------------------------------------------------- // LLTexLayerParamAlpha //----------------------------------------------------------------------------- // static LLTexLayerParamAlpha::param_alpha_ptr_list_t LLTexLayerParamAlpha::sInstances; // static void LLTexLayerParamAlpha::dumpCacheByteCount() { S32 gl_bytes = 0; getCacheByteCount( &gl_bytes ); llinfos << "Processed Alpha Texture Cache GL:" << (gl_bytes/1024) << "KB" << llendl; } // static void LLTexLayerParamAlpha::getCacheByteCount( S32* gl_bytes ) { *gl_bytes = 0; for( param_alpha_ptr_list_t::iterator iter = sInstances.begin(); iter != sInstances.end(); iter++ ) { LLTexLayerParamAlpha* instance = *iter; LLImageGL* image_gl = instance->mCachedProcessedImageGL; if( image_gl ) { S32 bytes = (S32)image_gl->getWidth() * image_gl->getHeight() * image_gl->getComponents(); if( image_gl->getHasGLTexture() ) { *gl_bytes += bytes; } } } } LLTexLayerParamAlpha::LLTexLayerParamAlpha( LLTexLayer* layer ) : mCachedProcessedImageGL( NULL ), mTexLayer( layer ), mNeedsCreateTexture( FALSE ), mStaticImageInvalid( FALSE ), mAvgDistortionVec(1.f, 1.f, 1.f), mCachedEffectiveWeight(0.f) { sInstances.push_front( this ); } LLTexLayerParamAlpha::~LLTexLayerParamAlpha() { deleteCaches(); sInstances.remove( this ); } //----------------------------------------------------------------------------- // setInfo() //----------------------------------------------------------------------------- BOOL LLTexLayerParamAlpha::setInfo(LLTexLayerParamAlphaInfo *info) { llassert(mInfo == NULL); if (info->mID < 0) return FALSE; mInfo = info; mID = info->mID; mTexLayer->getTexLayerSet()->getAvatar()->addVisualParam( this ); setWeight(getDefaultWeight(), FALSE ); return TRUE; } //----------------------------------------------------------------------------- void LLTexLayerParamAlpha::deleteCaches() { mStaticImageTGA = NULL; // deletes image mCachedProcessedImageGL = NULL; mStaticImageRaw = NULL; mNeedsCreateTexture = FALSE; } void LLTexLayerParamAlpha::setWeight(F32 weight, BOOL set_by_user) { if (mIsAnimating) { return; } F32 min_weight = getMinWeight(); F32 max_weight = getMaxWeight(); F32 new_weight = llclamp(weight, min_weight, max_weight); U8 cur_u8 = F32_to_U8( mCurWeight, min_weight, max_weight ); U8 new_u8 = F32_to_U8( new_weight, min_weight, max_weight ); if( cur_u8 != new_u8) { mCurWeight = new_weight; LLVOAvatar* avatar = mTexLayer->getTexLayerSet()->getAvatar(); if( avatar->getSex() & getSex() ) { if ( gAgent.cameraCustomizeAvatar() ) { set_by_user = FALSE; } avatar->invalidateComposite( mTexLayer->getTexLayerSet(), set_by_user ); mTexLayer->invalidateMorphMasks(); avatar->updateMeshTextures(); } } } void LLTexLayerParamAlpha::setAnimationTarget(F32 target_value, BOOL set_by_user) { mTargetWeight = target_value; setWeight(target_value, set_by_user); mIsAnimating = TRUE; if (mNext) { mNext->setAnimationTarget(target_value, set_by_user); } } void LLTexLayerParamAlpha::animate(F32 delta, BOOL set_by_user) { if (mNext) { mNext->animate(delta, set_by_user); } } BOOL LLTexLayerParamAlpha::getSkip() { LLVOAvatar *avatar = mTexLayer->getTexLayerSet()->getAvatar(); if( getInfo()->mSkipIfZeroWeight ) { F32 effective_weight = ( avatar->getSex() & getSex() ) ? mCurWeight : getDefaultWeight(); if (is_approx_zero( effective_weight )) { return TRUE; } } EWearableType type = (EWearableType)getWearableType(); if( (type != WT_INVALID) && !avatar->isWearingWearableType( type ) ) { return TRUE; } return FALSE; } BOOL LLTexLayerParamAlpha::render( S32 x, S32 y, S32 width, S32 height ) { BOOL success = TRUE; F32 effective_weight = ( mTexLayer->getTexLayerSet()->getAvatar()->getSex() & getSex() ) ? mCurWeight : getDefaultWeight(); BOOL weight_changed = effective_weight != mCachedEffectiveWeight; if( getSkip() ) { return success; } gGL.flush(); if( getInfo()->mMultiplyBlend ) { gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ZERO); // Multiplication: approximates a min() function } else { gGL.setSceneBlendType(LLRender::BT_ADD); // Addition: approximates a max() function } if( !getInfo()->mStaticImageFileName.empty() && !mStaticImageInvalid) { if( mStaticImageTGA.isNull() ) { // Don't load the image file until we actually need it the first time. Like now. mStaticImageTGA = gTexStaticImageList.getImageTGA( getInfo()->mStaticImageFileName ); // We now have something in one of our caches LLTexLayerSet::sHasCaches |= mStaticImageTGA.notNull() ? TRUE : FALSE; if( mStaticImageTGA.isNull() ) { llwarns << "Unable to load static file: " << getInfo()->mStaticImageFileName << llendl; mStaticImageInvalid = TRUE; // don't try again. return FALSE; } } const S32 image_tga_width = mStaticImageTGA->getWidth(); const S32 image_tga_height = mStaticImageTGA->getHeight(); if( !mCachedProcessedImageGL || (mCachedProcessedImageGL->getWidth() != image_tga_width) || (mCachedProcessedImageGL->getHeight() != image_tga_height) || (weight_changed) ) { // llinfos << "Building Cached Alpha: " << mName << ": (" << mStaticImageRaw->getWidth() << ", " << mStaticImageRaw->getHeight() << ") " << effective_weight << llendl; mCachedEffectiveWeight = effective_weight; if( !mCachedProcessedImageGL ) { mCachedProcessedImageGL = new LLImageGL( image_tga_width, image_tga_height, 1, FALSE); // We now have something in one of our caches LLTexLayerSet::sHasCaches |= mCachedProcessedImageGL ? TRUE : FALSE; mCachedProcessedImageGL->setExplicitFormat( GL_ALPHA8, GL_ALPHA ); } // Applies domain and effective weight to data as it is decoded. Also resizes the raw image if needed. mStaticImageRaw = NULL; mStaticImageRaw = new LLImageRaw; mStaticImageTGA->decodeAndProcess( mStaticImageRaw, getInfo()->mDomain, effective_weight ); mNeedsCreateTexture = TRUE; } if( mCachedProcessedImageGL ) { { // Create the GL texture, and then hang onto it for future use. if( mNeedsCreateTexture ) { mCachedProcessedImageGL->createGLTexture(0, mStaticImageRaw); mNeedsCreateTexture = FALSE; gGL.getTexUnit(0)->bind(mCachedProcessedImageGL); mCachedProcessedImageGL->setAddressMode(LLTexUnit::TAM_CLAMP); } LLGLSNoAlphaTest gls_no_alpha_test; gGL.getTexUnit(0)->bind(mCachedProcessedImageGL); gl_rect_2d_simple_tex( width, height ); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); stop_glerror(); } } // Don't keep the cache for other people's avatars // (It's not really a "cache" in that case, but the logic is the same) if( !mTexLayer->getTexLayerSet()->getAvatar()->isSelf() ) { mCachedProcessedImageGL = NULL; } } else { LLGLDisable no_alpha(GL_ALPHA_TEST); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.color4f( 0.f, 0.f, 0.f, effective_weight ); gl_rect_2d_simple( width, height ); } return success; } //----------------------------------------------------------------------------- // LLTexGlobalColorInfo //----------------------------------------------------------------------------- LLTexGlobalColorInfo::LLTexGlobalColorInfo() { } LLTexGlobalColorInfo::~LLTexGlobalColorInfo() { for_each(mColorInfoList.begin(), mColorInfoList.end(), DeletePointer()); } BOOL LLTexGlobalColorInfo::parseXml(LLXmlTreeNode* node) { // name attribute static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); if( !node->getFastAttributeString( name_string, mName ) ) { llwarns << " element is missing name attribute." << llendl; return FALSE; } // sub-element for (LLXmlTreeNode* child = node->getChildByName( "param" ); child; child = node->getNextNamedChild()) { if( child->getChildByName( "param_color" ) ) { // LLTexParamColorInfo* info = new LLTexParamColorInfo(); if (!info->parseXml(child)) { delete info; return FALSE; } mColorInfoList.push_back( info ); } } return TRUE; } //----------------------------------------------------------------------------- // LLTexGlobalColor //----------------------------------------------------------------------------- LLTexGlobalColor::LLTexGlobalColor( LLVOAvatar* avatar ) : mAvatar( avatar ), mInfo( NULL ) { } LLTexGlobalColor::~LLTexGlobalColor() { // mParamList are LLViewerVisualParam's and get deleted with ~LLCharacter() //std::for_each(mParamList.begin(), mParamList.end(), DeletePointer()); } BOOL LLTexGlobalColor::setInfo(LLTexGlobalColorInfo *info) { llassert(mInfo == NULL); mInfo = info; //mID = info->mID; // No ID LLTexGlobalColorInfo::color_info_list_t::iterator iter; mParamList.reserve(mInfo->mColorInfoList.size()); for (iter = mInfo->mColorInfoList.begin(); iter != mInfo->mColorInfoList.end(); iter++) { LLTexParamColor* param_color = new LLTexParamColor( this ); if (!param_color->setInfo(*iter)) { mInfo = NULL; return FALSE; } mParamList.push_back( param_color ); } return TRUE; } //----------------------------------------------------------------------------- LLColor4 LLTexGlobalColor::getColor() { // Sum of color params if( !mParamList.empty() ) { LLColor4 net_color( 0.f, 0.f, 0.f, 0.f ); for( param_list_t::iterator iter = mParamList.begin(); iter != mParamList.end(); iter++ ) { LLTexParamColor* param = *iter; LLColor4 param_net = param->getNetColor(); switch( param->getOperation() ) { case OP_ADD: net_color += param_net; break; case OP_MULTIPLY: net_color.mV[VX] *= param_net.mV[VX]; net_color.mV[VY] *= param_net.mV[VY]; net_color.mV[VZ] *= param_net.mV[VZ]; net_color.mV[VW] *= param_net.mV[VW]; break; case OP_BLEND: net_color = lerp(net_color, param_net, param->getWeight()); break; default: llassert(0); break; } } net_color.mV[VX] = llclampf( net_color.mV[VX] ); net_color.mV[VY] = llclampf( net_color.mV[VY] ); net_color.mV[VZ] = llclampf( net_color.mV[VZ] ); net_color.mV[VW] = llclampf( net_color.mV[VW] ); return net_color; } return LLColor4( 1.f, 1.f, 1.f, 1.f ); } //----------------------------------------------------------------------------- // LLTexParamColorInfo //----------------------------------------------------------------------------- LLTexParamColorInfo::LLTexParamColorInfo() : mOperation( OP_ADD ), mNumColors( 0 ) { } BOOL LLTexParamColorInfo::parseXml(LLXmlTreeNode *node) { llassert( node->hasName( "param" ) && node->getChildByName( "param_color" ) ); if (!LLViewerVisualParamInfo::parseXml(node)) return FALSE; LLXmlTreeNode* param_color_node = node->getChildByName( "param_color" ); if( !param_color_node ) { return FALSE; } std::string op_string; static LLStdStringHandle operation_string = LLXmlTree::addAttributeString("operation"); if( param_color_node->getFastAttributeString( operation_string, op_string ) ) { LLStringUtil::toLower(op_string); if ( op_string == "add" ) mOperation = OP_ADD; else if ( op_string == "multiply" ) mOperation = OP_MULTIPLY; else if ( op_string == "blend" ) mOperation = OP_BLEND; } mNumColors = 0; LLColor4U color4u; for (LLXmlTreeNode* child = param_color_node->getChildByName( "value" ); child; child = param_color_node->getNextNamedChild()) { if( (mNumColors < MAX_COLOR_VALUES) ) { static LLStdStringHandle color_string = LLXmlTree::addAttributeString("color"); if( child->getFastAttributeColor4U( color_string, color4u ) ) { mColors[ mNumColors ].setVec(color4u); mNumColors++; } } } if( !mNumColors ) { llwarns << " is missing sub-elements" << llendl; return FALSE; } if( (mOperation == OP_BLEND) && (mNumColors != 1) ) { llwarns << " with operation\"blend\" must have exactly one " << llendl; return FALSE; } return TRUE; } //----------------------------------------------------------------------------- // LLTexParamColor //----------------------------------------------------------------------------- LLTexParamColor::LLTexParamColor( LLTexGlobalColor* tex_global_color ) : mAvgDistortionVec(1.f, 1.f, 1.f), mTexGlobalColor( tex_global_color ), mTexLayer( NULL ), mAvatar( tex_global_color->getAvatar() ) { } LLTexParamColor::LLTexParamColor( LLTexLayer* layer ) : mAvgDistortionVec(1.f, 1.f, 1.f), mTexGlobalColor( NULL ), mTexLayer( layer ), mAvatar( layer->getTexLayerSet()->getAvatar() ) { } LLTexParamColor::~LLTexParamColor() { } //----------------------------------------------------------------------------- // setInfo() //----------------------------------------------------------------------------- BOOL LLTexParamColor::setInfo(LLTexParamColorInfo *info) { llassert(mInfo == NULL); if (info->mID < 0) return FALSE; mID = info->mID; mInfo = info; mAvatar->addVisualParam( this ); setWeight( getDefaultWeight(), FALSE ); return TRUE; } LLColor4 LLTexParamColor::getNetColor() { llassert( getInfo()->mNumColors >= 1 ); F32 effective_weight = ( mAvatar && (mAvatar->getSex() & getSex()) ) ? mCurWeight : getDefaultWeight(); S32 index_last = getInfo()->mNumColors - 1; F32 scaled_weight = effective_weight * index_last; S32 index_start = (S32) scaled_weight; S32 index_end = index_start + 1; if( index_start == index_last ) { return getInfo()->mColors[index_last]; } else { F32 weight = scaled_weight - index_start; const LLColor4 *start = &getInfo()->mColors[ index_start ]; const LLColor4 *end = &getInfo()->mColors[ index_end ]; return LLColor4( (1.f - weight) * start->mV[VX] + weight * end->mV[VX], (1.f - weight) * start->mV[VY] + weight * end->mV[VY], (1.f - weight) * start->mV[VZ] + weight * end->mV[VZ], (1.f - weight) * start->mV[VW] + weight * end->mV[VW] ); } } void LLTexParamColor::setWeight(F32 weight, BOOL set_by_user) { if (mIsAnimating) { return; } F32 min_weight = getMinWeight(); F32 max_weight = getMaxWeight(); F32 new_weight = llclamp(weight, min_weight, max_weight); U8 cur_u8 = F32_to_U8( mCurWeight, min_weight, max_weight ); U8 new_u8 = F32_to_U8( new_weight, min_weight, max_weight ); if( cur_u8 != new_u8) { mCurWeight = new_weight; if( getInfo()->mNumColors <= 0 ) { // This will happen when we set the default weight the first time. return; } if( mAvatar->getSex() & getSex() ) { if( mTexGlobalColor ) { mAvatar->onGlobalColorChanged( mTexGlobalColor, set_by_user ); } else if( mTexLayer ) { mAvatar->invalidateComposite( mTexLayer->getTexLayerSet(), set_by_user ); } } // llinfos << "param " << mName << " = " << new_weight << llendl; } } void LLTexParamColor::setAnimationTarget(F32 target_value, BOOL set_by_user) { // set value first then set interpolating flag to ignore further updates mTargetWeight = target_value; setWeight(target_value, set_by_user); mIsAnimating = TRUE; if (mNext) { mNext->setAnimationTarget(target_value, set_by_user); } } void LLTexParamColor::animate(F32 delta, BOOL set_by_user) { if (mNext) { mNext->animate(delta, set_by_user); } } //----------------------------------------------------------------------------- // LLTexStaticImageList //----------------------------------------------------------------------------- // static LLTexStaticImageList gTexStaticImageList; LLStringTable LLTexStaticImageList::sImageNames(16384); LLTexStaticImageList::LLTexStaticImageList() : mGLBytes( 0 ), mTGABytes( 0 ) {} LLTexStaticImageList::~LLTexStaticImageList() { deleteCachedImages(); } void LLTexStaticImageList::dumpByteCount() { llinfos << "Avatar Static Textures " << "KB GL:" << (mGLBytes / 1024) << "KB TGA:" << (mTGABytes / 1024) << "KB" << llendl; } void LLTexStaticImageList::deleteCachedImages() { if( mGLBytes || mTGABytes ) { llinfos << "Clearing Static Textures " << "KB GL:" << (mGLBytes / 1024) << "KB TGA:" << (mTGABytes / 1024) << "KB" << llendl; //mStaticImageLists uses LLPointers, clear() will cause deletion mStaticImageListTGA.clear(); mStaticImageListGL.clear(); mGLBytes = 0; mTGABytes = 0; } } // Note: in general, for a given image image we'll call either getImageTga() or getImageGL(). // We call getImageTga() if the image is used as an alpha gradient. // Otherwise, we call getImageGL() // Returns an LLImageTGA that contains the encoded data from a tga file named file_name. // Caches the result to speed identical subsequent requests. LLImageTGA* LLTexStaticImageList::getImageTGA(const std::string& file_name) { const char *namekey = sImageNames.addString(file_name); image_tga_map_t::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. LLImageGL* LLTexStaticImageList::getImageGL(const std::string& file_name, BOOL is_mask ) { LLPointer image_gl; const char *namekey = sImageNames.addString(file_name); image_gl_map_t::iterator iter = mStaticImageListGL.find(namekey); if( iter != mStaticImageListGL.end() ) { image_gl = iter->second; } else { image_gl = new LLImageGL( FALSE ); LLPointer image_raw = new LLImageRaw; if( loadImageRaw( file_name, image_raw ) ) { if( (image_raw->getComponents() == 1) && is_mask ) { // Note: these are static, unchanging images so it's ok to assume // that once an image is a mask it's always a mask. image_gl->setExplicitFormat( GL_ALPHA8, GL_ALPHA ); } image_gl->createGLTexture(0, image_raw); gGL.getTexUnit(0)->bind(image_gl); image_gl->setAddressMode(LLTexUnit::TAM_CLAMP); mStaticImageListGL [ namekey ] = image_gl; mGLBytes += (S32)image_gl->getWidth() * image_gl->getHeight() * image_gl->getComponents(); } else { image_gl = NULL; } } return image_gl; } // Reads a .tga file, decodes it, and puts the decoded data in image_raw. // Returns TRUE if successful. BOOL LLTexStaticImageList::loadImageRaw( const std::string& file_name, LLImageRaw* image_raw ) { 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; } //----------------------------------------------------------------------------- // LLMaskedMorph() //----------------------------------------------------------------------------- LLMaskedMorph::LLMaskedMorph( LLPolyMorphTarget *morph_target, BOOL invert ) : mMorphTarget(morph_target), mInvert(invert) { morph_target->addPendingMorphMask(); }