Skip to content
Snippets Groups Projects
llshadermgr.cpp 39.3 KiB
Newer Older
/** 
 * @file llshadermgr.cpp
 * @brief Shader manager implementation.
 *
 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llshadermgr.h"
#include "llrender.h"
Graham Linden's avatar
Graham Linden committed
#include "llfile.h"

// 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()
{
Graham Linden's avatar
Graham Linden committed
	if(NULL == sInstance)
	{
		LL_ERRS("Shaders") << "LLShaderMgr should already have been instantiated by the application!" << LL_ENDL;
	}
Graham Linden's avatar
Graham Linden committed
	return sInstance;
}

BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
{
Graham Linden's avatar
Graham Linden committed
	llassert_always(shader != NULL);
	LLShaderFeatures *features = & shader->mFeatures;

	if (features->attachNothing)
	{
		return TRUE;
	}
	//////////////////////////////////////
	// Attach Vertex Shader Features First
	//////////////////////////////////////
	
	// NOTE order of shader object attaching is VERY IMPORTANT!!!
	if (features->calculatesAtmospherics)
	{
		if (features->hasWaterFog)
		{
			if (!shader->attachVertexObject("windlight/atmosphericsVarsWaterV.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
		}
        else if (!shader->attachVertexObject("windlight/atmosphericsVarsV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->calculatesLighting || features->calculatesAtmospherics)
	{
		if (!shader->attachVertexObject("windlight/atmosphericsHelpersV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
		
	if (features->calculatesLighting)
	{
		if (features->isSpecular)
		{
            if (!shader->attachVertexObject("lighting/lightFuncSpecularV.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
		
			if (!features->isAlphaLighting)
			{
                if (!shader->attachVertexObject("lighting/sumLightsSpecularV.glsl"))
            if (!shader->attachVertexObject("lighting/lightSpecularV.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
		}
		else 
		{
            if (!shader->attachVertexObject("lighting/lightFuncV.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
			
			if (!features->isAlphaLighting)
			{
                if (!shader->attachVertexObject("lighting/sumLightsV.glsl"))
            if (!shader->attachVertexObject("lighting/lightV.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
		}
	}
	
	// NOTE order of shader object attaching is VERY IMPORTANT!!!
	if (features->calculatesAtmospherics)
    {
        if (!shader->attachVertexObject("windlight/atmosphericsFuncs.glsl")) {
            return FALSE;
        }

        if (!shader->attachVertexObject("windlight/atmosphericsV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->hasSkinning)
	{
        if (!shader->attachVertexObject("avatar/avatarSkinV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->hasObjectSkinning)
	{
        if (!shader->attachVertexObject("avatar/objectSkinV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
	
	///////////////////////////////////////
	// Attach Fragment Shader Features Next
	///////////////////////////////////////
// NOTE order of shader object attaching is VERY IMPORTANT!!!

Graham Linden's avatar
Graham Linden committed
	if(features->calculatesAtmospherics)
	{
		if (features->hasWaterFog)
		{
			if (!shader->attachFragmentObject("windlight/atmosphericsVarsWaterF.glsl"))
Graham Linden's avatar
Graham Linden committed
			{
				return FALSE;
			}
		}
        else if (!shader->attachFragmentObject("windlight/atmosphericsVarsF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
    if (features->calculatesLighting || features->calculatesAtmospherics)
        if (!shader->attachFragmentObject("windlight/atmosphericsHelpersF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
    // we want this BEFORE shadows and AO because those facilities use pos/norm access
        if (!shader->attachFragmentObject("deferred/deferredUtil.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
        if (!shader->attachFragmentObject("deferred/shadowUtil.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

    if (features->hasAmbientOcclusion)
        if (!shader->attachFragmentObject("deferred/aoUtil.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
    if (features->hasIndirect)
        if (!shader->attachFragmentObject("deferred/indirect.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->hasGamma)
	{
        if (!shader->attachFragmentObject("windlight/gammaF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->hasSrgb)
	{
        if (!shader->attachFragmentObject("environment/srgbF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
        if (!shader->attachFragmentObject("environment/encodeNormF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	if (features->hasAtmospherics)
    {
        if (!shader->attachFragmentObject("windlight/atmosphericsFuncs.glsl")) {
            return FALSE;
        }

        if (!shader->attachFragmentObject("windlight/atmosphericsF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
	
	if (features->hasTransport)
	{
        if (!shader->attachFragmentObject("windlight/transportF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			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->attachFragmentObject("environment/waterFogF.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
	
	if (features->hasLighting)
	{
		if (features->hasWaterFog)
		{
			if (features->disableTextureIndex)
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightWaterAlphaMaskNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightWaterNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
			}
			else 
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightWaterAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightWaterF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
		
		else
		{
			if (features->disableTextureIndex)
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightAlphaMaskNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
			}
			else 
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
	}
	
	// NOTE order of shader object attaching is VERY IMPORTANT!!!
	else if (features->isFullbright)
	{
	
		if (features->isShiny && features->hasWaterFog)
		{
			if (features->disableTextureIndex)
			{
                if (!shader->attachFragmentObject("lighting/lightFullbrightShinyWaterNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
			}
			else 
			{
                if (!shader->attachFragmentObject("lighting/lightFullbrightShinyWaterF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
		else if (features->hasWaterFog)
		{
			if (features->disableTextureIndex)
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightWaterNonIndexedAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
                else if (!shader->attachFragmentObject("lighting/lightFullbrightWaterNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
			}
			else 
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightWaterAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
                else if (!shader->attachFragmentObject("lighting/lightFullbrightWaterF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
		
		else if (features->isShiny)
		{
			if (features->disableTextureIndex)
			{
                if (!shader->attachFragmentObject("lighting/lightFullbrightShinyNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
			}
			else 
			{
                if (!shader->attachFragmentObject("lighting/lightFullbrightShinyF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
		
		else
		{
			if (features->disableTextureIndex)
			{

				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightNonIndexedAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
			}
			else 
			{
				if (features->hasAlphaMask)
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightAlphaMaskF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				else
				{
                    if (!shader->attachFragmentObject("lighting/lightFullbrightF.glsl"))
Graham Linden's avatar
Graham Linden committed
					{
						return FALSE;
					}
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
	}

	// NOTE order of shader object attaching is VERY IMPORTANT!!!
	else if (features->isShiny)
	{
	
		if (features->hasWaterFog)
		{
			if (features->disableTextureIndex)
			{
                if (!shader->attachFragmentObject("lighting/lightShinyWaterNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
			}
			else 
			{
                if (!shader->attachFragmentObject("lighting/lightShinyWaterF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
		
		else 
		{
			if (features->disableTextureIndex)
			{
                if (!shader->attachFragmentObject("lighting/lightShinyNonIndexedF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
			}
			else 
			{
                if (!shader->attachFragmentObject("lighting/lightShinyF.glsl"))
Graham Linden's avatar
Graham Linden committed
				{
					return FALSE;
				}
				shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
			}
		}
	}

	if (features->mIndexedTextureChannels <= 1)
	{
		if (!shader->attachVertexObject("objects/nonindexedTextureV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}
	else
	{
        if (!shader->attachVertexObject("objects/indexedTextureV.glsl"))
Graham Linden's avatar
Graham Linden committed
		{
			return FALSE;
		}
	}

	return TRUE;
}

//============================================================================
// Load Shader

Rye Mutt's avatar
Rye Mutt committed
static std::string get_shader_log(GLuint ret)
Graham Linden's avatar
Graham Linden committed
	std::string res;
	
	//get log length 
	GLint length;
Rye Mutt's avatar
Rye Mutt committed
	glGetShaderiv(ret, GL_INFO_LOG_LENGTH, &length);
Graham Linden's avatar
Graham Linden committed
	if (length > 0)
	{
		//the log could be any size, so allocate appropriately
Rye Mutt's avatar
Rye Mutt committed
		GLchar* log = new GLchar[length];
		glGetShaderInfoLog(ret, length, &length, log);
Graham Linden's avatar
Graham Linden committed
		res = std::string((char *)log);
		delete[] log;
	}
	return res;
Rye Mutt's avatar
Rye Mutt committed
static std::string get_program_log(GLuint ret)
{
	std::string res;

	//get log length 
	GLint length;
	glGetProgramiv(ret, GL_INFO_LOG_LENGTH, &length);
	if (length > 0)
	{
		//the log could be any size, so allocate appropriately
		GLchar* log = new GLchar[length];
		glGetProgramInfoLog(ret, length, &length, log);
		res = std::string((char*)log);
		delete[] log;
	}
	return res;
}

Graham Linden's avatar
Graham Linden committed
//dump shader source for debugging
Rye Mutt's avatar
Rye Mutt committed
void LLShaderMgr::dumpShaderSource(U32 shader_code_count, GLchar** shader_code_text)
	char num_str[16]; // U32 = max 10 digits

	LL_SHADER_LOADING_WARNS() << "\n";
		snprintf(num_str, sizeof(num_str), "%4d: ", i+1);
		std::string line_number(num_str);
		LL_CONT << line_number << shader_code_text[i];
    LL_CONT << LL_ENDL;
Graham Linden's avatar
Graham Linden committed
}

Rye Mutt's avatar
Rye Mutt committed
void LLShaderMgr::dumpObjectLog(bool is_program, GLuint ret, BOOL warns, const std::string& filename)
Rye Mutt's avatar
Rye Mutt committed
	std::string log = is_program ? get_program_log(ret) : get_shader_log(ret);
    std::string fname = filename;
    if (filename.empty())
    {
        fname = "unknown shader file";
    }
Graham Linden's avatar
Graham Linden committed
	if (log.length() > 0)
	{
        LL_SHADER_LOADING_WARNS() << "Shader loading from " << fname << LL_ENDL;
        LL_SHADER_LOADING_WARNS() << "\n" << log << LL_ENDL;
Rye Mutt's avatar
Rye Mutt committed
GLuint LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shader_level, GLenum type, boost::unordered_map<std::string, std::string>* defines, S32 texture_index_channels)

// endsure work-around for missing GLSL funcs gets propogated to feature shader files (e.g. srgbF.glsl)
#if LL_DARWIN
    if (defines)
    {
        (*defines)["OLD_SELECT"] = "1";
    }
#endif

Graham Linden's avatar
Graham Linden committed
	GLenum error = GL_NO_ERROR;
	if (gDebugGL)
	{
		error = glGetError();
		if (error != GL_NO_ERROR)
		{
			LL_SHADER_LOADING_WARNS() << "GL ERROR entering loadShaderFile(): " << error << LL_ENDL;
		}
	}
	
	if (filename.empty()) 
	{
		return 0;
	}
Graham Linden's avatar
Graham Linden committed
	//read in from file
	LLFILE* file = NULL;
Graham Linden's avatar
Graham Linden committed
	S32 try_gpu_class = shader_level;
	S32 gpu_class;
Graham Linden's avatar
Graham Linden committed
    std::string open_file_name;
Graham Linden's avatar
Graham Linden committed
	//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;
		
Graham Linden's avatar
Graham Linden committed
        open_file_name = fname.str();

        /*
        Would be awesome, if we didn't have shaders that re-use files
        with different environments to say, add skinning, etc
        can't depend on cached version to have evaluate ifdefs identically...
        if we can define a deterministic hash for the shader based on
        all the inputs, maybe we can save some time here.
        if (mShaderObjects.count(filename) > 0)
        {
            return mShaderObjects[filename];
        }

        */

Graham Linden's avatar
Graham Linden committed
 		LL_DEBUGS("ShaderLoading") << "Looking in " << open_file_name << LL_ENDL;
		file = LLFile::fopen(open_file_name, "r");		/* Flawfinder: ignore */
		if (file)
		{
			LL_DEBUGS("ShaderLoading") << "Loading file: " << open_file_name << " (Want class " << gpu_class << ")" << LL_ENDL;            
			break; // done
		}
	}
	
	if (file == NULL)
	{
		LL_SHADER_LOADING_WARNS() << "GLSL Shader file not found: " << open_file_name << LL_ENDL;
		return 0;
	}

	//we can't have any lines longer than 1024 characters 
	//or any shaders longer than 4096 lines... deal - DaveP
Rye Mutt's avatar
Rye Mutt committed
    GLchar buff[1024];
	GLchar*extra_code_text[1024];
	GLchar*shader_code_text[4096 + LL_ARRAY_SIZE(extra_code_text)] = { NULL };
    GLuint extra_code_count = 0, shader_code_count = 0;
    BOOST_STATIC_ASSERT(LL_ARRAY_SIZE(extra_code_text) < LL_ARRAY_SIZE(shader_code_text));
    
    
Graham Linden's avatar
Graham Linden committed
	S32 major_version = gGLManager.mGLSLVersionMajor;
	S32 minor_version = gGLManager.mGLSLVersionMinor;
	
	if (major_version == 1 && minor_version < 30)
	{
		if (minor_version < 10)
		{
			//should NEVER get here -- if major version is 1 and minor version is less than 10, 
			// viewer should never attempt to use shaders, continuing will result in undefined behavior
			LL_ERRS() << "Unsupported GLSL Version." << LL_ENDL;
		}

		if (minor_version <= 19)
		{
			shader_code_text[shader_code_count++] = strdup("#version 110\n");
			extra_code_text[extra_code_count++] = strdup("#define ATTRIBUTE attribute\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING varying\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING_FLAT varying\n");
		}
		else if (minor_version <= 29)
		{
			//set version to 1.20
			shader_code_text[shader_code_count++] = strdup("#version 120\n");
       		extra_code_text[extra_code_count++] = strdup("#define FXAA_GLSL_120 1\n");
			extra_code_text[extra_code_count++] = strdup("#define FXAA_FAST_PIXEL_OFFSET 0\n");
			extra_code_text[extra_code_count++] = strdup("#define ATTRIBUTE attribute\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING varying\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING_FLAT varying\n");
		}
	}
	else
	{  
Graham Linden's avatar
Graham Linden committed
        if (major_version >= 4)
        {
            //set version to 400
			shader_code_text[shader_code_count++] = strdup("#version 400\n");
        }
        else if (major_version == 3)
        {
            if (minor_version < 10)
		    {
			    shader_code_text[shader_code_count++] = strdup("#version 300\n");
		    }
		    else if (minor_version <= 19)
		    {
			    shader_code_text[shader_code_count++] = strdup("#version 310\n");
		    }
		    else if (minor_version <= 29)
		    {
			    shader_code_text[shader_code_count++] = strdup("#version 320\n");
		    }
            else
            {
                shader_code_text[shader_code_count++] = strdup("#version 330\n");
            }
        }
		else
Graham Linden's avatar
Graham Linden committed
		{
			//set version to 1.30
			shader_code_text[shader_code_count++] = strdup("#version 130\n");
			//some implementations of GLSL 1.30 require integer precision be explicitly declared
			extra_code_text[extra_code_count++] = strdup("precision mediump int;\n");
			extra_code_text[extra_code_count++] = strdup("precision highp float;\n");
		}

		extra_code_text[extra_code_count++] = strdup("#define DEFINE_GL_FRAGCOLOR 1\n");
		extra_code_text[extra_code_count++] = strdup("#define FXAA_GLSL_130 1\n");

		extra_code_text[extra_code_count++] = strdup("#define ATTRIBUTE in\n");

Rye Mutt's avatar
Rye Mutt committed
		if (type == GL_VERTEX_SHADER)
Graham Linden's avatar
Graham Linden committed
		{ //"varying" state is "out" in a vertex program, "in" in a fragment program 
			// ("varying" is deprecated after version 1.20)
			extra_code_text[extra_code_count++] = strdup("#define VARYING out\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING_FLAT flat out\n");
		}
		else
		{
			extra_code_text[extra_code_count++] = strdup("#define VARYING in\n");
			extra_code_text[extra_code_count++] = strdup("#define VARYING_FLAT flat in\n");
		}

		//backwards compatibility with legacy texture lookup syntax
		extra_code_text[extra_code_count++] = strdup("#define texture2D texture\n");
		extra_code_text[extra_code_count++] = strdup("#define textureCube texture\n");
		extra_code_text[extra_code_count++] = strdup("#define texture2DLod textureLod\n");
		extra_code_text[extra_code_count++] = strdup("#define shadow2D(a,b) vec2(texture(a,b))\n");
		
		if (major_version > 1 || minor_version >= 40)
		{ //GLSL 1.40 replaces texture2DRect et al with texture
			extra_code_text[extra_code_count++] = strdup("#define texture2DRect texture\n");
			extra_code_text[extra_code_count++] = strdup("#define shadow2DRect(a,b) vec2(texture(a,b))\n");
		}
	}
	
	if (defines)
	{
		for (boost::unordered_map<std::string,std::string>::iterator iter = defines->begin(); iter != defines->end(); ++iter)
		{
			std::string define = "#define " + iter->first + " " + iter->second + "\n";
Rye Mutt's avatar
Rye Mutt committed
			extra_code_text[extra_code_count++] = (GLchar*) strdup(define.c_str());
Graham Linden's avatar
Graham Linden committed
		}
	}

	if( gGLManager.mIsATI )
	{
		extra_code_text[extra_code_count++] = strdup( "#define IS_AMD_CARD 1\n" );
	}
	
Rye Mutt's avatar
Rye Mutt committed
	if (texture_index_channels > 0 && type == GL_FRAGMENT_SHADER)
Graham Linden's avatar
Graham Linden committed
	{
		//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_FLAT ivec4 vary_texture_index;

		vec4 ret = vec4(1,0,1,1);

		vec4 diffuseLookup(vec2 texcoord)
		{
			switch (vary_texture_index.r))
			{
				case 0: ret = texture2D(tex0, texcoord); break;
				case 1: ret = texture2D(tex1, texcoord); break;
				case 2: ret = texture2D(tex2, texcoord); break;
				.
				.
				.
				case N: return texture2D(texN, texcoord); break;
			}

			return ret;
		}
		*/

		extra_code_text[extra_code_count++] = strdup("#define HAS_DIFFUSE_LOOKUP\n");

		//uniform declartion
		for (S32 i = 0; i < texture_index_channels; ++i)
		{
			std::string decl = llformat("uniform sampler2D tex%d;\n", i);
Graham Linden's avatar
Graham Linden committed
			extra_code_text[extra_code_count++] = strdup(decl.c_str());
		}

		if (texture_index_channels > 1)
		{
			extra_code_text[extra_code_count++] = strdup("VARYING_FLAT int vary_texture_index;\n");
		}

		extra_code_text[extra_code_count++] = strdup("vec4 diffuseLookup(vec2 texcoord)\n");
		extra_code_text[extra_code_count++] = strdup("{\n");
		
		
		if (texture_index_channels == 1)
		{ //don't use flow control, that's silly
			extra_code_text[extra_code_count++] = strdup("return texture2D(tex0, texcoord);\n");
			extra_code_text[extra_code_count++] = strdup("}\n");
		}
		else if (major_version > 1 || minor_version >= 30)
		{  //switches are supported in GLSL 1.30 and later
			if (gGLManager.mIsNVIDIA)
			{ //switches are unreliable on some NVIDIA drivers
				for (U32 i = 0; i < texture_index_channels; ++i)
				{
					std::string if_string = llformat("\t%sif (vary_texture_index == %d) { return texture2D(tex%d, texcoord); }\n", i > 0 ? "else " : "", i, i); 
Graham Linden's avatar
Graham Linden committed
					extra_code_text[extra_code_count++] = strdup(if_string.c_str());
				}
				extra_code_text[extra_code_count++] = strdup("\treturn vec4(1,0,1,1);\n");
				extra_code_text[extra_code_count++] = strdup("}\n");
			}
			else
			{
				extra_code_text[extra_code_count++] = strdup("\tvec4 ret = vec4(1,0,1,1);\n");
				extra_code_text[extra_code_count++] = strdup("\tswitch (vary_texture_index)\n");
				extra_code_text[extra_code_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);
Graham Linden's avatar
Graham Linden committed
					extra_code_text[extra_code_count++] = strdup(case_str.c_str());
				}

				extra_code_text[extra_code_count++] = strdup("\t}\n");
				extra_code_text[extra_code_count++] = strdup("\treturn ret;\n");
				extra_code_text[extra_code_count++] = strdup("}\n");
			}
		}
		else
		{ //should never get here.  Indexed texture rendering requires GLSL 1.30 or later 
			// (for passing integers between vertex and fragment shaders)
			LL_ERRS() << "Indexed texture rendering requires GLSL 1.30 or later." << LL_ENDL;
		}
	}
Graham Linden's avatar
Graham Linden committed
	//copy file into memory
	enum {
		  flag_write_to_out_of_extra_block_area = 0x01
		, flag_extra_block_marker_was_found = 0x02
	};
	
	unsigned char flags = flag_write_to_out_of_extra_block_area;
	
	GLuint out_of_extra_block_counter = 0, start_shader_code = shader_code_count, file_lines_count = 0;
	
	while(NULL != fgets((char *)buff, 1024, file)
		  && shader_code_count < (LL_ARRAY_SIZE(shader_code_text) - LL_ARRAY_SIZE(extra_code_text)))
	{
		file_lines_count++;

		bool extra_block_area_found = NULL != strstr((const char*)buff, "[EXTRA_CODE_HERE]");
		
		if(extra_block_area_found && !(flag_extra_block_marker_was_found & flags))
		{
			if(!(flag_write_to_out_of_extra_block_area & flags))
			{
				//shift
				for(GLuint to = start_shader_code, from = extra_code_count + start_shader_code;
					from < shader_code_count; ++to, ++from)
				{
					shader_code_text[to] = shader_code_text[from];
				}
				
				shader_code_count -= extra_code_count;
			}
		  
			//copy extra code
			for(GLuint n = 0; n < extra_code_count
				&& shader_code_count < (LL_ARRAY_SIZE(shader_code_text) - LL_ARRAY_SIZE(extra_code_text)); ++n)
			{
				shader_code_text[shader_code_count++] = extra_code_text[n];
			}
			
			extra_code_count = 0;
			
			flags &= ~flag_write_to_out_of_extra_block_area;
			flags |= flag_extra_block_marker_was_found;
		}
Rye Mutt's avatar
Rye Mutt committed
            shader_code_text[shader_code_count] = (GLchar*)strdup((char *)buff);
            if(flag_write_to_out_of_extra_block_area & flags)
            {
                shader_code_text[extra_code_count + start_shader_code + out_of_extra_block_counter]
                    = shader_code_text[shader_code_count];
                out_of_extra_block_counter++;
                if(out_of_extra_block_counter == extra_code_count)
                {
                    shader_code_count += extra_code_count;
                    flags &= ~flag_write_to_out_of_extra_block_area;
                }
            }
            ++shader_code_count;
Graham Linden's avatar
Graham Linden committed
		}
	} //while
	
	if(!(flag_extra_block_marker_was_found & flags))
	{
		for(GLuint n = start_shader_code; n < extra_code_count + start_shader_code; ++n)
		{
			shader_code_text[n] = extra_code_text[n - start_shader_code];
		}
		
		if (file_lines_count < extra_code_count)
		{
			shader_code_count += extra_code_count;
		}

		extra_code_count = 0;
	}

	fclose(file);

	//create shader object
Rye Mutt's avatar
Rye Mutt committed
	GLuint ret = glCreateShader(type);
Graham Linden's avatar
Graham Linden committed
	if (gDebugGL)
	{
		error = glGetError();
		if (error != GL_NO_ERROR)
		{
Rye Mutt's avatar
Rye Mutt committed
			LL_WARNS("ShaderLoading") << "GL ERROR in glCreateShader: " << error << LL_ENDL;
Graham Linden's avatar
Graham Linden committed
		}
	}
	
	//load source
Rye Mutt's avatar
Rye Mutt committed
	glShaderSource(ret, shader_code_count, (const GLchar**) shader_code_text, NULL);
Graham Linden's avatar
Graham Linden committed

	if (gDebugGL)
	{
		error = glGetError();
		if (error != GL_NO_ERROR)
		{
Rye Mutt's avatar
Rye Mutt committed
			LL_WARNS("ShaderLoading") << "GL ERROR in glShaderSource: " << error << LL_ENDL;