diff options
Diffstat (limited to 'indra/newview/lltexlayer.cpp')
-rw-r--r-- | indra/newview/lltexlayer.cpp | 2839 |
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*)¶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() ); + + 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*)¶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()->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; + } +} |