Files
SingularityViewer/indra/llrender/llshadermgr.cpp

833 lines
19 KiB
C++

/**
* @file llshadermgr.cpp
* @brief Shader manager implementation.
*
* $LicenseInfo:firstyear=2005&license=viewergpl$
*
* Copyright (c) 2005-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 "linden_common.h"
#include "llshadermgr.h"
#include "llfile.h"
#include "llrender.h"
#if LL_DARWIN
#include "OpenGL/OpenGL.h"
#endif
#ifdef LL_RELEASE_FOR_DOWNLOAD
#define UNIFORM_ERRS LL_WARNS_ONCE("Shader")
#else
#define UNIFORM_ERRS LL_ERRS("Shader")
#endif
// Lots of STL stuff in here, using namespace std to keep things more readable
using std::vector;
using std::pair;
using std::make_pair;
using std::string;
LLShaderMgr * LLShaderMgr::sInstance = NULL;
LLShaderMgr::LLShaderMgr()
{
}
LLShaderMgr::~LLShaderMgr()
{
}
// static
LLShaderMgr * LLShaderMgr::instance()
{
if(NULL == sInstance)
{
LL_ERRS("Shaders") << "LLShaderMgr should already have been instantiated by the application!" << LL_ENDL;
}
return sInstance;
}
BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
{
llassert_always(shader != NULL);
LLShaderFeatures *features = & shader->mFeatures;
//////////////////////////////////////
// Attach Vertex Shader Features First
//////////////////////////////////////
// NOTE order of shader object attaching is VERY IMPORTANT!!!
if (features->calculatesAtmospherics)
{
if (!shader->attachObject("windlight/atmosphericsVarsV.glsl"))
{
return FALSE;
}
}
if (features->calculatesLighting)
{
if (!shader->attachObject("windlight/atmosphericsHelpersV.glsl"))
{
return FALSE;
}
if (features->isSpecular)
{
if (!shader->attachObject("lighting/lightFuncSpecularV.glsl"))
{
return FALSE;
}
if (!shader->attachObject("lighting/sumLightsSpecularV.glsl"))
{
return FALSE;
}
if (!shader->attachObject("lighting/lightSpecularV.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightFuncV.glsl"))
{
return FALSE;
}
if (!shader->attachObject("lighting/sumLightsV.glsl"))
{
return FALSE;
}
if (!shader->attachObject("lighting/lightV.glsl"))
{
return FALSE;
}
}
}
// NOTE order of shader object attaching is VERY IMPORTANT!!!
if (features->calculatesAtmospherics)
{
if (!shader->attachObject("windlight/atmosphericsV.glsl"))
{
return FALSE;
}
}
if (features->hasSkinning)
{
if (!shader->attachObject("avatar/avatarSkinV.glsl"))
{
return FALSE;
}
}
#if MESH_ENABLED
if (features->hasObjectSkinning)
{
if (!shader->attachObject("avatar/objectSkinV.glsl"))
{
return FALSE;
}
}
#endif //MESH_ENABLED
///////////////////////////////////////
// Attach Fragment Shader Features Next
///////////////////////////////////////
if(features->calculatesAtmospherics)
{
if (!shader->attachObject("windlight/atmosphericsVarsF.glsl"))
{
return FALSE;
}
}
// NOTE order of shader object attaching is VERY IMPORTANT!!!
if (features->hasGamma)
{
if (!shader->attachObject("windlight/gammaF.glsl"))
{
return FALSE;
}
}
if (features->hasAtmospherics)
{
if (!shader->attachObject("windlight/atmosphericsF.glsl"))
{
return FALSE;
}
}
if (features->hasTransport)
{
if (!shader->attachObject("windlight/transportF.glsl"))
{
return FALSE;
}
// Test hasFullbright and hasShiny and attach fullbright and
// fullbright shiny atmos transport if we split them out.
}
// NOTE order of shader object attaching is VERY IMPORTANT!!!
if (features->hasWaterFog)
{
if (!shader->attachObject("environment/waterFogF.glsl"))
{
return FALSE;
}
}
if (features->hasLighting)
{
if (features->hasWaterFog)
{
if (features->disableTextureIndex)
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightWaterAlphaMaskNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightWaterNonIndexedF.glsl"))
{
return FALSE;
}
}
}
else
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightWaterAlphaMaskF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightWaterF.glsl"))
{
return FALSE;
}
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
else
{
if (features->disableTextureIndex)
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightAlphaMaskNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightNonIndexedF.glsl"))
{
return FALSE;
}
}
}
else
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightAlphaMaskF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightF.glsl"))
{
return FALSE;
}
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
}
// NOTE order of shader object attaching is VERY IMPORTANT!!!
else if (features->isFullbright)
{
if (features->isShiny && features->hasWaterFog)
{
if (features->disableTextureIndex)
{
if (!shader->attachObject("lighting/lightFullbrightShinyWaterNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightFullbrightShinyWaterF.glsl"))
{
return FALSE;
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
else if (features->hasWaterFog)
{
if (features->disableTextureIndex)
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightFullbrightWaterNonIndexedAlphaMaskF.glsl"))
{
return FALSE;
}
}
else if (!shader->attachObject("lighting/lightFullbrightWaterNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightFullbrightWaterAlphaMaskF.glsl"))
{
return FALSE;
}
}
else if (!shader->attachObject("lighting/lightFullbrightWaterF.glsl"))
{
return FALSE;
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
else if (features->isShiny)
{
if (features->disableTextureIndex)
{
if (!shader->attachObject("lighting/lightFullbrightShinyNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightFullbrightShinyF.glsl"))
{
return FALSE;
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
else
{
if (features->disableTextureIndex)
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightFullbrightNonIndexedAlphaMaskF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightFullbrightNonIndexedF.glsl"))
{
return FALSE;
}
}
}
else
{
if (features->hasAlphaMask)
{
if (!shader->attachObject("lighting/lightFullbrightAlphaMaskF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightFullbrightF.glsl"))
{
return FALSE;
}
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
}
// NOTE order of shader object attaching is VERY IMPORTANT!!!
else if (features->isShiny)
{
if (features->hasWaterFog)
{
if (features->disableTextureIndex)
{
if (!shader->attachObject("lighting/lightShinyWaterNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightShinyWaterF.glsl"))
{
return FALSE;
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
else
{
if (features->disableTextureIndex)
{
if (!shader->attachObject("lighting/lightShinyNonIndexedF.glsl"))
{
return FALSE;
}
}
else
{
if (!shader->attachObject("lighting/lightShinyF.glsl"))
{
return FALSE;
}
shader->mFeatures.mIndexedTextureChannels = gGLManager.mNumTextureImageUnits-1;
}
}
}
return TRUE;
}
//============================================================================
// Load Shader
static std::string get_object_log(GLhandleARB ret)
{
std::string res;
//get log length
GLint length;
glGetObjectParameterivARB(ret, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);
if (length > 0)
{
//the log could be any size, so allocate appropriately
GLcharARB* log = new GLcharARB[length];
glGetInfoLogARB(ret, length, &length, log);
res = std::string((char *)log);
delete[] log;
}
return res;
}
void LLShaderMgr::dumpObjectLog(GLhandleARB ret, BOOL warns)
{
std::string log = get_object_log(ret);
if ( log.length() > 0 )
{
if (warns)
{
LL_WARNS("ShaderLoading") << log << LL_ENDL;
}
else
{
LL_INFOS("ShaderLoading") << log << LL_ENDL;
}
}
}
GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shader_level, GLenum type, S32 texture_index_channels)
{
GLenum error = GL_NO_ERROR;
if (gDebugGL)
{
error = glGetError();
if (error != GL_NO_ERROR)
{
LL_WARNS("ShaderLoading") << "GL ERROR entering loadShaderFile(): " << error << LL_ENDL;
}
}
LL_DEBUGS("ShaderLoading") << "Loading shader file: " << filename << " class " << shader_level << LL_ENDL;
if (filename.empty())
{
return 0;
}
//read in from file
LLFILE* file = NULL;
S32 try_gpu_class = shader_level;
S32 gpu_class;
//find the most relevant file
for (gpu_class = try_gpu_class; gpu_class > 0; gpu_class--)
{ //search from the current gpu class down to class 1 to find the most relevant shader
std::stringstream fname;
fname << getShaderDirPrefix();
fname << gpu_class << "/" << filename;
LL_DEBUGS("ShaderLoading") << "Looking in " << fname.str() << LL_ENDL;
file = LLFile::fopen(fname.str(), "r"); /* Flawfinder: ignore */
if (file)
{
LL_INFOS("ShaderLoading") << "Loading file: shaders/class" << gpu_class << "/" << filename << " (Want class " << gpu_class << ")" << LL_ENDL;
break; // done
}
}
if (file == NULL)
{
LL_WARNS("ShaderLoading") << "GLSL Shader file not found: " << filename << LL_ENDL;
return 0;
}
//we can't have any lines longer than 1024 characters
//or any shaders longer than 1024 lines... deal - DaveP
GLcharARB buff[1024] = {0};
GLcharARB* text[1024] = {0};
GLuint count = 0;
if (gGLManager.mGLVersion < 2.1f)
{
text[count++] = strdup("#version 110\n");
}
else if (gGLManager.mGLVersion < 3.f)
{
//set version to 1.20
text[count++] = strdup("#version 120\n");
}
else
{ //set version to 1.30
text[count++] = strdup("#version 130\n");
}
//copy preprocessor definitions into buffer
for (std::map<std::string,std::string>::iterator iter = mDefinitions.begin(); iter != mDefinitions.end(); ++iter)
{
std::string define = "#define " + iter->first + " " + iter->second + "\n";
text[count++] = (GLcharARB *) strdup(define.c_str());
}
if (texture_index_channels > 0 && type == GL_FRAGMENT_SHADER_ARB)
{
//use specified number of texture channels for indexed texture rendering
/* prepend shader code that looks like this:
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
.
.
.
uniform sampler2D texN;
varying float vary_texture_index;
vec4 diffuseLookup(vec2 texcoord)
{
switch (int(vary_texture_index+0.25))
{
case 0: return texture2D(tex0, texcoord);
case 1: return texture2D(tex1, texcoord);
case 2: return texture2D(tex2, texcoord);
.
.
.
case N: return texture2D(texN, texcoord);
}
return vec4(0,0,0,0);
}
*/
//uniform declartion
for (S32 i = 0; i < texture_index_channels; ++i)
{
std::string decl = llformat("uniform sampler2D tex%d;\n", i);
text[count++] = strdup(decl.c_str());
}
text[count++] = strdup("varying float vary_texture_index;\n");
text[count++] = strdup("vec4 diffuseLookup(vec2 texcoord)\n");
text[count++] = strdup("{\n");
if (texture_index_channels == 1)
{ //don't use flow control, that's silly
text[count++] = strdup("return texture2D(tex0, texcoord);\n");
text[count++] = strdup("}\n");
}
else if (gGLManager.mGLVersion >= 3.f)
{
text[count++] = strdup("\tswitch (int(vary_texture_index+0.25))\n");
text[count++] = strdup("\t{\n");
//switch body
for (S32 i = 0; i < texture_index_channels; ++i)
{
std::string case_str = llformat("\t\tcase %d: return texture2D(tex%d, texcoord);\n", i, i);
text[count++] = strdup(case_str.c_str());
}
text[count++] = strdup("\t}\n");
text[count++] = strdup("\treturn vec4(0,0,0,0);\n");
text[count++] = strdup("}\n");
}
else
{
//switches aren't supported, make block that looks like:
/*
int ti = int(vary_texture_index+0.25);
if (ti == 0) return texture2D(tex0, texcoord);
if (ti == 1) return texture2D(tex1, texcoord);
.
.
.
if (ti == N) return texture2D(texN, texcoord);
*/
text[count++] = strdup("int ti = int(vary_texture_index+0.25);\n");
for (S32 i = 0; i < texture_index_channels; ++i)
{
std::string if_str = llformat("if (ti == %d) return texture2D(tex%d, texcoord);\n", i, i);
text[count++] = strdup(if_str.c_str());
}
text[count++] = strdup("\treturn vec4(0,0,0,0);\n");
text[count++] = strdup("}\n");
}
}
//copy file into memory
while( fgets((char *)buff, 1024, file) != NULL && count < LL_ARRAY_SIZE(buff) )
{
text[count++] = (GLcharARB *)strdup((char *)buff);
}
fclose(file);
//create shader object
GLhandleARB ret = glCreateShaderObjectARB(type);
if (gDebugGL)
{
error = glGetError();
if (error != GL_NO_ERROR)
{
LL_WARNS("ShaderLoading") << "GL ERROR in glCreateShaderObjectARB: " << error << LL_ENDL;
glDeleteObjectARB(ret); //no longer need handle
}
}
//load source
glShaderSourceARB(ret, count, (const GLcharARB**) text, NULL);
if (gDebugGL)
{
error = glGetError();
if (error != GL_NO_ERROR)
{
LL_WARNS("ShaderLoading") << "GL ERROR in glShaderSourceARB: " << error << LL_ENDL;
glDeleteObjectARB(ret); //no longer need handle
}
}
//compile source
glCompileShaderARB(ret);
if (gDebugGL)
{
error = glGetError();
if (error != GL_NO_ERROR)
{
LL_WARNS("ShaderLoading") << "GL ERROR in glCompileShaderARB: " << error << LL_ENDL;
glDeleteObjectARB(ret); //no longer need handle
}
}
if (error == GL_NO_ERROR)
{
//check for errors
GLint success = GL_TRUE;
glGetObjectParameterivARB(ret, GL_OBJECT_COMPILE_STATUS_ARB, &success);
if (gDebugGL || success == GL_FALSE)
{
error = glGetError();
if (error != GL_NO_ERROR || success == GL_FALSE)
{
//an error occured, print log
LL_WARNS("ShaderLoading") << "GLSL Compilation Error: (" << error << ") in " << filename << LL_ENDL;
dumpObjectLog(ret);
std::stringstream ostr;
//dump shader source for debugging
for (GLuint i = 0; i < count; i++)
{
ostr << i << ": " << text[i];
}
LL_WARNS("ShaderLoading") << "\n" << ostr.str() << llendl;
glDeleteObjectARB(ret); //no longer need handle
ret = 0;
}
}
}
else
{
ret = 0;
}
stop_glerror();
//free memory
for (GLuint i = 0; i < count; i++)
{
free(text[i]);
}
//successfully loaded, save results
if (ret)
{
// Add shader file to map
mShaderObjects[filename] = ret;
shader_level = try_gpu_class;
}
else
{
if (shader_level > 1)
{
shader_level--;
return loadShaderFile(filename,shader_level,type,texture_index_channels);
}
LL_WARNS("ShaderLoading") << "Failed to load " << filename << LL_ENDL;
}
return ret;
}
BOOL LLShaderMgr::linkProgramObject(GLhandleARB obj, BOOL suppress_errors)
{
//check for errors
glLinkProgramARB(obj);
GLint success = GL_TRUE;
glGetObjectParameterivARB(obj, GL_OBJECT_LINK_STATUS_ARB, &success);
if (!suppress_errors && success == GL_FALSE)
{
//an error occured, print log
LL_WARNS("ShaderLoading") << "GLSL Linker Error:" << LL_ENDL;
}
// NOTE: Removing LL_DARWIN block as it doesn't seem to actually give the correct answer,
// but want it for reference once I move it.
#if 0
// Force an evaluation of the gl state so the driver can tell if the shader will run in hardware or software
// per Apple's suggestion
glBegin(gGL.mMode);
glEnd();
// Query whether the shader can or cannot run in hardware
// http://developer.apple.com/qa/qa2007/qa1502.html
long vertexGPUProcessing;
CGLContextObj ctx = CGLGetCurrentContext();
CGLGetParameter (ctx, kCGLCPGPUVertexProcessing, &vertexGPUProcessing);
long fragmentGPUProcessing;
CGLGetParameter (ctx, kCGLCPGPUFragmentProcessing, &fragmentGPUProcessing);
if (!fragmentGPUProcessing || !vertexGPUProcessing)
{
LL_WARNS("ShaderLoading") << "GLSL Linker: Running in Software:" << LL_ENDL;
success = GL_FALSE;
suppress_errors = FALSE;
}
#else
std::string log = get_object_log(obj);
LLStringUtil::toLower(log);
if (log.find("software") != std::string::npos)
{
LL_WARNS("ShaderLoading") << "GLSL Linker: Running in Software:" << LL_ENDL;
success = GL_FALSE;
suppress_errors = FALSE;
}
#endif
if (!suppress_errors)
{
dumpObjectLog(obj, !success);
}
return success;
}
BOOL LLShaderMgr::validateProgramObject(GLhandleARB obj)
{
//check program validity against current GL
glValidateProgramARB(obj);
GLint success = GL_TRUE;
glGetObjectParameterivARB(obj, GL_OBJECT_VALIDATE_STATUS_ARB, &success);
if (success == GL_FALSE)
{
LL_WARNS("ShaderLoading") << "GLSL program not valid: " << LL_ENDL;
dumpObjectLog(obj);
}
else
{
dumpObjectLog(obj, FALSE);
}
return success;
}