2705 lines
71 KiB
C++
2705 lines
71 KiB
C++
/**
|
|
* @file lltexlayer.cpp
|
|
* @brief A texture layer. Used for avatars.
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2002-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "imageids.h"
|
|
#include "llagent.h"
|
|
#include "llagentcamera.h"
|
|
#include "llagentwearables.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 "llviewertexturelist.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewerstats.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llvoavatarself.h"
|
|
#include "llxmltree.h"
|
|
#include "pipeline.h"
|
|
#include "v4coloru.h"
|
|
#include "llrender.h"
|
|
#include "llassetuploadresponders.h"
|
|
#include "llviewershadermgr.h"
|
|
|
|
//#include "../tools/imdebug/imdebug.h"
|
|
|
|
using namespace LLVOAvatarDefines;
|
|
|
|
const S32 MAX_BAKE_UPLOAD_ATTEMPTS = 4;
|
|
|
|
// static
|
|
S32 LLTexLayerSetBuffer::sGLByteCount = 0;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLBakedUploadData()
|
|
//-----------------------------------------------------------------------------
|
|
LLBakedUploadData::LLBakedUploadData( LLVOAvatar* avatar,
|
|
LLTexLayerSet* layerset,
|
|
LLTexLayerSetBuffer* layerset_buffer,
|
|
const LLUUID & id ) :
|
|
mAvatar( avatar ),
|
|
mTexLayerSet( layerset ),
|
|
mLayerSetBuffer( layerset_buffer ),
|
|
mID(id)
|
|
{
|
|
mStartTime = LLFrameTimer::getTotalTime(); // Record starting time
|
|
for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ )
|
|
{
|
|
LLWearable* wearable = gAgentWearables.getWearable( (LLWearableType::EType)i);
|
|
if( wearable )
|
|
{
|
|
mWearableAssets[i] = wearable->getAssetID();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexLayerSetBuffer
|
|
// The composite image that a LLTexLayerSet writes to. Each LLTexLayerSet has one.
|
|
//-----------------------------------------------------------------------------
|
|
LLTexLayerSetBuffer::LLTexLayerSetBuffer(LLTexLayerSet* owner, S32 width, S32 height)
|
|
:
|
|
// ORDER_LAST => must render these after the hints are created.
|
|
LLViewerDynamicTexture( width, height, 4, LLViewerDynamicTexture::ORDER_LAST, TRUE ),
|
|
mNeedsUpdate( TRUE ),
|
|
mNeedsUpload( FALSE ),
|
|
mUploadPending( FALSE ), // Not used for any logic here, just to sync sending of updates
|
|
mUploadFailCount( 0 ),
|
|
mUploadAfter( 0 ),
|
|
mTexLayerSet( owner )
|
|
{
|
|
LLTexLayerSetBuffer::sGLByteCount += getSize();
|
|
}
|
|
|
|
LLTexLayerSetBuffer::~LLTexLayerSetBuffer()
|
|
{
|
|
LLTexLayerSetBuffer::sGLByteCount -= getSize();
|
|
destroyGLTexture();
|
|
for (S32 order = 0; order < ORDER_COUNT; order++)
|
|
{
|
|
LLViewerDynamicTexture::sInstances[order].erase(this); // will fail in all but one case.
|
|
}
|
|
if (mTexLayerSet->mComposite == this)
|
|
{
|
|
// Destroy the pointer on this now gone buffer.
|
|
mTexLayerSet->mComposite = NULL;
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
S8 LLTexLayerSetBuffer::getType() const
|
|
{
|
|
return LLViewerDynamicTexture::LL_TEX_LAYER_SET_BUFFER ;
|
|
}
|
|
|
|
//virtual
|
|
void LLTexLayerSetBuffer::restoreGLTexture()
|
|
{
|
|
LLViewerDynamicTexture::restoreGLTexture() ;
|
|
}
|
|
|
|
//virtual
|
|
void LLTexLayerSetBuffer::destroyGLTexture()
|
|
{
|
|
LLViewerDynamicTexture::destroyGLTexture() ;
|
|
}
|
|
|
|
// static
|
|
void LLTexLayerSetBuffer::dumpTotalByteCount()
|
|
{
|
|
llinfos << "Composite System GL Buffers: " << (LLTexLayerSetBuffer::sGLByteCount/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;
|
|
mUploadAfter = 0;
|
|
}
|
|
}
|
|
|
|
// request an upload to start delay_usec microseconds from now
|
|
void LLTexLayerSetBuffer::requestDelayedUpload(U64 delay_usec)
|
|
{
|
|
if (!mNeedsUpload)
|
|
{
|
|
mNeedsUpload = TRUE;
|
|
mUploadPending = TRUE;
|
|
mUploadAfter = LLFrameTimer::getTotalTime() + delay_usec;
|
|
}
|
|
}
|
|
|
|
void LLTexLayerSetBuffer::cancelUpload()
|
|
{
|
|
if (mNeedsUpload)
|
|
{
|
|
mNeedsUpload = FALSE;
|
|
}
|
|
mUploadPending = FALSE;
|
|
mUploadAfter = 0;
|
|
}
|
|
|
|
// do we need to upload, and do we have sufficient data to create an uploadable composite?
|
|
BOOL LLTexLayerSetBuffer::needsUploadNow() const
|
|
{
|
|
BOOL upload = mNeedsUpload && mTexLayerSet->isLocalTextureDataFinal() && gAgentQueryManager.hasNoPendingQueries();
|
|
return (upload && (LLFrameTimer::getTotalTime() > mUploadAfter));
|
|
}
|
|
|
|
void LLTexLayerSetBuffer::pushProjection() const
|
|
{
|
|
gGL.matrixMode(LLRender::MM_PROJECTION);
|
|
gGL.pushMatrix();
|
|
gGL.loadIdentity();
|
|
gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f);
|
|
|
|
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
|
gGL.pushMatrix();
|
|
gGL.loadIdentity();
|
|
}
|
|
|
|
void LLTexLayerSetBuffer::popProjection() const
|
|
{
|
|
gGL.matrixMode(LLRender::MM_PROJECTION);
|
|
gGL.popMatrix();
|
|
|
|
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
|
gGL.popMatrix();
|
|
}
|
|
|
|
BOOL LLTexLayerSetBuffer::needsRender()
|
|
{
|
|
LLVOAvatar* avatar = mTexLayerSet->getAvatar();
|
|
BOOL upload_now = needsUploadNow();
|
|
BOOL needs_update = (mNeedsUpdate || upload_now) && !avatar->getIsAppearanceAnimating();
|
|
if (needs_update)
|
|
{
|
|
BOOL invalid_skirt = avatar->getBakedTE(mTexLayerSet) == TEX_SKIRT_BAKED && !avatar->isWearingWearableType(LLWearableType::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
|
|
LLViewerDynamicTexture::preRender(FALSE);
|
|
}
|
|
|
|
void LLTexLayerSetBuffer::postRender(BOOL success)
|
|
{
|
|
popProjection();
|
|
|
|
LLViewerDynamicTexture::postRender(success);
|
|
}
|
|
|
|
BOOL LLTexLayerSetBuffer::render()
|
|
{
|
|
// Default color mask for tex layer render
|
|
gGL.setColorMask(true, true);
|
|
|
|
// do we need to upload, and do we have sufficient data to create an uploadable composite?
|
|
// When do we upload the texture if gAgent.mNumPendingQueries is non-zero?
|
|
BOOL upload_now = needsUploadNow();
|
|
BOOL success = TRUE;
|
|
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.bind();
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
|
|
LLVertexBuffer::unbind();
|
|
|
|
// Composite the color data
|
|
LLGLSUIDefault gls_ui;
|
|
success &= mTexLayerSet->render( mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight );
|
|
gGL.flush();
|
|
|
|
if( upload_now )
|
|
{
|
|
if (!success)
|
|
{
|
|
llinfos << "Failed attempt to bake " << mTexLayerSet->getBodyRegion() << llendl;
|
|
mUploadPending = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (mTexLayerSet->isVisible())
|
|
{
|
|
readBackAndUpload();
|
|
}
|
|
else
|
|
{
|
|
mUploadPending = FALSE;
|
|
mNeedsUpload = FALSE;
|
|
LLVOAvatar* avatar = mTexLayerSet->getAvatar();
|
|
if (avatar)
|
|
{
|
|
avatar->setNewBakedTexture(avatar->getBakedTE(mTexLayerSet), IMG_INVISIBLE);
|
|
llinfos << "Invisible baked texture set for " << mTexLayerSet->getBodyRegion() << llendl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.unbind();
|
|
}
|
|
|
|
LLVertexBuffer::unbind();
|
|
|
|
// reset GL state
|
|
gGL.setColorMask(true, true);
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
|
|
// we have valid texture data now
|
|
mGLTexturep->setGLTextureCreated(true);
|
|
mNeedsUpdate = FALSE;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool LLTexLayerSetBuffer::isInitialized(void) const
|
|
{
|
|
return mGLTexturep.notNull() && mGLTexturep->isGLTextureCreated();
|
|
}
|
|
|
|
BOOL LLTexLayerSetBuffer::updateImmediate()
|
|
{
|
|
mNeedsUpdate = TRUE;
|
|
BOOL result = FALSE;
|
|
|
|
if (needsRender())
|
|
{
|
|
preRender(FALSE);
|
|
result = render();
|
|
postRender(result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void LLTexLayerSetBuffer::readBackAndUpload()
|
|
{
|
|
llinfos << "Baked " << mTexLayerSet->getBodyRegion() << llendl;
|
|
LLViewerStats::getInstance()->incStat(LLViewerStats::ST_TEX_BAKES);
|
|
|
|
// Don't need caches since we're baked now. (note: we won't *really* be baked
|
|
// until this image is sent to the server and the Avatar Appearance message is received.)
|
|
mTexLayerSet->deleteCaches();
|
|
|
|
// Get the COLOR information from our texture
|
|
U8* baked_color_data = new U8[ mFullWidth * mFullHeight * 4 ];
|
|
glReadPixels(mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, GL_RGBA, GL_UNSIGNED_BYTE, baked_color_data );
|
|
stop_glerror();
|
|
|
|
// Get the MASK information from our texture
|
|
LLGLSUIDefault gls_ui;
|
|
|
|
LLPointer<LLImageRaw> baked_mask_image = new LLImageRaw(mFullWidth, mFullHeight, 1 );
|
|
U8* baked_mask_data = baked_mask_image->getData();
|
|
|
|
mTexLayerSet->gatherAlphaMasks(baked_mask_data, mFullWidth, mFullHeight);
|
|
// imdebug("lum b=8 w=%d h=%d %p", mWidth, mHeight, baked_mask_data);
|
|
|
|
|
|
S32 baked_image_components = 5; // red green blue bump clothing
|
|
LLPointer<LLImageRaw> baked_image = new LLImageRaw( mFullWidth, mFullHeight, baked_image_components );
|
|
U8* baked_image_data = baked_image->getData();
|
|
S32 i = 0;
|
|
for (S32 u=0; u < mFullWidth; u++)
|
|
{
|
|
for (S32 v=0; v < mFullHeight; v++)
|
|
{
|
|
baked_image_data[5 * i + 0] = baked_color_data[4 * i + 0];
|
|
baked_image_data[5 * i + 1] = baked_color_data[4 * i + 1];
|
|
baked_image_data[5 * i + 2] = baked_color_data[4 * i + 2];
|
|
baked_image_data[5 * i + 3] = baked_color_data[4 * i + 3]; // alpha should be correct for eyelashes.
|
|
baked_image_data[5 * i + 4] = baked_mask_data[i];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C;
|
|
compressedImage->setRate(0.f);
|
|
const char* comment_text = LINDEN_J2C_COMMENT_PREFIX "RGBHM"; // writes into baked_color_data. 5 channels (rgb, heightfield/alpha, mask)
|
|
if (compressedImage->encode(baked_image, comment_text))
|
|
{
|
|
LLTransactionID tid;
|
|
tid.generate();
|
|
const LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
|
|
if (LLVFile::writeFile(compressedImage->getData(), compressedImage->getDataSize(),
|
|
gVFS, asset_id, LLAssetType::AT_TEXTURE))
|
|
{
|
|
// Read back the file and validate.
|
|
BOOL valid = FALSE;
|
|
LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C;
|
|
S32 file_size = 0;
|
|
U8* data = LLVFile::readFile(gVFS, asset_id, LLAssetType::AT_TEXTURE, &file_size);
|
|
if (data)
|
|
{
|
|
valid = integrity_test->validate(data, file_size); // integrity_test will delete 'data'
|
|
}
|
|
else
|
|
{
|
|
integrity_test->setLastError("Unable to read entire file");
|
|
}
|
|
|
|
if( valid )
|
|
{
|
|
// baked_upload_data is owned by the responder and deleted after the request completes
|
|
LLBakedUploadData* baked_upload_data =
|
|
new LLBakedUploadData( gAgentAvatarp, this->mTexLayerSet, this, asset_id );
|
|
mUploadID = asset_id;
|
|
|
|
// Upload the image
|
|
const std::string url = gAgent.getRegion()->getCapability("UploadBakedTexture");
|
|
if(!url.empty()
|
|
&& !LLPipeline::sForceOldBakedUpload) // toggle debug setting UploadBakedTexOld to change between the new caps method and old method
|
|
{
|
|
LLSD body = LLSD::emptyMap();
|
|
// The responder will call LLTexLayerSetBuffer::onTextureUploadComplete()
|
|
LLHTTPClient::post(url, body, new LLSendTexLayerResponder(body, mUploadID, LLAssetType::AT_TEXTURE, baked_upload_data));
|
|
llinfos << "Baked texture upload via capability of " << mUploadID << " to " << url << llendl;
|
|
}
|
|
else
|
|
{
|
|
gAssetStorage->storeAssetData(tid,
|
|
LLAssetType::AT_TEXTURE,
|
|
LLTexLayerSetBuffer::onTextureUploadComplete,
|
|
baked_upload_data,
|
|
TRUE, // temp_file
|
|
TRUE, // is_priority
|
|
TRUE); // store_local
|
|
llinfos << "Baked texture upload via Asset Store." << llendl;
|
|
}
|
|
|
|
mNeedsUpload = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// The read back and validate operation failed. Remove the uploaded file.
|
|
mUploadPending = FALSE;
|
|
LLVFile file(gVFS, asset_id, LLAssetType::AT_TEXTURE, LLVFile::WRITE);
|
|
file.remove();
|
|
llinfos << "Unable to create baked upload file (reason: corrupted)." << llendl;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The VFS write file operation failed.
|
|
mUploadPending = FALSE;
|
|
llinfos << "Unable to create baked upload file (reason: failed to write file)" << llendl;
|
|
}
|
|
|
|
delete [] baked_color_data;
|
|
}
|
|
|
|
|
|
// static
|
|
void LLTexLayerSetBuffer::onTextureUploadComplete(const LLUUID& uuid,
|
|
void* userdata,
|
|
S32 result,
|
|
LLExtStat ext_status) // StoreAssetData callback (not fixed)
|
|
{
|
|
LLBakedUploadData* baked_upload_data = (LLBakedUploadData*)userdata;
|
|
|
|
if ((result == 0) &&
|
|
isAgentAvatarValid() &&
|
|
!gAgentAvatarp->isDead() &&
|
|
(baked_upload_data->mAvatar == gAgentAvatarp) && // Sanity check: only the user's avatar should be uploading textures.
|
|
(baked_upload_data->mTexLayerSet->hasComposite()))
|
|
{
|
|
LLTexLayerSetBuffer* layerset_buffer = baked_upload_data->mTexLayerSet->getComposite();
|
|
S32 failures = layerset_buffer->mUploadFailCount;
|
|
layerset_buffer->mUploadFailCount = 0;
|
|
|
|
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
|
|
|
|
// BAP: does this really belong in this callback, as
|
|
// opposed to where the cancellation takes place?
|
|
// suspect this does nothing.
|
|
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();
|
|
layerset_buffer->mUploadPending = FALSE;
|
|
|
|
if (result >= 0)
|
|
{
|
|
ETextureIndex baked_te = gAgentAvatarp->getBakedTE(layerset_buffer->mTexLayerSet);
|
|
U64 now = LLFrameTimer::getTotalTime(); // Record starting time
|
|
llinfos << "Baked texture upload took " << (S32)((now - baked_upload_data->mStartTime) / 1000) << " ms" << llendl;
|
|
gAgentAvatarp->setNewBakedTexture(baked_te, uuid);
|
|
}
|
|
else
|
|
{
|
|
++failures;
|
|
llinfos << "Baked upload failed (attempt " << failures << "/" << MAX_BAKE_UPLOAD_ATTEMPTS << "), ";
|
|
if (failures >= MAX_BAKE_UPLOAD_ATTEMPTS)
|
|
{
|
|
llcont << "giving up.";
|
|
}
|
|
else
|
|
{
|
|
const F32 delay = 5.f;
|
|
llcont << llformat("retrying in %.2f seconds.", delay);
|
|
layerset_buffer->mUploadFailCount = failures;
|
|
layerset_buffer->requestDelayedUpload((U64)(delay * 1000000));
|
|
}
|
|
llcont << llendl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Received baked texture out of date, ignored." << llendl;
|
|
}
|
|
|
|
gAgentAvatarp->dirtyMesh();
|
|
}
|
|
else
|
|
{
|
|
// didn't set the new baked texture, it means that they'll try
|
|
// and rebake it at some point in the future (after login?)),
|
|
// or this response to upload is out of date, in which case a
|
|
// current response should be on the way or already processed.
|
|
llwarns << "Baked upload failed" << llendl;
|
|
}
|
|
|
|
delete baked_upload_data;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 ),
|
|
mIsVisible(TRUE),
|
|
mBakedTexIndex(BAKED_HEAD),
|
|
mInfo( NULL )
|
|
{
|
|
}
|
|
|
|
LLTexLayerSet::~LLTexLayerSet()
|
|
{
|
|
deleteCaches();
|
|
std::for_each(mLayerList.begin(), mLayerList.end(), DeletePointer());
|
|
std::for_each(mMaskLayerList.begin(), mMaskLayerList.end(), DeletePointer());
|
|
mComposite = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
if (!layer->isVisibilityMask())
|
|
{
|
|
mLayerList.push_back(layer);
|
|
}
|
|
else
|
|
{
|
|
mMaskLayerList.push_back(layer);
|
|
}
|
|
}
|
|
|
|
requestUpdate();
|
|
|
|
stop_glerror();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0 // obsolete
|
|
//-----------------------------------------------------------------------------
|
|
// parseData
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL LLTexLayerSet::parseData(LLXmlTreeNode* node)
|
|
{
|
|
LLTexLayerSetInfo *info = new LLTexLayerSetInfo;
|
|
|
|
if (!info->parseXml(node))
|
|
{
|
|
delete info;
|
|
return FALSE;
|
|
}
|
|
if (!setInfo(info))
|
|
{
|
|
delete info;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
void LLTexLayerSet::deleteCaches()
|
|
{
|
|
for( layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++ )
|
|
{
|
|
LLTexLayer* layer = *iter;
|
|
layer->deleteCaches();
|
|
}
|
|
for (layer_list_t::iterator iter = mMaskLayerList.begin(); iter != mMaskLayerList.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 );
|
|
}
|
|
|
|
|
|
void LLTexLayerSet::renderAlphaMaskTextures(S32 x, S32 y, S32 width, S32 height, bool forceClear)
|
|
{
|
|
const LLTexLayerSetInfo *info = getInfo();
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
|
|
gGL.setColorMask(false, true);
|
|
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
|
|
|
// (Optionally) replace alpha with a single component image from a tga file.
|
|
if (!info->mStaticAlphaFileName.empty())
|
|
{
|
|
gGL.flush();
|
|
{
|
|
LLViewerTexture* tex = gTexStaticImageList.getTexture(info->mStaticAlphaFileName, TRUE);
|
|
if (tex)
|
|
{
|
|
LLGLSUIDefault gls_ui;
|
|
gGL.getTexUnit(0)->bind(tex);
|
|
gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_REPLACE);
|
|
gl_rect_2d_simple_tex(width, height);
|
|
}
|
|
}
|
|
gGL.flush();
|
|
}
|
|
else if (forceClear || info->mClearAlpha || (mMaskLayerList.size() > 0))
|
|
{
|
|
// Set the alpha channel to one (clean up after previous blending)
|
|
gGL.flush();
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
|
|
|
|
gl_rect_2d_simple( width, height );
|
|
|
|
gGL.flush();
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
}
|
|
|
|
// (Optional) Mask out part of the baked texture with alpha masks
|
|
// will still have an effect even if mClearAlpha is set or the alpha component was replaced
|
|
if (mMaskLayerList.size() > 0)
|
|
{
|
|
gGL.setSceneBlendType(LLRender::BT_MULT_ALPHA);
|
|
gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_REPLACE);
|
|
for (layer_list_t::iterator iter = mMaskLayerList.begin(); iter != mMaskLayerList.end(); iter++)
|
|
{
|
|
LLTexLayer* layer = *iter;
|
|
gGL.flush();
|
|
layer->blendAlphaTexture(x, y, width, height);
|
|
gGL.flush();
|
|
}
|
|
}
|
|
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
|
|
gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
gGL.setColorMask(true, true);
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
}
|
|
|
|
BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height )
|
|
{
|
|
BOOL success = TRUE;
|
|
mIsVisible = TRUE;
|
|
|
|
if (mMaskLayerList.size() > 0)
|
|
{
|
|
for (layer_list_t::iterator iter = mMaskLayerList.begin(); iter != mMaskLayerList.end(); iter++)
|
|
{
|
|
LLTexLayer* layer = *iter;
|
|
if (layer->isInvisibleAlphaMask())
|
|
{
|
|
mIsVisible = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
LLGLSUIDefault gls_ui;
|
|
LLGLDepthTest gls_depth(GL_FALSE, GL_FALSE);
|
|
gGL.setColorMask(true, true);
|
|
|
|
// clear buffer area to ensure we don't pick up UI elements
|
|
{
|
|
gGL.flush();
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.0f);
|
|
}
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
|
|
|
|
gl_rect_2d_simple( width, height );
|
|
|
|
gGL.flush();
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
}
|
|
|
|
if (mIsVisible)
|
|
{
|
|
// composite color layers
|
|
for (layer_list_t::iterator iter = mLayerList.begin(); iter != mLayerList.end(); iter++)
|
|
{
|
|
LLTexLayer* layer = *iter;
|
|
if (layer->getRenderPass() == RP_COLOR || layer->getRenderPass() == RP_BUMP)
|
|
{
|
|
gGL.flush();
|
|
success &= layer->render(x, y, width, height);
|
|
gGL.flush();
|
|
}
|
|
}
|
|
|
|
renderAlphaMaskTextures(x, y, width, height, false);
|
|
|
|
stop_glerror();
|
|
}
|
|
else
|
|
{
|
|
gGL.flush();
|
|
|
|
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f( 0.f, 0.f, 0.f, 0.f );
|
|
|
|
gl_rect_2d_simple( width, height );
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
|
|
gGL.flush();
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void LLTexLayerSet::requestUpdate()
|
|
{
|
|
if( mUpdatesEnabled )
|
|
{
|
|
createComposite();
|
|
mComposite->requestUpdate();
|
|
}
|
|
}
|
|
|
|
void LLTexLayerSet::requestUpload()
|
|
{
|
|
createComposite();
|
|
mComposite->requestUpload();
|
|
}
|
|
|
|
void LLTexLayerSet::cancelUpload()
|
|
{
|
|
if(mComposite)
|
|
{
|
|
mComposite->cancelUpload();
|
|
}
|
|
}
|
|
|
|
void LLTexLayerSet::createComposite()
|
|
{
|
|
if( !mComposite )
|
|
{
|
|
S32 width = mInfo->mWidth;
|
|
S32 height = mInfo->mHeight;
|
|
// Composite other avatars at reduced resolution
|
|
if( !mAvatar->isSelf() )
|
|
{
|
|
width /= 2;
|
|
height /= 2;
|
|
}
|
|
mComposite = new LLTexLayerSetBuffer(this, width, height);
|
|
}
|
|
}
|
|
|
|
void LLTexLayerSet::destroyComposite()
|
|
{
|
|
if( 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set alpha back to that of our alpha masks.
|
|
renderAlphaMaskTextures(mComposite->getOriginX(), mComposite->getOriginY(), width, height, true);
|
|
}
|
|
|
|
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),
|
|
mIsVisibilityMask(FALSE)
|
|
{
|
|
}
|
|
|
|
LLTexLayerInfo::~LLTexLayerInfo( )
|
|
{
|
|
std::for_each(mColorInfoList.begin(), mColorInfoList.end(), DeletePointer());
|
|
std::for_each(mAlphaInfoList.begin(), mAlphaInfoList.end(), DeletePointer());
|
|
}
|
|
|
|
BOOL LLTexLayerInfo::parseXml(LLXmlTreeNode* node)
|
|
{
|
|
llassert( node->hasName( "layer" ) );
|
|
|
|
// name attribute
|
|
static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name");
|
|
if( !node->getFastAttributeString( name_string, mName ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static LLStdStringHandle write_all_channels_string = LLXmlTree::addAttributeString("write_all_channels");
|
|
node->getFastAttributeBOOL( write_all_channels_string, mWriteAllChannels );
|
|
|
|
std::string render_pass_name;
|
|
static LLStdStringHandle render_pass_string = LLXmlTree::addAttributeString("render_pass");
|
|
if( node->getFastAttributeString( render_pass_string, render_pass_name ) )
|
|
{
|
|
if( render_pass_name == "bump" )
|
|
{
|
|
mRenderPass = RP_BUMP;
|
|
}
|
|
}
|
|
|
|
// Note: layers can have either a "global_color" attrib, a "fixed_color" attrib, or a <param_color> child.
|
|
// global color attribute (optional)
|
|
static LLStdStringHandle global_color_string = LLXmlTree::addAttributeString("global_color");
|
|
node->getFastAttributeString( global_color_string, mGlobalColor );
|
|
|
|
// Visibility mask (optional)
|
|
BOOL is_visibility;
|
|
static LLStdStringHandle visibility_mask_string = LLXmlTree::addAttributeString("visibility_mask");
|
|
if (node->getFastAttributeBOOL(visibility_mask_string, is_visibility))
|
|
{
|
|
mIsVisibilityMask = is_visibility;
|
|
}
|
|
|
|
// color attribute (optional)
|
|
LLColor4U color4u;
|
|
static LLStdStringHandle fixed_color_string = LLXmlTree::addAttributeString("fixed_color");
|
|
if( node->getFastAttributeColor4U( fixed_color_string, color4u ) )
|
|
{
|
|
mFixedColor.setVec( color4u );
|
|
}
|
|
|
|
// <texture> optional sub-element
|
|
for (LLXmlTreeNode* texture_node = node->getChildByName( "texture" );
|
|
texture_node;
|
|
texture_node = node->getNextNamedChild())
|
|
{
|
|
std::string local_texture;
|
|
static LLStdStringHandle tga_file_string = LLXmlTree::addAttributeString("tga_file");
|
|
static LLStdStringHandle local_texture_string = LLXmlTree::addAttributeString("local_texture");
|
|
static LLStdStringHandle file_is_mask_string = LLXmlTree::addAttributeString("file_is_mask");
|
|
static LLStdStringHandle local_texture_alpha_only_string = LLXmlTree::addAttributeString("local_texture_alpha_only");
|
|
if( texture_node->getFastAttributeString( tga_file_string, mStaticImageFileName ) )
|
|
{
|
|
texture_node->getFastAttributeBOOL( file_is_mask_string, mStaticImageIsMask );
|
|
}
|
|
else if( texture_node->getFastAttributeString( local_texture_string, local_texture ) )
|
|
{
|
|
texture_node->getFastAttributeBOOL( local_texture_alpha_only_string, mUseLocalTextureAlphaOnly );
|
|
|
|
if( "upper_shirt" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_UPPER_SHIRT;
|
|
}
|
|
else if( "upper_bodypaint" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_UPPER_BODYPAINT;
|
|
}
|
|
else if( "lower_pants" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_PANTS;
|
|
}
|
|
else if( "lower_bodypaint" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_BODYPAINT;
|
|
}
|
|
else if( "lower_shoes" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_SHOES;
|
|
}
|
|
else if( "head_bodypaint" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_HEAD_BODYPAINT;
|
|
}
|
|
else if( "lower_socks" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_SOCKS;
|
|
}
|
|
else if( "upper_jacket" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_UPPER_JACKET;
|
|
}
|
|
else if( "lower_jacket" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_JACKET;
|
|
}
|
|
else if( "upper_gloves" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_UPPER_GLOVES;
|
|
}
|
|
else if( "upper_undershirt" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_UPPER_UNDERSHIRT;
|
|
}
|
|
else if( "lower_underpants" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_LOWER_UNDERPANTS;
|
|
}
|
|
else if( "eyes_iris" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_EYES_IRIS;
|
|
}
|
|
else if( "skirt" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_SKIRT;
|
|
}
|
|
else if( "hair_grain" == local_texture )
|
|
{
|
|
mLocalTexture = TEX_HAIR;
|
|
}
|
|
else if ("hair_alpha" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_HAIR_ALPHA;
|
|
}
|
|
else if ("head_alpha" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_HEAD_ALPHA;
|
|
}
|
|
else if ("upper_alpha" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_UPPER_ALPHA;
|
|
}
|
|
else if ("lower_alpha" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_LOWER_ALPHA;
|
|
}
|
|
else if ("eyes_alpha" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_EYES_ALPHA;
|
|
}
|
|
else if ("head_tattoo" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_HEAD_TATTOO;
|
|
}
|
|
else if ("upper_tattoo" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_UPPER_TATTOO;
|
|
}
|
|
else if ("lower_tattoo" == local_texture)
|
|
{
|
|
mLocalTexture = TEX_LOWER_TATTOO;
|
|
}
|
|
else
|
|
{
|
|
llwarns << "<texture> element has invalid local_texture 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())
|
|
{
|
|
std::string morph_name;
|
|
static LLStdStringHandle morph_name_string = LLXmlTree::addAttributeString("morph_name");
|
|
if (maskNode->getFastAttributeString(morph_name_string, morph_name))
|
|
{
|
|
BOOL invert = FALSE;
|
|
static LLStdStringHandle invert_string = LLXmlTree::addAttributeString("invert");
|
|
maskNode->getFastAttributeBOOL(invert_string, invert);
|
|
mMorphNameList.push_back(std::pair<std::string,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); // nyx says this is probably bogus but needs investigating
|
|
if (mInfo != NULL) // above llassert(), but softened into a warning
|
|
{
|
|
llwarns << "BAD STUFF! mInfo != NULL" << llendl;
|
|
}
|
|
mInfo = info;
|
|
//mID = info->mID; // No ID
|
|
|
|
{
|
|
LLTexLayerInfo::morph_name_list_t::iterator iter;
|
|
for (iter = mInfo->mMorphNameList.begin(); iter != mInfo->mMorphNameList.end(); iter++)
|
|
{
|
|
// *FIX: we assume that the referenced visual param is a
|
|
// morph target, need a better way of actually looking
|
|
// this up.
|
|
LLPolyMorphTarget *morph_param;
|
|
std::string *name = &(iter->first);
|
|
morph_param = (LLPolyMorphTarget *)(getTexLayerSet()->getAvatar()->getVisualParam(name->c_str()));
|
|
if (morph_param)
|
|
{
|
|
BOOL invert = iter->second;
|
|
addMaskedMorph(morph_param, invert);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
LLTexLayerInfo::color_info_list_t::iterator iter;
|
|
mParamColorList.reserve(mInfo->mColorInfoList.size());
|
|
for (iter = mInfo->mColorInfoList.begin(); iter != mInfo->mColorInfoList.end(); iter++)
|
|
{
|
|
LLTexParamColor* param_color = new LLTexParamColor( this );
|
|
if (!param_color->setInfo(*iter))
|
|
{
|
|
mInfo = NULL;
|
|
return FALSE;
|
|
}
|
|
mParamColorList.push_back( param_color );
|
|
}
|
|
}
|
|
{
|
|
LLTexLayerInfo::alpha_info_list_t::iterator iter;
|
|
mParamAlphaList.reserve(mInfo->mAlphaInfoList.size());
|
|
for (iter = mInfo->mAlphaInfoList.begin(); iter != mInfo->mAlphaInfoList.end(); iter++)
|
|
{
|
|
LLTexLayerParamAlpha* param_alpha = new LLTexLayerParamAlpha( this );
|
|
if (!param_alpha->setInfo(*iter))
|
|
{
|
|
mInfo = NULL;
|
|
return FALSE;
|
|
}
|
|
mParamAlphaList.push_back( param_alpha );
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0 // obsolete
|
|
//-----------------------------------------------------------------------------
|
|
// parseData
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLTexLayer::parseData( LLXmlTreeNode* node )
|
|
{
|
|
LLTexLayerInfo *info = new LLTexLayerInfo;
|
|
|
|
if (!info->parseXml(node))
|
|
{
|
|
delete info;
|
|
return FALSE;
|
|
}
|
|
if (!setInfo(info))
|
|
{
|
|
delete info;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
void LLTexLayer::deleteCaches()
|
|
{
|
|
for( alpha_list_t::iterator iter = mParamAlphaList.begin();
|
|
iter != mParamAlphaList.end(); iter++ )
|
|
{
|
|
LLTexLayerParamAlpha* param = *iter;
|
|
param->deleteCaches();
|
|
}
|
|
mStaticImageRaw = NULL;
|
|
}
|
|
|
|
BOOL LLTexLayer::render( S32 x, S32 y, S32 width, S32 height )
|
|
{
|
|
LLGLEnable color_mat(GL_COLOR_MATERIAL);
|
|
gPipeline.disableLights();
|
|
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
|
|
LLColor4 net_color;
|
|
BOOL color_specified = findNetColor(&net_color);
|
|
|
|
if (mTexLayerSet->getAvatar()->mIsDummy)
|
|
{
|
|
color_specified = true;
|
|
net_color = LLVOAvatar::getDummyColor();
|
|
}
|
|
|
|
BOOL success = TRUE;
|
|
|
|
// If you can't see the layer, don't render it.
|
|
if( is_approx_zero( net_color.mV[VW] ) )
|
|
{
|
|
return success;
|
|
}
|
|
|
|
BOOL alpha_mask_specified = FALSE;
|
|
alpha_list_t::iterator iter = mParamAlphaList.begin();
|
|
if( iter != mParamAlphaList.end() )
|
|
{
|
|
// If we have alpha masks, but we're skipping all of them, skip the whole layer.
|
|
// However, we can't do this optimization if we have morph masks that need updating.
|
|
/* if( mMaskedMorphs.empty() )
|
|
{
|
|
BOOL skip_layer = TRUE;
|
|
|
|
while( iter != mParamAlphaList.end() )
|
|
{
|
|
LLTexLayerParamAlpha* param = *iter;
|
|
|
|
if( !param->getSkip() )
|
|
{
|
|
skip_layer = FALSE;
|
|
break;
|
|
}
|
|
|
|
iter++;
|
|
}
|
|
|
|
if( skip_layer )
|
|
{
|
|
return success;
|
|
}
|
|
}*/
|
|
|
|
renderAlphaMasks( x, y, width, height, &net_color );
|
|
alpha_mask_specified = TRUE;
|
|
gGL.flush();
|
|
gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ONE_MINUS_DEST_ALPHA);
|
|
}
|
|
|
|
gGL.color4fv( net_color.mV);
|
|
|
|
if( getInfo()->mWriteAllChannels )
|
|
{
|
|
gGL.flush();
|
|
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
|
}
|
|
else if (getInfo()->mUseLocalTextureAlphaOnly)
|
|
{
|
|
// Use the alpha channel only
|
|
gGL.setColorMask(false, true);
|
|
}
|
|
|
|
if( (getInfo()->mLocalTexture != -1) && !getInfo()->mUseLocalTextureAlphaOnly )
|
|
{
|
|
{
|
|
LLViewerTexture* tex = NULL;
|
|
if( mTexLayerSet->getAvatar()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &tex ) )
|
|
{
|
|
if (mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)getInfo()->mLocalTexture) == IMG_DEFAULT_AVATAR)
|
|
{
|
|
tex = NULL;
|
|
}
|
|
if( tex )
|
|
{
|
|
bool no_alpha_test = getInfo()->mWriteAllChannels;
|
|
LLGLDisable alpha_test(no_alpha_test ? GL_ALPHA_TEST : 0);
|
|
if (use_shaders && no_alpha_test)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
|
|
LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode();
|
|
|
|
gGL.getTexUnit(0)->bind(tex, TRUE);
|
|
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
gl_rect_2d_simple_tex( width, height );
|
|
|
|
gGL.getTexUnit(0)->setTextureAddressMode(old_mode);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
if (use_shaders && no_alpha_test)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !getInfo()->mStaticImageFileName.empty() )
|
|
{
|
|
{
|
|
LLViewerTexture* tex = gTexStaticImageList.getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask);
|
|
if( tex )
|
|
{
|
|
gGL.getTexUnit(0)->bind(tex, TRUE);
|
|
gl_rect_2d_simple_tex( width, height );
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
else
|
|
{
|
|
success = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ((-1 == getInfo()->mLocalTexture) ||
|
|
getInfo()->mUseLocalTextureAlphaOnly) &&
|
|
getInfo()->mStaticImageFileName.empty() &&
|
|
color_specified )
|
|
{
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4fv( net_color.mV);
|
|
gl_rect_2d_simple( width, height );
|
|
}
|
|
|
|
if( alpha_mask_specified || getInfo()->mWriteAllChannels )
|
|
{
|
|
// Restore standard blend func value
|
|
gGL.flush();
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
stop_glerror();
|
|
}
|
|
|
|
if (getInfo()->mUseLocalTextureAlphaOnly)
|
|
{
|
|
// Restore color + alpha mode.
|
|
gGL.setColorMask(true, true);
|
|
}
|
|
|
|
if( !success )
|
|
{
|
|
llinfos << "LLTexLayer::render() partial: " << getInfo()->mName << llendl;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
BOOL LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height)
|
|
{
|
|
BOOL success = TRUE;
|
|
|
|
gGL.flush();
|
|
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
if (!getInfo()->mStaticImageFileName.empty())
|
|
{
|
|
LLViewerTexture* tex = gTexStaticImageList.getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask);
|
|
if (tex)
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
gGL.getTexUnit(0)->bind(tex, TRUE);
|
|
gl_rect_2d_simple_tex( width, height );
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (getInfo()->mLocalTexture >=0 && getInfo()->mLocalTexture < TEX_NUM_INDICES)
|
|
{
|
|
LLViewerTexture* tex = NULL;
|
|
if (mTexLayerSet->getAvatar()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &tex))
|
|
{
|
|
if (tex)
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
gGL.getTexUnit(0)->bind(tex);
|
|
gl_rect_2d_simple_tex( width, height );
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
success = TRUE;
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
U8* LLTexLayer::getAlphaData()
|
|
{
|
|
LLCRC alpha_mask_crc;
|
|
const LLUUID& uuid = mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)getInfo()->mLocalTexture);
|
|
alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES);
|
|
|
|
for( alpha_list_t::iterator iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ )
|
|
{
|
|
LLTexLayerParamAlpha* param = *iter;
|
|
F32 param_weight = param->getWeight();
|
|
alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32));
|
|
}
|
|
|
|
U32 cache_index = alpha_mask_crc.getCRC();
|
|
|
|
alpha_cache_t::iterator iter2 = mAlphaCache.find(cache_index);
|
|
return (iter2 == mAlphaCache.end()) ? 0 : iter2->second;
|
|
}
|
|
|
|
BOOL LLTexLayer::findNetColor( LLColor4* net_color )
|
|
{
|
|
// Color is either:
|
|
// * one or more color parameters (weighted colors) (which may make use of a global color or fixed color)
|
|
// * a reference to a global color
|
|
// * a fixed color with non-zero alpha
|
|
// * opaque white (the default)
|
|
|
|
if( !mParamColorList.empty() )
|
|
{
|
|
if( !getGlobalColor().empty() )
|
|
{
|
|
net_color->setVec( mTexLayerSet->getAvatar()->getGlobalColor( getInfo()->mGlobalColor ) );
|
|
}
|
|
else
|
|
if( getInfo()->mFixedColor.mV[VW] )
|
|
{
|
|
net_color->setVec( getInfo()->mFixedColor );
|
|
}
|
|
else
|
|
{
|
|
net_color->setVec( 0.f, 0.f, 0.f, 0.f );
|
|
}
|
|
|
|
for( color_list_t::iterator iter = mParamColorList.begin();
|
|
iter != mParamColorList.end(); iter++ )
|
|
{
|
|
LLTexParamColor* param = *iter;
|
|
LLColor4 param_net = param->getNetColor();
|
|
switch( param->getOperation() )
|
|
{
|
|
case OP_ADD:
|
|
*net_color += param_net;
|
|
break;
|
|
case OP_MULTIPLY:
|
|
net_color->mV[VX] *= param_net.mV[VX];
|
|
net_color->mV[VY] *= param_net.mV[VY];
|
|
net_color->mV[VZ] *= param_net.mV[VZ];
|
|
net_color->mV[VW] *= param_net.mV[VW];
|
|
break;
|
|
case OP_BLEND:
|
|
net_color->setVec( lerp(*net_color, param_net, param->getWeight()) );
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if( !getGlobalColor().empty() )
|
|
{
|
|
net_color->setVec( mTexLayerSet->getAvatar()->getGlobalColor( getGlobalColor() ) );
|
|
return TRUE;
|
|
}
|
|
|
|
if( getInfo()->mFixedColor.mV[VW] )
|
|
{
|
|
net_color->setVec( getInfo()->mFixedColor );
|
|
return TRUE;
|
|
}
|
|
|
|
net_color->setToWhite();
|
|
|
|
return FALSE; // No need to draw a separate colored polygon
|
|
}
|
|
|
|
|
|
BOOL LLTexLayer::renderAlphaMasks( S32 x, S32 y, S32 width, S32 height, LLColor4* colorp )
|
|
{
|
|
BOOL success = TRUE;
|
|
|
|
llassert( !mParamAlphaList.empty() );
|
|
|
|
bool use_shaders = LLGLSLShader::sNoFixedFunction;
|
|
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
|
}
|
|
gGL.setColorMask(false, true);
|
|
|
|
alpha_list_t::iterator iter = mParamAlphaList.begin();
|
|
LLTexLayerParamAlpha* first_param = *iter;
|
|
|
|
// Note: if the first param is a mulitply, multiply against the current buffer's alpha
|
|
if( !first_param || !first_param->getMultiplyBlend() )
|
|
{
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
|
|
// Clear the alpha
|
|
gGL.flush();
|
|
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
|
|
|
gGL.color4f( 0.f, 0.f, 0.f, 0.f );
|
|
gl_rect_2d_simple( width, height );
|
|
}
|
|
|
|
// Accumulate alphas
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
gGL.color4f( 1.f, 1.f, 1.f, 1.f );
|
|
|
|
for( iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ )
|
|
{
|
|
LLTexLayerParamAlpha* param = *iter;
|
|
success &= param->render( x, y, width, height );
|
|
}
|
|
|
|
// Approximates a min() function
|
|
gGL.flush();
|
|
gGL.setSceneBlendType(LLRender::BT_MULT_ALPHA);
|
|
|
|
// Accumulate the alpha component of the texture
|
|
if( getInfo()->mLocalTexture != -1 )
|
|
{
|
|
{
|
|
LLViewerTexture* tex = NULL;
|
|
if( mTexLayerSet->getAvatar()->getLocalTextureGL((ETextureIndex)getInfo()->mLocalTexture, &tex ) )
|
|
{
|
|
if( tex && (tex->getComponents() == 4) )
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
|
|
LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode();
|
|
|
|
gGL.getTexUnit(0)->bind(tex, TRUE);
|
|
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
gl_rect_2d_simple_tex( width, height );
|
|
|
|
gGL.getTexUnit(0)->setTextureAddressMode(old_mode);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !getInfo()->mStaticImageFileName.empty() )
|
|
{
|
|
LLViewerTexture* tex = gTexStaticImageList.getTexture(getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask);
|
|
if( tex )
|
|
{
|
|
if( (tex->getComponents() == 4) ||
|
|
( (tex->getComponents() == 1) && getInfo()->mStaticImageIsMask ) )
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
gGL.getTexUnit(0)->bind(tex, TRUE);
|
|
gl_rect_2d_simple_tex( width, height );
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw a rectangle with the layer color to multiply the alpha by that color's alpha.
|
|
// Note: we're still using gGL.blendFunc( GL_DST_ALPHA, GL_ZERO );
|
|
if( colorp->mV[VW] != 1.f )
|
|
{
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4fv( colorp->mV );
|
|
gl_rect_2d_simple( width, height );
|
|
}
|
|
|
|
if (use_shaders)
|
|
{
|
|
gAlphaMaskProgram.setMinimumAlpha(0.004f);
|
|
}
|
|
|
|
LLGLSUIDefault gls_ui;
|
|
|
|
gGL.setColorMask(true, true);
|
|
|
|
if (success && !mMorphMasksValid && !mMaskedMorphs.empty())
|
|
{
|
|
LLCRC alpha_mask_crc;
|
|
const LLUUID& uuid = mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)getInfo()->mLocalTexture);
|
|
alpha_mask_crc.update((U8*)(&uuid.mData), UUID_BYTES);
|
|
|
|
for( alpha_list_t::iterator iter = mParamAlphaList.begin(); iter != mParamAlphaList.end(); iter++ )
|
|
{
|
|
LLTexLayerParamAlpha* param = *iter;
|
|
F32 param_weight = param->getWeight();
|
|
alpha_mask_crc.update((U8*)¶m_weight, sizeof(F32));
|
|
}
|
|
|
|
U32 cache_index = alpha_mask_crc.getCRC();
|
|
|
|
alpha_cache_t::iterator iter2 = mAlphaCache.find(cache_index);
|
|
U8* alpha_data;
|
|
if (iter2 != mAlphaCache.end())
|
|
{
|
|
alpha_data = iter2->second;
|
|
}
|
|
else
|
|
{
|
|
// clear out a slot if we have filled our cache
|
|
S32 max_cache_entries = getTexLayerSet()->getAvatar()->isSelf() ? 4 : 1;
|
|
while ((S32)mAlphaCache.size() >= max_cache_entries)
|
|
{
|
|
iter2 = mAlphaCache.begin(); // arbitrarily grab the first entry
|
|
alpha_data = iter2->second;
|
|
delete [] alpha_data;
|
|
mAlphaCache.erase(iter2);
|
|
}
|
|
alpha_data = new U8[width * height];
|
|
mAlphaCache[cache_index] = alpha_data;
|
|
glReadPixels(x, y, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, alpha_data);
|
|
}
|
|
|
|
getTexLayerSet()->getAvatar()->dirtyMesh();
|
|
|
|
mMorphMasksValid = TRUE;
|
|
|
|
for( morph_list_t::iterator iter3 = mMaskedMorphs.begin();
|
|
iter3 != mMaskedMorphs.end(); iter3++ )
|
|
{
|
|
LLVOAvatar::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++ )
|
|
{
|
|
LLVOAvatar::LLMaskedMorph* maskedMorph = &(*iter);
|
|
maskedMorph->mMorphTarget->applyMask(tex_data, width, height, num_components, maskedMorph->mInvert);
|
|
}
|
|
}
|
|
|
|
// Returns TRUE on success.
|
|
BOOL LLTexLayer::renderImageRaw( U8* in_data, S32 in_width, S32 in_height, S32 in_components, S32 width, S32 height, BOOL is_mask )
|
|
{
|
|
if (!in_data)
|
|
{
|
|
return FALSE;
|
|
}
|
|
GLenum format_options[4] = { GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA };
|
|
GLenum format = format_options[in_components-1];
|
|
if( is_mask )
|
|
{
|
|
llassert( 1 == in_components );
|
|
format = GL_ALPHA;
|
|
}
|
|
|
|
if( (in_width != SCRATCH_TEX_WIDTH) || (in_height != SCRATCH_TEX_HEIGHT) )
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
|
|
GLenum internal_format_options[4] = { GL_LUMINANCE8, GL_LUMINANCE8_ALPHA8, GL_RGB8, GL_RGBA8 };
|
|
GLenum internal_format = internal_format_options[in_components-1];
|
|
if( is_mask )
|
|
{
|
|
llassert( 1 == in_components );
|
|
internal_format = GL_ALPHA8;
|
|
}
|
|
|
|
U32 name = 0;
|
|
LLImageGL::generateTextures(1, &name );
|
|
stop_glerror();
|
|
|
|
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, name);
|
|
stop_glerror();
|
|
|
|
LLImageGL::setManualImage(
|
|
GL_TEXTURE_2D, 0, internal_format,
|
|
in_width, in_height,
|
|
format, GL_UNSIGNED_BYTE, in_data );
|
|
stop_glerror();
|
|
|
|
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
|
|
|
|
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
gl_rect_2d_simple_tex( width, height );
|
|
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
|
|
LLImageGL::deleteTextures(1, &name );
|
|
stop_glerror();
|
|
}
|
|
else
|
|
{
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
|
|
if( !mTexLayerSet->getAvatar()->bindScratchTexture(format) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, in_width, in_height, format, GL_UNSIGNED_BYTE, in_data );
|
|
stop_glerror();
|
|
|
|
gl_rect_2d_simple_tex( width, height );
|
|
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLTexLayer::requestUpdate()
|
|
{
|
|
mTexLayerSet->requestUpdate();
|
|
}
|
|
|
|
void LLTexLayer::addMaskedMorph(LLPolyMorphTarget* morph_target, BOOL invert)
|
|
{
|
|
mMaskedMorphs.push_front(LLVOAvatar::LLMaskedMorph(morph_target, invert));
|
|
}
|
|
|
|
void LLTexLayer::invalidateMorphMasks()
|
|
{
|
|
mMorphMasksValid = FALSE;
|
|
}
|
|
|
|
BOOL LLTexLayer::isVisibilityMask() const
|
|
{
|
|
return mInfo->mIsVisibilityMask;
|
|
}
|
|
|
|
BOOL LLTexLayer::isInvisibleAlphaMask()
|
|
{
|
|
const LLTexLayerInfo *info = getInfo();
|
|
|
|
if (info && info->mLocalTexture >= 0 && info->mLocalTexture < TEX_NUM_INDICES)
|
|
{
|
|
if (mTexLayerSet->getAvatar()->getLocalTextureID((ETextureIndex)info->mLocalTexture) == IMG_INVISIBLE)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return 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 );
|
|
|
|
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;
|
|
LLViewerTexture* tex = instance->mCachedProcessedTexture;
|
|
if( tex )
|
|
{
|
|
S32 bytes = (S32)tex->getWidth() * tex->getHeight() * tex->getComponents();
|
|
|
|
if( tex->hasGLTexture() )
|
|
{
|
|
*gl_bytes += bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LLTexLayerParamAlpha::LLTexLayerParamAlpha( LLTexLayer* layer )
|
|
:
|
|
mCachedProcessedTexture( 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
|
|
mCachedProcessedTexture = NULL;
|
|
mStaticImageRaw = NULL;
|
|
mNeedsCreateTexture = FALSE;
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::setWeight(F32 weight, BOOL set_by_user)
|
|
{
|
|
if (mIsAnimating)
|
|
{
|
|
return;
|
|
}
|
|
F32 min_weight = getMinWeight();
|
|
F32 max_weight = getMaxWeight();
|
|
F32 new_weight = llclamp(weight, min_weight, max_weight);
|
|
U8 cur_u8 = F32_to_U8( mCurWeight, min_weight, max_weight );
|
|
U8 new_u8 = F32_to_U8( new_weight, min_weight, max_weight );
|
|
if( cur_u8 != new_u8)
|
|
{
|
|
mCurWeight = new_weight;
|
|
|
|
LLVOAvatar* avatar = mTexLayer->getTexLayerSet()->getAvatar();
|
|
if( avatar->getSex() & getSex() )
|
|
{
|
|
if ( gAgentCamera.cameraCustomizeAvatar() )
|
|
{
|
|
set_by_user = FALSE;
|
|
}
|
|
avatar->invalidateComposite( mTexLayer->getTexLayerSet(), set_by_user );
|
|
mTexLayer->invalidateMorphMasks();
|
|
avatar->updateMeshTextures();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::setAnimationTarget(F32 target_value, BOOL set_by_user)
|
|
{
|
|
mTargetWeight = target_value;
|
|
setWeight(target_value, set_by_user);
|
|
mIsAnimating = TRUE;
|
|
if (mNext)
|
|
{
|
|
mNext->setAnimationTarget(target_value, set_by_user);
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::animate(F32 delta, BOOL set_by_user)
|
|
{
|
|
if (mNext)
|
|
{
|
|
mNext->animate(delta, set_by_user);
|
|
}
|
|
}
|
|
|
|
BOOL LLTexLayerParamAlpha::getSkip()
|
|
{
|
|
LLVOAvatar *avatar = mTexLayer->getTexLayerSet()->getAvatar();
|
|
|
|
if( getInfo()->mSkipIfZeroWeight )
|
|
{
|
|
F32 effective_weight = ( avatar->getSex() & getSex() ) ? mCurWeight : getDefaultWeight();
|
|
if (is_approx_zero( effective_weight ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
LLWearableType::EType type = (LLWearableType::EType)getWearableType();
|
|
if( (type != LLWearableType::WT_INVALID) && !avatar->isWearingWearableType( type ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL LLTexLayerParamAlpha::render( S32 x, S32 y, S32 width, S32 height )
|
|
{
|
|
BOOL success = TRUE;
|
|
|
|
F32 effective_weight = ( mTexLayer->getTexLayerSet()->getAvatar()->getSex() & getSex() ) ? mCurWeight : getDefaultWeight();
|
|
BOOL weight_changed = effective_weight != mCachedEffectiveWeight;
|
|
if( getSkip() )
|
|
{
|
|
return success;
|
|
}
|
|
|
|
gGL.flush();
|
|
if( getInfo()->mMultiplyBlend )
|
|
{
|
|
gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ZERO); // Multiplication: approximates a min() function
|
|
}
|
|
else
|
|
{
|
|
gGL.setSceneBlendType(LLRender::BT_ADD); // Addition: approximates a max() function
|
|
}
|
|
|
|
if( !getInfo()->mStaticImageFileName.empty() && !mStaticImageInvalid)
|
|
{
|
|
if( mStaticImageTGA.isNull() )
|
|
{
|
|
// Don't load the image file until we actually need it the first time. Like now.
|
|
mStaticImageTGA = gTexStaticImageList.getImageTGA( getInfo()->mStaticImageFileName );
|
|
// We now have something in one of our caches
|
|
LLTexLayerSet::sHasCaches |= mStaticImageTGA.notNull() ? TRUE : FALSE;
|
|
|
|
if( mStaticImageTGA.isNull() )
|
|
{
|
|
llwarns << "Unable to load static file: " << getInfo()->mStaticImageFileName << llendl;
|
|
mStaticImageInvalid = TRUE; // don't try again.
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
const S32 image_tga_width = mStaticImageTGA->getWidth();
|
|
const S32 image_tga_height = mStaticImageTGA->getHeight();
|
|
if( !mCachedProcessedTexture ||
|
|
(mCachedProcessedTexture->getWidth() != image_tga_width) ||
|
|
(mCachedProcessedTexture->getHeight() != image_tga_height) ||
|
|
(weight_changed) )
|
|
{
|
|
// llinfos << "Building Cached Alpha: " << mName << ": (" << mStaticImageRaw->getWidth() << ", " << mStaticImageRaw->getHeight() << ") " << effective_weight << llendl;
|
|
mCachedEffectiveWeight = effective_weight;
|
|
|
|
if( !mCachedProcessedTexture )
|
|
{
|
|
mCachedProcessedTexture = LLViewerTextureManager::getLocalTexture( image_tga_width, image_tga_height, 1, FALSE );
|
|
|
|
// We now have something in one of our caches
|
|
LLTexLayerSet::sHasCaches |= mCachedProcessedTexture ? TRUE : FALSE;
|
|
|
|
mCachedProcessedTexture->setExplicitFormat( GL_ALPHA8, GL_ALPHA );
|
|
}
|
|
|
|
// Applies domain and effective weight to data as it is decoded. Also resizes the raw image if needed.
|
|
mStaticImageRaw = NULL;
|
|
mStaticImageRaw = new LLImageRaw;
|
|
mStaticImageTGA->decodeAndProcess( mStaticImageRaw, getInfo()->mDomain, effective_weight );
|
|
mNeedsCreateTexture = TRUE;
|
|
}
|
|
|
|
if( mCachedProcessedTexture )
|
|
{
|
|
{
|
|
// Create the GL texture, and then hang onto it for future use.
|
|
if( mNeedsCreateTexture )
|
|
{
|
|
mCachedProcessedTexture->createGLTexture(0, mStaticImageRaw, 0, TRUE, LLViewerTexture::BOOST_AVATAR_SELF);
|
|
mNeedsCreateTexture = FALSE;
|
|
gGL.getTexUnit(0)->bind(mCachedProcessedTexture);
|
|
mCachedProcessedTexture->setAddressMode(LLTexUnit::TAM_CLAMP);
|
|
}
|
|
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
gGL.getTexUnit(0)->bind(mCachedProcessedTexture, TRUE);
|
|
gl_rect_2d_simple_tex( width, height );
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
stop_glerror();
|
|
}
|
|
}
|
|
|
|
// Don't keep the cache for other people's avatars
|
|
// (It's not really a "cache" in that case, but the logic is the same)
|
|
if( !mTexLayer->getTexLayerSet()->getAvatar()->isSelf() )
|
|
{
|
|
mCachedProcessedTexture = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f( 0.f, 0.f, 0.f, effective_weight );
|
|
gl_rect_2d_simple( width, height );
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexGlobalColorInfo
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LLTexGlobalColorInfo::LLTexGlobalColorInfo()
|
|
{
|
|
}
|
|
|
|
|
|
LLTexGlobalColorInfo::~LLTexGlobalColorInfo()
|
|
{
|
|
for_each(mColorInfoList.begin(), mColorInfoList.end(), DeletePointer());
|
|
}
|
|
|
|
BOOL LLTexGlobalColorInfo::parseXml(LLXmlTreeNode* node)
|
|
{
|
|
// name attribute
|
|
static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name");
|
|
if( !node->getFastAttributeString( name_string, mName ) )
|
|
{
|
|
llwarns << "<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;
|
|
}
|
|
|
|
std::string op_string;
|
|
static LLStdStringHandle operation_string = LLXmlTree::addAttributeString("operation");
|
|
if( param_color_node->getFastAttributeString( operation_string, op_string ) )
|
|
{
|
|
LLStringUtil::toLower(op_string);
|
|
if ( op_string == "add" ) mOperation = OP_ADD;
|
|
else if ( op_string == "multiply" ) mOperation = OP_MULTIPLY;
|
|
else if ( op_string == "blend" ) mOperation = OP_BLEND;
|
|
}
|
|
|
|
mNumColors = 0;
|
|
|
|
LLColor4U color4u;
|
|
for (LLXmlTreeNode* child = param_color_node->getChildByName( "value" );
|
|
child;
|
|
child = param_color_node->getNextNamedChild())
|
|
{
|
|
if( (mNumColors < MAX_COLOR_VALUES) )
|
|
{
|
|
static LLStdStringHandle color_string = LLXmlTree::addAttributeString("color");
|
|
if( child->getFastAttributeColor4U( color_string, color4u ) )
|
|
{
|
|
mColors[ mNumColors ].setVec(color4u);
|
|
mNumColors++;
|
|
}
|
|
}
|
|
}
|
|
if( !mNumColors )
|
|
{
|
|
llwarns << "<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()
|
|
:
|
|
mGLBytes( 0 ),
|
|
mTGABytes( 0 )
|
|
{}
|
|
|
|
LLTexStaticImageList::~LLTexStaticImageList()
|
|
{
|
|
deleteCachedImages();
|
|
}
|
|
|
|
void LLTexStaticImageList::dumpByteCount() const
|
|
{
|
|
llinfos << "Avatar Static Textures " <<
|
|
"KB GL:" << (mGLBytes / 1024) <<
|
|
"KB TGA:" << (mTGABytes / 1024) << "KB" << llendl;
|
|
}
|
|
|
|
void LLTexStaticImageList::deleteCachedImages()
|
|
{
|
|
if( mGLBytes || mTGABytes )
|
|
{
|
|
llinfos << "Clearing Static Textures " <<
|
|
"KB GL:" << (mGLBytes / 1024) <<
|
|
"KB TGA:" << (mTGABytes / 1024) << "KB" << llendl;
|
|
|
|
//mStaticImageLists uses LLPointers, clear() will cause deletion
|
|
|
|
mStaticImageListTGA.clear();
|
|
mStaticImageList.clear();
|
|
|
|
mGLBytes = 0;
|
|
mTGABytes = 0;
|
|
}
|
|
}
|
|
|
|
// Note: in general, for a given image image we'll call either getImageTga() or getImageGL().
|
|
// We call getImageTga() if the image is used as an alpha gradient.
|
|
// Otherwise, we call getImageGL()
|
|
|
|
// Returns an LLImageTGA that contains the encoded data from a tga file named file_name.
|
|
// Caches the result to speed identical subsequent requests.
|
|
LLImageTGA* LLTexStaticImageList::getImageTGA(const std::string& file_name)
|
|
{
|
|
const char *namekey = sImageNames.addString(file_name);
|
|
image_tga_map_t::iterator iter = mStaticImageListTGA.find(namekey);
|
|
if( iter != mStaticImageListTGA.end() )
|
|
{
|
|
return iter->second;
|
|
}
|
|
else
|
|
{
|
|
std::string path;
|
|
path = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,file_name);
|
|
LLPointer<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 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.
|
|
LLViewerTexture* LLTexStaticImageList::getTexture(const std::string& file_name, BOOL is_mask)
|
|
{
|
|
LLPointer<LLViewerTexture> tex;
|
|
const char *namekey = sImageNames.addString(file_name);
|
|
|
|
texture_map_t::const_iterator iter = mStaticImageList.find(namekey);
|
|
if( iter != mStaticImageList.end() )
|
|
{
|
|
tex = iter->second;
|
|
}
|
|
else
|
|
{
|
|
tex = LLViewerTextureManager::getLocalTexture( 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.
|
|
tex->setExplicitFormat( GL_ALPHA8, GL_ALPHA );
|
|
}
|
|
tex->createGLTexture(0, image_raw, 0, TRUE, LLViewerTexture::LOCAL);
|
|
|
|
gGL.getTexUnit(0)->bind(tex);
|
|
tex->setAddressMode(LLTexUnit::TAM_CLAMP);
|
|
|
|
mStaticImageList [ namekey ] = tex;
|
|
mGLBytes += (S32)tex->getWidth() * tex->getHeight() * tex->getComponents();
|
|
}
|
|
else
|
|
{
|
|
tex = NULL;
|
|
}
|
|
}
|
|
|
|
return tex;
|
|
}
|
|
|
|
// Reads a .tga file, decodes it, and puts the decoded data in image_raw.
|
|
// Returns TRUE if successful.
|
|
BOOL LLTexStaticImageList::loadImageRaw( const std::string& file_name, LLImageRaw* image_raw )
|
|
{
|
|
BOOL success = FALSE;
|
|
std::string path;
|
|
path = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,file_name);
|
|
LLPointer<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;
|
|
}
|
|
|