summaryrefslogtreecommitdiff
path: root/indra/newview/lltexlayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/lltexlayer.cpp')
-rw-r--r--indra/newview/lltexlayer.cpp2839
1 files changed, 2839 insertions, 0 deletions
diff --git a/indra/newview/lltexlayer.cpp b/indra/newview/lltexlayer.cpp
new file mode 100644
index 0000000000..da468d4ab1
--- /dev/null
+++ b/indra/newview/lltexlayer.cpp
@@ -0,0 +1,2839 @@
+/**
+ * @file lltexlayer.cpp
+ * @brief A texture layer. Used for avatars.
+ *
+ * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#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 "llviewerstats.h"
+#include "llviewerwindow.h"
+#include "llvoavatar.h"
+#include "llxmltree.h"
+#include "pipeline.h"
+#include "v4coloru.h"
+#include "viewer.h"
+
+//#include "../tools/imdebug/imdebug.h"
+
+
+// SJB: We really always want to use the GL cache;
+// let GL page textures in and out of video RAM instead of trying to do so by hand.
+// const U32 USE_AVATAR_GL_CACHE_THRESHOLD = 1024 * 1024 * 35; // 35 MB
+BOOL gUseAvatarGLCache = TRUE; //FALSE;
+
+LLGradientPaletteList gGradientPaletteList;
+
+// static
+S32 LLTexLayerSetBuffer::sGLByteCount = 0;
+S32 LLTexLayerSetBuffer::sGLBumpByteCount = 0;
+
+//-----------------------------------------------------------------------------
+// LLBakedUploadData()
+//-----------------------------------------------------------------------------
+LLBakedUploadData::LLBakedUploadData( LLVOAvatar* avatar, LLTexLayerSetBuffer* layerset_buffer ) :
+ mAvatar( avatar ),
+ mLayerSetBuffer( layerset_buffer )
+{
+ mID.generate();
+ 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 ),
+ mInitialized( FALSE ),
+ mBumpTexName(0)
+{
+ LLTexLayerSetBuffer::sGLByteCount += getSize();
+
+ if( has_bump )
+ {
+ LLGLSUIDefault gls_ui;
+ glGenTextures(1, (GLuint*) &mBumpTexName);
+
+ LLImageGL::bindExternalTexture(mBumpTexName, 0, GL_TEXTURE_2D);
+ stop_glerror();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ stop_glerror();
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+
+ LLImageGL::sGlobalTextureMemory += mWidth * mHeight * 4;
+ LLTexLayerSetBuffer::sGLBumpByteCount += mWidth * mHeight * 4;
+ }
+}
+
+LLTexLayerSetBuffer::~LLTexLayerSetBuffer()
+{
+ LLTexLayerSetBuffer::sGLByteCount -= getSize();
+
+ if( mBumpTexName )
+ {
+ glDeleteTextures(1, (GLuint*) &mBumpTexName);
+ stop_glerror();
+ mBumpTexName = 0;
+
+ 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);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0.0f, mWidth, 0.0f, mHeight, -1.0f, 1.0f);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+}
+
+void LLTexLayerSetBuffer::popProjection()
+{
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+}
+
+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) == LLVOAvatar::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;
+
+// gUseAvatarGLCache = ( gImageList.getMaxResidentTexMem() > USE_AVATAR_GL_CACHE_THRESHOLD );
+
+ // 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( mBumpTexName )
+ {
+ // 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)
+ LLImageGL::bindExternalTexture( mBumpTexName, 0, GL_TEXTURE_2D );
+ 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 );
+
+ 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
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ // we have valid texture data now
+ mInitialized = TRUE;
+ mNeedsUpdate = FALSE;
+
+ return success;
+}
+
+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;
+ gViewerStats->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<LLImageRaw> 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 = mBumpTexName ? 5 : 4; // red green blue [bump] clothing
+ LLPointer<LLImageRaw> baked_image = new LLImageRaw( mWidth, mHeight, baked_image_components );
+ U8* baked_image_data = baked_image->getData();
+
+ if( mBumpTexName )
+ {
+ 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] < 255 ? baked_color_data[4*i + 3] : baked_bump_data[4*i];
+ 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] = baked_bump_data[4*i];
+ baked_image_data[5*i + 4] = baked_mask_data[i];
+ i++;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (mTexLayerSet->getBodyRegion() == "skirt")
+ {
+ 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] = baked_mask_data[i];
+ i++;
+ }
+ }
+ }
+ }
+
+ LLPointer<LLImageJ2C> 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<LLImageJ2C> 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 )
+ {
+ LLBakedUploadData* baked_upload_data = new LLBakedUploadData( gAgent.getAvatarObject(), this );
+ mUploadID = baked_upload_data->mID;
+
+ gAssetStorage->storeAssetData(tid,
+ LLAssetType::AT_TEXTURE,
+ LLTexLayerSetBuffer::onTextureUploadComplete,
+ baked_upload_data,
+ TRUE, // temp_file
+ FALSE, // 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) // StoreAssetData callback (not fixed)
+{
+ LLBakedUploadData* baked_upload_data = (LLBakedUploadData*)(U32)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 )
+ {
+ // Because the avatar is still valid, it's layerset buffers should be valid also.
+ LLTexLayerSetBuffer* layerset_buffer = baked_upload_data->mLayerSetBuffer;
+ layerset_buffer->mUploadPending = FALSE;
+
+ if (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
+ layerset_buffer->requestUpload();
+ }
+ else if( baked_upload_data->mID == layerset_buffer->mUploadID )
+ {
+ // This is the upload we're currently waiting for.
+ layerset_buffer->mUploadID.setNull();
+
+ if( result >= 0 )
+ {
+ LLVOAvatar::ETextureIndex baked_te = avatar->getBakedTE( layerset_buffer->mTexLayerSet );
+ if( !gAgent.cameraCustomizeAvatar() )
+ {
+ avatar->setNewBakedTexture( baked_te, uuid );
+ }
+ else
+ {
+ llinfos << "LLTexLayerSetBuffer::onTextureUploadComplete() when in Customize Avatar" << llendl;
+ }
+ }
+ else
+ {
+ llinfos << "Baked upload failed. Reason: " << result << llendl;
+ //FIXME: 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::bindTexture()
+{
+ if( mInitialized )
+ {
+ LLDynamicTexture::bindTexture();
+ }
+ else
+ {
+ gImageList.getImage(IMG_DEFAULT)->bind();
+ }
+}
+
+void LLTexLayerSetBuffer::bindBumpTexture( U32 stage )
+{
+ if( mBumpTexName )
+ {
+ LLImageGL::bindExternalTexture(mBumpTexName, stage, GL_TEXTURE_2D);
+
+ if( mLastBindTime != LLImageGL::sLastFrameTime )
+ {
+ mLastBindTime = LLImageGL::sLastFrameTime;
+ LLImageGL::updateBoundTexMem(mWidth * mHeight * 4);
+ }
+ }
+ else
+ {
+ LLImageGL::unbindTexture(stage, GL_TEXTURE_2D);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 << "<layer_set> 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 );
+
+ // <layer>
+ 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);
+
+ // composite color layers
+ for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ )
+ {
+ LLTexLayer* layer = *iter;
+ if( layer->getRenderPass() == RP_COLOR )
+ {
+ success &= layer->render( x, y, width, height );
+ }
+ }
+
+ // (Optionally) replace alpha with a single component image from a tga file.
+ if( !getInfo()->mStaticAlphaFileName.empty() )
+ {
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE );
+ glBlendFunc( GL_ONE, GL_ZERO );
+
+ if( gUseAvatarGLCache )
+ {
+ LLImageGL* image_gl = gTexStaticImageList.getImageGL( getInfo()->mStaticAlphaFileName, TRUE );
+ if( image_gl )
+ {
+ LLGLSUIDefault gls_ui;
+ image_gl->bind();
+ gl_rect_2d_simple_tex( width, height );
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ LLImageRaw* image_raw = gTexStaticImageList.getImageRaw( getInfo()->mStaticAlphaFileName );
+ if( image_raw )
+ {
+ GLenum format = GL_ALPHA;
+ if( mAvatar->bindScratchTexture(format) )
+ {
+ glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, image_raw->getWidth(), image_raw->getHeight(), format, GL_UNSIGNED_BYTE, image_raw->getData() );
+ stop_glerror();
+
+ gl_rect_2d_simple_tex( width, height );
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ }
+ else
+ if( getInfo()->mClearAlpha )
+ {
+ // Set the alpha channel to one (clean up after previous blending)
+ LLGLSNoTextureNoAlphaTest gls_no_alpha;
+ glColor4f( 0.f, 0.f, 0.f, 1.f );
+ glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE );
+
+ gl_rect_2d_simple( width, height );
+
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_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)
+ LLGLSNoTextureNoAlphaTest gls_no_texture_no_alpha;
+ glColor4f( 0.f, 0.f, 0.f, 1.f );
+ glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE );
+
+ gl_rect_2d_simple( width, height );
+
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_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->mIsSelf )
+ {
+ 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 );
+
+ LLString 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 <param_color> 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 );
+ }
+
+ // <texture> optional sub-element
+ for (LLXmlTreeNode* texture_node = node->getChildByName( "texture" );
+ texture_node;
+ texture_node = node->getNextNamedChild())
+ {
+ LLString 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 = LLVOAvatar::LOCTEX_UPPER_SHIRT;
+ }
+ else if( "upper_bodypaint" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_UPPER_BODYPAINT;
+ }
+ else if( "lower_pants" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_PANTS;
+ }
+ else if( "lower_bodypaint" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_BODYPAINT;
+ }
+ else if( "lower_shoes" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_SHOES;
+ }
+ else if( "head_bodypaint" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_HEAD_BODYPAINT;
+ }
+ else if( "lower_socks" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_SOCKS;
+ }
+ else if( "upper_jacket" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_UPPER_JACKET;
+ }
+ else if( "lower_jacket" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_JACKET;
+ }
+ else if( "upper_gloves" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_UPPER_GLOVES;
+ }
+ else if( "upper_undershirt" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_UPPER_UNDERSHIRT;
+ }
+ else if( "lower_underpants" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_LOWER_UNDERPANTS;
+ }
+ else if( "eyes_iris" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_EYES_IRIS;
+ }
+ else if( "skirt" == local_texture )
+ {
+ mLocalTexture = LLVOAvatar::LOCTEX_SKIRT;
+ }
+ else
+ {
+ llwarns << "<texture> element has invalid local_texure attribute: " << mName << " " << local_texture << llendl;
+ return FALSE;
+ }
+ }
+ else
+ {
+ llwarns << "<texture> element is missing a required attribute. " << mName << llendl;
+ return FALSE;
+ }
+ }
+
+ for (LLXmlTreeNode* maskNode = node->getChildByName( "morph_mask" );
+ maskNode;
+ maskNode = node->getNextNamedChild())
+ {
+ LLString 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<LLString,BOOL>(morph_name,invert));
+ }
+ }
+
+ // <param> optional sub-element (color or alpha params)
+ for (LLXmlTreeNode* child = node->getChildByName( "param" );
+ child;
+ child = node->getNextNamedChild())
+ {
+ if( child->getChildByName( "param_color" ) )
+ {
+ // <param><param_color/></param>
+ LLTexParamColorInfo* info = new LLTexParamColorInfo( );
+ if (!info->parseXml(child))
+ {
+ delete info;
+ return FALSE;
+ }
+ mColorInfoList.push_back( info );
+ }
+ else if( child->getChildByName( "param_alpha" ) )
+ {
+ // <param><param_alpha/></param>
+ 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++)
+ {
+ // FIXME: we assume that the referenced visual param is a morph target,
+ // need a better way of actually looking this up
+ LLPolyMorphTarget *morph_param;
+ LLString *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
+
+//-----------------------------------------------------------------------------
+
+
+BOOL LLTexLayer::loadStaticImageRaw()
+{
+ if( mStaticImageRaw.isNull() && !mStaticImageInvalid)
+ {
+ mStaticImageRaw = gTexStaticImageList.getImageRaw( getInfo()->mStaticImageFileName );
+ // We now have something in one of our caches
+ LLTexLayerSet::sHasCaches |= mStaticImageRaw.notNull() ? TRUE : FALSE;
+ if( mStaticImageRaw.isNull() )
+ {
+ llwarns << "Unable to load static file: " << getInfo()->mStaticImageFileName << llendl;
+ mStaticImageInvalid = TRUE; // don't try again.
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+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 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;
+ glBlendFunc( GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA );
+ }
+
+ glColor4fv( net_color.mV);
+
+ if( getInfo()->mWriteAllChannels )
+ {
+ glBlendFunc( GL_ONE, GL_ZERO );
+ }
+
+ if( (getInfo()->mLocalTexture != -1) && !getInfo()->mUseLocalTextureAlphaOnly )
+ {
+ if( gUseAvatarGLCache )
+ {
+ LLImageGL* image_gl = NULL;
+ if( mTexLayerSet->getAvatar()->getLocalTextureGL( getInfo()->mLocalTexture, &image_gl ) )
+ {
+ if( image_gl )
+ {
+ LLGLDisable alpha_test(getInfo()->mWriteAllChannels ? GL_ALPHA_TEST : 0);
+
+ BOOL old_clamps = image_gl->getClampS();
+ BOOL old_clampt = image_gl->getClampT();
+
+ image_gl->bind();
+ image_gl->setClamp(TRUE, TRUE);
+
+ gl_rect_2d_simple_tex( width, height );
+
+ image_gl->setClamp(old_clamps, old_clampt);
+ image_gl->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ LLPointer<LLImageRaw> image_raw = new LLImageRaw;
+ if( mTexLayerSet->getAvatar()->getLocalTextureRaw( getInfo()->mLocalTexture, image_raw ) )
+ {
+ success &= renderImageRaw( image_raw->getData(),
+ image_raw->getWidth(),
+ image_raw->getHeight(),
+ image_raw->getComponents(),
+ width,
+ height,
+ FALSE );
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ }
+
+ if( !getInfo()->mStaticImageFileName.empty() )
+ {
+ if( gUseAvatarGLCache )
+ {
+ LLImageGL* image_gl = gTexStaticImageList.getImageGL( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask );
+ if( image_gl )
+ {
+ image_gl->bind();
+ gl_rect_2d_simple_tex( width, height );
+ image_gl->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ // Don't load the image file until we actually need it the first time. Like now.
+ if (!loadStaticImageRaw())
+ {
+ success = FALSE;
+ }
+ if( mStaticImageRaw.notNull() )
+ {
+ success &= renderImageRaw(
+ mStaticImageRaw->getData(),
+ mStaticImageRaw->getWidth(),
+ mStaticImageRaw->getHeight(),
+ mStaticImageRaw->getComponents(), width, height, getInfo()->mStaticImageIsMask );
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ }
+
+ if( ((-1 == getInfo()->mLocalTexture) ||
+ getInfo()->mUseLocalTextureAlphaOnly) &&
+ getInfo()->mStaticImageFileName.empty() &&
+ color_specified )
+ {
+ LLGLSNoTextureNoAlphaTest gls;
+ glColor4fv( net_color.mV);
+ gl_rect_2d_simple( width, height );
+ }
+
+ if( alpha_mask_specified || getInfo()->mWriteAllChannels )
+ {
+ // Restore standard blend func value
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_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(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*)&param_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() );
+
+ glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_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() )
+ {
+ LLGLSNoTextureNoAlphaTest gls_no_texture_no_alpha_test;
+
+ // Clear the alpha
+ glBlendFunc( GL_ONE, GL_ZERO );
+
+ glColor4f( 0.f, 0.f, 0.f, 0.f );
+ gl_rect_2d_simple( width, height );
+ }
+
+ // Accumulate alphas
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ glColor4f( 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
+ glBlendFunc( GL_DST_ALPHA, GL_ZERO );
+
+ // Accumulate the alpha component of the texture
+ if( getInfo()->mLocalTexture != -1 )
+ {
+ if( gUseAvatarGLCache )
+ {
+ LLImageGL* image_gl = NULL;
+ if( mTexLayerSet->getAvatar()->getLocalTextureGL( getInfo()->mLocalTexture, &image_gl ) )
+ {
+ if( image_gl && (image_gl->getComponents() == 4) )
+ {
+ LLGLSNoAlphaTest gls_no_alpha_test;
+
+ BOOL old_clamps = image_gl->getClampS();
+ BOOL old_clampt = image_gl->getClampT();
+ image_gl->bind();
+ image_gl->setClamp(TRUE, TRUE);
+
+ gl_rect_2d_simple_tex( width, height );
+
+ image_gl->setClamp(old_clamps, old_clampt);
+ image_gl->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ LLPointer<LLImageRaw> image_raw = new LLImageRaw;
+ if( mTexLayerSet->getAvatar()->getLocalTextureRaw( getInfo()->mLocalTexture, image_raw ) )
+ {
+ if(image_raw->getComponents() == 4)
+ {
+ success &= renderImageRaw(
+ image_raw->getData(),
+ image_raw->getWidth(),
+ image_raw->getHeight(),
+ image_raw->getComponents(), width, height, FALSE );
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ }
+
+ if( !getInfo()->mStaticImageFileName.empty() )
+ {
+ if( gUseAvatarGLCache )
+ {
+ 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;
+ image_gl->bind();
+ gl_rect_2d_simple_tex( width, height );
+ image_gl->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ // Don't load the image file until we actually need it the first time. Like now.
+ if (!loadStaticImageRaw())
+ {
+ success = FALSE;
+ }
+
+ if( mStaticImageRaw.notNull() )
+ {
+ if( (mStaticImageRaw->getComponents() == 4) ||
+ ( (mStaticImageRaw->getComponents() == 1) && getInfo()->mStaticImageIsMask ) )
+ {
+ success &= renderImageRaw(
+ mStaticImageRaw->getData(),
+ mStaticImageRaw->getWidth(),
+ mStaticImageRaw->getHeight(),
+ mStaticImageRaw->getComponents(), width, height, getInfo()->mStaticImageIsMask );
+ }
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ }
+
+ // Draw a rectangle with the layer color to multiply the alpha by that color's alpha.
+ // Note: we're still using glBlendFunc( GL_DST_ALPHA, GL_ZERO );
+ if( colorp->mV[VW] != 1.f )
+ {
+ LLGLSNoTextureNoAlphaTest gls_no_texture_no_alpha_test;
+ glColor4fv( colorp->mV );
+ gl_rect_2d_simple( width, height );
+ }
+
+
+ LLGLSUIDefault gls_ui;
+
+ glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
+
+ if (!mMorphMasksValid && !mMaskedMorphs.empty())
+ {
+ LLCRC alpha_mask_crc;
+ const LLUUID& uuid = mTexLayerSet->getAvatar()->getLocalTextureID(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*)&param_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()->mIsSelf ? 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 != VOAVATAR_SCRATCH_TEX_WIDTH) || (in_height != VOAVATAR_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;
+ }
+
+ GLuint name = 0;
+ glGenTextures(1, &name );
+ stop_glerror();
+
+ LLImageGL::bindExternalTexture( name, 0, GL_TEXTURE_2D );
+ stop_glerror();
+
+ glTexImage2D(
+ GL_TEXTURE_2D, 0, internal_format,
+ in_width, in_height,
+ 0, format, GL_UNSIGNED_BYTE, in_data );
+ stop_glerror();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ gl_rect_2d_simple_tex( width, height );
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+
+ glDeleteTextures(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 );
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+ }
+
+ 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 << "<param_alpha> 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 );
+
+ gGradientPaletteList.initPalette(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() )
+ {
+ avatar->invalidateComposite( mTexLayer->getTexLayerSet(), set_by_user );
+ mTexLayer->invalidateMorphMasks();
+ }
+ }
+}
+
+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;
+ }
+
+ if( getInfo()->mMultiplyBlend )
+ {
+ glBlendFunc( GL_DST_ALPHA, GL_ZERO ); // Multiplication: approximates a min() function
+ }
+ else
+ {
+ glBlendFunc( GL_ONE, GL_ONE ); // 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 && !(gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))) ||
+ (!gUseAvatarGLCache) )
+ {
+// 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;
+
+ if (gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))
+ {
+ // interpret luminance values as color index table
+ mCachedProcessedImageGL->setExplicitFormat( GL_COLOR_INDEX8_EXT, GL_COLOR_INDEX );
+ }
+ else
+ {
+ mCachedProcessedImageGL->setExplicitFormat( GL_ALPHA8, GL_ALPHA );
+ }
+ }
+
+ // Applies domain and effective weight to data as it is decoded. Also resizes the raw image if needed.
+ if (gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))
+ {
+ mStaticImageRaw = NULL;
+ mStaticImageRaw = new LLImageRaw;
+ mStaticImageTGA->decode(mStaticImageRaw);
+ mNeedsCreateTexture = TRUE;
+ }
+ else
+ {
+ mStaticImageRaw = NULL;
+ mStaticImageRaw = new LLImageRaw;
+ mStaticImageTGA->decodeAndProcess( mStaticImageRaw, getInfo()->mDomain, effective_weight );
+ mNeedsCreateTexture = TRUE;
+ }
+ }
+
+ if( mCachedProcessedImageGL )
+ {
+ if( gUseAvatarGLCache ) // 64 MB
+ {
+ if (gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))
+ {
+ if( mNeedsCreateTexture )
+ {
+ mCachedProcessedImageGL->createGLTexture(0, mStaticImageRaw);
+ mNeedsCreateTexture = FALSE;
+
+ mCachedProcessedImageGL->bind();
+ mCachedProcessedImageGL->setClamp(TRUE, TRUE);
+ }
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ mCachedProcessedImageGL->bind();
+ gGradientPaletteList.setHardwarePalette( getInfo()->mDomain, effective_weight );
+ gl_rect_2d_simple_tex( width, height );
+ mCachedProcessedImageGL->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ else
+ {
+ // Create the GL texture, and then hang onto it for future use.
+ if( mNeedsCreateTexture )
+ {
+ mCachedProcessedImageGL->createGLTexture(0, mStaticImageRaw);
+ mNeedsCreateTexture = FALSE;
+
+ mCachedProcessedImageGL->bind();
+ mCachedProcessedImageGL->setClamp(TRUE, TRUE);
+ }
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ mCachedProcessedImageGL->bind();
+ gl_rect_2d_simple_tex( width, height );
+ mCachedProcessedImageGL->unbindTexture(0, GL_TEXTURE_2D);
+ }
+ stop_glerror();
+ }
+ else
+ {
+ if( (mCachedProcessedImageGL->getWidth() != VOAVATAR_SCRATCH_TEX_WIDTH) ||
+ (mCachedProcessedImageGL->getHeight() != VOAVATAR_SCRATCH_TEX_HEIGHT) )
+ {
+ if (gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))
+ {
+ mCachedProcessedImageGL->createGLTexture(0, mStaticImageRaw);
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+
+ mCachedProcessedImageGL->bind();
+ mCachedProcessedImageGL->setClamp(TRUE, TRUE);
+
+ gGradientPaletteList.setHardwarePalette( getInfo()->mDomain, effective_weight );
+ gl_rect_2d_simple_tex( width, height );
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+ mCachedProcessedImageGL->destroyGLTexture();
+ }
+ else
+ {
+ // Create the GL texture, bind it and draw a rect, and then immediately destroy it.
+ mCachedProcessedImageGL->createGLTexture(0, mStaticImageRaw);
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+
+ mCachedProcessedImageGL->bind();
+ mCachedProcessedImageGL->setClamp(TRUE, TRUE);
+
+ gl_rect_2d_simple_tex( width, height );
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+
+ mCachedProcessedImageGL->destroyGLTexture();
+ }
+ stop_glerror();
+ }
+ else
+ {
+ if (gGLManager.mHasPalettedTextures && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_PALETTE))
+ {
+ // Write into a pre-existing GL Image, and then bind and render that.
+ // Faster than creating a new GL Image and then destroying it.
+ if( mTexLayer->getTexLayerSet()->getAvatar()->bindScratchTexture( GL_COLOR_INDEX ) )
+ {
+ glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,
+ mCachedProcessedImageGL->getWidth(),
+ mCachedProcessedImageGL->getHeight(),
+ GL_COLOR_INDEX, GL_UNSIGNED_BYTE,
+ mStaticImageRaw->getData() );
+ stop_glerror();
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ gGradientPaletteList.setHardwarePalette( getInfo()->mDomain, effective_weight );
+ gl_rect_2d_simple_tex( width, height );
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ else
+ {
+ // Write into a pre-existing GL Image, and then bind and render that.
+ // Faster than creating a new GL Image and then destroying it.
+ if( mTexLayer->getTexLayerSet()->getAvatar()->bindScratchTexture( GL_ALPHA ) )
+ {
+ glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,
+ mCachedProcessedImageGL->getWidth(),
+ mCachedProcessedImageGL->getHeight(),
+ GL_ALPHA, GL_UNSIGNED_BYTE,
+ mStaticImageRaw->getData() );
+ stop_glerror();
+
+ LLGLSNoAlphaTest gls_no_alpha_test;
+ gl_rect_2d_simple_tex( width, height );
+
+ LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
+ }
+ else
+ {
+ success = FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ // 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()->mIsSelf )
+ {
+ mCachedProcessedImageGL = NULL;
+ }
+ }
+ else
+ {
+ LLGLSNoTextureNoAlphaTest gls_no_texture_no_alpha_test;
+ glColor4f( 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 << "<global_color> element is missing name attribute." << llendl;
+ return FALSE;
+ }
+ // <param> sub-element
+ for (LLXmlTreeNode* child = node->getChildByName( "param" );
+ child;
+ child = node->getNextNamedChild())
+ {
+ if( child->getChildByName( "param_color" ) )
+ {
+ // <param><param_color/></param>
+ 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;
+ }
+
+ LLString op_string;
+ static LLStdStringHandle operation_string = LLXmlTree::addAttributeString("operation");
+ if( param_color_node->getFastAttributeString( operation_string, op_string ) )
+ {
+ LLString::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 << "<param_color> is missing <value> sub-elements" << llendl;
+ return FALSE;
+ }
+
+ if( (mOperation == OP_BLEND) && (mNumColors != 1) )
+ {
+ llwarns << "<param_color> with operation\"blend\" must have exactly one <value>" << 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()
+ :
+ mRawBytes( 0 ),
+ mGLBytes( 0 ),
+ mTGABytes( 0 )
+{}
+
+LLTexStaticImageList::~LLTexStaticImageList()
+{
+ deleteCachedImages();
+}
+
+void LLTexStaticImageList::dumpByteCount()
+{
+ llinfos << "Avatar Static Textures " <<
+ " Raw:" << (mRawBytes / 1024) <<
+ "KB GL:" << (mGLBytes / 1024) <<
+ "KB TGA:" << (mTGABytes / 1024) << "KB" << llendl;
+}
+
+void LLTexStaticImageList::deleteCachedImages()
+{
+ if( mRawBytes || mGLBytes || mTGABytes )
+ {
+ llinfos << "Clearing Static Textures " <<
+ " Raw:" << (mRawBytes / 1024) <<
+ "KB GL:" << (mGLBytes / 1024) <<
+ "KB TGA:" << (mTGABytes / 1024) << "KB" << llendl;
+
+ //mStaticImageLists uses LLPointers, clear() will cause deletion
+
+ mStaticImageListRaw.clear();
+ mStaticImageListTGA.clear();
+ mStaticImageListGL.clear();
+
+ mRawBytes = 0;
+ mGLBytes = 0;
+ mTGABytes = 0;
+ }
+}
+
+// Note: in general, for a given image image we'll call either getImageTga(), getImageRaw() or getImageGL().
+// We call getImageTga() if the image is used as an alpha gradient.
+// Otherwise, we call getImageRaw() if we have 32 MB or less of video RAM or less and getImageGL() if we have
+// more video RAM than that.
+
+// 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 LLString& 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.c_str());
+ LLPointer<LLImageTGA> 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 an LLImageRaw that contains the decoded data from a tga file named file_name.
+// Caches the result to speed identical subsequent requests.
+LLImageRaw* LLTexStaticImageList::getImageRaw(const LLString& file_name)
+{
+ LLPointer<LLImageRaw> image_raw;
+ const char *namekey = sImageNames.addString(file_name);
+ image_raw_map_t::iterator iter = mStaticImageListRaw.find(namekey);
+ if( iter != mStaticImageListRaw.end() )
+ {
+ image_raw = iter->second;
+ }
+ else
+ {
+ image_raw = new LLImageRaw();
+ if( loadImageRaw( file_name, image_raw ) )
+ {
+ mStaticImageListRaw[ namekey ] = image_raw;
+ mRawBytes += image_raw->getDataSize();
+ }
+ else
+ {
+ image_raw = NULL;
+ }
+ }
+
+ return image_raw;
+}
+
+// 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 LLString& file_name, BOOL is_mask )
+{
+ LLPointer<LLImageGL> 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<LLImageRaw> 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);
+
+ image_gl->bind();
+ image_gl->setClamp(TRUE, TRUE);
+
+ 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 LLString& file_name, LLImageRaw* image_raw )
+{
+ BOOL success = FALSE;
+ std::string path;
+ path = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,file_name.c_str());
+ LLPointer<LLImageTGA> 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();
+}
+
+
+//-----------------------------------------------------------------------------
+// LLGradientPaletteList
+//-----------------------------------------------------------------------------
+
+LLGradientPaletteList::~LLGradientPaletteList()
+{
+ // Note: can't just call deleteAllData() because the data values are arrays.
+ for( palette_map_t::iterator iter = mPaletteMap.begin();
+ iter != mPaletteMap.end(); iter++ )
+ {
+ U8* data = iter->second;
+ delete []data;
+ }
+}
+
+void LLGradientPaletteList::initPalette(F32 domain)
+{
+ palette_map_t::iterator iter = mPaletteMap.find( domain );
+ if( iter == mPaletteMap.end() )
+ {
+ U8 *palette = new U8[512 * 4];
+ mPaletteMap[domain] = palette;
+ S32 ramp_start = 255 - llfloor(domain * 255.f);
+ S32 ramp_end = 255;
+ F32 ramp_factor = (ramp_end == ramp_start) ? 0.f : (255.f / ((F32)ramp_end - (F32)ramp_start));
+
+ //FIXME: move conditionals outside of loop, since this really is just a sequential process
+ for (S32 i = 0; i < 512; i++)
+ {
+ palette[(i * 4) + 1] = 0;
+ palette[(i * 4) + 2] = 0;
+ if (i <= ramp_start)
+ {
+ palette[(i * 4)] = 0;
+ palette[(i * 4) + 3] = 0;
+ }
+ else if (i < ramp_end)
+ {
+ palette[(i * 4)] = llfloor(((F32)i - (F32)ramp_start) * ramp_factor);
+ palette[(i * 4) + 3] = llfloor(((F32)i - (F32)ramp_start) * ramp_factor);
+ }
+ else
+ {
+ palette[(i * 4)] = 255;
+ palette[(i * 4) + 3] = 255;
+ }
+ }
+ }
+}
+
+void LLGradientPaletteList::setHardwarePalette( F32 domain, F32 effective_weight )
+{
+ palette_map_t::iterator iter = mPaletteMap.find( domain );
+ if( iter != mPaletteMap.end() )
+ {
+ U8* palette = iter->second;
+ set_palette( palette + llfloor(effective_weight * (255.f * (1.f - domain))) * 4);
+ }
+ else
+ {
+ llwarns << "LLGradientPaletteList::setHardwarePalette() missing domain " << domain << llendl;
+ }
+}