Files
SingularityViewer/indra/llrender/llimagegl.cpp
Shyotl e756140e1d Innitial commit of experimental v2 texture system port work. Compiles and runs on windows, at least. Fixing bugs as they come.
Need to test:
localassetbrowser
preview related floaters
hgfloatertexteditor
maps
media textures! Currently very hacky
web browser
alpha masks on avatars
bumpmaps
Are all sky components appearing?
LLViewerDynamicTexture (texture baking, browser, animated textures, anim previews, etc)
Snapshot related features
Customize avatar
vfs floater
UI textures in general
Texture priority issues
2011-03-31 03:22:01 -05:00

1935 lines
47 KiB
C++

/**
* @file llimagegl.cpp
* @brief Generic GL image handler
*
* $LicenseInfo:firstyear=2001&license=viewergpl$
*
* Copyright (c) 2001-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$
*/
// TODO: create 2 classes for images w/ and w/o discard levels?
#include "linden_common.h"
#include "llimagegl.h"
#include "llerror.h"
#include "llimage.h"
#include "llmath.h"
#include "llgl.h"
#include "llrender.h"
//----------------------------------------------------------------------------
const F32 MIN_TEXTURE_LIFETIME = 10.f;
//statics
LLGLuint LLImageGL::sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS] = { 0 };
U32 LLImageGL::sUniqueCount = 0;
U32 LLImageGL::sBindCount = 0;
S32 LLImageGL::sGlobalTextureMemoryInBytes = 0;
S32 LLImageGL::sBoundTextureMemoryInBytes = 0;
S32 LLImageGL::sCurBoundTextureMemory = 0;
S32 LLImageGL::sCount = 0;
std::list<U32> LLImageGL::sDeadTextureList;
BOOL LLImageGL::sGlobalUseAnisotropic = FALSE;
F32 LLImageGL::sLastFrameTime = 0.f;
BOOL LLImageGL::sAllowReadBackRaw = FALSE ;
LLImageGL* LLImageGL::sDefaultGLTexture = NULL ;
std::set<LLImageGL*> LLImageGL::sImageList;
//****************************************************************************************************
//The below for texture auditing use only
//****************************************************************************************************
//-----------------------
//debug use
BOOL gAuditTexture = FALSE ;
#define MAX_TEXTURE_LOG_SIZE 22 //2048 * 2048
std::vector<S32> LLImageGL::sTextureLoadedCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
std::vector<S32> LLImageGL::sTextureBoundCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
std::vector<S32> LLImageGL::sTextureCurBoundCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
S32 LLImageGL::sCurTexSizeBar = -1 ;
S32 LLImageGL::sCurTexPickSize = -1 ;
LLPointer<LLImageGL> LLImageGL::sHighlightTexturep = NULL;
S32 LLImageGL::sMaxCatagories = 1 ;
std::vector<S32> LLImageGL::sTextureMemByCategory;
std::vector<S32> LLImageGL::sTextureMemByCategoryBound ;
std::vector<S32> LLImageGL::sTextureCurMemByCategoryBound ;
//------------------------
//****************************************************************************************************
//End for texture auditing use only
//****************************************************************************************************
//**************************************************************************************
//below are functions for debug use
//do not delete them even though they are not currently being used.
void check_all_images()
{
for (std::set<LLImageGL*>::iterator iter = LLImageGL::sImageList.begin();
iter != LLImageGL::sImageList.end(); iter++)
{
LLImageGL* glimage = *iter;
if (glimage->getTexName() && glimage->isGLTextureCreated())
{
gGL.getTexUnit(0)->bind(glimage) ;
glimage->checkTexSize() ;
gGL.getTexUnit(0)->unbind(glimage->getTarget()) ;
}
}
}
void LLImageGL::checkTexSize(bool forced) const
{
if ((forced || gDebugGL) && mTarget == GL_TEXTURE_2D)
{
GLint texname;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &texname);
if (texname != mTexName)
{
llerrs << "Invalid texture bound!" << llendl;
}
stop_glerror() ;
LLGLint x = 0, y = 0 ;
glGetTexLevelParameteriv(mTarget, 0, GL_TEXTURE_WIDTH, (GLint*)&x);
glGetTexLevelParameteriv(mTarget, 0, GL_TEXTURE_HEIGHT, (GLint*)&y) ;
stop_glerror() ;
if(!x || !y)
{
return ;
}
if(x != (mWidth >> mCurrentDiscardLevel) || y != (mHeight >> mCurrentDiscardLevel))
{
llerrs << "wrong texture size and discard level!" << llendl ;
}
}
}
//end of debug functions
//**************************************************************************************
//----------------------------------------------------------------------------
BOOL is_little_endian()
{
S32 a = 0x12345678;
U8 *c = (U8*)(&a);
return (*c == 0x78) ;
}
//static
void LLImageGL::initClass(S32 num_catagories)
{
sMaxCatagories = num_catagories ;
sTextureMemByCategory.resize(sMaxCatagories);
sTextureMemByCategoryBound.resize(sMaxCatagories) ;
sTextureCurMemByCategoryBound.resize(sMaxCatagories) ;
}
//static
void LLImageGL::cleanupClass()
{
sTextureMemByCategory.clear() ;
sTextureMemByCategoryBound.clear() ;
sTextureCurMemByCategoryBound.clear() ;
}
//static
void LLImageGL::setHighlightTexture(S32 category)
{
const S32 dim = 128;
sHighlightTexturep = new LLImageGL() ;
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
U8* data = image_raw->getData();
for (S32 i = 0; i<dim; i++)
{
for (S32 j = 0; j<dim; j++)
{
const S32 border = 2;
if (i<border || j<border || i>=(dim-border) || j>=(dim-border))
{
*data++ = 0xff;
*data++ = 0xff;
*data++ = 0xff;
}
else
{
*data++ = 0xff;
*data++ = 0xff;
*data++ = 0x00;
}
}
}
sHighlightTexturep->createGLTexture(0, image_raw, 0, TRUE, category);
image_raw = NULL;
}
//static
S32 LLImageGL::dataFormatBits(S32 dataformat)
{
switch (dataformat)
{
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 4;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 8;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 8;
case GL_LUMINANCE: return 8;
case GL_ALPHA: return 8;
case GL_COLOR_INDEX: return 8;
case GL_LUMINANCE_ALPHA: return 16;
case GL_RGB: return 24;
case GL_RGB8: return 24;
case GL_RGBA: return 32;
case GL_BGRA: return 32; // Used for QuickTime media textures on the Mac
default:
llerrs << "LLImageGL::Unknown format: " << dataformat << llendl;
return 0;
}
}
//static
S32 LLImageGL::dataFormatBytes(S32 dataformat, S32 width, S32 height)
{
if (dataformat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT &&
dataformat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
{
if (width < 4) width = 4;
if (height < 4) height = 4;
}
S32 bytes ((width*height*dataFormatBits(dataformat)+7)>>3);
S32 aligned = (bytes+3)&~3;
return aligned;
}
//static
S32 LLImageGL::dataFormatComponents(S32 dataformat)
{
switch (dataformat)
{
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 3;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 4;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 4;
case GL_LUMINANCE: return 1;
case GL_ALPHA: return 1;
case GL_COLOR_INDEX: return 1;
case GL_LUMINANCE_ALPHA: return 2;
case GL_RGB: return 3;
case GL_RGBA: return 4;
case GL_BGRA: return 4; // Used for QuickTime media textures on the Mac
default:
llerrs << "LLImageGL::Unknown format: " << dataformat << llendl;
return 0;
}
}
//----------------------------------------------------------------------------
// static
void LLImageGL::updateStats(F32 current_time)
{
sLastFrameTime = current_time;
sBoundTextureMemoryInBytes = sCurBoundTextureMemory;
sCurBoundTextureMemory = 0;
if(gAuditTexture)
{
for(U32 i = 0 ; i < sTextureCurBoundCounter.size() ; i++)
{
sTextureBoundCounter[i] = sTextureCurBoundCounter[i] ;
sTextureCurBoundCounter[i] = 0 ;
}
for(U32 i = 0 ; i < sTextureCurMemByCategoryBound.size() ; i++)
{
sTextureMemByCategoryBound[i] = sTextureCurMemByCategoryBound[i] ;
sTextureCurMemByCategoryBound[i] = 0 ;
}
}
}
//static
S32 LLImageGL::updateBoundTexMem(const S32 mem, const S32 ncomponents, S32 category)
{
if(gAuditTexture && ncomponents > 0 && category > -1)
{
sTextureCurBoundCounter[getTextureCounterIndex(mem / ncomponents)]++ ;
sTextureCurMemByCategoryBound[category] += mem ;
}
LLImageGL::sCurBoundTextureMemory += mem ;
return LLImageGL::sCurBoundTextureMemory;
}
//----------------------------------------------------------------------------
//static
void LLImageGL::destroyGL(BOOL save_state)
{
for (S32 stage = 0; stage < gGLManager.mNumTextureUnits; stage++)
{
gGL.getTexUnit(stage)->unbind(LLTexUnit::TT_TEXTURE);
}
sAllowReadBackRaw = true ;
for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
iter != sImageList.end(); iter++)
{
LLImageGL* glimage = *iter;
if (glimage->mTexName)
{
if (save_state && glimage->isGLTextureCreated() && glimage->mComponents)
{
glimage->mSaveData = new LLImageRaw;
if(!glimage->readBackRaw(glimage->mCurrentDiscardLevel, glimage->mSaveData, false)) //necessary, keep it.
{
glimage->mSaveData = NULL ;
}
}
glimage->destroyGLTexture();
stop_glerror();
}
}
sAllowReadBackRaw = false ;
}
//static
void LLImageGL::restoreGL()
{
for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
iter != sImageList.end(); iter++)
{
LLImageGL* glimage = *iter;
if(glimage->getTexName())
{
llerrs << "tex name is not 0." << llendl ;
}
if (glimage->mSaveData.notNull())
{
if (glimage->getComponents() && glimage->mSaveData->getComponents())
{
glimage->createGLTexture(glimage->mCurrentDiscardLevel, glimage->mSaveData, 0, TRUE, glimage->getCategory());
stop_glerror();
}
glimage->mSaveData = NULL; // deletes data
}
}
}
//static
void LLImageGL::dirtyTexOptions()
{
for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
iter != sImageList.end(); iter++)
{
LLImageGL* glimage = *iter;
glimage->mTexOptionsDirty = true;
stop_glerror();
}
}
//----------------------------------------------------------------------------
//for server side use only.
//static
BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, BOOL usemipmaps)
{
dest = new LLImageGL(usemipmaps);
return TRUE;
}
//for server side use only.
BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, U32 width, U32 height, U8 components, BOOL usemipmaps)
{
dest = new LLImageGL(width, height, components, usemipmaps);
return TRUE;
}
//for server side use only.
BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, const LLImageRaw* imageraw, BOOL usemipmaps)
{
dest = new LLImageGL(imageraw, usemipmaps);
return TRUE;
}
//----------------------------------------------------------------------------
LLImageGL::LLImageGL(BOOL usemipmaps)
: mSaveData(0)
{
init(usemipmaps);
setSize(0, 0, 0);
sImageList.insert(this);
sCount++;
}
LLImageGL::LLImageGL(U32 width, U32 height, U8 components, BOOL usemipmaps)
: mSaveData(0)
{
llassert( components <= 4 );
init(usemipmaps);
setSize(width, height, components);
sImageList.insert(this);
sCount++;
}
LLImageGL::LLImageGL(const LLImageRaw* imageraw, BOOL usemipmaps)
: mSaveData(0)
{
init(usemipmaps);
setSize(0, 0, 0);
sImageList.insert(this);
sCount++;
createGLTexture(0, imageraw);
}
LLImageGL::~LLImageGL()
{
LLImageGL::cleanup();
sImageList.erase(this);
delete [] mPickMask;
mPickMask = NULL;
sCount--;
}
void LLImageGL::init(BOOL usemipmaps)
{
// keep these members in the same order as declared in llimagehl.h
// so that it is obvious by visual inspection if we forgot to
// init a field.
mTextureMemory = 0;
mLastBindTime = 0.f;
mPickMask = NULL;
mPickMaskWidth = 0;
mPickMaskHeight = 0;
mUseMipMaps = usemipmaps;
mHasExplicitFormat = FALSE;
mAutoGenMips = FALSE;
mIsMask = FALSE;
mNeedsAlphaAndPickMask = TRUE ;
mAlphaStride = 0 ;
mAlphaOffset = 0 ;
mGLTextureCreated = FALSE ;
mTexName = 0;
mWidth = 0;
mHeight = 0;
mCurrentDiscardLevel = -1;
mTarget = GL_TEXTURE_2D;
mBindTarget = LLTexUnit::TT_TEXTURE;
mHasMipMaps = false;
mIsResident = 0;
mComponents = 0;
mMaxDiscardLevel = MAX_DISCARD_LEVEL;
mTexOptionsDirty = true;
mAddressMode = LLTexUnit::TAM_WRAP;
mFilterOption = LLTexUnit::TFO_ANISOTROPIC;
mFormatInternal = -1;
mFormatPrimary = (LLGLenum) 0;
mFormatType = GL_UNSIGNED_BYTE;
mFormatSwapBytes = FALSE;
#ifdef DEBUG_MISS
mMissed = FALSE;
#endif
mCategory = -1;
}
void LLImageGL::cleanup()
{
if (!gGLManager.mIsDisabled)
{
destroyGLTexture();
}
mSaveData = NULL; // deletes data
}
//----------------------------------------------------------------------------
//this function is used to check the size of a texture image.
//so dim should be a positive number
static bool check_power_of_two(S32 dim)
{
if(dim < 0)
{
return false ;
}
if(!dim)//0 is a power-of-two number
{
return true ;
}
return !(dim & (dim - 1)) ;
}
//static
bool LLImageGL::checkSize(S32 width, S32 height)
{
return check_power_of_two(width) && check_power_of_two(height);
}
void LLImageGL::setSize(S32 width, S32 height, S32 ncomponents)
{
if (width != mWidth || height != mHeight || ncomponents != mComponents)
{
// Check if dimensions are a power of two!
if (!checkSize(width,height))
{
llerrs << llformat("Texture has non power of two dimension: %dx%d",width,height) << llendl;
}
if (mTexName)
{
// llwarns << "Setting Size of LLImageGL with existing mTexName = " << mTexName << llendl;
destroyGLTexture();
}
// pickmask validity depends on old image size, delete it
delete [] mPickMask;
mPickMask = NULL;
mPickMaskWidth = mPickMaskHeight = 0;
mWidth = width;
mHeight = height;
mComponents = ncomponents;
if (ncomponents > 0)
{
mMaxDiscardLevel = 0;
while (width > 1 && height > 1 && mMaxDiscardLevel < MAX_DISCARD_LEVEL)
{
mMaxDiscardLevel++;
width >>= 1;
height >>= 1;
}
}
else
{
mMaxDiscardLevel = MAX_DISCARD_LEVEL;
}
}
}
//----------------------------------------------------------------------------
// virtual
void LLImageGL::dump()
{
llinfos << "mMaxDiscardLevel " << S32(mMaxDiscardLevel)
<< " mLastBindTime " << mLastBindTime
<< " mTarget " << S32(mTarget)
<< " mBindTarget " << S32(mBindTarget)
<< " mUseMipMaps " << S32(mUseMipMaps)
<< " mHasMipMaps " << S32(mHasMipMaps)
<< " mCurrentDiscardLevel " << S32(mCurrentDiscardLevel)
<< " mFormatInternal " << S32(mFormatInternal)
<< " mFormatPrimary " << S32(mFormatPrimary)
<< " mFormatType " << S32(mFormatType)
<< " mFormatSwapBytes " << S32(mFormatSwapBytes)
<< " mHasExplicitFormat " << S32(mHasExplicitFormat)
#if DEBUG_MISS
<< " mMissed " << mMissed
#endif
<< llendl;
llinfos << " mTextureMemory " << mTextureMemory
<< " mTexNames " << mTexName
<< " mIsResident " << S32(mIsResident)
<< llendl;
}
//----------------------------------------------------------------------------
void LLImageGL::forceUpdateBindStats(void) const
{
mLastBindTime = sLastFrameTime;
}
BOOL LLImageGL::updateBindStats(S32 tex_mem) const
{
if (mTexName != 0)
{
#ifdef DEBUG_MISS
mMissed = ! getIsResident(TRUE);
#endif
sBindCount++;
if (mLastBindTime != sLastFrameTime)
{
// we haven't accounted for this texture yet this frame
sUniqueCount++;
updateBoundTexMem(tex_mem, mComponents, mCategory);
mLastBindTime = sLastFrameTime;
return TRUE ;
}
}
return FALSE ;
}
F32 LLImageGL::getTimePassedSinceLastBound()
{
return sLastFrameTime - mLastBindTime ;
}
void LLImageGL::setExplicitFormat( LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, BOOL swap_bytes )
{
// Note: must be called before createTexture()
// Note: it's up to the caller to ensure that the format matches the number of components.
mHasExplicitFormat = TRUE;
mFormatInternal = internal_format;
mFormatPrimary = primary_format;
if(type_format == 0)
mFormatType = GL_UNSIGNED_BYTE;
else
mFormatType = type_format;
mFormatSwapBytes = swap_bytes;
calcAlphaChannelOffsetAndStride() ;
}
//----------------------------------------------------------------------------
void LLImageGL::setImage(const LLImageRaw* imageraw)
{
llassert((imageraw->getWidth() == getWidth(mCurrentDiscardLevel)) &&
(imageraw->getHeight() == getHeight(mCurrentDiscardLevel)) &&
(imageraw->getComponents() == getComponents()));
const U8* rawdata = imageraw->getData();
setImage(rawdata, FALSE);
}
void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
{
// LLFastTimer t1(LLFastTimer::FTM_TEMP1);
bool is_compressed = false;
if (mFormatPrimary >= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT && mFormatPrimary <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
{
is_compressed = true;
}
// LLFastTimer t2(LLFastTimer::FTM_TEMP2);
gGL.getTexUnit(0)->bind(this);
if (mUseMipMaps)
{
// LLFastTimer t2(LLFastTimer::FTM_TEMP3);
if (data_hasmips)
{
// NOTE: data_in points to largest image; smaller images
// are stored BEFORE the largest image
for (S32 d=mCurrentDiscardLevel; d<=mMaxDiscardLevel; d++)
{
S32 w = getWidth(d);
S32 h = getHeight(d);
S32 gl_level = d-mCurrentDiscardLevel;
if (d > mCurrentDiscardLevel)
{
data_in -= dataFormatBytes(mFormatPrimary, w, h); // see above comment
}
if (is_compressed)
{
// LLFastTimer t2(LLFastTimer::FTM_TEMP4);
S32 tex_size = dataFormatBytes(mFormatPrimary, w, h);
glCompressedTexImage2DARB(mTarget, gl_level, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in);
stop_glerror();
}
else
{
// LLFastTimer t2(LLFastTimer::FTM_TEMP4);
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
stop_glerror();
}
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in);
if (gl_level == 0)
{
analyzeAlpha(data_in, w, h);
}
updatePickMask(w, h, data_in);
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
stop_glerror();
}
stop_glerror();
}
stop_glerror();
}
}
else if (!is_compressed)
{
if (mAutoGenMips)
{
glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_GENERATE_MIPMAP_SGIS, TRUE);
stop_glerror();
{
// LLFastTimer t2(LLFastTimer::FTM_TEMP4);
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
stop_glerror();
}
S32 w = getWidth(mCurrentDiscardLevel);
S32 h = getHeight(mCurrentDiscardLevel);
LLImageGL::setManualImage(mTarget, 0, mFormatInternal,
w, h,
mFormatPrimary, mFormatType,
data_in);
analyzeAlpha(data_in, w, h);
stop_glerror();
updatePickMask(w, h, data_in);
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
stop_glerror();
}
}
}
else
{
// Create mips by hand
// about 30% faster than autogen on ATI 9800, 50% slower on nVidia 4800
// ~4x faster than gluBuild2DMipmaps
S32 width = getWidth(mCurrentDiscardLevel);
S32 height = getHeight(mCurrentDiscardLevel);
S32 nummips = mMaxDiscardLevel - mCurrentDiscardLevel + 1;
S32 w = width, h = height;
const U8* prev_mip_data = 0;
const U8* cur_mip_data = 0;
S32 prev_mip_size = 0;
S32 cur_mip_size = 0;
for (int m=0; m<nummips; m++)
{
if (m==0)
{
cur_mip_data = data_in;
cur_mip_size = width * height * mComponents;
}
else
{
S32 bytes = w * h * mComponents;
llassert(prev_mip_data);
llassert(prev_mip_size == bytes*4);
U8* new_data = new U8[bytes];
llassert_always(new_data);
LLImageBase::generateMip(prev_mip_data, new_data, w, h, mComponents);
cur_mip_data = new_data;
cur_mip_size = bytes;
}
llassert(w > 0 && h > 0 && cur_mip_data);
{
// LLFastTimer t1(LLFastTimer::FTM_TEMP4);
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
stop_glerror();
}
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data);
if (m == 0)
{
analyzeAlpha(data_in, w, h);
}
stop_glerror();
if (m == 0)
{
updatePickMask(w, h, cur_mip_data);
}
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
stop_glerror();
}
}
if (prev_mip_data && prev_mip_data != data_in)
{
delete[] prev_mip_data;
}
prev_mip_data = cur_mip_data;
prev_mip_size = cur_mip_size;
w >>= 1;
h >>= 1;
}
if (prev_mip_data && prev_mip_data != data_in)
{
delete[] prev_mip_data;
prev_mip_data = NULL;
}
}
}
else
{
llerrs << "Compressed Image has mipmaps but data does not (can not auto generate compressed mips)" << llendl;
}
mHasMipMaps = true;
}
else
{
// LLFastTimer t2(LLFastTimer::FTM_TEMP5);
S32 w = getWidth();
S32 h = getHeight();
if (is_compressed)
{
S32 tex_size = dataFormatBytes(mFormatPrimary, w, h);
glCompressedTexImage2DARB(mTarget, 0, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in);
stop_glerror();
}
else
{
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
stop_glerror();
}
LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h,
mFormatPrimary, mFormatType, (GLvoid *)data_in);
analyzeAlpha(data_in, w, h);
updatePickMask(w, h, data_in);
stop_glerror();
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
stop_glerror();
}
}
mHasMipMaps = false;
}
stop_glerror();
mGLTextureCreated = true;
}
BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update)
{
if (!width || !height)
{
return TRUE;
}
if (mTexName == 0)
{
llwarns << "Setting subimage on image without GL texture" << llendl;
return FALSE;
}
if (datap == NULL)
{
llwarns << "Setting subimage on image with NULL datap" << llendl;
return FALSE;
}
// HACK: allow the caller to explicitly force the fast path (i.e. using glTexSubImage2D here instead of calling setImage) even when updating the full texture.
if (!force_fast_update && x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight() && data_width == width && data_height == height)
{
setImage(datap, FALSE);
}
else
{
if (mUseMipMaps)
{
dump();
llerrs << "setSubImage called with mipmapped image (not supported)" << llendl;
}
llassert_always(mCurrentDiscardLevel == 0);
llassert_always(x_pos >= 0 && y_pos >= 0);
if (((x_pos + width) > getWidth()) ||
(y_pos + height) > getHeight())
{
dump();
llerrs << "Subimage not wholly in target image!"
<< " x_pos " << x_pos
<< " y_pos " << y_pos
<< " width " << width
<< " height " << height
<< " getWidth() " << getWidth()
<< " getHeight() " << getHeight()
<< llendl;
}
if ((x_pos + width) > data_width ||
(y_pos + height) > data_height)
{
dump();
llerrs << "Subimage not wholly in source image!"
<< " x_pos " << x_pos
<< " y_pos " << y_pos
<< " width " << width
<< " height " << height
<< " source_width " << data_width
<< " source_height " << data_height
<< llendl;
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, data_width);
stop_glerror();
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
stop_glerror();
}
datap += (y_pos * data_width + x_pos) * getComponents();
// Update the GL texture
BOOL res = gGL.getTexUnit(0)->bindManual(mBindTarget, mTexName);
if (!res) llerrs << "LLImageGL::setSubImage(): bindTexture failed" << llendl;
stop_glerror();
glTexSubImage2D(mTarget, 0, x_pos, y_pos,
width, height, mFormatPrimary, mFormatType, datap);
gGL.getTexUnit(0)->disable();
stop_glerror();
if(mFormatSwapBytes)
{
glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
stop_glerror();
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
stop_glerror();
mGLTextureCreated = true;
}
return TRUE;
}
BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update)
{
return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height, force_fast_update);
}
// Copy sub image from frame buffer
BOOL LLImageGL::setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height)
{
if (gGL.getTexUnit(0)->bind(this, false, true))
{
//checkTexSize() ;
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, fb_x, fb_y, x_pos, y_pos, width, height);
mGLTextureCreated = true;
stop_glerror();
return TRUE;
}
else
{
return FALSE;
}
}
// static
void LLImageGL::generateTextures(S32 numTextures, U32 *textures)
{
glGenTextures(numTextures, (GLuint*)textures);
}
// static
void LLImageGL::deleteTextures(S32 numTextures, U32 *textures)
{
for (S32 i = 0; i < numTextures; i++)
{
sDeadTextureList.push_back(textures[i]);
}
}
// static
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels)
{
glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, pixels);
}
//create an empty GL texture: just create a texture name
//the texture is assiciate with some image by calling glTexImage outside LLImageGL
BOOL LLImageGL::createGLTexture()
{
if (gGLManager.mIsDisabled)
{
llwarns << "Trying to create a texture while GL is disabled!" << llendl;
return FALSE;
}
mGLTextureCreated = false ; //do not save this texture when gl is destroyed.
llassert(gGLManager.mInited);
stop_glerror();
if(mTexName)
{
glDeleteTextures(1, (reinterpret_cast<GLuint*>(&mTexName))) ;
}
glGenTextures(1, (GLuint*)&mTexName);
stop_glerror();
if (!mTexName)
{
llerrs << "LLImageGL::createGLTexture failed to make an empty texture" << llendl;
}
return TRUE ;
}
BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename/*=0*/, BOOL to_create, S32 category)
{
if (gGLManager.mIsDisabled)
{
llwarns << "Trying to create a texture while GL is disabled!" << llendl;
return FALSE;
}
mGLTextureCreated = false ;
llassert(gGLManager.mInited);
stop_glerror();
if (discard_level < 0)
{
llassert(mCurrentDiscardLevel >= 0);
discard_level = mCurrentDiscardLevel;
}
discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel);
// Actual image width/height = raw image width/height * 2^discard_level
S32 raw_w = imageraw->getWidth() ;
S32 raw_h = imageraw->getHeight() ;
S32 w = raw_w << discard_level;
S32 h = raw_h << discard_level;
// setSize may call destroyGLTexture if the size does not match
setSize(w, h, imageraw->getComponents());
if( !mHasExplicitFormat )
{
switch (mComponents)
{
case 1:
// Use luminance alpha (for fonts)
mFormatInternal = GL_LUMINANCE8;
mFormatPrimary = GL_LUMINANCE;
mFormatType = GL_UNSIGNED_BYTE;
break;
case 2:
// Use luminance alpha (for fonts)
mFormatInternal = GL_LUMINANCE8_ALPHA8;
mFormatPrimary = GL_LUMINANCE_ALPHA;
mFormatType = GL_UNSIGNED_BYTE;
break;
case 3:
mFormatInternal = GL_RGB8;
mFormatPrimary = GL_RGB;
mFormatType = GL_UNSIGNED_BYTE;
break;
case 4:
mFormatInternal = GL_RGBA8;
mFormatPrimary = GL_RGBA;
mFormatType = GL_UNSIGNED_BYTE;
break;
default:
LL_DEBUGS("Openjpeg") << "Bad number of components for texture: " << (U32)getComponents() << LL_ENDL;
to_create = false;
break;
}
calcAlphaChannelOffsetAndStride() ;
}
if(!to_create) //not create a gl texture
{
destroyGLTexture();
mCurrentDiscardLevel = discard_level;
mLastBindTime = sLastFrameTime;
return TRUE ;
}
setCategory(category) ;
const U8* rawdata = imageraw->getData();
return createGLTexture(discard_level, rawdata, FALSE, usename);
}
BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename)
{
llassert(data_in);
if (discard_level < 0)
{
llassert(mCurrentDiscardLevel >= 0);
discard_level = mCurrentDiscardLevel;
}
discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel);
if (mTexName != 0 && discard_level == mCurrentDiscardLevel)
{
// This will only be true if the size has not changed
setImage(data_in, data_hasmips);
return TRUE;
}
U32 old_name = mTexName;
// S32 old_discard = mCurrentDiscardLevel;
if (usename != 0)
{
mTexName = usename;
}
else
{
LLImageGL::generateTextures(1, &mTexName);
stop_glerror();
{
// LLFastTimer t1(LLFastTimer::FTM_TEMP6);
llverify(gGL.getTexUnit(0)->bind(this));
glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_MAX_LEVEL, mMaxDiscardLevel-discard_level);
}
}
if (!mTexName)
{
llerrs << "LLImageGL::createGLTexture failed to make texture" << llendl;
}
if (mUseMipMaps)
{
mAutoGenMips = gGLManager.mHasMipMapGeneration;
}
mCurrentDiscardLevel = discard_level;
setImage(data_in, data_hasmips);
// Set texture options to our defaults.
gGL.getTexUnit(0)->setHasMipMaps(mHasMipMaps);
gGL.getTexUnit(0)->setTextureAddressMode(mAddressMode);
gGL.getTexUnit(0)->setTextureFilteringOption(mFilterOption);
// things will break if we don't unbind after creation
gGL.getTexUnit(0)->unbind(mBindTarget);
stop_glerror();
if (old_name != 0)
{
sGlobalTextureMemoryInBytes -= mTextureMemory;
if(gAuditTexture)
{
decTextureCounter(mTextureMemory, mComponents, mCategory) ;
}
LLImageGL::deleteTextures(1, &old_name);
stop_glerror();
}
mTextureMemory = getMipBytes(discard_level);
sGlobalTextureMemoryInBytes += mTextureMemory;
if(gAuditTexture)
{
incTextureCounter(mTextureMemory, mComponents, mCategory) ;
}
// mark this as bound at this point, so we don't throw it out immediately
mLastBindTime = sLastFrameTime;
return TRUE;
}
#if 0
BOOL LLImageGL::setDiscardLevel(S32 discard_level)
{
llassert(discard_level >= 0);
llassert(mCurrentDiscardLevel >= 0);
discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel);
if (mDontDiscard)
{
// don't discard!
return FALSE;
}
else if (discard_level == mCurrentDiscardLevel)
{
// nothing to do
return FALSE;
}
else if (discard_level < mCurrentDiscardLevel)
{
// larger image
dump();
llerrs << "LLImageGL::setDiscardLevel() called with larger discard level; use createGLTexture()" << llendl;
return FALSE;
}
else if (mUseMipMaps)
{
LLPointer<LLImageRaw> imageraw = new LLImageRaw;
while(discard_level > mCurrentDiscardLevel)
{
if (readBackRaw(discard_level, imageraw, false))
{
break;
}
discard_level--;
}
if (discard_level == mCurrentDiscardLevel)
{
// unable to increase the discard level
return FALSE;
}
return createGLTexture(discard_level, imageraw);
}
else
{
#if !LL_LINUX && !LL_SOLARIS
// *FIX: This should not be skipped for the linux client.
llerrs << "LLImageGL::setDiscardLevel() called on image without mipmaps" << llendl;
#endif
return FALSE;
}
}
#endif
BOOL LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok)
{
// VWR-13505 : Merov : Allow gl texture read back so save texture works again (temporary)
//llassert_always(sAllowReadBackRaw) ;
//llerrs << "should not call this function!" << llendl ;
if (discard_level < 0)
{
discard_level = mCurrentDiscardLevel;
}
if (mTexName == 0 || discard_level < mCurrentDiscardLevel || discard_level > mMaxDiscardLevel )
{
return FALSE;
}
S32 gl_discard = discard_level - mCurrentDiscardLevel;
//explicitly unbind texture
gGL.getTexUnit(0)->unbind(mBindTarget);
llverify(gGL.getTexUnit(0)->bindManual(mBindTarget, mTexName));
//debug code, leave it there commented.
//checkTexSize() ;
LLGLint glwidth = 0;
glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_WIDTH, (GLint*)&glwidth);
if (glwidth == 0)
{
// No mip data smaller than current discard level
return FALSE;
}
S32 width = getWidth(discard_level);
S32 height = getHeight(discard_level);
S32 ncomponents = getComponents();
if (ncomponents == 0)
{
return FALSE;
}
if(width < glwidth)
{
llwarns << "texture size is smaller than it should be." << llendl ;
llwarns << "width: " << width << " glwidth: " << glwidth << " mWidth: " << mWidth <<
" mCurrentDiscardLevel: " << (S32)mCurrentDiscardLevel << " discard_level: " << (S32)discard_level << llendl ;
return FALSE ;
}
if (width <= 0 || width > 2048 || height <= 0 || height > 2048 || ncomponents < 1 || ncomponents > 4)
{
llerrs << llformat("LLImageGL::readBackRaw: bogus params: %d x %d x %d",width,height,ncomponents) << llendl;
}
LLGLint is_compressed = 0;
if (compressed_ok)
{
glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed);
}
//-----------------------------------------------------------------------------------------------
GLenum error ;
while((error = glGetError()) != GL_NO_ERROR)
{
llwarns << "GL Error happens before reading back texture. Error code: " << error << llendl ;
}
//-----------------------------------------------------------------------------------------------
if (is_compressed)
{
LLGLint glbytes;
glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes);
if(!imageraw->allocateDataSize(width, height, ncomponents, glbytes))
{
llwarns << "Memory allocation failed for reading back texture. Size is: " << glbytes << llendl ;
llwarns << "width: " << width << "height: " << height << "components: " << ncomponents << llendl ;
return FALSE ;
}
glGetCompressedTexImageARB(mTarget, gl_discard, (GLvoid*)(imageraw->getData()));
//stop_glerror();
}
else
{
if(!imageraw->allocateDataSize(width, height, ncomponents))
{
llwarns << "Memory allocation failed for reading back texture." << llendl ;
llwarns << "width: " << width << "height: " << height << "components: " << ncomponents << llendl ;
return FALSE ;
}
glGetTexImage(GL_TEXTURE_2D, gl_discard, mFormatPrimary, mFormatType, (GLvoid*)(imageraw->getData()));
//stop_glerror();
}
//-----------------------------------------------------------------------------------------------
if((error = glGetError()) != GL_NO_ERROR)
{
llwarns << "GL Error happens after reading back texture. Error code: " << error << llendl ;
imageraw->deleteData() ;
while((error = glGetError()) != GL_NO_ERROR)
{
llwarns << "GL Error happens after reading back texture. Error code: " << error << llendl ;
}
return FALSE ;
}
//-----------------------------------------------------------------------------------------------
return TRUE ;
}
void LLImageGL::deleteDeadTextures()
{
while (!sDeadTextureList.empty())
{
GLuint tex = sDeadTextureList.front();
sDeadTextureList.pop_front();
for (int i = 0; i < gGLManager.mNumTextureUnits; i++)
{
if (sCurrentBoundTextures[i] == tex)
{
gGL.getTexUnit(i)->unbind(LLTexUnit::TT_TEXTURE);
stop_glerror();
}
}
glDeleteTextures(1, &tex);
stop_glerror();
}
}
void LLImageGL::destroyGLTexture()
{
if (mTexName != 0)
{
if(mTextureMemory)
{
if(gAuditTexture)
{
decTextureCounter(mTextureMemory, mComponents, mCategory) ;
}
sGlobalTextureMemoryInBytes -= mTextureMemory;
mTextureMemory = 0;
}
LLImageGL::deleteTextures(1, &mTexName);
mTexName = 0;
mCurrentDiscardLevel = -1 ; //invalidate mCurrentDiscardLevel.
mGLTextureCreated = FALSE ;
}
}
//----------------------------------------------------------------------------
void LLImageGL::setAddressMode(LLTexUnit::eTextureAddressMode mode)
{
if (mAddressMode != mode)
{
mTexOptionsDirty = true;
mAddressMode = mode;
}
if (gGL.getTexUnit(gGL.getCurrentTexUnitIndex())->getCurrTexture() == mTexName)
{
gGL.getTexUnit(gGL.getCurrentTexUnitIndex())->setTextureAddressMode(mode);
mTexOptionsDirty = false;
}
}
void LLImageGL::setFilteringOption(LLTexUnit::eTextureFilterOptions option)
{
if (mFilterOption != option)
{
mTexOptionsDirty = true;
mFilterOption = option;
}
if (mTexName != 0 && gGL.getTexUnit(gGL.getCurrentTexUnitIndex())->getCurrTexture() == mTexName)
{
gGL.getTexUnit(gGL.getCurrentTexUnitIndex())->setTextureFilteringOption(option);
mTexOptionsDirty = false;
stop_glerror();
}
}
BOOL LLImageGL::getIsResident(BOOL test_now)
{
if (test_now)
{
if (mTexName != 0)
{
glAreTexturesResident(1, (GLuint*)&mTexName, &mIsResident);
}
else
{
mIsResident = FALSE;
}
}
return mIsResident;
}
S32 LLImageGL::getHeight(S32 discard_level) const
{
if (discard_level < 0)
{
discard_level = mCurrentDiscardLevel;
}
S32 height = mHeight >> discard_level;
if (height < 1) height = 1;
return height;
}
S32 LLImageGL::getWidth(S32 discard_level) const
{
if (discard_level < 0)
{
discard_level = mCurrentDiscardLevel;
}
S32 width = mWidth >> discard_level;
if (width < 1) width = 1;
return width;
}
S32 LLImageGL::getBytes(S32 discard_level) const
{
if (discard_level < 0)
{
discard_level = mCurrentDiscardLevel;
}
S32 w = mWidth>>discard_level;
S32 h = mHeight>>discard_level;
if (w == 0) w = 1;
if (h == 0) h = 1;
return dataFormatBytes(mFormatPrimary, w, h);
}
S32 LLImageGL::getMipBytes(S32 discard_level) const
{
if (discard_level < 0)
{
discard_level = mCurrentDiscardLevel;
}
S32 w = mWidth>>discard_level;
S32 h = mHeight>>discard_level;
S32 res = dataFormatBytes(mFormatPrimary, w, h);
if (mUseMipMaps)
{
while (w > 1 && h > 1)
{
w >>= 1; if (w == 0) w = 1;
h >>= 1; if (h == 0) h = 1;
res += dataFormatBytes(mFormatPrimary, w, h);
}
}
return res;
}
BOOL LLImageGL::isJustBound() const
{
return (BOOL)(sLastFrameTime - mLastBindTime < 0.5f);
}
BOOL LLImageGL::getBoundRecently() const
{
return (BOOL)(sLastFrameTime - mLastBindTime < MIN_TEXTURE_LIFETIME);
}
void LLImageGL::setTarget(const LLGLenum target, const LLTexUnit::eTextureType bind_target)
{
mTarget = target;
mBindTarget = bind_target;
}
//Used by media in V2
const S8 INVALID_OFFSET = -99 ;
void LLImageGL::setNeedsAlphaAndPickMask(BOOL need_mask)
{
if(mNeedsAlphaAndPickMask != need_mask)
{
mNeedsAlphaAndPickMask = need_mask;
if(mNeedsAlphaAndPickMask)
{
mAlphaOffset = 0 ;
}
else //do not need alpha mask
{
mAlphaOffset = INVALID_OFFSET ;
mIsMask = FALSE;
}
}
}
void LLImageGL::calcAlphaChannelOffsetAndStride()
{
if(mAlphaOffset == INVALID_OFFSET)//do not need alpha mask
{
return ;
}
mAlphaStride = -1 ;
switch (mFormatPrimary)
{
case GL_LUMINANCE:
case GL_ALPHA:
mAlphaStride = 1;
break;
case GL_LUMINANCE_ALPHA:
mAlphaStride = 2;
break;
case GL_RGB:
mNeedsAlphaAndPickMask = FALSE ;
mIsMask = FALSE;
return ; //no alpha channel.
case GL_RGBA:
mAlphaStride = 4;
break;
case GL_BGRA_EXT:
mAlphaStride = 4;
break;
default:
break;
}
mAlphaOffset = -1 ;
if (mFormatType == GL_UNSIGNED_BYTE)
{
mAlphaOffset = mAlphaStride - 1 ;
}
else if(is_little_endian())
{
if (mFormatType == GL_UNSIGNED_INT_8_8_8_8)
{
mAlphaOffset = 0 ;
}
else if (mFormatType == GL_UNSIGNED_INT_8_8_8_8_REV)
{
mAlphaOffset = 3 ;
}
}
else //big endian
{
if (mFormatType == GL_UNSIGNED_INT_8_8_8_8)
{
mAlphaOffset = 3 ;
}
else if (mFormatType == GL_UNSIGNED_INT_8_8_8_8_REV)
{
mAlphaOffset = 0 ;
}
}
if( mAlphaStride < 1 || //unsupported format
mAlphaOffset < 0 || //unsupported type
(mFormatPrimary == GL_BGRA_EXT && mFormatType != GL_UNSIGNED_BYTE)) //unknown situation
{
llwarns << "Cannot analyze alpha for image with format type " << std::hex << mFormatType << std::dec << llendl;
mNeedsAlphaAndPickMask = FALSE ;
mIsMask = FALSE;
}
}
void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
{
if(!mNeedsAlphaAndPickMask)
{
return ;
}
U32 length = w * h;
U32 alphatotal = 0;
U32 sample[16];
memset(sample, 0, sizeof(U32)*16);
// generate histogram of quantized alpha.
// also add-in the histogram of a 2x2 box-sampled version. The idea is
// this will mid-skew the data (and thus increase the chances of not
// being used as a mask) from high-frequency alpha maps which
// suffer the worst from aliasing when used as alpha masks.
if (w >= 2 && h >= 2)
{
llassert(w%2 == 0);
llassert(h%2 == 0);
const GLubyte* rowstart = ((const GLubyte*) data_in) + mAlphaOffset;
for (U32 y = 0; y < h; y+=2)
{
const GLubyte* current = rowstart;
for (U32 x = 0; x < w; x+=2)
{
const U32 s1 = current[0];
alphatotal += s1;
const U32 s2 = current[w * mAlphaStride];
alphatotal += s2;
current += mAlphaStride;
const U32 s3 = current[0];
alphatotal += s3;
const U32 s4 = current[w * mAlphaStride];
alphatotal += s4;
current += mAlphaStride;
++sample[s1/16];
++sample[s2/16];
++sample[s3/16];
++sample[s4/16];
const U32 asum = (s1+s2+s3+s4);
alphatotal += asum;
sample[asum/(16*4)] += 4;
}
rowstart += 2 * w * mAlphaStride;
}
length *= 2; // we sampled everything twice, essentially
}
else
{
const GLubyte* current = ((const GLubyte*) data_in) + mAlphaOffset;
for (U32 i = 0; i < length; i++)
{
const U32 s1 = *current;
alphatotal += s1;
++sample[s1/16];
current += mAlphaStride;
}
}
// if more than 1/16th of alpha samples are mid-range, this
// shouldn't be treated as a 1-bit mask
// also, if all of the alpha samples are clumped on one half
// of the range (but not at an absolute extreme), then consider
// this to be an intentional effect and don't treat as a mask.
U32 midrangetotal = 0;
for (U32 i = 4; i < 11; i++)
{
midrangetotal += sample[i];
}
U32 lowerhalftotal = 0;
for (U32 i = 0; i < 8; i++)
{
lowerhalftotal += sample[i];
}
U32 upperhalftotal = 0;
for (U32 i = 8; i < 16; i++)
{
upperhalftotal += sample[i];
}
if (midrangetotal > length/16 || // lots of midrange, or
(lowerhalftotal == length && alphatotal != 0) || // all close to transparent but not all totally transparent, or
(upperhalftotal == length && alphatotal != 255*length)) // all close to opaque but not all totally opaque
{
mIsMask = FALSE; // not suitable for masking
}
else
{
mIsMask = TRUE;
}
}
//----------------------------------------------------------------------------
void LLImageGL::updatePickMask(S32 width, S32 height, const U8* data_in)
{
if(!mNeedsAlphaAndPickMask)
{
return ;
}
delete [] mPickMask;
mPickMask = NULL;
mPickMaskWidth = mPickMaskHeight = 0;
if (mFormatType != GL_UNSIGNED_BYTE ||
mFormatPrimary != GL_RGBA)
{
//cannot generate a pick mask for this texture
return;
}
U32 pick_width = width/2 + 1;
U32 pick_height = height/2 + 1;
U32 size = pick_width * pick_height;
size = (size + 7) / 8; // pixelcount-to-bits
mPickMask = new U8[size];
mPickMaskWidth = pick_width - 1;
mPickMaskHeight = pick_height - 1;
memset(mPickMask, 0, sizeof(U8) * size);
U32 pick_bit = 0;
for (S32 y = 0; y < height; y += 2)
{
for (S32 x = 0; x < width; x += 2)
{
U8 alpha = data_in[(y*width+x)*4+3];
if (alpha > 32)
{
U32 pick_idx = pick_bit/8;
U32 pick_offset = pick_bit%8;
llassert(pick_idx < size);
mPickMask[pick_idx] |= 1 << pick_offset;
}
++pick_bit;
}
}
}
BOOL LLImageGL::getMask(const LLVector2 &tc)
{
BOOL res = TRUE;
if (mPickMask)
{
F32 u,v;
if (LL_LIKELY(tc.isFinite()))
{
u = tc.mV[0] - floorf(tc.mV[0]);
v = tc.mV[1] - floorf(tc.mV[1]);
}
else
{
LL_WARNS_ONCE("render") << "Ugh, non-finite u/v in mask pick" << LL_ENDL;
u = v = 0.f;
// removing assert per EXT-4388
// llassert(false);
}
if (LL_UNLIKELY(u < 0.f || u > 1.f ||
v < 0.f || v > 1.f))
{
LL_WARNS_ONCE("render") << "Ugh, u/v out of range in image mask pick" << LL_ENDL;
u = v = 0.f;
// removing assert per EXT-4388
// llassert(false);
}
S32 x = llfloor(u * mPickMaskWidth);
S32 y = llfloor(v * mPickMaskHeight);
if (LL_UNLIKELY(x > mPickMaskWidth))
{
LL_WARNS_ONCE("render") << "Ooh, width overrun on pick mask read, that coulda been bad." << LL_ENDL;
x = llmax((U16)0, mPickMaskWidth);
}
if (LL_UNLIKELY(y > mPickMaskHeight))
{
LL_WARNS_ONCE("render") << "Ooh, height overrun on pick mask read, that woulda been bad." << LL_ENDL;
y = llmax((U16)0, mPickMaskHeight);
}
S32 idx = y*mPickMaskWidth+x;
S32 offset = idx%8;
res = mPickMask[idx/8] & (1 << offset) ? TRUE : FALSE;
}
return res;
}
void LLImageGL::setCategory(S32 category)
{
if(!gAuditTexture)
{
return ;
}
if(mCategory != category)
{
if(mCategory > -1)
{
sTextureMemByCategory[mCategory] -= mTextureMemory ;
}
if(category > -1 && category < sMaxCatagories)
{
sTextureMemByCategory[category] += mTextureMemory ;
mCategory = category;
}
else
{
mCategory = -1 ;
}
}
}
//for debug use
//val is a "power of two" number
S32 LLImageGL::getTextureCounterIndex(U32 val)
{
//index range is [0, MAX_TEXTURE_LOG_SIZE].
if(val < 2)
{
return 0 ;
}
else if(val >= (1 << MAX_TEXTURE_LOG_SIZE))
{
return MAX_TEXTURE_LOG_SIZE ;
}
else
{
S32 ret = 0 ;
while(val >>= 1)
{
++ret;
}
return ret ;
}
}
//static
void LLImageGL::incTextureCounter(U32 val, S32 ncomponents, S32 category)
{
sTextureLoadedCounter[getTextureCounterIndex(val)]++ ;
sTextureMemByCategory[category] += (S32)val * ncomponents ;
}
//static
void LLImageGL::decTextureCounter(U32 val, S32 ncomponents, S32 category)
{
sTextureLoadedCounter[getTextureCounterIndex(val)]-- ;
sTextureMemByCategory[category] += (S32)val * ncomponents ;
}
void LLImageGL::setCurTexSizebar(S32 index, BOOL set_pick_size)
{
sCurTexSizeBar = index ;
if(set_pick_size)
{
sCurTexPickSize = (1 << index) ;
}
else
{
sCurTexPickSize = -1 ;
}
}
void LLImageGL::resetCurTexSizebar()
{
sCurTexSizeBar = -1 ;
sCurTexPickSize = -1 ;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
// Manual Mip Generation
/*
S32 width = getWidth(discard_level);
S32 height = getHeight(discard_level);
S32 w = width, h = height;
S32 nummips = 1;
while (w > 4 && h > 4)
{
w >>= 1; h >>= 1;
nummips++;
}
stop_glerror();
w = width, h = height;
const U8* prev_mip_data = 0;
const U8* cur_mip_data = 0;
for (int m=0; m<nummips; m++)
{
if (m==0)
{
cur_mip_data = rawdata;
}
else
{
S32 bytes = w * h * mComponents;
U8* new_data = new U8[bytes];
LLImageBase::generateMip(prev_mip_data, new_data, w, h, mComponents);
cur_mip_data = new_data;
}
llassert(w > 0 && h > 0 && cur_mip_data);
U8 test = cur_mip_data[w*h*mComponents-1];
{
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data);
stop_glerror();
}
if (prev_mip_data && prev_mip_data != rawdata)
{
delete prev_mip_data;
}
prev_mip_data = cur_mip_data;
w >>= 1;
h >>= 1;
}
if (prev_mip_data && prev_mip_data != rawdata)
{
delete prev_mip_data;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, nummips);
*/