Sync light state, bound shader, and various gl context states similarly to render matrices. Texture handles now refcounted, as multiple viewer textures could ref the same handle (cubemaps do this) Clean up gl extension loading a bit. Not necessary, but only look for ARB variants if not included in current core version. Removed unused extensions. Use core shader api if supported, else use ARB. (FN signatures are identical. Just doing some pointer substitution to ARB if not core.) Attempt at improving VBO update batching. Subdata updates better batched to gether per-frame. There's probably other stuff I forgot that is in this changeset, too. Todo: Fix lightstate assertion when toggling fullscreen with shaders off.
1541 lines
40 KiB
C++
1541 lines
40 KiB
C++
/**
|
|
* @file lldrawpoolbump.cpp
|
|
* @brief LLDrawPoolBump class implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2003&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2003-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 "lldrawpoolbump.h"
|
|
|
|
#include "llstl.h"
|
|
#include "llviewercontrol.h"
|
|
#include "lldir.h"
|
|
#include "m3math.h"
|
|
#include "m4math.h"
|
|
#include "v4math.h"
|
|
#include "llglheaders.h"
|
|
#include "llrender.h"
|
|
|
|
#include "llcubemap.h"
|
|
#include "lldrawable.h"
|
|
#include "llface.h"
|
|
#include "llsky.h"
|
|
#include "lltextureentry.h"
|
|
#include "llviewercamera.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "pipeline.h"
|
|
#include "llspatialpartition.h"
|
|
#include "llviewershadermgr.h"
|
|
|
|
//#include "llimagebmp.h"
|
|
//#include "../tools/imdebug/imdebug.h"
|
|
|
|
// static
|
|
LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT];
|
|
|
|
// static
|
|
U32 LLStandardBumpmap::sStandardBumpmapCount = 0;
|
|
|
|
// static
|
|
LLBumpImageList gBumpImageList;
|
|
|
|
const S32 STD_BUMP_LATEST_FILE_VERSION = 1;
|
|
|
|
const U32 VERTEX_MASK_SHINY = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR;
|
|
const U32 VERTEX_MASK_BUMP = LLVertexBuffer::MAP_VERTEX |LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1;
|
|
|
|
U32 LLDrawPoolBump::sVertexMask = VERTEX_MASK_SHINY;
|
|
|
|
static LLGLSLShader* shader = NULL;
|
|
static S32 cube_channel = -1;
|
|
static S32 diffuse_channel = -1;
|
|
static S32 bump_channel = -1;
|
|
|
|
// static
|
|
void LLStandardBumpmap::init()
|
|
{
|
|
LLStandardBumpmap::restoreGL();
|
|
}
|
|
|
|
// static
|
|
void LLStandardBumpmap::shutdown()
|
|
{
|
|
LLStandardBumpmap::destroyGL();
|
|
}
|
|
|
|
// static
|
|
void LLStandardBumpmap::restoreGL()
|
|
{
|
|
addstandard();
|
|
}
|
|
|
|
// static
|
|
void LLStandardBumpmap::addstandard()
|
|
{
|
|
if(!gTextureList.isInitialized())
|
|
{
|
|
//Note: loading pre-configuration sometimes triggers this call.
|
|
//But it is safe to return here because bump images will be reloaded during initialization later.
|
|
return ;
|
|
}
|
|
|
|
// can't assert; we destroyGL and restoreGL a lot during *first* startup, which populates this list already, THEN we explicitly init the list as part of *normal* startup. Sigh. So clear the list every time before we (re-)add the standard bumpmaps.
|
|
//llassert( LLStandardBumpmap::sStandardBumpmapCount == 0 );
|
|
clear();
|
|
LL_INFOS() << "Adding standard bumpmaps." << LL_ENDL;
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("None"); // BE_NO_BUMP
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Brightness"); // BE_BRIGHTNESS
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Darkness"); // BE_DARKNESS
|
|
|
|
std::string file_name = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "std_bump.ini" );
|
|
LLFILE* file = LLFile::fopen( file_name, "rt" ); /*Flawfinder: ignore*/
|
|
if( !file )
|
|
{
|
|
LL_WARNS() << "Could not open std_bump <" << file_name << ">" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
S32 file_version = 0;
|
|
|
|
S32 fields_read = fscanf( file, "LLStandardBumpmap version %d", &file_version );
|
|
if( fields_read != 1 )
|
|
{
|
|
LL_WARNS() << "Bad LLStandardBumpmap header" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if( file_version > STD_BUMP_LATEST_FILE_VERSION )
|
|
{
|
|
LL_WARNS() << "LLStandardBumpmap has newer version (" << file_version << ") than viewer (" << STD_BUMP_LATEST_FILE_VERSION << ")" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
while( !feof(file) && (LLStandardBumpmap::sStandardBumpmapCount < (U32)TEM_BUMPMAP_COUNT) )
|
|
{
|
|
// *NOTE: This buffer size is hard coded into scanf() below.
|
|
char label[2048] = ""; /* Flawfinder: ignore */
|
|
char bump_image_id[2048] = ""; /* Flawfinder: ignore */
|
|
fields_read = fscanf( /* Flawfinder: ignore */
|
|
file, "\n%2047s %2047s", label, bump_image_id);
|
|
if( EOF == fields_read )
|
|
{
|
|
break;
|
|
}
|
|
if( fields_read != 2 )
|
|
{
|
|
LL_WARNS() << "Bad LLStandardBumpmap entry" << LL_ENDL;
|
|
return;
|
|
}
|
|
if(strlen(bump_image_id) == (UUID_STR_LENGTH - 1) + 4 && !stricmp(&(bump_image_id[UUID_STR_LENGTH-1]),".j2c"))
|
|
bump_image_id[UUID_STR_LENGTH-1] = 0; // truncate to a valid uuid (hopefully)
|
|
// LL_INFOS() << "Loading bumpmap: " << bump_file << " from viewerart" << LL_ENDL;
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mLabel = label;
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage =
|
|
LLViewerTextureManager::getFetchedTexture(LLUUID(bump_image_id));
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setBoostLevel(LLGLTexture::BOOST_BUMP) ;
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setLoadedCallback(LLBumpImageList::onSourceStandardLoaded, 0, TRUE, FALSE, NULL, NULL );
|
|
gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->forceToSaveRawImage(0) ;
|
|
LLStandardBumpmap::sStandardBumpmapCount++;
|
|
}
|
|
|
|
fclose( file );
|
|
}
|
|
|
|
// static
|
|
void LLStandardBumpmap::clear()
|
|
{
|
|
LL_INFOS() << "Clearing standard bumpmaps." << LL_ENDL;
|
|
for( U32 i = 0; i < LLStandardBumpmap::sStandardBumpmapCount; i++ )
|
|
{
|
|
gStandardBumpmapList[i].mLabel.assign("");
|
|
gStandardBumpmapList[i].mImage = NULL;
|
|
}
|
|
sStandardBumpmapCount = 0;
|
|
}
|
|
|
|
// static
|
|
void LLStandardBumpmap::destroyGL()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LLDrawPoolBump::LLDrawPoolBump()
|
|
: LLRenderPass(LLDrawPool::POOL_BUMP)
|
|
{
|
|
mShiny = FALSE;
|
|
}
|
|
|
|
|
|
void LLDrawPoolBump::prerender()
|
|
{
|
|
mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT);
|
|
}
|
|
|
|
// static
|
|
S32 LLDrawPoolBump::numBumpPasses()
|
|
{
|
|
static const LLCachedControl<bool> render_object_bump("RenderObjectBump",false);
|
|
if (render_object_bump)
|
|
{
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
if (LLPipeline::sImpostorRender)
|
|
{
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
else if (LLPipeline::sImpostorRender)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
S32 LLDrawPoolBump::getNumPasses()
|
|
{
|
|
return numBumpPasses();
|
|
}
|
|
|
|
void LLDrawPoolBump::beginRenderPass(S32 pass)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
switch( pass )
|
|
{
|
|
case 0:
|
|
beginShiny();
|
|
break;
|
|
case 1:
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
beginFullbrightShiny();
|
|
}
|
|
else
|
|
{
|
|
beginBump();
|
|
}
|
|
break;
|
|
case 2:
|
|
beginBump();
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::render(S32 pass)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
|
|
if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SIMPLE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch( pass )
|
|
{
|
|
case 0:
|
|
renderShiny();
|
|
break;
|
|
case 1:
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
renderFullbrightShiny();
|
|
}
|
|
else
|
|
{
|
|
renderBump();
|
|
}
|
|
break;
|
|
case 2:
|
|
renderBump();
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::endRenderPass(S32 pass)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
switch( pass )
|
|
{
|
|
case 0:
|
|
endShiny();
|
|
break;
|
|
case 1:
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
endFullbrightShiny();
|
|
}
|
|
else
|
|
{
|
|
endBump();
|
|
}
|
|
break;
|
|
case 2:
|
|
endBump();
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
break;
|
|
}
|
|
|
|
//to cleanup texture channels
|
|
LLRenderPass::endRenderPass(pass);
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::beginShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
mShiny = TRUE;
|
|
sVertexMask = VERTEX_MASK_SHINY;
|
|
// Second pass: environment map
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD0;
|
|
}
|
|
|
|
if (getVertexShaderLevel() > 0)
|
|
{
|
|
shader = &gObjectSimpleProgram[LLPipeline::sUnderWaterRender<<SHD_WATER_BIT | 1<<SHD_SHINY_BIT];
|
|
shader->bind();
|
|
}
|
|
else
|
|
{
|
|
shader = NULL;
|
|
}
|
|
|
|
bindCubeMap(shader, mVertexShaderLevel, diffuse_channel, cube_channel);
|
|
|
|
if (mVertexShaderLevel > 1)
|
|
{ //indexed texture rendering, channel 0 is always diffuse
|
|
diffuse_channel = 0;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel)
|
|
{
|
|
LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
|
|
if( cube_map )
|
|
{
|
|
if (shader)
|
|
{
|
|
LLMatrix4 mat(gGLModelView.getF32ptr());
|
|
LLVector3 vec = LLVector3(gShinyOrigin) * mat;
|
|
LLVector4 vec4(vec, gShinyOrigin.mV[3]);
|
|
shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV);
|
|
if (shader_level > 1)
|
|
{
|
|
cube_map->setMatrix(1);
|
|
// Make sure that texture coord generation happens for tex unit 1, as that's the one we use for
|
|
// the cube map in the one pass shiny shaders
|
|
cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP);
|
|
cube_map->enableTexture(cube_channel);
|
|
cube_map->enableTextureCoords(1);
|
|
diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
}
|
|
else
|
|
{
|
|
cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP);
|
|
diffuse_channel = -1;
|
|
cube_map->setMatrix(0);
|
|
cube_map->enable(cube_channel);
|
|
}
|
|
gGL.getTexUnit(cube_channel)->bind(cube_map);
|
|
gGL.getTexUnit(0)->activate();
|
|
}
|
|
else
|
|
{
|
|
cube_channel = 0;
|
|
diffuse_channel = -1;
|
|
gGL.getTexUnit(0)->disable();
|
|
cube_map->enable(0);
|
|
cube_map->setMatrix(0);
|
|
gGL.getTexUnit(0)->bind(cube_map);
|
|
|
|
gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_TEX_COLOR);
|
|
gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_VERT_ALPHA);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::renderShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( gSky.mVOSkyp->getCubeMap() )
|
|
{
|
|
LLGLEnable<GL_BLEND> blend_enable;
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
LLRenderPass::pushBatches(LLRenderPass::PASS_SHINY, sVertexMask | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
renderGroups(LLRenderPass::PASS_SHINY, sVertexMask);
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel)
|
|
{
|
|
LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
|
|
if( cube_map )
|
|
{
|
|
if (shader_level > 1)
|
|
{
|
|
shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP);
|
|
|
|
if (LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 0)
|
|
{
|
|
if (diffuse_channel != 0)
|
|
{
|
|
shader->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
}
|
|
}
|
|
}
|
|
// Moved below shader->disableTexture call to avoid false alarms from auto-re-enable of textures on stage 0
|
|
// MAINT-755
|
|
cube_map->disable();
|
|
cube_map->restoreMatrix();
|
|
}
|
|
|
|
if (!LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.getTexUnit(diffuse_channel)->disable();
|
|
gGL.getTexUnit(cube_channel)->disable();
|
|
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::endShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
unbindCubeMap(shader, mVertexShaderLevel, diffuse_channel, cube_channel);
|
|
if (shader)
|
|
{
|
|
shader->unbind();
|
|
}
|
|
|
|
diffuse_channel = -1;
|
|
cube_channel = 0;
|
|
mShiny = FALSE;
|
|
}
|
|
|
|
void LLDrawPoolBump::beginFullbrightShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD0;
|
|
|
|
// Second pass: environment map
|
|
|
|
if(!LLPipeline::sUnderWaterRender && LLPipeline::sRenderDeferred)
|
|
{
|
|
shader = &gDeferredFullbrightShinyProgram;
|
|
}
|
|
else
|
|
{
|
|
shader = &gObjectFullbrightProgram[LLPipeline::sUnderWaterRender<<SHD_WATER_BIT | 1<<SHD_SHINY_BIT];
|
|
}
|
|
|
|
LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
|
|
if( cube_map )
|
|
{
|
|
LLMatrix4 mat(gGLModelView.getF32ptr());
|
|
shader->bind();
|
|
LLVector3 vec = LLVector3(gShinyOrigin) * mat;
|
|
LLVector4 vec4(vec, gShinyOrigin.mV[3]);
|
|
shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV);
|
|
|
|
cube_map->setMatrix(1);
|
|
// Make sure that texture coord generation happens for tex unit 1, as that's the one we use for
|
|
// the cube map in the one pass shiny shaders
|
|
gGL.getTexUnit(1)->disable();
|
|
cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP);
|
|
cube_map->enableTexture(cube_channel);
|
|
cube_map->enableTextureCoords(1);
|
|
diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
|
|
gGL.getTexUnit(cube_channel)->bind(cube_map);
|
|
gGL.getTexUnit(0)->activate();
|
|
}
|
|
|
|
if (mVertexShaderLevel > 1)
|
|
{ //indexed texture rendering, channel 0 is always diffuse
|
|
diffuse_channel = 0;
|
|
}
|
|
|
|
mShiny = TRUE;
|
|
}
|
|
|
|
void LLDrawPoolBump::renderFullbrightShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( gSky.mVOSkyp->getCubeMap() )
|
|
{
|
|
LLGLEnable<GL_BLEND> blend_enable;
|
|
|
|
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
|
if (mVertexShaderLevel > 1)
|
|
{
|
|
LLRenderPass::pushBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY, sVertexMask | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
LLRenderPass::renderTexture(LLRenderPass::PASS_FULLBRIGHT_SHINY, sVertexMask);
|
|
}
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::endFullbrightShiny()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY);
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
|
|
if( cube_map )
|
|
{
|
|
cube_map->disable();
|
|
cube_map->restoreMatrix();
|
|
|
|
/*if (diffuse_channel != 0)
|
|
{
|
|
shader->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
}
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);*/
|
|
|
|
shader->unbind();
|
|
//gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
}
|
|
|
|
//gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
//gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
|
|
diffuse_channel = -1;
|
|
cube_channel = 0;
|
|
mShiny = FALSE;
|
|
}
|
|
|
|
void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE)
|
|
{
|
|
LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];
|
|
|
|
for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)
|
|
{
|
|
LLDrawInfo& params = **k;
|
|
|
|
applyModelMatrix(params);
|
|
|
|
if (params.mGroup)
|
|
{
|
|
params.mGroup->rebuildMesh();
|
|
}
|
|
params.mVertexBuffer->setBuffer(mask);
|
|
params.mVertexBuffer->drawRange(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset);
|
|
gPipeline.addTrianglesDrawn(params.mCount, params.mDrawMode);
|
|
}
|
|
}
|
|
|
|
|
|
// static
|
|
BOOL LLDrawPoolBump::bindBumpMap(LLDrawInfo& params, S32 channel)
|
|
{
|
|
U8 bump_code = params.mBump;
|
|
|
|
return bindBumpMap(bump_code, params.mTexture, params.mVSize, channel);
|
|
}
|
|
|
|
//static
|
|
BOOL LLDrawPoolBump::bindBumpMap(LLFace* face, S32 channel)
|
|
{
|
|
const LLTextureEntry* te = face->getTextureEntry();
|
|
if (te)
|
|
{
|
|
U8 bump_code = te->getBumpmap();
|
|
return bindBumpMap(bump_code, face->getTexture(), face->getVirtualSize(), channel);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//static
|
|
BOOL LLDrawPoolBump::bindBumpMap(U8 bump_code, LLViewerTexture* texture, F32 vsize, S32 channel)
|
|
{
|
|
//Note: texture atlas does not support bump texture now.
|
|
LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(texture) ;
|
|
if(!tex)
|
|
{
|
|
//if the texture is not a fetched texture
|
|
return FALSE;
|
|
}
|
|
|
|
LLViewerTexture* bump = NULL;
|
|
|
|
switch( bump_code )
|
|
{
|
|
case BE_NO_BUMP:
|
|
break;
|
|
case BE_BRIGHTNESS:
|
|
case BE_DARKNESS:
|
|
bump = gBumpImageList.getBrightnessDarknessImage( tex, bump_code );
|
|
break;
|
|
|
|
default:
|
|
if( bump_code < LLStandardBumpmap::sStandardBumpmapCount )
|
|
{
|
|
bump = gStandardBumpmapList[bump_code].mImage;
|
|
gBumpImageList.addTextureStats(bump_code, tex->getID(), vsize);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bump)
|
|
{
|
|
if (channel == -2)
|
|
{
|
|
gGL.getTexUnit(1)->bind(bump);
|
|
gGL.getTexUnit(0)->bind(bump);
|
|
}
|
|
else
|
|
{
|
|
gGL.getTexUnit(channel)->bind(bump);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::beginBump(U32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(pass))
|
|
{
|
|
return;
|
|
}
|
|
|
|
sVertexMask = VERTEX_MASK_BUMP;
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
// Optional second pass: emboss bump map
|
|
stop_glerror();
|
|
|
|
if (LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gObjectBumpProgram.bind();
|
|
}
|
|
else
|
|
{
|
|
// TEXTURE UNIT 0
|
|
// Output.rgb = texture at texture coord 0
|
|
gGL.getTexUnit(0)->activate();
|
|
|
|
gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_TEX_ALPHA);
|
|
gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_TEX_ALPHA);
|
|
|
|
// TEXTURE UNIT 1
|
|
gGL.getTexUnit(1)->activate();
|
|
|
|
gGL.getTexUnit(1)->enable(LLTexUnit::TT_TEXTURE);
|
|
|
|
gGL.getTexUnit(1)->setTextureColorBlend(LLTexUnit::TBO_ADD_SIGNED, LLTexUnit::TBS_PREV_COLOR, LLTexUnit::TBS_ONE_MINUS_TEX_ALPHA);
|
|
gGL.getTexUnit(1)->setTextureAlphaBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_TEX_ALPHA);
|
|
|
|
// src = tex0 + (1 - tex1) - 0.5
|
|
// = (bump0/2 + 0.5) + (1 - (bump1/2 + 0.5)) - 0.5
|
|
// = (1 + bump0 - bump1) / 2
|
|
|
|
|
|
// Blend: src * dst + dst * src
|
|
// = 2 * src * dst
|
|
// = 2 * ((1 + bump0 - bump1) / 2) * dst [0 - 2 * dst]
|
|
// = (1 + bump0 - bump1) * dst.rgb
|
|
// = dst.rgb + dst.rgb * (bump0 - bump1)
|
|
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
|
|
gGL.setSceneBlendType(LLRender::BT_MULT_X2);
|
|
stop_glerror();
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::renderBump(U32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(pass))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
LLGLDisable<GL_FOG> fog;
|
|
LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_LEQUAL);
|
|
LLGLEnable<GL_BLEND> blend;
|
|
gGL.diffuseColor4f(1,1,1,1);
|
|
/// Get rid of z-fighting with non-bump pass.
|
|
LLGLEnable<GL_POLYGON_OFFSET_FILL> polyOffset;
|
|
gGL.setPolygonOffset(-1.0f, -1.0f);
|
|
renderBump(pass, sVertexMask);
|
|
}
|
|
|
|
//static
|
|
void LLDrawPoolBump::endBump(U32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(pass))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gObjectBumpProgram.unbind();
|
|
}
|
|
else
|
|
{
|
|
// Disable texture blending on unit 1
|
|
gGL.getTexUnit(1)->activate();
|
|
gGL.getTexUnit(1)->disable();
|
|
gGL.getTexUnit(1)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
|
|
// Disable texture blending on unit 0
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT);
|
|
}
|
|
|
|
gGL.setSceneBlendType(LLRender::BT_ALPHA);
|
|
}
|
|
|
|
S32 LLDrawPoolBump::getNumDeferredPasses()
|
|
{
|
|
static const LLCachedControl<bool> render_object_bump("RenderObjectBump",false);
|
|
return render_object_bump ? 1 : 0;
|
|
}
|
|
|
|
void LLDrawPoolBump::beginDeferredPass(S32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
|
|
{
|
|
return;
|
|
}
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
mShiny = TRUE;
|
|
gDeferredBumpProgram.bind();
|
|
diffuse_channel = gDeferredBumpProgram.enableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
bump_channel = gDeferredBumpProgram.enableTexture(LLViewerShaderMgr::BUMP_MAP);
|
|
gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.getTexUnit(bump_channel)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
|
|
void LLDrawPoolBump::endDeferredPass(S32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
|
|
{
|
|
return;
|
|
}
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
mShiny = FALSE;
|
|
gDeferredBumpProgram.disableTexture(LLViewerShaderMgr::DIFFUSE_MAP);
|
|
gDeferredBumpProgram.disableTexture(LLViewerShaderMgr::BUMP_MAP);
|
|
gDeferredBumpProgram.unbind();
|
|
gGL.getTexUnit(0)->activate();
|
|
}
|
|
|
|
void LLDrawPoolBump::renderDeferred(S32 pass)
|
|
{
|
|
if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
|
|
{
|
|
return;
|
|
}
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
|
|
|
|
U32 type = LLRenderPass::PASS_BUMP;
|
|
LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type);
|
|
LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type);
|
|
|
|
U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR;
|
|
|
|
for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i)
|
|
{
|
|
LLDrawInfo& params = **i;
|
|
|
|
LLDrawPoolBump::bindBumpMap(params, bump_channel);
|
|
pushBatch(params, mask, TRUE);
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::beginPostDeferredPass(S32 pass)
|
|
{
|
|
switch (pass)
|
|
{
|
|
case 0:
|
|
beginFullbrightShiny();
|
|
break;
|
|
case 1:
|
|
beginBump(LLRenderPass::PASS_POST_BUMP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::endPostDeferredPass(S32 pass)
|
|
{
|
|
switch (pass)
|
|
{
|
|
case 0:
|
|
endFullbrightShiny();
|
|
break;
|
|
case 1:
|
|
endBump(LLRenderPass::PASS_POST_BUMP);
|
|
break;
|
|
}
|
|
|
|
//to disable texture channels
|
|
LLRenderPass::endRenderPass(pass);
|
|
}
|
|
|
|
void LLDrawPoolBump::renderPostDeferred(S32 pass)
|
|
{
|
|
switch (pass)
|
|
{
|
|
case 0:
|
|
gGL.setColorMask(true, true);
|
|
renderFullbrightShiny();
|
|
gGL.setColorMask(true, false);
|
|
break;
|
|
case 1:
|
|
renderBump(LLRenderPass::PASS_POST_BUMP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// List of one-component bump-maps created from other texures.
|
|
|
|
|
|
//const LLUUID TEST_BUMP_ID("3d33eaf2-459c-6f97-fd76-5fce3fc29447");
|
|
|
|
void LLBumpImageList::init()
|
|
{
|
|
llassert( mBrightnessEntries.size() == 0 );
|
|
llassert( mDarknessEntries.size() == 0 );
|
|
|
|
LLStandardBumpmap::init();
|
|
}
|
|
|
|
void LLBumpImageList::clear()
|
|
{
|
|
LL_INFOS() << "Clearing dynamic bumpmaps." << LL_ENDL;
|
|
// these will be re-populated on-demand
|
|
mBrightnessEntries.clear();
|
|
mDarknessEntries.clear();
|
|
|
|
LLStandardBumpmap::clear();
|
|
}
|
|
|
|
void LLBumpImageList::shutdown()
|
|
{
|
|
clear();
|
|
LLStandardBumpmap::shutdown();
|
|
}
|
|
|
|
void LLBumpImageList::destroyGL()
|
|
{
|
|
clear();
|
|
LLStandardBumpmap::destroyGL();
|
|
}
|
|
|
|
void LLBumpImageList::restoreGL()
|
|
{
|
|
if(!gTextureList.isInitialized())
|
|
{
|
|
//safe to return here because bump images will be reloaded during initialization later.
|
|
return ;
|
|
}
|
|
LLStandardBumpmap::restoreGL();
|
|
// Images will be recreated as they are needed.
|
|
}
|
|
|
|
|
|
LLBumpImageList::~LLBumpImageList()
|
|
{
|
|
// Shutdown should have already been called.
|
|
llassert( mBrightnessEntries.size() == 0 );
|
|
llassert( mDarknessEntries.size() == 0 );
|
|
}
|
|
|
|
|
|
// Note: Does nothing for entries in gStandardBumpmapList that are not actually standard bump images (e.g. none, brightness, and darkness)
|
|
void LLBumpImageList::addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size)
|
|
{
|
|
bump &= TEM_BUMP_MASK;
|
|
LLViewerFetchedTexture* bump_image = gStandardBumpmapList[bump].mImage;
|
|
if( bump_image )
|
|
{
|
|
bump_image->addTextureStats(virtual_size);
|
|
}
|
|
}
|
|
|
|
|
|
void LLBumpImageList::updateImages()
|
|
{
|
|
for (bump_image_map_t::iterator iter = mBrightnessEntries.begin(); iter != mBrightnessEntries.end(); )
|
|
{
|
|
bump_image_map_t::iterator curiter = iter++;
|
|
LLViewerTexture* image = curiter->second;
|
|
if( image )
|
|
{
|
|
BOOL destroy = TRUE;
|
|
if( image->hasGLTexture())
|
|
{
|
|
if( image->getBoundRecently() )
|
|
{
|
|
destroy = FALSE;
|
|
}
|
|
else
|
|
{
|
|
image->destroyGLTexture();
|
|
}
|
|
}
|
|
|
|
if( destroy )
|
|
{
|
|
//LL_INFOS() << "*** Destroying bright " << (void*)image << LL_ENDL;
|
|
mBrightnessEntries.erase(curiter); // deletes the image thanks to reference counting
|
|
}
|
|
}
|
|
}
|
|
|
|
for (bump_image_map_t::iterator iter = mDarknessEntries.begin(); iter != mDarknessEntries.end(); )
|
|
{
|
|
bump_image_map_t::iterator curiter = iter++;
|
|
LLViewerTexture* image = curiter->second;
|
|
if( image )
|
|
{
|
|
BOOL destroy = TRUE;
|
|
if( image->hasGLTexture())
|
|
{
|
|
if( image->getBoundRecently() )
|
|
{
|
|
destroy = FALSE;
|
|
}
|
|
else
|
|
{
|
|
image->destroyGLTexture();
|
|
}
|
|
}
|
|
|
|
if( destroy )
|
|
{
|
|
//LL_INFOS() << "*** Destroying dark " << (void*)image << LL_ENDL;;
|
|
mDarknessEntries.erase(curiter); // deletes the image thanks to reference counting
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Note: the caller SHOULD NOT keep the pointer that this function returns. It may be updated as more data arrives.
|
|
LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code )
|
|
{
|
|
llassert( (bump_code == BE_BRIGHTNESS) || (bump_code == BE_DARKNESS) );
|
|
|
|
LLViewerTexture* bump = NULL;
|
|
|
|
bump_image_map_t* entries_list = NULL;
|
|
void (*callback_func)( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata ) = NULL;
|
|
|
|
switch( bump_code )
|
|
{
|
|
case BE_BRIGHTNESS:
|
|
entries_list = &mBrightnessEntries;
|
|
callback_func = LLBumpImageList::onSourceBrightnessLoaded;
|
|
break;
|
|
case BE_DARKNESS:
|
|
entries_list = &mDarknessEntries;
|
|
callback_func = LLBumpImageList::onSourceDarknessLoaded;
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
return NULL;
|
|
}
|
|
|
|
bump_image_map_t::iterator iter = entries_list->find(src_image->getID());
|
|
if (iter != entries_list->end() && iter->second.notNull())
|
|
{
|
|
bump = iter->second;
|
|
}
|
|
else
|
|
{
|
|
LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,1);
|
|
raw->clear(0x77, 0x77, 0xFF, 0xFF);
|
|
|
|
(*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( raw.get(), TRUE);
|
|
bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image
|
|
}
|
|
|
|
if (!src_image->hasCallbacks())
|
|
{ //if image has no callbacks but resolutions don't match, trigger raw image loaded callback again
|
|
if (src_image->getWidth() != bump->getWidth() ||
|
|
src_image->getHeight() != bump->getHeight())// ||
|
|
//(LLPipeline::sRenderDeferred && bump->getComponents() != 4))
|
|
{
|
|
src_image->setBoostLevel(LLGLTexture::BOOST_BUMP) ;
|
|
src_image->setLoadedCallback( callback_func, 0, TRUE, FALSE, new LLUUID(src_image->getID()), NULL );
|
|
src_image->forceToSaveRawImage(0) ;
|
|
}
|
|
}
|
|
|
|
return bump;
|
|
}
|
|
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_STANDARD_LOADED("Bump Standard Callback");
|
|
|
|
// static
|
|
void LLBumpImageList::onSourceBrightnessLoaded( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
|
|
{
|
|
LLUUID* source_asset_id = (LLUUID*)userdata;
|
|
LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_BRIGHTNESS );
|
|
if( final )
|
|
{
|
|
delete source_asset_id;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLBumpImageList::onSourceDarknessLoaded( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
|
|
{
|
|
LLUUID* source_asset_id = (LLUUID*)userdata;
|
|
LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_DARKNESS );
|
|
if( final )
|
|
{
|
|
delete source_asset_id;
|
|
}
|
|
}
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_GEN_NORMAL("Generate Normal Map");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_CREATE_TEXTURE("Create GL Normal Map");
|
|
|
|
void LLBumpImageList::onSourceStandardLoaded( BOOL success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata)
|
|
{
|
|
if (success && LLPipeline::sRenderDeferred)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_STANDARD_LOADED);
|
|
LLPointer<LLImageRaw> nrm_image = new LLImageRaw(src->getWidth(), src->getHeight(), 4);
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_GEN_NORMAL);
|
|
generateNormalMapFromAlpha(src, nrm_image);
|
|
}
|
|
src_vi->setExplicitFormat(GL_RGBA, GL_RGBA);
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_CREATE_TEXTURE);
|
|
src_vi->createGLTexture(src_vi->getDiscardLevel(), nrm_image);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLBumpImageList::generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image)
|
|
{
|
|
U8* nrm_data = nrm_image->getData();
|
|
S32 resX = src->getWidth();
|
|
S32 resY = src->getHeight();
|
|
|
|
U8* src_data = src->getData();
|
|
|
|
S32 src_cmp = src->getComponents();
|
|
|
|
F32 norm_scale = gSavedSettings.getF32("RenderNormalMapScale");
|
|
|
|
U32 idx = 0;
|
|
//generate normal map from pseudo-heightfield
|
|
for (S32 j = 0; j < resY; ++j)
|
|
{
|
|
for (S32 i = 0; i < resX; ++i)
|
|
{
|
|
S32 rX = (i+1)%resX;
|
|
S32 rY = (j+1)%resY;
|
|
S32 lX = (i-1)%resX;
|
|
S32 lY = (j-1)%resY;
|
|
|
|
if (lX < 0)
|
|
{
|
|
lX += resX;
|
|
}
|
|
if (lY < 0)
|
|
{
|
|
lY += resY;
|
|
}
|
|
|
|
F32 cH = (F32) src_data[(j*resX+i)*src_cmp+src_cmp-1];
|
|
|
|
LLVector3 right = LLVector3(norm_scale, 0, (F32) src_data[(j*resX+rX)*src_cmp+src_cmp-1]-cH);
|
|
LLVector3 left = LLVector3(-norm_scale, 0, (F32) src_data[(j*resX+lX)*src_cmp+src_cmp-1]-cH);
|
|
LLVector3 up = LLVector3(0, -norm_scale, (F32) src_data[(lY*resX+i)*src_cmp+src_cmp-1]-cH);
|
|
LLVector3 down = LLVector3(0, norm_scale, (F32) src_data[(rY*resX+i)*src_cmp+src_cmp-1]-cH);
|
|
|
|
LLVector3 norm = right%down + down%left + left%up + up%right;
|
|
|
|
norm.normVec();
|
|
|
|
norm *= 0.5f;
|
|
norm += LLVector3(0.5f,0.5f,0.5f);
|
|
|
|
idx = (j*resX+i)*4;
|
|
nrm_data[idx+0]= (U8) (norm.mV[0]*255);
|
|
nrm_data[idx+1]= (U8) (norm.mV[1]*255);
|
|
nrm_data[idx+2]= (U8) (norm.mV[2]*255);
|
|
nrm_data[idx+3]= src_data[(j*resX+i)*src_cmp+src_cmp-1];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_LOADED("Bump Source Loaded");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_ENTRIES_UPDATE("Entries Update");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_MIN_MAX("Min/Max");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_RGB2LUM("RGB to Luminance");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_RESCALE("Rescale");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_GEN_NORMAL("Generate Normal");
|
|
static LLTrace::BlockTimerStatHandle FTM_BUMP_SOURCE_CREATE("Bump Source Create");
|
|
|
|
// static
|
|
void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump_code )
|
|
{
|
|
if( success )
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_LOADED);
|
|
|
|
|
|
bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries );
|
|
bump_image_map_t::iterator iter = entries_list.find(source_asset_id);
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_ENTRIES_UPDATE);
|
|
if (iter == entries_list.end() ||
|
|
iter->second.isNull() ||
|
|
iter->second->getWidth() != src->getWidth() ||
|
|
iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
|
|
{ //make sure an entry exists for this image
|
|
LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,1);
|
|
raw->clear(0x77, 0x77, 0xFF, 0xFF);
|
|
|
|
entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture( raw.get(), TRUE);
|
|
iter = entries_list.find(src_vi->getID());
|
|
}
|
|
}
|
|
|
|
//if (iter->second->getWidth() != src->getWidth() ||
|
|
// iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
|
|
{
|
|
LLPointer<LLImageRaw> dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1);
|
|
U8* dst_data = dst_image->getData();
|
|
S32 dst_data_size = dst_image->getDataSize();
|
|
|
|
U8* src_data = src->getData();
|
|
S32 src_data_size = src->getDataSize();
|
|
|
|
S32 src_components = src->getComponents();
|
|
|
|
// Convert to luminance and then scale and bias that to get ready for
|
|
// embossed bump mapping. (0-255 maps to 127-255)
|
|
|
|
// Convert to fixed point so we don't have to worry about precision/clamping.
|
|
const S32 FIXED_PT = 8;
|
|
const S32 R_WEIGHT = S32(0.2995f * (1<<FIXED_PT));
|
|
const S32 G_WEIGHT = S32(0.5875f * (1<<FIXED_PT));
|
|
const S32 B_WEIGHT = S32(0.1145f * (1<<FIXED_PT));
|
|
|
|
S32 minimum = 255;
|
|
S32 maximum = 0;
|
|
|
|
switch( src_components )
|
|
{
|
|
case 1:
|
|
case 2:
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_MIN_MAX);
|
|
if( src_data_size == dst_data_size * src_components )
|
|
{
|
|
for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
|
|
{
|
|
dst_data[i] = src_data[j];
|
|
if( dst_data[i] < minimum )
|
|
{
|
|
minimum = dst_data[i];
|
|
}
|
|
if( dst_data[i] > maximum )
|
|
{
|
|
maximum = dst_data[i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llassert(0);
|
|
dst_image->clear();
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_RGB2LUM);
|
|
if( src_data_size == dst_data_size * src_components )
|
|
{
|
|
for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
|
|
{
|
|
// RGB to luminance
|
|
dst_data[i] = (R_WEIGHT * src_data[j] + G_WEIGHT * src_data[j+1] + B_WEIGHT * src_data[j+2]) >> FIXED_PT;
|
|
//llassert( dst_data[i] <= 255 );true because it's 8bit
|
|
if( dst_data[i] < minimum )
|
|
{
|
|
minimum = dst_data[i];
|
|
}
|
|
if( dst_data[i] > maximum )
|
|
{
|
|
maximum = dst_data[i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llassert(0);
|
|
dst_image->clear();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
llassert(0);
|
|
dst_image->clear();
|
|
break;
|
|
}
|
|
|
|
if( maximum > minimum )
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_RESCALE);
|
|
U8 bias_and_scale_lut[256];
|
|
F32 twice_one_over_range = 2.f / (maximum - minimum);
|
|
S32 i;
|
|
|
|
const F32 ARTIFICIAL_SCALE = 2.f; // Advantage: exagerates the effect in midrange. Disadvantage: clamps at the extremes.
|
|
if( BE_DARKNESS == bump_code )
|
|
{
|
|
for( i = minimum; i <= maximum; i++ )
|
|
{
|
|
F32 minus_one_to_one = F32(maximum - i) * twice_one_over_range - 1.f;
|
|
bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// BE_LIGHTNESS
|
|
for( i = minimum; i <= maximum; i++ )
|
|
{
|
|
F32 minus_one_to_one = F32(i - minimum) * twice_one_over_range - 1.f;
|
|
bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < dst_data_size; i++ )
|
|
{
|
|
dst_data[i] = bias_and_scale_lut[dst_data[i]];
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// immediately assign bump to a global smart pointer in case some local smart pointer
|
|
// accidentally releases it.
|
|
LLPointer<LLViewerTexture> bump = LLViewerTextureManager::getLocalTexture( TRUE );
|
|
|
|
if (!LLPipeline::sRenderDeferred)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_CREATE);
|
|
bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
|
|
bump->createGLTexture(0, dst_image);
|
|
}
|
|
else
|
|
{ //convert to normal map
|
|
|
|
//disable compression on normal maps to prevent errors below
|
|
bump->getGLTexture()->setAllowCompression(false);
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_CREATE);
|
|
bump->setExplicitFormat(GL_RGBA8, GL_ALPHA);
|
|
bump->createGLTexture(0, dst_image);
|
|
}
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BUMP_SOURCE_GEN_NORMAL);
|
|
gPipeline.mScreen.bindTarget();
|
|
|
|
LLGLDepthTest depth(GL_FALSE);
|
|
LLGLDisable<GL_CULL_FACE> cull;
|
|
LLGLDisable<GL_BLEND> blend;
|
|
gGL.setColorMask(TRUE, TRUE);
|
|
gNormalMapGenProgram.bind();
|
|
|
|
static LLStaticHashedString sNormScale("norm_scale");
|
|
static LLStaticHashedString sStepX("stepX");
|
|
static LLStaticHashedString sStepY("stepY");
|
|
|
|
gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale"));
|
|
gNormalMapGenProgram.uniform1f(sStepX, 1.f/bump->getWidth());
|
|
gNormalMapGenProgram.uniform1f(sStepY, 1.f/bump->getHeight());
|
|
|
|
LLVector2 v((F32) bump->getWidth()/gPipeline.mScreen.getWidth(),
|
|
(F32) bump->getHeight()/gPipeline.mScreen.getHeight());
|
|
|
|
gGL.getTexUnit(0)->bind(bump);
|
|
|
|
S32 width = bump->getWidth();
|
|
S32 height = bump->getHeight();
|
|
|
|
S32 screen_width = gPipeline.mScreen.getWidth();
|
|
S32 screen_height = gPipeline.mScreen.getHeight();
|
|
|
|
gGL.setViewport(0, 0, screen_width, screen_height);
|
|
|
|
for (S32 left = 0; left < width; left += screen_width)
|
|
{
|
|
S32 right = left + screen_width;
|
|
right = llmin(right, width);
|
|
|
|
F32 left_tc = (F32) left/ width;
|
|
F32 right_tc = (F32) right/width;
|
|
|
|
for (S32 bottom = 0; bottom < height; bottom += screen_height)
|
|
{
|
|
S32 top = bottom+screen_height;
|
|
top = llmin(top, height);
|
|
|
|
F32 bottom_tc = (F32) bottom/height;
|
|
F32 top_tc = (F32)(bottom+screen_height)/height;
|
|
top_tc = llmin(top_tc, 1.f);
|
|
|
|
F32 screen_right = (F32) (right-left)/screen_width;
|
|
F32 screen_top = (F32) (top-bottom)/screen_height;
|
|
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.texCoord2f(left_tc, bottom_tc);
|
|
gGL.vertex2f(0, 0);
|
|
|
|
gGL.texCoord2f(left_tc, top_tc);
|
|
gGL.vertex2f(0, screen_top);
|
|
|
|
gGL.texCoord2f(right_tc, bottom_tc);
|
|
gGL.vertex2f(screen_right, 0);
|
|
|
|
gGL.texCoord2f(right_tc, top_tc);
|
|
gGL.vertex2f(screen_right, screen_top);
|
|
|
|
gGL.end();
|
|
|
|
gGL.flush();
|
|
|
|
S32 w = right-left;
|
|
S32 h = top-bottom;
|
|
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, left, bottom, 0, 0, w, h);
|
|
}
|
|
}
|
|
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
gPipeline.mScreen.flush();
|
|
|
|
gNormalMapGenProgram.unbind();
|
|
|
|
//generateNormalMapFromAlpha(dst_image, nrm_image);
|
|
}
|
|
}
|
|
|
|
iter->second = bump; // derefs (and deletes) old image
|
|
//---------------------------------------------------
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::renderBump(U32 type, U32 mask)
|
|
{
|
|
LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type);
|
|
LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type);
|
|
|
|
for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i)
|
|
{
|
|
LLDrawInfo& params = **i;
|
|
|
|
if (LLDrawPoolBump::bindBumpMap(params))
|
|
{
|
|
pushBatch(params, mask, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLDrawPoolBump::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures)
|
|
{
|
|
applyModelMatrix(params);
|
|
|
|
bool tex_setup = false;
|
|
|
|
if (batch_textures && params.mTextureList.size() > 1)
|
|
{
|
|
for (U32 i = 0; i < params.mTextureList.size(); ++i)
|
|
{
|
|
if (params.mTextureList[i].notNull())
|
|
{
|
|
gGL.getTexUnit(i)->bind(params.mTextureList[i], TRUE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ //not batching textures or batch has only 1 texture -- might need a texture matrix
|
|
if (params.mTextureMatrix)
|
|
{
|
|
if (mShiny)
|
|
{
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.matrixMode(LLRender::MM_TEXTURE);
|
|
}
|
|
else
|
|
{
|
|
if (!LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.getTexUnit(1)->activate();
|
|
gGL.matrixMode(LLRender::MM_TEXTURE);
|
|
gGL.loadMatrix(*params.mTextureMatrix);
|
|
}
|
|
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.matrixMode(LLRender::MM_TEXTURE);
|
|
gGL.loadMatrix(*params.mTextureMatrix);
|
|
gPipeline.mTextureMatrixOps++;
|
|
}
|
|
|
|
gGL.loadMatrix(*params.mTextureMatrix);
|
|
gPipeline.mTextureMatrixOps++;
|
|
|
|
tex_setup = true;
|
|
}
|
|
|
|
if (mShiny && mVertexShaderLevel > 1 && texture)
|
|
{
|
|
if (params.mTexture.notNull())
|
|
{
|
|
gGL.getTexUnit(diffuse_channel)->bind(params.mTexture) ;
|
|
params.mTexture->addTextureStats(params.mVSize);
|
|
}
|
|
else
|
|
{
|
|
gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE);
|
|
}
|
|
}
|
|
}
|
|
if (params.mGroup)
|
|
{
|
|
params.mGroup->rebuildMesh();
|
|
}
|
|
params.mVertexBuffer->setBuffer(mask);
|
|
params.mVertexBuffer->drawRange(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset);
|
|
gPipeline.addTrianglesDrawn(params.mCount, params.mDrawMode);
|
|
if (tex_setup)
|
|
{
|
|
if (mShiny)
|
|
{
|
|
gGL.getTexUnit(0)->activate();
|
|
}
|
|
else
|
|
{
|
|
if (!LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.getTexUnit(1)->activate();
|
|
gGL.matrixMode(LLRender::MM_TEXTURE);
|
|
gGL.loadIdentity();
|
|
}
|
|
gGL.getTexUnit(0)->activate();
|
|
gGL.matrixMode(LLRender::MM_TEXTURE);
|
|
}
|
|
gGL.loadIdentity();
|
|
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
|
}
|
|
}
|