/*
Copyright (C) 2002-2003 Victor Luchits

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_shader.c - based on code by Stephen C. Taylor
// Ported to FTE from qfusion, there are numerous changes since then.


#include "quakedef.h"
#ifndef SERVERONLY
#include "glquake.h"
#include "shader.h"

#include "hash.h"


#include <ctype.h>

extern texid_t missing_texture;
texid_t r_whiteimage;
static qboolean shader_reload_needed;
static qboolean shader_rescan_needed;
static char **saveshaderbody;

sh_config_t sh_config;

//cvars that affect shader generation
cvar_t r_vertexlight = CVARFD("r_vertexlight", "0", CVAR_SHADERSYSTEM, "Hack loaded shaders to remove detail pass and lightmap sampling for faster rendering.");
extern cvar_t r_glsl_offsetmapping_reliefmapping;
extern cvar_t r_deluxemapping;
extern cvar_t r_fastturb, r_fastsky, r_skyboxname, r_softwarebanding;
extern cvar_t r_drawflat;
extern cvar_t r_shaderblobs;

//backend fills this in to say the max pass count
int be_maxpasses;


#define Q_stricmp stricmp
#define Q_strnicmp strnicmp
#define clamp(v,min, max) (v) = (((v)<(min))?(min):(((v)>(max))?(max):(v)));

typedef union {
	float			f;
	unsigned int	i;
} float_int_t;
qbyte FloatToByte( float x )
{
	static float_int_t f2i;

	// shift float to have 8bit fraction at base of number
	f2i.f = x + 32768.0f;

	// then read as integer and kill float bits...
	return (qbyte) min(f2i.i & 0x7FFFFF, 255);
}



cvar_t r_detailtextures;

#define MAX_TOKEN_CHARS sizeof(com_token)

char *COM_ParseExt (char **data_p, qboolean nl, qboolean comma)
{
	int		c;
	int		len;
	char	*data;
	qboolean newlines = false;

	data = *data_p;
	len = 0;
	com_token[0] = 0;

	if (!data)
	{
		*data_p = NULL;
		return "";
	}

// skip whitespace
skipwhite:
	while ((c = *data) <= ' ')
	{
		if (c == 0)
		{
			*data_p = NULL;
			return "";
		}
		if (c == '\n')
			newlines = true;
		data++;
	}

	if (newlines && !nl)
	{
		*data_p = data;
		return com_token;
	}

// skip // comments
	if (c == '/' && data[1] == '/')
	{
		while (*data && *data != '\n')
			data++;
		goto skipwhite;
	}

// handle quoted strings specially
	if (c == '\"')
	{
		data++;
		while (1)
		{
			c = *data++;
			if (c=='\"' || !c)
			{
				com_token[len] = 0;
				*data_p = data;
				return com_token;
			}
			if (len < MAX_TOKEN_CHARS)
			{
				com_token[len] = c;
				len++;
			}
		}
	}

// parse a regular word
	do
	{
		if (len < MAX_TOKEN_CHARS)
		{
			com_token[len] = c;
			len++;
		}
		data++;
		c = *data;
		if (c == ',' && len && comma)
			break;
	} while (c>32);

	if (len == MAX_TOKEN_CHARS)
	{
		Con_DPrintf ("Token exceeded %i chars, discarded.\n", (int)MAX_TOKEN_CHARS);
		len = 0;
	}
	com_token[len] = 0;

	*data_p = data;
	return com_token;
}

static float Shader_FloatArgument(shader_t *shader, char *arg)
{
	char *var;
	int arglen = strlen(arg);

	//grab an argument instead, otherwise 0
	var = shader->name;
	while((var = strchr(var, '#')))
	{
		if (!strnicmp(var, arg, arglen))
		{
			if (var[arglen] == '=')
				return strtod(var+arglen+1, NULL);
			if (var[arglen] == '#' || !var[arglen])
				return 1;	//present, but no value
		}
		var++;
	}
	return 0;	//not present.
}





#define HASH_SIZE	128

enum shaderparsemode_e
{
	SPM_DEFAULT, /*quake3/fte internal*/
	SPM_DOOM3,
};

static struct
{
	enum shaderparsemode_e mode;
} parsestate;

typedef struct shaderkey_s
{
    char			*keyword;
    void			(*func)( shader_t *shader, shaderpass_t *pass, char **ptr );
	char			*prefix;
} shaderkey_t;
typedef struct shadercachefile_s {
	char *data;
	size_t length;
	enum shaderparsemode_e parsemode;
	struct shadercachefile_s *next;
	char name[1];
} shadercachefile_t;
typedef struct shadercache_s {
	shadercachefile_t *source;
	size_t offset;
	struct shadercache_s *hash_next;
	char name[1];
} shadercache_t;

static shadercachefile_t *shaderfiles;	//contents of a .shader file
static shadercache_t **shader_hash;		//locations of known inactive shaders.

unsigned int r_numshaders;	//number of active slots in r_shaders array.
unsigned int r_maxshaders;	//max length of r_shaders array. resized if exceeded.
shader_t	**r_shaders;	//list of active shaders for a id->shader lookup
static hashtable_t shader_active_hash;	//list of active shaders for a name->shader lookup
void *shader_active_hash_mem;

//static char		r_skyboxname[MAX_QPATH];
//static float	r_skyheight;

char *Shader_Skip( char *ptr );
static qboolean Shader_Parsetok(shader_t *shader, shaderpass_t *pass, shaderkey_t *keys, char *token, char **ptr);
static void Shader_ParseFunc(shader_t *shader, char **args, shaderfunc_t *func);
static void Shader_MakeCache(const char *path);
static qboolean Shader_LocateSource(char *name, char **buf, size_t *bufsize, size_t *offset, enum shaderparsemode_e *parsemode);
static void Shader_ReadShader(shader_t *s, char *shadersource, int parsemode);
static qboolean Shader_ParseShader(char *parsename, shader_t *s);

//===========================================================================

static qboolean Shader_EvaluateCondition(shader_t *shader, char **ptr)
{
	char *token;
	cvar_t *cv;
	qboolean conditiontrue = true;
	token = COM_ParseExt(ptr, false, false);
	if (*token == '!')
	{
		conditiontrue = false;
		token++;
	}
	if (*token == '$')
	{
		token++;
		if (*token == '#')
			conditiontrue = conditiontrue == !!Shader_FloatArgument(shader, token);
		else if (!Q_stricmp(token, "lpp"))
			conditiontrue = conditiontrue == r_lightprepass.ival;
		else if (!Q_stricmp(token, "lightmap"))
			conditiontrue = conditiontrue == !r_fullbright.value;
		else if (!Q_stricmp(token, "deluxmap"))
			conditiontrue = conditiontrue == r_deluxemapping.ival;

		//normalmaps are generated if they're not already known.
		else if (!Q_stricmp(token, "normalmap"))
			conditiontrue = conditiontrue == r_loadbumpmapping;

		else if (!Q_stricmp(token, "opengl"))
			conditiontrue = conditiontrue == (qrenderer == QR_OPENGL);
		else if (!Q_stricmp(token, "d3d9"))
			conditiontrue = conditiontrue == (qrenderer == QR_DIRECT3D9);
		else if (!Q_stricmp(token, "d3d11"))
			conditiontrue = conditiontrue == (qrenderer == QR_DIRECT3D11);
		else if (!Q_stricmp(token, "gles"))
			conditiontrue = conditiontrue == ((qrenderer == QR_OPENGL) && sh_config.minver == 100);
		else if (!Q_stricmp(token, "nofixed"))
			conditiontrue = conditiontrue == sh_config.progs_required;
		else if (!Q_stricmp(token, "glsl"))
			conditiontrue = conditiontrue == ((qrenderer == QR_OPENGL) && sh_config.progs_supported);
		else if (!Q_stricmp(token, "hlsl"))
			conditiontrue = conditiontrue == ((qrenderer == QR_DIRECT3D9 || qrenderer == QR_DIRECT3D11) && sh_config.progs_supported);	
		else if (!Q_stricmp(token, "haveprogram"))
			conditiontrue = conditiontrue == !!shader->prog;
		else if (!Q_stricmp(token, "programs"))
			conditiontrue = conditiontrue == sh_config.progs_supported;
		else if (!Q_stricmp(token, "diffuse"))
			conditiontrue = conditiontrue == true;
		else if (!Q_stricmp(token, "specular"))
			conditiontrue = conditiontrue == false;
		else if (!Q_stricmp(token, "fullbright"))
			conditiontrue = conditiontrue == false;
		else if (!Q_stricmp(token, "topoverlay"))
			conditiontrue = conditiontrue == false;
		else if (!Q_stricmp(token, "loweroverlay"))
			conditiontrue = conditiontrue == false;
		else
		{
			Con_Printf("Unrecognised builtin shader condition '%s'\n", token);
			conditiontrue = conditiontrue == false;
		}
	}
	else
	{
		float lhs;
		if (*token >= '0' && *token <= '9')
			lhs = strtod(token, NULL);
		else
		{
			cv = Cvar_Get(token, "", 0, "Shader Conditions");
			if (cv)
			{
				cv->flags |= CVAR_SHADERSYSTEM;
				lhs = cv->value;
			}
			else
			{
				Con_Printf("Shader_EvaluateCondition: '%s' is not a cvar\n", token);
				return conditiontrue;
			}
		}
		if (*token)
			token = COM_ParseExt(ptr, false, false);
		if (*token)
		{
			float rhs;
			char cmp[4];
			memcpy(cmp, token, 4);
			token = COM_ParseExt(ptr, false, false);
			rhs = atof(token);
			if (!strcmp(cmp, "!="))
				conditiontrue = lhs != rhs;
			else if (!strcmp(cmp, "=="))
				conditiontrue = lhs == rhs;
			else if (!strcmp(cmp, "<"))
				conditiontrue = lhs < rhs;
			else if (!strcmp(cmp, "<="))
				conditiontrue = lhs <= rhs;
			else if (!strcmp(cmp, ">"))
				conditiontrue = lhs > rhs;
			else if (!strcmp(cmp, ">="))
				conditiontrue = lhs >= rhs;
			else
				conditiontrue = false;
		}
		else
		{
			conditiontrue = conditiontrue == !!lhs;
		}
	}
	if (*token)
		token = COM_ParseExt(ptr, false, false);
	if (!strcmp(token, "&&"))
		return Shader_EvaluateCondition(shader, ptr) && conditiontrue;
	if (!strcmp(token, "||"))
		return Shader_EvaluateCondition(shader, ptr) || conditiontrue;

	return conditiontrue;
}

static char *Shader_ParseExactString(char **ptr)
{
	char *token;

	if (!ptr || !(*ptr))
		return "";
	if (!**ptr || **ptr == '}')
		return "";

	token = COM_ParseExt(ptr, false, false);
	return token;
}

static char *Shader_ParseString(char **ptr)
{
	char *token;

	if (!ptr || !(*ptr))
		return "";
	if (!**ptr || **ptr == '}')
		return "";

	token = COM_ParseExt(ptr, false, true);
	Q_strlwr ( token );

	return token;
}

static char *Shader_ParseSensString(char **ptr)
{
	char *token;

	if (!ptr || !(*ptr))
		return "";
	if (!**ptr || **ptr == '}')
		return "";

	token = COM_ParseExt(ptr, false, true);

	return token;
}

static float Shader_ParseFloat(shader_t *shader, char **ptr, float defaultval)
{
	char *token;
	if (!ptr || !(*ptr))
		return defaultval;
	if (!**ptr || **ptr == '}')
		return defaultval;

	token = COM_ParseExt(ptr, false, true);
	if (*token == '$')
	{
		if (token[1] == '#')
		{
			return Shader_FloatArgument(shader, token+1);
		}
		else
		{
			cvar_t *var;
			var = Cvar_FindVar(token+1);
			if (var)
			{
				if (*var->string)
					return var->value;
				else
					return defaultval;
			}
		}
	}
	if (!*token)
		return defaultval;
	return atof(token);
}

static void Shader_ParseVector(shader_t *shader, char **ptr, vec3_t v)
{
	char *scratch;
	char *token;
	qboolean bracket;
	qboolean fromcvar = false;

	token = Shader_ParseString(ptr);
	if (*token == '$')
	{
		cvar_t *var;
		var = Cvar_FindVar(token+1);
		if (!var)
		{
			v[0] = 1;
			v[1] = 1;
			v[2] = 1;
			return;
		}
		var->flags |= CVAR_SHADERSYSTEM;
		ptr = &scratch;
		scratch = var->string;

		token = Shader_ParseString( ptr);
		fromcvar = true;
	}
	if (!Q_stricmp (token, "("))
	{
		bracket = true;
		token = Shader_ParseString(ptr);
	}
	else if (token[0] == '(')
	{
		bracket = true;
		token = &token[1];
	}
	else
		bracket = false;

	v[0] = atof ( token );
	
	token = Shader_ParseString ( ptr );
	if ( !token[0] ) {
		v[1] = fromcvar?v[0]:0;
	} else if (bracket &&  token[strlen(token)-1] == ')' ) {
		bracket = false;
		token[strlen(token)-1] = 0;
		v[1] = atof ( token );
	} else {
		v[1] = atof ( token );
	}

	token = Shader_ParseString ( ptr );
	if ( !token[0] ) {
		v[2] = fromcvar?v[1]:0;
	} else if (bracket && token[strlen(token)-1] == ')' ) {
		token[strlen(token)-1] = 0;
		v[2] = atof ( token );
	} else {
		v[2] = atof ( token );
		if ( bracket ) {
			Shader_ParseString ( ptr );
		}
	}

	if (v[0] > 5 || v[1] > 5 || v[2] > 5)
	{
		VectorScale(v, 1.0f/255, v);
	}
}

qboolean Shader_ParseSkySides (char *shadername, char *texturename, texid_t *images)
{
	qboolean allokay = true;
	int i, ss, sp;
	char path[MAX_QPATH];

	static char	*skyname_suffix[][6] = {
		{"rt", "bk", "lf", "ft", "up", "dn"},
		{"px", "py", "nx", "ny", "pz", "nz"},
		{"posx", "posy", "negx", "negy", "posz", "negz"},
		{"_px", "_py", "_nx", "_ny", "_pz", "_nz"},
		{"_posx", "_posy", "_negx", "_negy", "_posz", "_negz"},
		{"_rt", "_bk", "_lf", "_ft", "_up", "_dn"}
	};

	static char *skyname_pattern[] = {
		"%s_%s",
		"%s%s",
		"env/%s%s",
		"gfx/env/%s%s"
	};

	if (*texturename == '$')
	{
		cvar_t *v;
		v = Cvar_FindVar(texturename+1);
		if (v)
			texturename = v->string;
	}
	if (!*texturename)
		texturename = "-";

	for ( i = 0; i < 6; i++ )
	{
		if ( texturename[0] == '-' )
		{
			images[i] = r_nulltex;
		}
		else
		{
			for (sp = 0; sp < sizeof(skyname_pattern)/sizeof(skyname_pattern[0]); sp++)
			{
				for (ss = 0; ss < sizeof(skyname_suffix)/sizeof(skyname_suffix[0]); ss++)
				{
					Q_snprintfz ( path, sizeof(path), skyname_pattern[sp], texturename, skyname_suffix[ss][i] );
					images[i] = R_LoadHiResTexture ( path, NULL, IF_NOALPHA|IF_CLAMP|IF_NOWORKER);
					if (images[i]->status == TEX_LOADING)	//FIXME: unsafe, as it can recurse through shader loading and mess up internal parse state.
						COM_WorkerPartialSync(images[i], &images[i]->status, TEX_LOADING);
					if (TEXLOADED(images[i]))
						break;
				}
				if (TEXLOADED(images[i]))
					break;
			}
			if (!TEXVALID(images[i]))
			{
				Con_Printf("Sky \"%s\" missing texture: %s\n", shadername, path);
				images[i] = missing_texture;
				allokay = false;
			}
		}
	}
	return allokay;
}

static void Shader_ParseFunc (shader_t *shader, char **ptr, shaderfunc_t *func)
{
	char *token;

	token = Shader_ParseString (ptr);
	if (!Q_stricmp (token, "sin"))
	    func->type = SHADER_FUNC_SIN;
	else if (!Q_stricmp (token, "triangle"))
	    func->type = SHADER_FUNC_TRIANGLE;
	else if (!Q_stricmp (token, "square"))
	    func->type = SHADER_FUNC_SQUARE;
	else if (!Q_stricmp (token, "sawtooth"))
	    func->type = SHADER_FUNC_SAWTOOTH;
	else if (!Q_stricmp (token, "inversesawtooth"))
	    func->type = SHADER_FUNC_INVERSESAWTOOTH;
	else if (!Q_stricmp (token, "noise"))
	    func->type = SHADER_FUNC_NOISE;

	func->args[0] = Shader_ParseFloat (shader, ptr, 0);
	func->args[1] = Shader_ParseFloat (shader, ptr, 0);
	func->args[2] = Shader_ParseFloat (shader, ptr, 0);
	func->args[3] = Shader_ParseFloat (shader, ptr, 0);
}

//===========================================================================

static int Shader_SetImageFlags(shader_t *shader, shaderpass_t *pass, char **name)
{
	int flags = 0;

	for(;name;)
	{
		if (!Q_strnicmp(*name, "$rt:", 4))
		{
			*name += 4;
			flags |= IF_NOMIPMAP|IF_CLAMP|IF_LINEAR|IF_RENDERTARGET;
		}
		else if (!Q_strnicmp(*name, "$clamp:", 7))
		{
			*name += 7;
			flags |= IF_CLAMP;
		}
		else if (!Q_strnicmp(*name, "$3d:", 4))
		{
			*name+=4;
			flags = (flags&~IF_TEXTYPE) | IF_3DMAP;
		}
		else if (!Q_strnicmp(*name, "$cube:", 6))
		{
			*name+=6;
			flags = (flags&~IF_TEXTYPE) | IF_CUBEMAP;
		}
		else if (!Q_strnicmp(*name, "$nearest:", 9))
		{
			*name+=9;
			flags|= IF_NEAREST;
			pass->flags |= SHADER_PASS_NEAREST;
		}
		else if (!Q_strnicmp(*name, "$linear:", 8))
		{
			*name+=8;
			flags|= IF_LINEAR;
			pass->flags |= SHADER_PASS_LINEAR;
		}
		else
			break;
	}

//	if (shader->flags & SHADER_SKY)
//		flags |= IF_SKY;
	if (shader->flags & SHADER_NOMIPMAPS)
		flags |= IF_NOMIPMAP;
	if (shader->flags & SHADER_NOPICMIP)
		flags |= IF_NOPICMIP;

	return flags;
}

texid_t R_LoadColourmapImage(void)
{
	unsigned int w = 256, h = VID_GRADES-1;
	unsigned int x;
	unsigned int data[256*(VID_GRADES-1)];
	qbyte *colourmappal = (qbyte *)FS_LoadMallocFile ("gfx/colormap.lmp", NULL);
	if (colourmappal)
	{
		for (x = 0; x < sizeof(data)/sizeof(data[0]); x++)
			data[x] = d_8to24rgbtable[colourmappal[x]];
	}
	else
	{	//erk
		for (x = 0; x < sizeof(data)/sizeof(data[0]); x++)
			data[x] = d_8to24rgbtable[x & 0xff];
	}
	BZ_Free(colourmappal);
	return R_LoadTexture("$colourmap", w, h, TF_RGBA32, data, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA|IF_CLAMP);
}

static texid_t Shader_FindImage ( char *name, int flags )
{
	if (parsestate.mode == SPM_DOOM3)
	{
		if (!Q_stricmp (name, "_default"))
			return r_whiteimage; /*fixme*/
		if (!Q_stricmp (name, "_white"))
			return r_whiteimage;
		if (!Q_stricmp (name, "_black"))
		{
			int wibuf[16] = {0};
			return R_LoadTexture("$blackimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA);
		}
	}
	else
	{
		if (!Q_stricmp (name, "$whiteimage"))
			return r_whiteimage;
		if (!Q_stricmp (name, "$colourmap"))
			return R_LoadColourmapImage();
	}
	if (flags & IF_RENDERTARGET)
		return R2D_RT_Configure(name, 0, 0, TF_INVALID);
	return R_LoadHiResTexture(name, NULL, flags);
}


/****************** shader keyword functions ************************/

static void Shader_Cull ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	char *token;

	shader->flags &= ~(SHADER_CULL_FRONT|SHADER_CULL_BACK);

	token = Shader_ParseString ( ptr );
	if ( !Q_stricmp (token, "disable") || !Q_stricmp (token, "none") || !Q_stricmp (token, "twosided") ) {
	} else if ( !Q_stricmp (token, "front") ) {
		shader->flags |= SHADER_CULL_FRONT;
	} else if ( !Q_stricmp (token, "back") || !Q_stricmp (token, "backside") || !Q_stricmp (token, "backsided") ) {
		shader->flags |= SHADER_CULL_BACK;
	} else {
		shader->flags |= SHADER_CULL_FRONT;
	}
}

static void Shader_NoMipMaps ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	shader->flags |= (SHADER_NOMIPMAPS|SHADER_NOPICMIP);
}

static void Shader_NoPicMip ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	shader->flags |= SHADER_NOPICMIP;
}

static void Shader_DeformVertexes ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	char *token;
	deformv_t *deformv;

	if ( shader->numdeforms >= SHADER_DEFORM_MAX )
		return;

	deformv = &shader->deforms[shader->numdeforms];

	token = Shader_ParseString ( ptr );
	if ( !Q_stricmp (token, "wave") )
	{
		deformv->type = DEFORMV_WAVE;
		deformv->args[0] = Shader_ParseFloat (shader, ptr, 0);
		if (deformv->args[0])
			deformv->args[0] = 1.0f / deformv->args[0];
		Shader_ParseFunc (shader, ptr, &deformv->func );
	}
	else if ( !Q_stricmp (token, "normal") )
	{
		deformv->type = DEFORMV_NORMAL;
		deformv->args[0] = Shader_ParseFloat (shader, ptr, 0);
		deformv->args[1] = Shader_ParseFloat (shader, ptr, 0);
	}
	else if ( !Q_stricmp (token, "bulge") )
	{
		deformv->type = DEFORMV_BULGE;
		Shader_ParseVector (shader, ptr, deformv->args);
		shader->flags |= SHADER_DEFORMV_BULGE;
	}
	else if ( !Q_stricmp (token, "move") )
	{
		deformv->type = DEFORMV_MOVE;
		Shader_ParseVector (shader, ptr, deformv->args );
		Shader_ParseFunc (shader, ptr, &deformv->func );
	}
	else if ( !Q_stricmp (token, "autosprite") )
	{
		deformv->type = DEFORMV_AUTOSPRITE;
		shader->flags |= SHADER_AUTOSPRITE;
	}
	else if ( !Q_stricmp (token, "autosprite2") )
	{
		deformv->type = DEFORMV_AUTOSPRITE2;
		shader->flags |= SHADER_AUTOSPRITE;
	}
	else if ( !Q_stricmp (token, "projectionShadow") )
		deformv->type = DEFORMV_PROJECTION_SHADOW;
	else
		return;

	shader->numdeforms++;
}

static void Shader_ClutterParms(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	struct shader_clutter_s *clut;
	char *modelname;

	modelname = Shader_ParseString(ptr);
	clut = Z_Malloc(sizeof(*clut) + strlen(modelname));
	strcpy(clut->modelname, modelname);
	clut->spacing	= Shader_ParseFloat(shader, ptr, 1000);
	clut->scalemin	= Shader_ParseFloat(shader, ptr, 1);
	clut->scalemax	= Shader_ParseFloat(shader, ptr, 1);
	clut->zofs		= Shader_ParseFloat(shader, ptr, 0);
	clut->anglemin	= Shader_ParseFloat(shader, ptr, 0) * M_PI * 2 / 360.;
	clut->anglemax	= Shader_ParseFloat(shader, ptr, 360) * M_PI * 2 / 360.;

	clut->next = shader->clutter;
	shader->clutter = clut;
}

static void Shader_SkyParms(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	skydome_t *skydome;
	float skyheight;
	char *boxname;

	if (shader->skydome)
	{
		Z_Free(shader->skydome);
	}

	skydome = (skydome_t *)Z_Malloc(sizeof(skydome_t));
	shader->skydome = skydome;

	boxname = Shader_ParseString(ptr);
	Shader_ParseSkySides(shader->name, boxname, skydome->farbox_textures);

	skyheight = Shader_ParseFloat(shader, ptr, 512);

	boxname = Shader_ParseString(ptr);
	Shader_ParseSkySides(shader->name, boxname, skydome->nearbox_textures);

	shader->flags |= SHADER_SKY;
	shader->sort = SHADER_SORT_SKY;
}

static void Shader_FogParms ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	float div;
	vec3_t color, fcolor;

//	if ( !r_ignorehwgamma->value )
//		div = 1.0f / pow(2, max(0, floor(r_overbrightbits->value)));
//	else
		div = 1.0f;

	Shader_ParseVector (shader, ptr, color );
	VectorScale ( color, div, color );
	ColorNormalize ( color, fcolor );

	shader->fog_color[0] = FloatToByte ( fcolor[0] );
	shader->fog_color[1] = FloatToByte ( fcolor[1] );
	shader->fog_color[2] = FloatToByte ( fcolor[2] );
	shader->fog_color[3] = 255;
	shader->fog_dist = Shader_ParseFloat (shader, ptr, 128);

	if ( shader->fog_dist <= 0.0f ) {
		shader->fog_dist = 128.0f;
	}
	shader->fog_dist = 1.0f / shader->fog_dist;

	shader->flags |= SHADER_NODLIGHT|SHADER_NOSHADOWS;
}

static void Shader_SurfaceParm ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	char *token;

	token = Shader_ParseString ( ptr );
	if ( !Q_stricmp( token, "nodraw" ) )
		shader->flags |= SHADER_NODRAW;
	else if ( !Q_stricmp( token, "nodraw2" ) )
		shader->flags |= SHADER_NODRAW;
	else if ( !Q_stricmp( token, "nodlight" ) )
		shader->flags |= SHADER_NODLIGHT;
	else if ( !Q_stricmp( token, "noshadows" ) )
		shader->flags |= SHADER_NOSHADOWS;
}

static void Shader_Sort ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	char *token;

	token = Shader_ParseString ( ptr );
	if ( !Q_stricmp( token, "portal" ) )
		shader->sort = SHADER_SORT_PORTAL;
	else if( !Q_stricmp( token, "sky" ) )
		shader->sort = SHADER_SORT_SKY;
	else if( !Q_stricmp( token, "opaque" ) )
		shader->sort = SHADER_SORT_OPAQUE;
	else if( !Q_stricmp( token, "decal" ) )
		shader->sort = SHADER_SORT_DECAL;
	else if( !Q_stricmp( token, "seethrough" ) )
		shader->sort = SHADER_SORT_SEETHROUGH;
	else if( !Q_stricmp( token, "banner" ) )
		shader->sort = SHADER_SORT_BANNER;
	else if( !Q_stricmp( token, "additive" ) )
		shader->sort = SHADER_SORT_ADDITIVE;
	else if( !Q_stricmp( token, "underwater" ) )
		shader->sort = SHADER_SORT_UNDERWATER;
	else if( !Q_stricmp( token, "nearest" ) )
		shader->sort = SHADER_SORT_NEAREST;
	else if( !Q_stricmp( token, "blend" ) )
		shader->sort = SHADER_SORT_BLEND;
	else if ( !Q_stricmp( token, "lpp_light" ) )
		shader->sort = SHADER_SORT_PRELIGHT;
	else if ( !Q_stricmp( token, "ripple" ) )
		shader->sort = SHADER_SORT_RIPPLE;
	else
	{
		shader->sort = atoi ( token );
		clamp ( shader->sort, SHADER_SORT_NONE, SHADER_SORT_NEAREST );
	}
}

static void Shader_Prelight ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	shader->sort = SHADER_SORT_PRELIGHT;
}

static void Shader_Portal ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	shader->sort = SHADER_SORT_PORTAL;
}

static void Shader_PolygonOffset ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	/*the q3 defaults*/
	shader->polyoffset.factor = -0.05;
	shader->polyoffset.unit = -25;
}

static void Shader_EntityMergable ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	shader->flags |= SHADER_ENTITY_MERGABLE;
}

/*program text is already loaded, this function parses the 'header' of it to see which permutations it provides, and how many times we need to recompile it*/
static qboolean Shader_LoadPermutations(char *name, program_t *prog, char *script, int qrtype, int ver, char *blobfilename)
{
	static char *permutationname[] =
	{
		"#define BUMP\n",
		"#define FULLBRIGHT\n",
		"#define UPPERLOWER\n",
		"#define DELUXE\n",
		"#define SKELETAL\n",
		"#define FOG\n",
		"#define FRAMEBLEND\n",
		"#define LIGHTSTYLED\n",
		NULL
	};
#define MAXMODIFIERS 64
	const char *permutationdefines[sizeof(permutationname)/sizeof(permutationname[0]) + MAXMODIFIERS + 1];
	unsigned int nopermutation = ~0u;
	int nummodifiers = 0;
	int p, n, pn;
	char *end;
	vfsfile_t *blobfile;
	unsigned int permuoffsets[PERMUTATIONS], initoffset=0;
	unsigned int blobheaderoffset=0;
	qboolean blobadded;
	qboolean tess = false;

	char *cvarnames[64];
	int cvartypes[64];
	int cvarcount = 0;
	qboolean onefailed = false;
	extern cvar_t gl_specular;

	ver = 0;

	if (qrenderer != qrtype)
	{
		return false;
	}
	if (!sh_config.pCreateProgram && !sh_config.pLoadBlob)
		return false;

	cvarnames[cvarcount] = NULL;

	prog->nofixedcompat = true;
	prog->numsamplers = 0;
	prog->defaulttextures = 0;
	for(;;)
	{
		while (*script == ' ' || *script == '\r' || *script == '\n' || *script == '\t')
			script++;
		if (!strncmp(script, "!!fixed", 7))
		{
			prog->nofixedcompat = false;
			script += 7;
		}
		else if (!strncmp(script, "!!tess", 6))
		{
			tess = true;
			script += 6;
		}
		else if (!strncmp(script, "!!cvardf", 8))
		{
			script += 8;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			if (nummodifiers < MAXMODIFIERS && end - script < 64)
			{
				cvar_t *var;
				char namebuf[64];
				memcpy(namebuf, script, end - script);
				namebuf[end - script] = 0;
				var = Cvar_Get(namebuf, "0", CVAR_SHADERSYSTEM, "GLSL Variables");
				if (var)
					permutationdefines[nummodifiers++] = Z_StrDup(va("#define %s %g\n", namebuf, var->value));
			}
			script = end;
		}
		else if (!strncmp(script, "!!cvarf", 7))
		{
			script += 7;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			if (cvarcount+1 != sizeof(cvarnames)/sizeof(cvarnames[0]))
			{
				cvartypes[cvarcount] = SP_CVARF;
				cvarnames[cvarcount++] = script;
				cvarnames[cvarcount] = NULL;
			}
			script = end;
		}
		else if (!strncmp(script, "!!cvari", 7))
		{
			script += 7;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			if (cvarcount+1 != sizeof(cvarnames)/sizeof(cvarnames[0]))
			{
				cvartypes[cvarcount] = SP_CVARI;
				cvarnames[cvarcount++] = script;
				cvarnames[cvarcount] = NULL;
			}
			script = end;
		}
		else if (!strncmp(script, "!!cvarv", 7))
		{
			script += 7;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			if (cvarcount+1 != sizeof(cvarnames)/sizeof(cvarnames[0]))
			{
				cvartypes[cvarcount] = SP_CVAR3F;
				cvarnames[cvarcount++] = script;
				cvarnames[cvarcount] = NULL;
			}
			script = end;
		}
		else if (!strncmp(script, "!!permu", 7))
		{
			script += 7;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			for (p = 0; permutationname[p]; p++)
			{
				if (!strncmp(permutationname[p]+8, script, end - script) && permutationname[p][8+end-script] == '\n')
				{
					nopermutation &= ~(1u<<p);
					break;
				}
			}
			if (!permutationname[p])
			{
				//we 'recognise' ones that are force-defined, despite not being actual permutations.
				if (strncmp("SPECULAR", script, end - script))
				if (strncmp("OFFSETMAPPING", script, end - script))
				if (strncmp("RELIEFMAPPING", script, end - script))
					Con_DPrintf("Unknown pemutation in glsl program %s\n", name);
			}
			script = end;
		}
		else if (!strncmp(script, "!!ver", 5))
		{
			script += 5;
			while (*script == ' ' || *script == '\t')
				script++;
			end = script;
			while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_')
				end++;
			ver = strtol(script, NULL, 0);
			script = end;
		}
		else if (!strncmp(script, "//", 2))
		{
			script += 2;
			while (*script == ' ' || *script == '\t')
				script++;
		}
		else
			break;
		while (*script && *script != '\n')
			script++;
	};

	if (sh_config.pLoadBlob && blobfilename && *blobfilename)
		blobfile = FS_OpenVFS(blobfilename, "w+b", FS_GAMEONLY);
	else
		blobfile = NULL;

	if (blobfile)
	{
		unsigned int magic;
		unsigned int corrupt = false;
		char ever[MAX_QPATH];
		char *thisever = version_string();
		corrupt |= VFS_READ(blobfile, &magic, sizeof(magic)) != sizeof(magic);
		corrupt |= magic != *(unsigned int*)"FBLB";
		corrupt |= VFS_READ(blobfile, &blobheaderoffset, sizeof(blobheaderoffset)) != sizeof(blobheaderoffset);
		corrupt |= VFS_READ(blobfile, ever, sizeof(ever)) != sizeof(ever);

		corrupt |= strcmp(ever, thisever);
		//if the magic or header didn't read properly then the file is corrupt
		if (corrupt)
		{
			//close and reopen it without the + flag, to replace it with a new file.
			VFS_CLOSE(blobfile);
			blobfile = FS_OpenVFS(blobfilename, "wb", FS_GAMEONLY);

			if (blobfile)
			{
				blobheaderoffset = 0;
				VFS_SEEK(blobfile, 0);
				magic = *(unsigned int*)"FBLB";	//magic
				VFS_WRITE(blobfile, &magic, sizeof(magic));
				VFS_WRITE(blobfile, &blobheaderoffset, sizeof(blobheaderoffset));
				memset(ever, 0, sizeof(ever));	//make sure we don't leak stuff.
				Q_strncpyz(ever, thisever, sizeof(ever));
				VFS_WRITE(blobfile, ever, sizeof(ever));
				blobheaderoffset = 0;
			}
		}
	}
	blobadded = false;

	if (gl_specular.value)
	{
		if (nummodifiers < MAXMODIFIERS)
			permutationdefines[nummodifiers++] = Z_StrDup("#define SPECULAR\n");
	}
	for (end = strchr(name, '#'); end && *end; )
	{
		char *start = end+1, *d;
		end = strchr(start, '#');
		if (!end)
			end = start + strlen(start);
		if (nummodifiers < MAXMODIFIERS)
		{
			permutationdefines[nummodifiers] = d = BZ_Malloc(10 + end - start);
			memcpy(d, "#define ", 8);
			memcpy(d+8, start, end - start);
			memcpy(d+8+(end-start), "\n", 2);

			start = strchr(d+8, '=');
			if (start)
				*start = ' ';

			for (start = d+8; *start; start++)
				*start = toupper(*start);
			nummodifiers++;
			permutationdefines[nummodifiers] = NULL;
		}
	}

	if (blobfile)
	{
		unsigned int next;
		unsigned int argsz;
		char *args, *mp;
		const char *mv;
		int ml, mi;
		unsigned int bloblink = 4;

		//walk through looking for an argset match
		while (blobheaderoffset)
		{
			VFS_SEEK(blobfile, blobheaderoffset);
			VFS_READ(blobfile, &next, sizeof(next));
			VFS_READ(blobfile, &argsz, sizeof(argsz));
			args = Z_Malloc(argsz+1);
			VFS_READ(blobfile, args, argsz);
			args[argsz] = 0;
			for (mi = 0, mp = args; mi < nummodifiers; mi++)
			{
				mv = permutationdefines[mi]+8;
				ml = strlen(mv);
				if (mp+ml > args+argsz)
					break;
				if (strncmp(mp, mv, ml))
					break;
				mp += ml;
			}
			//this one is a match. 
			if (mi == nummodifiers && mp == args+argsz)
			{
				blobheaderoffset = VFS_TELL(blobfile);
				VFS_READ(blobfile, permuoffsets, sizeof(permuoffsets));
				break;
			}

			bloblink = blobheaderoffset;
			blobheaderoffset = next;
		}

		//these arguments have never been seen before. add a new argset.
		if (!blobheaderoffset)
		{
			unsigned int link = 0;
			initoffset = VFS_GETLEN(blobfile);
			VFS_SEEK(blobfile, initoffset);
			VFS_WRITE(blobfile, &link, sizeof(link));

			for (mi = 0, argsz = 0; mi < nummodifiers; mi++)
			{
				mv = permutationdefines[mi]+8;
				ml = strlen(mv);
				argsz += ml;
			}
			VFS_WRITE(blobfile, &argsz, sizeof(argsz));
			for (mi = 0; mi < nummodifiers; mi++)
			{
				mv = permutationdefines[mi]+8;
				ml = strlen(mv);
				VFS_WRITE(blobfile, mv, ml);
			}

			//and the offsets come here
			blobheaderoffset = VFS_TELL(blobfile);
			memset(permuoffsets, 0, sizeof(permuoffsets));
			VFS_WRITE(blobfile, permuoffsets, sizeof(permuoffsets));

			//now rewrite the link to add us. the value in the file should always be set to 0.
			VFS_SEEK(blobfile, bloblink);
			VFS_WRITE(blobfile, &initoffset, sizeof(initoffset));
		}
	}

	for (p = 0; p < PERMUTATIONS; p++)
	{
		memset(&prog->permu[p].handle, 0, sizeof(prog->permu[p].handle));
		if (nopermutation & p)
		{
			continue;
		}
		pn = nummodifiers;
		for (n = 0; permutationname[n]; n++)
		{
			if (p & (1u<<n))
				permutationdefines[pn++] = permutationname[n];
		}
		if (p & PERMUTATION_UPPERLOWER)
			permutationdefines[pn++] = "#define UPPER\n#define LOWER\n";
		if (p & PERMUTATION_BUMPMAP)
		{
			if (r_glsl_offsetmapping.ival)
			{
				permutationdefines[pn++] = "#define OFFSETMAPPING\n";
				if (r_glsl_offsetmapping_reliefmapping.ival && (p & PERMUTATION_BUMPMAP))
					permutationdefines[pn++] = "#define RELIEFMAPPING\n";
			}
		}
		permutationdefines[pn++] = NULL;


		if (blobfile && permuoffsets[p])
		{
			VFS_SEEK(blobfile, permuoffsets[p]);
			if (sh_config.pLoadBlob(prog, name, p, blobfile))
				continue;	//blob was loaded from disk, yay.
			//otherwise fall through.
		}
		if (blobfile && !sh_config.pValidateProgram)
		{
			initoffset = VFS_GETLEN(blobfile);
			VFS_SEEK(blobfile, initoffset);
		}
		if (!sh_config.pCreateProgram(prog, name, p, ver, permutationdefines, script, tess?script:NULL, tess?script:NULL, script, (p & PERMUTATION_SKELETAL)?true:onefailed, sh_config.pValidateProgram?NULL:blobfile))
		{
			if (!(p & PERMUTATION_SKELETAL))
				onefailed = true;	//don't flag it if skeletal failed.
			if (!p)	//give up if permutation 0 failed. that one failing is fatal.
				break;
		}
		if (!sh_config.pValidateProgram && blobfile && initoffset != VFS_GETLEN(blobfile))
		{
			permuoffsets[p] = initoffset;
			blobadded = true;
		}
	}
	while(nummodifiers)
		Z_Free((char*)permutationdefines[--nummodifiers]);

	//extra loop to validate the programs actually linked properly.
	//delaying it like this gives certain threaded drivers a chance to compile them all while we're messing around with other junk
	if (sh_config.pValidateProgram)
	for (p = 0; p < PERMUTATIONS; p++)
	{
		if (nopermutation & p)
			continue;
		if (blobfile)
		{
			initoffset = VFS_GETLEN(blobfile);
			VFS_SEEK(blobfile, initoffset);
		}
		if (!sh_config.pValidateProgram(prog, name, p, (p & PERMUTATION_SKELETAL)?true:onefailed, blobfile))
		{
			if (!(p & PERMUTATION_SKELETAL))
			{
				onefailed = true;	//don't flag it if skeletal failed.
				continue;
			}
			if (!p)
				break;
		}
		if (blobfile && initoffset != VFS_GETLEN(blobfile))
		{
			permuoffsets[p] = initoffset;
			blobadded = true;
		}
	}

	if (sh_config.pProgAutoFields)
		sh_config.pProgAutoFields(prog, cvarnames, cvartypes);

	if (blobfile && blobadded)
	{
		VFS_SEEK(blobfile, blobheaderoffset);
		VFS_WRITE(blobfile, permuoffsets, sizeof(permuoffsets));
	}
	if (blobfile)
		VFS_CLOSE(blobfile);

	if (p == PERMUTATIONS)
		return true;
	return false;
}
typedef struct sgeneric_s
{
	program_t prog;
	struct sgeneric_s *next;
	char *name;
	qboolean failed;
} sgeneric_t;
static sgeneric_t *sgenerics;
struct sbuiltin_s
{
	int qrtype;
	int apiver;
	char name[MAX_QPATH];
	char *body;
} sbuiltins[] =
{
#include "r_bishaders.h"
	{QR_NONE}
};
void Shader_UnloadProg(program_t *prog)
{
	if (sh_config.pDeleteProg)
	{
		int p;
		for (p = 0; p < PERMUTATIONS; p++)
			sh_config.pDeleteProg(prog, p);
	}

	free(prog);
}
static void Shader_FlushGenerics(void)
{
	sgeneric_t *g;
	while (sgenerics)
	{
		g = sgenerics;
		sgenerics = g->next;

		if (g->prog.refs == 1)
		{
			g->prog.refs--;
			Shader_UnloadProg(&g->prog);
		}
		else
			Con_Printf("generic shader still used\n"); 
	}
}
static void Shader_LoadGeneric(sgeneric_t *g, int qrtype)
{
	unsigned int i;
	void *file;
	char basicname[MAX_QPATH];
	char blobname[MAX_QPATH];
	char *h;

	g->failed = true;

	basicname[1] = 0;
	Q_strncpyz(basicname, g->name, sizeof(basicname));
	h = strchr(basicname+1, '#');
	if (h)
		*h = '\0';

	if (strchr(basicname, '/') || strchr(basicname, '.'))
	{
		FS_LoadFile(basicname, &file);
		*blobname = 0;
	}
	else
	{
		if (sh_config.progpath)
		{
			Q_snprintfz(blobname, sizeof(blobname), sh_config.progpath, basicname);
			FS_LoadFile(blobname, &file);
		}
		else
			file = NULL;
		if (sh_config.blobpath && r_shaderblobs.ival)
			Q_snprintfz(blobname, sizeof(blobname), sh_config.blobpath, basicname);
		else
			*blobname = 0;
	}

	if (file)
	{
		Con_DPrintf("Loaded %s from disk\n", basicname);
		g->failed = !Shader_LoadPermutations(g->name, &g->prog, file, qrtype, 0, blobname);
		FS_FreeFile(file);
		return;
	}
	else
	{
		int ver;
		for (i = 0; *sbuiltins[i].name; i++)
		{
			if (sbuiltins[i].qrtype == qrenderer && !strcmp(sbuiltins[i].name, basicname))
			{
				ver = sbuiltins[i].apiver;

				if (ver < sh_config.minver || ver > sh_config.maxver)
					continue;

				g->failed = !Shader_LoadPermutations(g->name, &g->prog, sbuiltins[i].body, sbuiltins[i].qrtype, ver, blobname);

				if (g->failed)
					continue;

				return;
			}
		}
	}
}
static program_t *Shader_FindGeneric(char *name, int qrtype)
{
	sgeneric_t *g;

	for (g = sgenerics; g; g = g->next)
	{
		if (!strcmp(name, g->name))
		{
			if (g->failed)
				return NULL;
			g->prog.refs++;
			return &g->prog;
		}
	}

	//don't even try if we know it won't work.
	if (!sh_config.progs_supported)
		return NULL;

	g = malloc(sizeof(*g) + strlen(name)+1);
	memset(g, 0, sizeof(*g));
	g->name = (char*)(g+1);
	strcpy(g->name, name);
	g->next = sgenerics;
	sgenerics = g;

	g->prog.refs = 1;

	Shader_LoadGeneric(g, qrtype);
	if (g->failed)
		return NULL;
	g->prog.refs++;
	return &g->prog;
}
static void Shader_ReloadGenerics(void)
{
	sgeneric_t *g;
	for (g = sgenerics; g; g = g->next)
	{
		Shader_LoadGeneric(g, qrenderer);
	}

	//this shader can take a while to load due to its number of permutations.
	//because this all happens on the main thread, try to avoid random stalls by pre-loading it.
	if (sh_config.progs_supported)
	{
		program_t *p = Shader_FindGeneric("defaultskin", qrenderer);
		if (p)	//generics get held on to in order to avoid so much churn. so we can just release the reference we just created and it'll be held until shutdown anyway.
			p->refs--;
	}
}
void Shader_WriteOutGenerics_f(void)
{
	int i;
	char *name;
	for (i = 0; *sbuiltins[i].name; i++)
	{
		name = NULL;
		if (sbuiltins[i].qrtype == QR_OPENGL)
		{
			if (sbuiltins[i].apiver == 100)
				name = va("gles/eg_%s.glsl", sbuiltins[i].name);
			else
				name = va("glsl/eg_%s.glsl", sbuiltins[i].name);
		}
		else if (sbuiltins[i].qrtype == QR_DIRECT3D9)
			name = va("hlsl/eg_%s.hlsl", sbuiltins[i].name);
		else if (sbuiltins[i].qrtype == QR_DIRECT3D11)
			name = va("hlsl11/eg_%s.hlsl", sbuiltins[i].name);

		if (name)
		{
			vfsfile_t *f = FS_OpenVFS(name, "rb", FS_GAMEONLY);
			if (f)
			{
				int len = VFS_GETLEN(f);
				char *buf = Hunk_TempAlloc(len);
				VFS_READ(f, buf, len);
				if (len != strlen(sbuiltins[i].body) || memcmp(buf, sbuiltins[i].body, len))
					Con_Printf("Not writing %s - modified version in the way\n", name);
				else
					Con_Printf("%s is unmodified\n", name);
				VFS_CLOSE(f);
			}
			else
			{
				Con_Printf("Writing %s\n", name);
				FS_WriteFile(name, sbuiltins[i].body, strlen(sbuiltins[i].body), FS_GAMEONLY);
			}
		}
	}
}

struct shader_field_names_s shader_attr_names[] =
{
	/*vertex attributes*/
	{"v_position1",				VATTR_VERTEX1},
	{"v_position2",				VATTR_VERTEX2},
	{"v_colour",				VATTR_COLOUR},
	{"v_texcoord",				VATTR_TEXCOORD},
	{"v_lmcoord",				VATTR_LMCOORD},
	{"v_normal",				VATTR_NORMALS},
	{"v_svector",				VATTR_SNORMALS},
	{"v_tvector",				VATTR_TNORMALS},
	{"v_bone",					VATTR_BONENUMS},
	{"v_weight",				VATTR_BONEWEIGHTS},
#if MAXRLIGHTMAPS > 1
	{"v_lmcoord1",				VATTR_LMCOORD},
	{"v_lmcoord2",				VATTR_LMCOORD2},
	{"v_lmcoord3",				VATTR_LMCOORD3},
	{"v_lmcoord4",				VATTR_LMCOORD4},
	{"v_colour1",				VATTR_COLOUR},
	{"v_colour2",				VATTR_COLOUR2},
	{"v_colour3",				VATTR_COLOUR3},
	{"v_colour4",				VATTR_COLOUR4},
#endif
	{NULL}
};

struct shader_field_names_s shader_unif_names[] =
{
	/*matricies*/
	{"m_model",					SP_M_MODEL},
	{"m_view",					SP_M_VIEW},
	{"m_modelview",				SP_M_MODELVIEW},
	{"m_projection",			SP_M_PROJECTION},
	{"m_modelviewprojection",	SP_M_MODELVIEWPROJECTION},
	{"m_bones",					SP_M_ENTBONES},
	{"m_invviewprojection",		SP_M_INVVIEWPROJECTION},
	{"m_invmodelviewprojection",SP_M_INVMODELVIEWPROJECTION},

	/*viewer properties*/
	{"v_eyepos",				SP_V_EYEPOS},
	{"w_fog",					SP_W_FOG},

	/*ent properties*/
	{"e_vblend",				SP_E_VBLEND},
	{"e_lmscale",				SP_E_LMSCALE}, /*overbright shifting*/
	{"e_origin",				SP_E_ORIGIN},
	{"e_time",					SP_E_TIME},
	{"e_eyepos",				SP_E_EYEPOS},
	{"e_colour",				SP_E_COLOURS},
	{"e_colourident",			SP_E_COLOURSIDENT},
	{"e_glowmod",				SP_E_GLOWMOD},
	{"e_uppercolour",			SP_E_TOPCOLOURS},
	{"e_lowercolour",			SP_E_BOTTOMCOLOURS},
	{"e_light_dir",				SP_E_L_DIR},
	{"e_light_mul",				SP_E_L_MUL},
	{"e_light_ambient",			SP_E_L_AMBIENT},

	/*rtlight properties, use with caution*/
	{"l_lightscreen",			SP_LIGHTSCREEN},
	{"l_lightradius",			SP_LIGHTRADIUS},
	{"l_lightcolour",			SP_LIGHTCOLOUR},
	{"l_lightposition",			SP_LIGHTPOSITION},
	{"l_lightcolourscale",		SP_LIGHTCOLOURSCALE},
	{"l_cubematrix",			SP_LIGHTCUBEMATRIX},
	{"l_shadowmapproj",			SP_LIGHTSHADOWMAPPROJ},
	{"l_shadowmapscale",		SP_LIGHTSHADOWMAPSCALE},

	{"e_rendertexturescale",	SP_RENDERTEXTURESCALE},
	{NULL}
};

static char *Shader_ParseBody(char *debugname, char **ptr)
{
	char *body;
	char *start, *end;

	end = *ptr;
	while (*end == ' ' || *end == '\t' || *end == '\r')
		end++;
	if (*end == '\n')
	{
		int count;
		end++;
		while (*end == ' ' || *end == '\t')
			end++;
		if (*end != '{')
		{
			Con_Printf("shader \"%s\" missing program string\n", debugname);
		}
		else
		{
			end++;
			start = end;
			for (count = 1; *end; end++)
			{
				if (*end == '}')
				{
					count--;
					if (!count)
						break;
				}
				else if (*end == '{')
					count++;
			}
			body = BZ_Malloc(end - start + 1);
			memcpy(body, start, end-start);
			body[end-start] = 0;
			*ptr = end+1;/*skip over it all*/

			return body;
		}
	}
	return NULL;
}

static void Shader_SLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr, int qrtype)
{
	/*accepts:
	program
	{
		BLAH
	}
	where BLAH is both vertex+frag with #ifdefs
	or
	program fname
	on one line.
	*/
	char *programbody;
	char *hash;

	programbody = Shader_ParseBody(shader->name, ptr);
	if (programbody)
	{
		shader->prog = malloc(sizeof(*shader->prog));
		memset(shader->prog, 0, sizeof(*shader->prog));
		shader->prog->refs = 1;
		if (!Shader_LoadPermutations(shader->name, shader->prog, programbody, qrtype, 0, NULL))
		{
			free(shader->prog);
			shader->prog = NULL;
		}

		BZ_Free(programbody);
		return;
	}

	hash = strchr(shader->name, '#');
	if (hash)
	{
		//pass the # postfixes from the shader name onto the generic glsl to use
		char newname[512];
		Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash);
		shader->prog = Shader_FindGeneric(newname, qrtype);
	}
	else
		shader->prog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype);
}

static void Shader_GLSLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	Shader_SLProgramName(shader,pass,ptr,QR_OPENGL);
}
static void Shader_ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	Shader_SLProgramName(shader,pass,ptr,qrenderer);
}
static void Shader_HLSL9ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D9);
}
static void Shader_HLSL11ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D11);
}

static void Shader_ProgramParam ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	cvar_t *cv = NULL;
	int specialint = 0;
	float specialfloat = 0;
	vec3_t specialvec = {0};
	enum shaderprogparmtype_e parmtype = SP_BAD;
	char *token;
	qboolean silent = false;
	char *forcename = NULL;

	token = Shader_ParseString(ptr);
	if (!Q_stricmp(token, "opt"))
	{
		silent = true;
		token = Shader_ParseString(ptr);
	}
	if (!Q_stricmp(token, "texture"))
	{
		token = Shader_ParseString(ptr);
		specialint = atoi(token);
		parmtype = SP_TEXTURE;
	}
	else if (!Q_stricmp(token, "consti"))
	{
		token = Shader_ParseSensString(ptr);
		specialint = atoi(token);
		parmtype = SP_CONSTI;
	}
	else if (!Q_stricmp(token, "constf"))
	{
		token = Shader_ParseSensString(ptr);
		specialfloat = atof(token);
		parmtype = SP_CONSTF;
	}
	else if (!Q_stricmp(token, "cvari"))
	{
		token = Shader_ParseSensString(ptr);
		cv = Cvar_Get(token, "", 0, "GLSL Shader parameters");
		if (!cv)
			return;
		parmtype = SP_CVARI;
	}
	else if (!Q_stricmp(token, "cvarf"))
	{
		token = Shader_ParseSensString(ptr);
		cv = Cvar_Get(token, "", 0, "GLSL Shader parameters");
		if (!cv)
			return;
		parmtype = SP_CVARF;
	}
	else if (!Q_stricmp(token, "cvar3f"))
	{
		token = Shader_ParseSensString(ptr);
		cv = Cvar_Get(token, "", 0, "GLSL Shader parameters");
		if (!cv)
			return;
		parmtype = SP_CVAR3F;
	}
	else if (!Q_stricmp(token, "time"))
		parmtype = SP_E_TIME;
	else if (!Q_stricmp(token, "eyepos"))
		parmtype = SP_E_EYEPOS;
	else if (!Q_stricmp(token, "entmatrix"))
		parmtype = SP_M_MODEL;
	else if (!Q_stricmp(token, "colours") || !Q_stricmp(token, "colors"))
		parmtype = SP_E_COLOURS;
	else if (!Q_stricmp(token, "upper"))
		parmtype = SP_E_TOPCOLOURS;
	else if (!Q_stricmp(token, "lower"))
		parmtype = SP_E_BOTTOMCOLOURS;
	else if (!Q_stricmp(token, "lightradius"))
		parmtype = SP_LIGHTRADIUS;
	else if (!Q_stricmp(token, "lightcolour"))
		parmtype = SP_LIGHTCOLOUR;
	else if (!Q_stricmp(token, "lightpos"))
		parmtype = SP_LIGHTPOSITION;
	else if (!Q_stricmp(token, "rendertexturescale"))
		parmtype = SP_RENDERTEXTURESCALE;
	else
		Con_Printf("shader %s: parameter type \"%s\" not known\n", shader->name, token);

	if (forcename)
		token = forcename;
	else
		token = Shader_ParseSensString(ptr);

#ifdef GLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		int p;
		qboolean foundone;
		unsigned int uniformloc;
		program_t *prog = shader->prog;
		if (!prog)
		{
			Con_Printf("shader %s: param without program set\n", shader->name);
		}
		else if (prog->numparams == SHADER_PROGPARMS_MAX)
			Con_Printf("shader %s: too many parms\n", shader->name);
		else
		{
			if (prog->refs != 1)
				Con_Printf("shader %s: parms on shared shader\n", shader->name);

			foundone = false;
			prog->parm[prog->numparams].type = parmtype;
			for (p = 0; p < PERMUTATIONS; p++)
			{
				if (!prog->permu[p].handle.glsl.handle)
					continue;
				GLSlang_UseProgram(prog->permu[p].handle.glsl.handle);

				uniformloc = qglGetUniformLocationARB(prog->permu[p].handle.glsl.handle, token);
				prog->permu[p].parm[prog->numparams] = uniformloc;

				if (uniformloc != -1)
				{
					foundone = true;
					switch(parmtype)
					{
					case SP_BAD:
						foundone = false;
						break;
					case SP_TEXTURE:
					case SP_CONSTI:
						prog->parm[prog->numparams].ival = specialint;
						break;
					case SP_CONSTF:
						prog->parm[prog->numparams].fval = specialfloat;
						break;
					case SP_CVARF:
					case SP_CVARI:
						prog->parm[prog->numparams].pval = cv;
						break;
					case SP_CVAR3F:
						prog->parm[prog->numparams].pval = cv;
						qglUniform3fvARB(uniformloc, 1, specialvec);
						break;
					default:
						break;
					}
				}
			}
			if (!foundone)
			{
				if (!silent)
					Con_Printf("shader %s: param \"%s\" not found\n", shader->name, token);
			}
			else
				prog->numparams++;

			GLSlang_UseProgram(0);
		}
	}
#endif
}

static void Shader_DiffuseMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.base = R_LoadHiResTexture(token, NULL, 0);
}
static void Shader_SpecularMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.specular = R_LoadHiResTexture(token, NULL, 0);
}
static void Shader_BumpMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.bump = R_LoadHiResTexture(token, NULL, 0);
}
static void Shader_FullbrightMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.fullbright = R_LoadHiResTexture(token, NULL, 0);
}
static void Shader_UpperMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.upperoverlay = R_LoadHiResTexture(token, NULL, 0);
}
static void Shader_LowerMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;
	token = Shader_ParseString(ptr);
	shader->defaulttextures.loweroverlay = R_LoadHiResTexture(token, NULL, 0);
}

static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *tname);
static void Shader_ProgMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	//fixme
//	Shaderpass_BlendFunc (shader, pass, ptr);
}
static void Shader_ProgBlendFunc(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	//fixme
}

static void Shader_Translucent(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	shader->flags |= SHADER_BLEND;
}

static void Shader_DP_Camera(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	shader->sort = SHADER_SORT_PORTAL;
}

static void Shader_BEMode(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	int mode;
	char tokencopy[1024];
	char *token;
	char *embed = NULL;
	token = Shader_ParseString(ptr);
	if (!Q_stricmp(token, "rtlight"))
		mode = -1;	//all light types
	else if (!Q_stricmp(token, "rtlight_only"))
		mode = LSHADER_STANDARD;
	else if (!Q_stricmp(token, "rtlight_smap"))
		mode = LSHADER_SMAP;
	else if (!Q_stricmp(token, "rtlight_spot"))
		mode = LSHADER_SPOT;
	else if (!Q_stricmp(token, "rtlight_cube"))
		mode = LSHADER_CUBE;
	else if (!Q_stricmp(token, "rtlight_cube_smap"))
		mode = LSHADER_CUBE|LSHADER_SMAP;
	else if (!Q_stricmp(token, "rtlight_cube_spot"))		//doesn't make sense.
		mode = LSHADER_CUBE|LSHADER_SPOT;
	else if (!Q_stricmp(token, "rtlight_spot_smap"))
		mode = LSHADER_SMAP|LSHADER_SPOT;
	else if (!Q_stricmp(token, "rtlight_cube_spot_smap"))	//doesn't make sense.
		mode = LSHADER_CUBE|LSHADER_SPOT|LSHADER_SMAP;
	else if (!Q_stricmp(token, "crepuscular"))
		mode = bemoverride_crepuscular;
	else if (!Q_stricmp(token, "depthonly"))
		mode = bemoverride_depthonly;
	else if (!Q_stricmp(token, "depthdark"))
		mode = bemoverride_depthdark;
	else if (!Q_stricmp(token, "prelight"))
		mode = bemoverride_prelight;
	else if (!Q_stricmp(token, "fog"))
		mode = bemoverride_fog;
	else
	{
		Con_DPrintf(CON_WARNING "Shader %s specifies unknown bemode %s.\n", shader->name, token);
		return;	//not supported.
	}

	embed = Shader_ParseBody(shader->name, ptr);
	if (embed)
	{
		int l = strlen(embed) + 6;
		char *b = BZ_Malloc(l);
		Q_snprintfz(b, l, "{\n%s\n}\n", embed);
		BZ_Free(embed);
		embed = b;
		//generate a unique name
		Q_snprintfz(tokencopy, sizeof(tokencopy), "%s_mode%i", shader->name, mode);
	}
	else
	{
		token = Shader_ParseString(ptr);
		Q_strncpyz(tokencopy, token, sizeof(tokencopy));	//make sure things don't go squiff.
	}

	if (mode == -1)
	{
		//shorthand for rtlights
		for (mode = 0; mode < LSHADER_MODES; mode++)
		{
			if ((mode & LSHADER_CUBE) && (mode & LSHADER_SPOT))
				continue;
			shader->bemoverrides[mode] = R_RegisterCustom(va("%s%s%s%s%s", 
																tokencopy,
																(mode & LSHADER_SMAP)?"#PCF":"",
																(mode & LSHADER_SPOT)?"#SPOT":"",
																(mode & LSHADER_CUBE)?"#CUBE":"",
#ifdef GLQUAKE
																(qrenderer == QR_OPENGL && gl_config.arb_shadow && (mode & (LSHADER_SMAP|LSHADER_SPOT)))?"#USE_ARB_SHADOW":""
#else
																""
#endif
																)
														, shader->usageflags, embed?Shader_DefaultScript:NULL, embed);
		}
	}
	else
	{
		shader->bemoverrides[mode] = R_RegisterCustom(tokencopy, shader->usageflags, embed?Shader_DefaultScript:NULL, embed);
	}
	if (embed)
		BZ_Free(embed);
}

static shaderkey_t shaderkeys[] =
{
	{"cull",			Shader_Cull},
	{"skyparms",		Shader_SkyParms},
	{"fogparms",		Shader_FogParms},
	{"surfaceparm",		Shader_SurfaceParm},
	{"nomipmaps",		Shader_NoMipMaps},
	{"nopicmip",		Shader_NoPicMip},
	{"polygonoffset",	Shader_PolygonOffset},
	{"sort",			Shader_Sort},
	{"deformvertexes",	Shader_DeformVertexes},
	{"portal",			Shader_Portal},
	{"entitymergable",	Shader_EntityMergable},

	//fte extensions
	{"clutter",			Shader_ClutterParms,		"fte"},
	{"lpp_light",		Shader_Prelight,			"fte"},
	{"glslprogram",		Shader_GLSLProgramName,		"fte"},
	{"program",			Shader_ProgramName,			"fte"},	//gl or d3d
	{"hlslprogram",		Shader_HLSL9ProgramName,	"fte"},	//for d3d
	{"hlsl11program",	Shader_HLSL11ProgramName,	"fte"},	//for d3d
	{"param",			Shader_ProgramParam,		"fte"},	//legacy

	{"bemode",			Shader_BEMode,				"fte"},

	//dp compat
	{"camera",			Shader_DP_Camera,			"dp"},

	/*doom3 compat*/
	{"diffusemap",		Shader_DiffuseMap,			"doom3"},	//macro for "{\nstage diffusemap\nmap <map>\n}"
	{"bumpmap",			Shader_BumpMap,				"doom3"},	//macro for "{\nstage bumpmap\nmap <map>\n}"
	{"specularmap",		Shader_SpecularMap,			"doom3"},//macro for "{\nstage specularmap\nmap <map>\n}"
	{"fullbrightmap",	Shader_FullbrightMap,		"doom3"},//macro for "{\nstage specularmap\nmap <map>\n}"
	{"uppermap",		Shader_UpperMap,			"doom3"},//macro for "{\nstage specularmap\nmap <map>\n}"
	{"lowermap",		Shader_LowerMap,			"doom3"},//macro for "{\nstage specularmap\nmap <map>\n}"
	{"discrete",		NULL,						"doom3"},
	{"nonsolid",		NULL,						"doom3"},
	{"noimpact",		NULL,						"doom3"},
	{"translucent",		Shader_Translucent,			"doom3"},
	{"noshadows",		NULL,						"doom3"},
	{"nooverlays",		NULL,						"doom3"},
	{"nofragment",		NULL,						"doom3"},

	/*simpler parsing for fte shaders*/
	{"progblendfunc",	Shader_ProgBlendFunc,		"fte"},
	{"progmap",			Shader_ProgMap,				"fte"},

	{NULL,				NULL}
};

static struct
{
	char *name;
	char *body;
} shadermacros[] =
{
	{"decal_macro", 	"polygonOffset 1\ndiscrete\nsort decal\nnoShadows"},
//	{"diffusemap", 		"{\nblend diffusemap\nmap %1\n}"},
//	{"bumpmap", 		"{\nblend bumpmap\nmap %1\n}"},
//	{"specularmap", 	"{\nblend specularmap\nmap %1\n}"},
	{NULL}
};

// ===============================================================

static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *tname)
{
	int tcgen = TC_GEN_BASE;
	if (!Q_stricmp (tname, "$lightmap"))
	{
		tcgen = TC_GEN_LIGHTMAP;
		pass->flags |= SHADER_PASS_LIGHTMAP | SHADER_PASS_NOMIPMAP;
		pass->texgen = T_GEN_LIGHTMAP;
		shader->flags |= SHADER_HASLIGHTMAP;
	}
	else if (!Q_stricmp (tname, "$deluxmap"))
	{
		tcgen = TC_GEN_LIGHTMAP;
		pass->flags |= SHADER_PASS_DELUXMAP | SHADER_PASS_NOMIPMAP;
		pass->texgen = T_GEN_DELUXMAP;
	}
	else if (!Q_stricmp (tname, "$diffuse"))
	{
		pass->texgen = T_GEN_DIFFUSE;
		shader->flags |= SHADER_HASDIFFUSE;
	}
	else if (!Q_stricmp (tname, "$paletted"))
	{
		pass->texgen = T_GEN_PALETTED;
		shader->flags |= SHADER_HASPALETTED;
	}
	else if (!Q_stricmp (tname, "$normalmap"))
	{
		pass->texgen = T_GEN_NORMALMAP;
		shader->flags |= SHADER_HASNORMALMAP;
	}
	else if (!Q_stricmp (tname, "$specular"))
	{
		pass->texgen = T_GEN_SPECULAR;
		shader->flags |= SHADER_HASGLOSS;
	}
	else if (!Q_stricmp (tname, "$fullbright"))
	{
		pass->texgen = T_GEN_FULLBRIGHT;
		shader->flags |= SHADER_HASFULLBRIGHT;
	}
	else if (!Q_stricmp (tname, "$upperoverlay"))
	{
		shader->flags |= SHADER_HASTOPBOTTOM;
		pass->texgen = T_GEN_UPPEROVERLAY;
	}
	else if (!Q_stricmp (tname, "$loweroverlay"))
	{
		shader->flags |= SHADER_HASTOPBOTTOM;
		pass->texgen = T_GEN_LOWEROVERLAY;
	}
	else if (!Q_stricmp (tname, "$shadowmap"))
	{
		pass->texgen = T_GEN_SHADOWMAP;
		pass->flags |= SHADER_PASS_DEPTHCMP;
	}
	else if (!Q_stricmp (tname, "$lightcubemap"))
	{
		pass->texgen = T_GEN_LIGHTCUBEMAP;
	}
	else if (!Q_stricmp (tname, "$currentrender"))
	{
		pass->texgen = T_GEN_CURRENTRENDER;
	}
	else if (!Q_stricmp (tname, "$sourcecolour"))
	{
		pass->texgen = T_GEN_SOURCECOLOUR;
	}
	else if (!Q_stricmp (tname, "$sourcecube"))
	{
		pass->texgen = T_GEN_SOURCECUBE;
	}
	else if (!Q_stricmp (tname, "$sourcedepth"))
	{
		pass->texgen = T_GEN_SOURCEDEPTH;
	}
	else if (!Q_stricmp (tname, "$reflection"))
	{
		shader->flags |= SHADER_HASREFLECT;
		pass->texgen = T_GEN_REFLECTION;
	}
	else if (!Q_stricmp (tname, "$refraction"))
	{
		shader->flags |= SHADER_HASREFRACT;
		pass->texgen = T_GEN_REFRACTION;
	}
	else if (!Q_stricmp (tname, "$refractiondepth"))
	{
		shader->flags |= SHADER_HASREFRACT;
		pass->texgen = T_GEN_REFRACTIONDEPTH;
	}
	else if (!Q_stricmp (tname, "$ripplemap"))
	{
		shader->flags |= SHADER_HASRIPPLEMAP;
		pass->texgen = T_GEN_RIPPLEMAP;
	}
	else if (!Q_stricmp (tname, "$null"))
	{
		pass->flags |= SHADER_PASS_NOMIPMAP|SHADER_PASS_DETAIL;
		pass->texgen = T_GEN_SINGLEMAP;
	}
	else
		return false;

	if (pass->tcgen == TC_GEN_UNSPECIFIED)
		pass->tcgen = tcgen;
	return true;
}

static void Shaderpass_Map (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	int flags;
	char *token;

	pass->anim_frames[0] = r_nulltex;

	token = Shader_ParseString (ptr);

	flags = Shader_SetImageFlags (shader, pass, &token);
	if (!Shaderpass_MapGen(shader, pass, token))
	{
		switch((flags & IF_TEXTYPE) >> IF_TEXTYPESHIFT)
		{
		case 0:
			pass->texgen = T_GEN_SINGLEMAP;
			break;
		case 1:
			pass->texgen = T_GEN_3DMAP;
			break;
		default:
			pass->texgen = T_GEN_CUBEMAP;
			break;
		}

		if (pass->tcgen == TC_GEN_UNSPECIFIED)
			pass->tcgen = TC_GEN_BASE;
		if (!*shader->mapname && *token != '$' && pass->tcgen == TC_GEN_BASE)
			Q_strncpyz(shader->mapname, token, sizeof(shader->mapname));
		pass->anim_frames[0] = Shader_FindImage (token, flags);
	}
}

static void Shaderpass_AnimMap (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	int flags;
	char *token;
	texid_t image;

	flags = Shader_SetImageFlags (shader, pass, NULL);

	if (pass->tcgen == TC_GEN_UNSPECIFIED)
		pass->tcgen = TC_GEN_BASE;
	pass->flags |= SHADER_PASS_ANIMMAP;
	pass->texgen = T_GEN_ANIMMAP;
	pass->anim_fps = (int)Shader_ParseFloat (shader, ptr, 0);
	pass->anim_numframes = 0;

	for ( ; ; )
	{
		token = Shader_ParseString(ptr);
		if (!token[0])
		{
			break;
		}

		if (pass->anim_numframes < SHADER_MAX_ANIMFRAMES)
		{
			image = Shader_FindImage (token, flags);

			if (!TEXVALID(image))
			{
				pass->anim_frames[pass->anim_numframes++] = missing_texture;
				Con_DPrintf (CON_WARNING "Shader %s has an animmap with no image: %s.\n", shader->name, token );
			}
			else
			{
				pass->anim_frames[pass->anim_numframes++] = image;
			}
		}
	}
}

static void Shaderpass_ClampMap (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	int flags;
	char *token;

	token = Shader_ParseString (ptr);

	flags = Shader_SetImageFlags (shader, pass, &token);
	if (!Shaderpass_MapGen(shader, pass, token))
	{
		if (pass->tcgen == TC_GEN_UNSPECIFIED)
			pass->tcgen = TC_GEN_BASE;
		pass->anim_frames[0] = Shader_FindImage (token, flags | IF_CLAMP);

		switch((flags & IF_TEXTYPE) >> IF_TEXTYPESHIFT)
		{
		case 0:
			pass->texgen = T_GEN_SINGLEMAP;
			break;
		case 1:
			pass->texgen = T_GEN_3DMAP;
			break;
		default:
			pass->texgen = T_GEN_CUBEMAP;
			break;
		}

		if (!TEXVALID(pass->anim_frames[0]))
		{
			if (flags & (IF_3DMAP | IF_CUBEMAP))
				pass->anim_frames[0] = r_nulltex;
			else
				pass->anim_frames[0] = missing_texture;
			Con_DPrintf (CON_WARNING "Shader %s has a stage with no image: %s.\n", shader->name, token);
		}
	}
	pass->flags |= SHADER_PASS_CLAMP;
}

static void Shaderpass_VideoMap (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char		*token;

	token = Shader_ParseSensString (ptr);

#ifdef NOMEDIA
#else
	if (pass->cin)
		Z_Free (pass->cin);

	pass->cin = Media_StartCin(token);
	if (!pass->cin)
		pass->cin = Media_StartCin(va("video/%s.roq", token));
	if (!pass->cin)
		Con_DPrintf (CON_WARNING "(shader %s) Couldn't load video %s\n", shader->name, token);

	if (pass->cin)
	{
		pass->flags |= SHADER_PASS_VIDEOMAP;
		shader->flags |= SHADER_VIDEOMAP;
		pass->texgen = T_GEN_VIDEOMAP;
	}
	else
	{
		pass->texgen = T_GEN_DIFFUSE;
		pass->rgbgen = RGB_GEN_CONST;
		pass->rgbgen_func.type = SHADER_FUNC_CONSTANT;
		pass->rgbgen_func.args[0] = pass->rgbgen_func.args[1] = pass->rgbgen_func.args[2] = 0;
	}
#endif
}

static void Shaderpass_RGBGen (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char		*token;

	token = Shader_ParseString (ptr);
	if (!Q_stricmp (token, "identitylighting"))
		pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING;
	else if (!Q_stricmp (token, "identity"))
		pass->rgbgen = RGB_GEN_IDENTITY;
	else if (!Q_stricmp (token, "wave"))
	{
		pass->rgbgen = RGB_GEN_WAVE;
		Shader_ParseFunc (shader, ptr, &pass->rgbgen_func);
	}
	else if (!Q_stricmp(token, "entity"))
		pass->rgbgen = RGB_GEN_ENTITY;
	else if (!Q_stricmp (token, "oneMinusEntity"))
		pass->rgbgen = RGB_GEN_ONE_MINUS_ENTITY;
	else if (!Q_stricmp (token, "vertex"))
		pass->rgbgen = RGB_GEN_VERTEX_LIGHTING;
	else if (!Q_stricmp (token, "oneMinusVertex"))
		pass->rgbgen = RGB_GEN_ONE_MINUS_VERTEX;
	else if (!Q_stricmp (token, "lightingDiffuse"))
		pass->rgbgen = RGB_GEN_LIGHTING_DIFFUSE;
	else if (!Q_stricmp (token, "exactvertex"))
		pass->rgbgen = RGB_GEN_VERTEX_EXACT;
	else if (!Q_stricmp (token, "const") || !Q_stricmp (token, "constant"))
	{
		pass->rgbgen = RGB_GEN_CONST;
		pass->rgbgen_func.type = SHADER_FUNC_CONSTANT;

		Shader_ParseVector (shader, ptr, pass->rgbgen_func.args);
	}
	else if (!Q_stricmp (token, "topcolor"))
		pass->rgbgen = RGB_GEN_TOPCOLOR;
	else if (!Q_stricmp (token, "bottomcolor"))
		pass->rgbgen = RGB_GEN_BOTTOMCOLOR;
}

static void Shaderpass_AlphaGen (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char		*token;

	token = Shader_ParseString(ptr);
	if (!Q_stricmp (token, "portal"))
	{
		pass->alphagen = ALPHA_GEN_PORTAL;
		shader->portaldist = Shader_ParseFloat(shader, ptr, 256);
		if (!shader->portaldist)
			shader->portaldist = 256;
		shader->flags |= SHADER_AGEN_PORTAL;
	}
	else if (!Q_stricmp (token, "vertex"))
	{
		pass->alphagen = ALPHA_GEN_VERTEX;
	}
	else if (!Q_stricmp (token, "entity"))
	{
		pass->alphagen = ALPHA_GEN_ENTITY;
	}
	else if (!Q_stricmp (token, "wave"))
	{
		pass->alphagen = ALPHA_GEN_WAVE;

		Shader_ParseFunc (shader, ptr, &pass->alphagen_func);
	}
	else if ( !Q_stricmp (token, "lightingspecular"))
	{
		pass->alphagen = ALPHA_GEN_SPECULAR;
	}
	else if ( !Q_stricmp (token, "const") || !Q_stricmp (token, "constant"))
	{
		pass->alphagen = ALPHA_GEN_CONST;
		pass->alphagen_func.type = SHADER_FUNC_CONSTANT;
		pass->alphagen_func.args[0] = fabs(Shader_ParseFloat(shader, ptr, 0));
	}
}
static void Shaderpass_AlphaShift (shader_t *shader, shaderpass_t *pass, char **ptr)	//for alienarena
{
	float speed;
	float min, max;
	pass->alphagen = ALPHA_GEN_WAVE;

	pass->alphagen_func.type = SHADER_FUNC_SIN;


	//arg0 = add
	//arg1 = scale
	//arg2 = timeshift
	//arg3 = timescale

	speed = Shader_ParseFloat(shader, ptr, 0);
	min = Shader_ParseFloat(shader, ptr, 0);
	max = Shader_ParseFloat(shader, ptr, 0);

	pass->alphagen_func.args[0] = min + (max - min)/2;
	pass->alphagen_func.args[1] = (max - min)/2;
	pass->alphagen_func.args[2] = 0;
	pass->alphagen_func.args[3] = 1/speed;
}

static int Shader_BlendFactor(char *name, qboolean dstnotsrc)
{
	int factor;
	if (!strnicmp(name, "gl_", 3))
		name += 3;

	if (!Q_stricmp(name, "zero"))
		factor = SBITS_SRCBLEND_ZERO;
	else if ( !Q_stricmp(name, "one"))
		factor = SBITS_SRCBLEND_ONE;
	else if (!Q_stricmp(name, "dst_color"))
		factor = SBITS_SRCBLEND_DST_COLOR;
	else if (!Q_stricmp(name, "one_minus_src_alpha"))
		factor = SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA;
	else if (!Q_stricmp(name, "src_alpha"))
		factor = SBITS_SRCBLEND_SRC_ALPHA;
	else if (!Q_stricmp(name, "src_color"))
		factor = SBITS_SRCBLEND_SRC_COLOR_INVALID;
	else if (!Q_stricmp(name, "one_minus_dst_color"))
		factor = SBITS_SRCBLEND_ONE_MINUS_DST_COLOR;
	else if (!Q_stricmp(name, "one_minus_src_color"))
		factor = SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID;
	else if (!Q_stricmp(name, "dst_alpha") )
		factor = SBITS_SRCBLEND_DST_ALPHA;
	else if (!Q_stricmp(name, "one_minus_dst_alpha"))
		factor = SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA;
	else
		factor = SBITS_SRCBLEND_NONE;

	if (dstnotsrc)
	{
		//dest factors are shifted
		factor <<= 4;

		/*gl doesn't accept dst_color for destinations*/
		if (factor == SBITS_DSTBLEND_NONE ||
			factor == SBITS_DSTBLEND_DST_COLOR_INVALID ||
			factor == SBITS_DSTBLEND_ONE_MINUS_DST_COLOR_INVALID ||
			factor == SBITS_DSTBLEND_ALPHA_SATURATE_INVALID)
		{
			Con_DPrintf("Invalid shader dst blend \"%s\"\n", name);
			factor = SBITS_DSTBLEND_ONE;
		}
	}
	else
	{
		/*gl doesn't accept src_color for sources*/
		if (factor == SBITS_SRCBLEND_NONE ||
			factor == SBITS_SRCBLEND_SRC_COLOR_INVALID ||
			factor == SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID)
		{
			Con_DPrintf("Unrecognised shader src blend \"%s\"\n", name);
			factor = SBITS_SRCBLEND_ONE;
		}
	}

	return factor;
}

static void Shaderpass_BlendFunc (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char		*token;

	//reset to defaults
	pass->shaderbits &= ~(SBITS_BLEND_BITS);
	pass->stagetype = ST_AMBIENT;

	token = Shader_ParseString (ptr);
	if ( !Q_stricmp (token, "bumpmap"))				//doom3 is awkward...
		pass->stagetype = ST_BUMPMAP;
	else if ( !Q_stricmp (token, "specularmap"))	//doom3 is awkward...
		pass->stagetype = ST_SPECULARMAP;
	else if ( !Q_stricmp (token, "diffusemap"))		//doom3 is awkward...
		pass->stagetype = ST_DIFFUSEMAP;
	else if ( !Q_stricmp (token, "blend"))
		pass->shaderbits |= SBITS_SRCBLEND_SRC_ALPHA | SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
	else if (!Q_stricmp (token, "filter"))
		pass->shaderbits |= SBITS_SRCBLEND_DST_COLOR | SBITS_DSTBLEND_ZERO;
	else if (!Q_stricmp (token, "add"))
		pass->shaderbits |= SBITS_SRCBLEND_ONE | SBITS_DSTBLEND_ONE;
	else if (!Q_stricmp (token, "replace"))
		pass->shaderbits |= SBITS_SRCBLEND_NONE | SBITS_DSTBLEND_NONE;
	else
	{
		pass->shaderbits |= Shader_BlendFactor(token, false);

		token = Shader_ParseString (ptr);
		if (*token == ',')
			token = Shader_ParseString (ptr);
		pass->shaderbits |= Shader_BlendFactor(token, true);
	}
}

static void Shaderpass_AlphaFunc (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;

	pass->shaderbits &= ~SBITS_ATEST_BITS;

	token = Shader_ParseString (ptr);
	if (!Q_stricmp (token, "gt0"))
	{
		pass->shaderbits = SBITS_ATEST_GT0;
	}
	else if (!Q_stricmp (token, "lt128"))
	{
		pass->shaderbits = SBITS_ATEST_LT128;
	}
	else if (!Q_stricmp (token, "ge128"))
	{
		pass->shaderbits = SBITS_ATEST_GE128;
	}
}

static void Shaderpass_DepthFunc (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token;

	token = Shader_ParseString (ptr);
	if (!Q_stricmp (token, "equal"))
		pass->shaderbits |= SBITS_MISC_DEPTHEQUALONLY;
	else if (!Q_stricmp (token, "lequal"))
		pass->shaderbits &= ~SBITS_MISC_DEPTHEQUALONLY;
	else
		Con_DPrintf("Invalid depth func %s\n", token);
}

static void Shaderpass_DepthWrite (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	shader->flags |= SHADER_DEPTHWRITE;
	pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
}

static void Shaderpass_NoDepthTest (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	shader->flags |= SHADER_DEPTHWRITE;
	pass->shaderbits |= SBITS_MISC_NODEPTHTEST;
}

static void Shaderpass_NoDepth (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	shader->flags |= SHADER_DEPTHWRITE;
}

static void Shaderpass_TcMod (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	int i;
	tcmod_t *tcmod;
	char *token;

	if (pass->numtcmods >= SHADER_MAX_TC_MODS)
	{
		return;
	}

	tcmod = &pass->tcmods[pass->numtcmods];

	token = Shader_ParseString (ptr);
	if (!Q_stricmp (token, "rotate"))
	{
		tcmod->args[0] = -Shader_ParseFloat(shader, ptr, 0) / 360.0f;
		if (!tcmod->args[0])
		{
			return;
		}

		tcmod->type = SHADER_TCMOD_ROTATE;
	}
	else if ( !Q_stricmp (token, "scale") )
	{
		tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->type = SHADER_TCMOD_SCALE;
	}
	else if ( !Q_stricmp (token, "scroll") )
	{
		tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->type = SHADER_TCMOD_SCROLL;
	}
	else if (!Q_stricmp(token, "stretch"))
	{
		shaderfunc_t func;

		Shader_ParseFunc(shader, ptr, &func);

		tcmod->args[0] = func.type;
		for (i = 1; i < 5; ++i)
			tcmod->args[i] = func.args[i-1];
		tcmod->type = SHADER_TCMOD_STRETCH;
	}
	else if (!Q_stricmp (token, "transform"))
	{
		for (i = 0; i < 6; ++i)
			tcmod->args[i] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->type = SHADER_TCMOD_TRANSFORM;
	}
	else if (!Q_stricmp (token, "turb"))
	{
		for (i = 0; i < 4; i++)
			tcmod->args[i] = Shader_ParseFloat (shader, ptr, 0);
		tcmod->type = SHADER_TCMOD_TURB;
	}
	else
	{
		return;
	}

	pass->numtcmods++;
}

static void Shaderpass_Scale ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	//seperate x and y
	char *token;
	tcmod_t *tcmod;

	tcmod = &pass->tcmods[pass->numtcmods];

	tcmod->type = SHADER_TCMOD_SCALE;

	token = Shader_ParseString (ptr);
	if (!strcmp(token, "static"))
	{
		tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0);
	}
	else
	{
		tcmod->args[0] = atof(token);
	}

	while (**ptr == ' ' || **ptr == '\t')
		*ptr+=1;
	if (**ptr == ',')
		*ptr+=1;

	token = Shader_ParseString (ptr);
	if (!strcmp(token, "static"))
	{
		tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0);
	}
	else
	{
		tcmod->args[1] = atof(token);
	}

	pass->numtcmods++;
}

static void Shaderpass_Scroll (shader_t *shader, shaderpass_t *pass, char **ptr)
{
	//seperate x and y
	char *token;
	tcmod_t *tcmod;

	tcmod = &pass->tcmods[pass->numtcmods];

	token = Shader_ParseString ( ptr );
	if (!strcmp(token, "static"))
	{
		tcmod->type = SHADER_TCMOD_SCROLL;
		tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0);
	}
	else
	{
		Con_Printf("Bad shader scale\n");
		return;
	}

	token = Shader_ParseString ( ptr );
	if (!strcmp(token, "static"))
	{
		tcmod->type = SHADER_TCMOD_SCROLL;
		tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0);
	}
	else
	{
		Con_Printf("Bad shader scale\n");
		return;
	}

	pass->numtcmods++;
}


static void Shaderpass_TcGen ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	char *token;

	token = Shader_ParseString ( ptr );
	if ( !Q_stricmp (token, "base") ) {
		pass->tcgen = TC_GEN_BASE;
	} else if ( !Q_stricmp (token, "lightmap") ) {
		pass->tcgen = TC_GEN_LIGHTMAP;
	} else if ( !Q_stricmp (token, "environment") ) {
		pass->tcgen = TC_GEN_ENVIRONMENT;
	} else if ( !Q_stricmp (token, "vector") ) {
		pass->tcgen = TC_GEN_BASE;
	} else if ( !Q_stricmp (token, "normal") ) {
		pass->tcgen = TC_GEN_NORMAL;
	} else if ( !Q_stricmp (token, "svector") ) {
		pass->tcgen = TC_GEN_SVECTOR;
	} else if ( !Q_stricmp (token, "tvector") ) {
		pass->tcgen = TC_GEN_TVECTOR;
	}
}
static void Shaderpass_EnvMap ( shader_t *shader, shaderpass_t *pass, char **ptr )	//for alienarena
{
	pass->tcgen = TC_GEN_ENVIRONMENT;
}

static void Shaderpass_Detail ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	pass->flags |= SHADER_PASS_DETAIL;
}

static void Shaderpass_AlphaMask ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	pass->shaderbits &= ~SBITS_ATEST_BITS;
	pass->shaderbits |= SBITS_ATEST_GE128;
}

static void Shaderpass_NoLightMap ( shader_t *shader, shaderpass_t *pass, char **ptr )
{
	pass->rgbgen = RGB_GEN_IDENTITY;
}

static void Shaderpass_Red(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->rgbgen = RGB_GEN_CONST;
	pass->rgbgen_func.args[0] = Shader_ParseFloat(shader, ptr, 0);
}
static void Shaderpass_Green(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->rgbgen = RGB_GEN_CONST;
	pass->rgbgen_func.args[1] = Shader_ParseFloat(shader, ptr, 0);
}
static void Shaderpass_Blue(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->rgbgen = RGB_GEN_CONST;
	pass->rgbgen_func.args[2] = Shader_ParseFloat(shader, ptr, 0);
}
static void Shaderpass_Alpha(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->alphagen = ALPHA_GEN_CONST;
	pass->alphagen_func.args[0] = Shader_ParseFloat(shader, ptr, 0);
}
static void Shaderpass_MaskColor(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->shaderbits |= SBITS_MASK_RED|SBITS_MASK_GREEN|SBITS_MASK_BLUE;
}
static void Shaderpass_MaskRed(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->shaderbits |= SBITS_MASK_RED;
}
static void Shaderpass_MaskGreen(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->shaderbits |= SBITS_MASK_GREEN;
}
static void Shaderpass_MaskBlue(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->shaderbits |= SBITS_MASK_BLUE;
}
static void Shaderpass_MaskAlpha(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	pass->shaderbits |= SBITS_MASK_ALPHA;
}
static void Shaderpass_AlphaTest(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	if (Shader_ParseFloat(shader, ptr, 0) == 0.5)
		pass->shaderbits |= SBITS_ATEST_GE128;
	else
		Con_Printf("unsupported alphatest value\n");
}
static void Shaderpass_TexGen(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token = Shader_ParseString(ptr);
	if (!strcmp(token, "normal"))
		pass->tcgen = TC_GEN_NORMAL;
	else if (!strcmp(token, "skybox"))
		pass->tcgen = TC_GEN_SKYBOX;
	else if (!strcmp(token, "wobblesky"))
	{
		pass->tcgen = TC_GEN_WOBBLESKY;
		token = Shader_ParseString(ptr);
		token = Shader_ParseString(ptr);
		token = Shader_ParseString(ptr);
	}
	else if (!strcmp(token, "reflect"))
		pass->tcgen = TC_GEN_REFLECT;
	else
	{
		Con_Printf("texgen token not understood\n");
	}
}
static void Shaderpass_CubeMap(shader_t *shader, shaderpass_t *pass, char **ptr)
{
	char *token = Shader_ParseString(ptr);

	if (pass->tcgen == TC_GEN_BASE)
		pass->tcgen = TC_GEN_SKYBOX;
	pass->texgen = T_GEN_CUBEMAP;
	pass->anim_frames[0] = Shader_FindImage(token, IF_CUBEMAP);

	if (!TEXVALID(pass->anim_frames[0]))
	{
		pass->texgen = T_GEN_SINGLEMAP;
		pass->anim_frames[0] = missing_texture;
	}
}

static shaderkey_t shaderpasskeys[] =
{
	{"rgbgen",		Shaderpass_RGBGen },
	{"blendfunc",	Shaderpass_BlendFunc },
	{"depthfunc",	Shaderpass_DepthFunc },
	{"depthwrite",	Shaderpass_DepthWrite },
	{"nodepthtest",	Shaderpass_NoDepthTest },
	{"nodepth",		Shaderpass_NoDepth },
	{"alphafunc",	Shaderpass_AlphaFunc },
	{"tcmod",		Shaderpass_TcMod },
	{"map",			Shaderpass_Map },
	{"animmap",		Shaderpass_AnimMap },
	{"clampmap",	Shaderpass_ClampMap },
	{"videomap",	Shaderpass_VideoMap },
	{"tcgen",		Shaderpass_TcGen },
	{"envmap",		Shaderpass_EnvMap,			"rscript"},//for alienarena
	{"nolightmap",	Shaderpass_NoLightMap,		"rscript"},//for alienarena
	{"scale",		Shaderpass_Scale,			"rscript"},//for alienarena
	{"scroll",		Shaderpass_Scroll,			"rscript"},//for alienarena
	{"alphagen",	Shaderpass_AlphaGen,		"rscript"},
	{"alphashift",	Shaderpass_AlphaShift,		"rscript"},//for alienarena
	{"alphamask",	Shaderpass_AlphaMask,		"rscript"},//for alienarena
	{"detail",		Shaderpass_Detail,			"rscript"},

	/*doom3 compat*/
	{"blend",		Shaderpass_BlendFunc,		"doom3"},
	{"maskcolor",	Shaderpass_MaskColor,		"doom3"},
	{"maskred",		Shaderpass_MaskRed,			"doom3"},
	{"maskgreen",	Shaderpass_MaskGreen,		"doom3"},
	{"maskblue",	Shaderpass_MaskBlue,		"doom3"},
	{"maskalpha",	Shaderpass_MaskAlpha,		"doom3"},
	{"alphatest",	Shaderpass_AlphaTest,		"doom3"},
	{"texgen",		Shaderpass_TexGen,			"doom3"},
	{"cubemap",		Shaderpass_CubeMap,			"doom3"},	//one of these is wrong
	{"cameracubemap",Shaderpass_CubeMap,		"doom3"},	//one of these is wrong
	{"red",			Shaderpass_Red,				"doom3"},
	{"green",		Shaderpass_Green,			"doom3"},
	{"blue",		Shaderpass_Blue,			"doom3"},
	{"alpha",		Shaderpass_Alpha,			"doom3"},
	{NULL,			NULL}
};

// ===============================================================


void Shader_FreePass (shaderpass_t *pass)
{
#ifndef NOMEDIA
	if ( pass->flags & SHADER_PASS_VIDEOMAP )
	{
		Media_ShutdownCin(pass->cin);
		pass->cin = NULL;
	}
#endif
}

void Shader_Free (shader_t *shader)
{
	int i;
	shaderpass_t *pass;

	if (shader->bucket.data == shader)
		Hash_RemoveData(&shader_active_hash, shader->name, shader);
	shader->bucket.data = NULL;

	if (shader->prog)
	{
		if (shader->prog->refs-- == 1)
			Shader_UnloadProg(shader->prog);
	}
	shader->prog = NULL;

	if (shader->skydome)
		Z_Free (shader->skydome);
	while (shader->clutter)
	{
		void *t = shader->clutter;
		shader->clutter = shader->clutter->next;
		Z_Free(t);
	}

	pass = shader->passes;
	for (i = 0; i < shader->numpasses; i++, pass++)
	{
		Shader_FreePass (pass);
	}
	shader->numpasses = 0;

	if (shader->genargs)
	{
		free(shader->genargs);
		shader->genargs = NULL;
	}
	shader->uses = 0;

	memset(&shader->defaulttextures, 0, sizeof(shader->defaulttextures));
}





int QDECL Shader_InitCallback (const char *name, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath)
{
	Shader_MakeCache(name);
	return true;
}

qboolean Shader_Init (void)
{
	int wibuf[16];

	if (!r_shaders)
	{
		r_numshaders = 0;
		r_maxshaders = 256;
		r_shaders = calloc(r_maxshaders, sizeof(*r_shaders));

		shader_hash = calloc (HASH_SIZE, sizeof(*shader_hash));

		shader_active_hash_mem = malloc(Hash_BytesForBuckets(1024));
		memset(shader_active_hash_mem, 0, Hash_BytesForBuckets(1024));
		Hash_InitTable(&shader_active_hash, 1024, shader_active_hash_mem);

		Shader_FlushGenerics();
	}
	Shader_NeedReload(true);
	Shader_DoReload();

	memset(wibuf, 0xff, sizeof(wibuf));
	if (!qrenderer)
		r_whiteimage = r_nulltex;
	else
		r_whiteimage = R_LoadTexture("$whiteimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA);
	return true;
}

void Shader_FlushCache(void)
{
	shadercachefile_t *sf;
	shadercache_t *cache, *cache_next;
	int i;

	for (i = 0; i < HASH_SIZE; i++)
	{
		cache = shader_hash[i];
		shader_hash[i] = NULL;

		for (; cache; cache = cache_next)
		{
			cache_next = cache->hash_next;
			cache->hash_next = NULL;
			Z_Free(cache);
		}
	}

	while(shaderfiles)
	{
		sf = shaderfiles;
		shaderfiles = sf->next;
		if (sf->data)
			FS_FreeFile(sf->data);
		Z_Free(sf);
	}
}

static void Shader_MakeCache(const char *path)
{
	unsigned int key;
	char *buf, *ptr, *token;
	shadercache_t *cache;
	shadercachefile_t *cachefile, *filelink = NULL;
	qofs_t size;

	for (cachefile = shaderfiles; cachefile; cachefile = cachefile->next)
	{
		if (!Q_stricmp(cachefile->name, path))
			return;	//already loaded. there's no source package or anything.
		filelink = cachefile;
	}

	
	Con_DPrintf ("...loading '%s'\n", path);

	cachefile = Z_Malloc(sizeof(*cachefile) + strlen(path));
	strcpy(cachefile->name, path);
	size = FS_LoadFile(path, (void **)&cachefile->data);
	cachefile->length = size;
	if (filelink)
		filelink->next = cachefile;
	else
		shaderfiles = cachefile;

	if (qofs_Error(size))
	{
		Con_Printf("Unable to read %s\n", path);
		cachefile->length = 0;
		return;
	}
	if (size > 1024*1024*64)	//sanity limit
	{
		Con_Printf("Refusing to parse %s due to size\n", path);
		cachefile->length = 0;
		FS_FreeFile(cachefile->data);
		cachefile->data = NULL;
		return;
	}

	ptr = buf = cachefile->data;
	size = cachefile->length;
	do
	{
		if ( ptr - buf >= size )
			break;

		token = COM_ParseExt (&ptr, true, true);
		if ( !token[0] || ptr - buf >= size )
			break;

		COM_CleanUpPath(token);

		if (Shader_LocateSource(token, NULL, NULL, NULL, NULL))
		{
			ptr = Shader_Skip ( ptr );
			continue;
		}

		key = Hash_Key ( token, HASH_SIZE );

		cache = ( shadercache_t * )Z_Malloc(sizeof(shadercache_t) + strlen(token));
		strcpy(cache->name, token);
		cache->hash_next = shader_hash[key];
		cache->source = cachefile;
		cache->offset = ptr - cachefile->data;

		shader_hash[key] = cache;

		ptr = Shader_Skip ( ptr );
	} while ( ptr );
}

static qboolean Shader_LocateSource(char *name, char **buf, size_t *bufsize, size_t *offset, enum shaderparsemode_e *parsemode)
{
	unsigned int key;
	shadercache_t *cache;

	key = Hash_Key ( name, HASH_SIZE );
	cache = shader_hash[key];

	for ( ; cache; cache = cache->hash_next )
	{
		if ( !Q_stricmp (cache->name, name) )
		{
			if (buf)
			{
				*buf = cache->source->data;
				*bufsize = cache->source->length;
				*offset = cache->offset;
				*parsemode = cache->source->parsemode;
			}
			return true;
		}
	}
	return false;
}

char *Shader_Skip ( char *ptr )
{
	char *tok;
	int brace_count;

    // Opening brace
	tok = COM_ParseExt(&ptr, true, true);

	if (!ptr)
		return NULL;

	if ( tok[0] != '{' )
	{
		tok = COM_ParseExt (&ptr, true, true);
	}

	for (brace_count = 1; brace_count > 0 ; ptr++)
	{
		tok = COM_ParseExt (&ptr, true, true);

		if ( !tok[0] )
			return NULL;

		if (tok[0] == '{')
		{
			brace_count++;
		} else if (tok[0] == '}')
		{
			brace_count--;
		}
	}

	return ptr;
}

void Shader_Reset(shader_t *s)
{
	char name[MAX_QPATH];
	int id = s->id;
	int uses = s->uses;
	shader_gen_t *defaultgen = s->generator;
	char *genargs = s->genargs;
	texnums_t dt = s->defaulttextures;
	int w = s->width;
	int h = s->height;
	unsigned int uf = s->usageflags;
	Q_strncpyz(name, s->name, sizeof(name));
	s->genargs = NULL;
	Shader_Free(s);
	memset(s, 0, sizeof(*s));

	s->flags |= SHADER_IMAGEPENDING;

	s->remapto = s;
	s->id = id;
	s->width = w;
	s->height = h;
	s->defaulttextures = dt;
	s->generator = defaultgen;
	s->genargs = genargs;
	s->usageflags = uf;
	s->uses = uses;
	Q_strncpyz(s->name, name, sizeof(s->name));
	Hash_Add(&shader_active_hash, s->name, s, &s->bucket);
}

void Shader_Shutdown (void)
{
	int i;
	shader_t *shader;

	if (!r_shaders)
		return;	/*nothing needs freeing yet*/
	for (i = 0; i < r_numshaders; i++)
	{
		shader = r_shaders[i];
		if (!shader)
			continue;

		Shader_Free(shader);
		Z_Free(r_shaders[i]);
		r_shaders[i] = NULL;
	}

	Shader_FlushCache();
	Shader_FlushGenerics();

	r_maxshaders = 0;
	r_numshaders = 0;

	free(r_shaders);
	r_shaders = NULL;
	free(shader_hash);
	shader_hash = NULL;
	free(shader_active_hash_mem);
	shader_active_hash_mem = NULL;

	shader_reload_needed = false;
}

void Shader_SetBlendmode (shaderpass_t *pass)
{
	if (pass->texgen == T_GEN_DELUXMAP)
	{
		pass->blendmode = PBM_DOTPRODUCT;
		return;
	}

	if (pass->texgen < T_GEN_DIFFUSE && !TEXVALID(pass->anim_frames[0]) && !(pass->flags & SHADER_PASS_LIGHTMAP))
	{
		pass->blendmode = PBM_MODULATE;
		return;
	}

	if (!(pass->shaderbits & SBITS_BLEND_BITS))
	{
		if ((pass->rgbgen == RGB_GEN_IDENTITY) && (pass->alphagen == ALPHA_GEN_IDENTITY))
		{
			pass->blendmode = PBM_REPLACE;
			return;
		}
		else if ((pass->rgbgen == RGB_GEN_IDENTITY_LIGHTING) && (pass->alphagen == ALPHA_GEN_IDENTITY))
		{
			pass->shaderbits &= ~SBITS_BLEND_BITS;
			pass->shaderbits |= SBITS_SRCBLEND_ONE;
			pass->shaderbits |= SBITS_DSTBLEND_ZERO;
			pass->blendmode = PBM_REPLACELIGHT;
		}
		else
		{
			pass->shaderbits &= ~SBITS_BLEND_BITS;
			pass->shaderbits |= SBITS_SRCBLEND_ONE;
			pass->shaderbits |= SBITS_DSTBLEND_ZERO;
			pass->blendmode = PBM_MODULATE;
		}
		return;
	}

	if (((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ZERO|SBITS_DSTBLEND_SRC_COLOR)) ||
		((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_DST_COLOR|SBITS_DSTBLEND_ZERO)))
		pass->blendmode = PBM_MODULATE;
	else if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ONE))
		pass->blendmode = PBM_ADD;
	else if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_SRC_ALPHA|SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA))
		pass->blendmode = PBM_DECAL;
	else
		pass->blendmode = PBM_MODULATE;
}

void Shader_Readpass (shader_t *shader, char **ptr)
{
	char *token;
	shaderpass_t *pass;
	qboolean ignore;
	static shader_t dummy;
	int conddepth = 0;
	int cond[8] = {0};
#define COND_IGNORE 1
#define COND_IGNOREPARENT 2
#define COND_ALLOWELSE 4

	if ( shader->numpasses >= SHADER_PASS_MAX )
	{
		ignore = true;
		shader = &dummy;
		shader->numpasses = 1;
		pass = shader->passes;
	}
	else
	{
		ignore = false;
		pass = &shader->passes[shader->numpasses++];
	}

    // Set defaults
	pass->flags = 0;
	pass->anim_frames[0] = r_nulltex;
	pass->anim_numframes = 0;
	pass->rgbgen = RGB_GEN_UNKNOWN;
	pass->alphagen = ALPHA_GEN_IDENTITY;
	pass->tcgen = TC_GEN_UNSPECIFIED;
	pass->numtcmods = 0;
	pass->numMergedPasses = 1;
	pass->stagetype = ST_AMBIENT;

	if (shader->flags & SHADER_NOMIPMAPS)
		pass->flags |= SHADER_PASS_NOMIPMAP;

	while ( *ptr )
	{
		token = COM_ParseExt (ptr, true, true);

		if ( !token[0] )
		{
			continue;
		}
		else if (!Q_stricmp(token, "if"))
		{
			if (conddepth+1 == sizeof(cond)/sizeof(cond[0]))
			{
				Con_Printf("if statements nest too deeply in shader %s\n", shader->name);
				break;
			}
			conddepth++;
			cond[conddepth] = (Shader_EvaluateCondition(shader, ptr)?0:COND_IGNORE);
			cond[conddepth] |= COND_ALLOWELSE;
			if (cond[conddepth-1] & (COND_IGNORE|COND_IGNOREPARENT))
				cond[conddepth] |= COND_IGNOREPARENT;
		}
		else if (!Q_stricmp(token, "endif"))
		{
			if (!conddepth)
			{
				Con_Printf("endif without if in shader %s\n", shader->name);
				break;
			}
			conddepth--;
		}
		else if (!Q_stricmp(token, "else"))
		{
			if (cond[conddepth] & COND_ALLOWELSE)
			{
				cond[conddepth] ^= COND_IGNORE;
				cond[conddepth] &= ~COND_ALLOWELSE;
			}
			else
				Con_Printf("unexpected else statement in shader %s\n", shader->name);
		}
		else if (cond[conddepth] & (COND_IGNORE|COND_IGNOREPARENT))
		{
			//eat it
			while (ptr)
			{
				token = COM_ParseExt(ptr, false, true);
				if ( !token[0] )
					break;
			}
		}
		else
		{
			if ( token[0] == '}' )
				break;
			else if ( Shader_Parsetok (shader, pass, shaderpasskeys, token, ptr) )
				break;
		}
	}

	if (conddepth)
	{
		Con_Printf("if statements without endif in shader %s\n", shader->name);
	}

	if (pass->tcgen == TC_GEN_UNSPECIFIED)
		pass->tcgen = TC_GEN_BASE;

	if (!ignore)
	{
		switch(pass->stagetype)
		{
		case ST_DIFFUSEMAP:
			if (pass->texgen == T_GEN_SINGLEMAP)
				shader->defaulttextures.base = pass->anim_frames[0];
			break;
		case ST_AMBIENT:
			break;
		case ST_BUMPMAP:
			if (pass->texgen == T_GEN_SINGLEMAP)
				shader->defaulttextures.bump = pass->anim_frames[0];
			ignore = true;	//fixme: scrolling etc may be important. but we're not doom3.
			break;
		case ST_SPECULARMAP:
			if (pass->texgen == T_GEN_SINGLEMAP)
				shader->defaulttextures.specular = pass->anim_frames[0];
			ignore = true;	//fixme: scrolling etc may be important. but we're not doom3.
			break;
		}
	}

	// check some things
	if (ignore)
	{
		Shader_FreePass (pass);
		shader->numpasses--;
		return;
	}

	if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO))
	{
		pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
		shader->flags |= SHADER_DEPTHWRITE;
	}

	switch (pass->rgbgen)
	{
		case RGB_GEN_IDENTITY_LIGHTING:
		case RGB_GEN_IDENTITY:
		case RGB_GEN_CONST:
		case RGB_GEN_WAVE:
		case RGB_GEN_ENTITY:
		case RGB_GEN_ONE_MINUS_ENTITY:
		case RGB_GEN_UNKNOWN:	// assume RGB_GEN_IDENTITY or RGB_GEN_IDENTITY_LIGHTING

			switch (pass->alphagen)
			{
				case ALPHA_GEN_IDENTITY:
				case ALPHA_GEN_CONST:
				case ALPHA_GEN_WAVE:
				case ALPHA_GEN_ENTITY:
					pass->flags |= SHADER_PASS_NOCOLORARRAY;
					break;
				default:
					break;
			}

			break;
		default:
			break;
	}

	/*if ((shader->flags & SHADER_SKY) && (shader->flags & SHADER_DEPTHWRITE))
	{
#ifdef warningmsg
#pragma warningmsg("is this valid?")
#endif
		pass->shaderbits &= ~SBITS_MISC_DEPTHWRITE;
	}
	*/
}

static qboolean Shader_Parsetok (shader_t *shader, shaderpass_t *pass, shaderkey_t *keys, char *token, char **ptr)
{
	shaderkey_t *key;
	char *prefix;

	//handle known prefixes.
	if		(!Q_strncasecmp(token, "fte", 3))		{prefix = token; token += 3; }
	else if	(!Q_strncasecmp(token, "dp", 2))		{prefix = token; token += 2; }
	else if (!Q_strncasecmp(token, "doom3", 5))		{prefix = token; token += 5; }
	else if (!Q_strncasecmp(token, "rscript", 7))	{prefix = token; token += 7; }
	else	prefix = NULL;
	if (prefix && *token == '_')
		token++;

	for (key = keys; key->keyword != NULL; key++)
	{
		if (!Q_stricmp (token, key->keyword))
		{
			if (!prefix || (prefix && key->prefix && !Q_strncasecmp(prefix, key->prefix, strlen(key->prefix))))
			{
				if (key->func)
					key->func ( shader, pass, ptr );

				return ( ptr && *ptr && **ptr == '}' );
			}
		}
	}

	if (prefix)
		Con_DPrintf("Unknown shader directive: \"%s_%s\"\n", prefix, token);
	else
		Con_DPrintf("Unknown shader directive: \"%s\"\n", token);

	// Next Line
	while (ptr)
	{
		token = COM_ParseExt(ptr, false, true);
		if ( !token[0] )
		{
			break;
		}
	}

	return false;
}

void Shader_SetPassFlush (shaderpass_t *pass, shaderpass_t *pass2)
{
	if (((pass->flags & SHADER_PASS_DETAIL) && !r_detailtextures.value) ||
		((pass2->flags & SHADER_PASS_DETAIL) && !r_detailtextures.value) ||
		 (pass->flags & SHADER_PASS_VIDEOMAP) || (pass2->flags & SHADER_PASS_VIDEOMAP))
	{
		return;
	}

	/*identity alpha is required for merging*/
	if (pass->alphagen != ALPHA_GEN_IDENTITY || pass2->alphagen != ALPHA_GEN_IDENTITY)
		return;

	/*rgbgen must be identity too except if the later pass is identity_ligting, in which case all is well and we can switch the first pass to identity_lighting instead*/
	if (pass2->rgbgen == RGB_GEN_IDENTITY_LIGHTING && pass2->blendmode == PBM_MODULATE && pass->rgbgen == RGB_GEN_IDENTITY)
	{
		pass->blendmode = PBM_REPLACELIGHT;
		pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING;
		pass2->rgbgen = RGB_GEN_IDENTITY;
	}
	/*rgbgen must be identity (or the first is identity_lighting)*/
	else if (pass2->rgbgen != RGB_GEN_IDENTITY || (pass->rgbgen != RGB_GEN_IDENTITY && pass->rgbgen != RGB_GEN_IDENTITY_LIGHTING))
		return;

	/*if its alphatest, don't merge with anything other than lightmap*/
	if ((pass->shaderbits & SBITS_ATEST_BITS) && (!(pass2->shaderbits & SBITS_MISC_DEPTHEQUALONLY) || pass2->texgen != T_GEN_LIGHTMAP))
		return;

	if ((pass->shaderbits & SBITS_MASK_BITS) != (pass2->shaderbits & SBITS_MASK_BITS))
		return;

	/*don't merge passes if the hardware cannot support it*/
	if (pass->numMergedPasses >= be_maxpasses)
		return;

	// check if we can use multiple passes
	if (pass2->blendmode == PBM_DOTPRODUCT)
	{
		pass->numMergedPasses++;
	}
	else if (pass->numMergedPasses < be_maxpasses)
	{
		if (pass->blendmode == PBM_REPLACE || pass->blendmode == PBM_REPLACELIGHT)
		{
			if ((pass2->blendmode == PBM_DECAL && sh_config.tex_env_combine) ||
				(pass2->blendmode == PBM_ADD && sh_config.env_add) ||
				(pass2->blendmode && pass2->blendmode != PBM_ADD) ||	sh_config.nv_tex_env_combine4)
			{
				pass->numMergedPasses++;
			}
		}
		else if (pass->blendmode == PBM_ADD &&
			pass2->blendmode == PBM_ADD && sh_config.env_add)
		{
			pass->numMergedPasses++;
		}
		else if (pass->blendmode == PBM_MODULATE && pass2->blendmode == PBM_MODULATE)
		{
			pass->numMergedPasses++;
		}
		else
			return;
	}
	else return;

	if (pass->texgen == T_GEN_LIGHTMAP && pass->blendmode == PBM_REPLACELIGHT && pass2->blendmode == PBM_MODULATE && sh_config.tex_env_combine)
	{
		if (pass->rgbgen == RGB_GEN_IDENTITY)
			pass->rgbgen = RGB_GEN_IDENTITY_OVERBRIGHT;	//get the light levels right
		pass2->blendmode = PBM_OVERBRIGHT;
	}
	if (pass2->texgen == T_GEN_LIGHTMAP && pass2->blendmode == PBM_MODULATE && sh_config.tex_env_combine)
	{
		if (pass->rgbgen == RGB_GEN_IDENTITY)
			pass->rgbgen = RGB_GEN_IDENTITY_OVERBRIGHT;	//get the light levels right
		pass->blendmode = PBM_REPLACELIGHT;
		pass2->blendmode = PBM_OVERBRIGHT;
	}
}

const char *Shader_AlphaMaskProgArgs(shader_t *s)
{
	if (s->numpasses)
	{
		//alpha mask values ALWAYS come from the first pass.
		shaderpass_t *pass = &s->passes[0];
		switch(pass->shaderbits & SBITS_ATEST_BITS)
		{
		default:
			break;
		//cases inverted. the test is to enable 
		case SBITS_ATEST_GT0:
			return "#MASK=0.0#MASKOP=>";
		case SBITS_ATEST_LT128:
			return "#MASK=0.5#MASKOP=<";
		case SBITS_ATEST_GE128:
			return "#MASK=0.5";
		}
	}
	return "";
}

void Shader_Programify (shader_t *s)
{
	char *prog = NULL;
	const char *mask;
/*	enum
	{
		T_UNKNOWN,
		T_WALL,
		T_MODEL
	} type = 0;*/
	int i;
	shaderpass_t *pass, *lightmap = NULL, *modellighting = NULL;
	for (i = 0; i < s->numpasses; i++)
	{
		pass = &s->passes[i];
		if (pass->rgbgen == RGB_GEN_LIGHTING_DIFFUSE)
			modellighting = pass;
		else if (pass->rgbgen == RGB_GEN_ENTITY)
			modellighting = pass;
		else if (pass->texgen == T_GEN_LIGHTMAP && pass->tcgen == TC_GEN_LIGHTMAP)
			lightmap = pass;
	}

	if (modellighting)
	{
		pass = modellighting;
		prog = "defaultskin";
	}
	else if (lightmap)
	{
		pass = modellighting;
		prog = "defaultwall";
	}
	else
	{
		pass = NULL;
		prog = "default2d";
		return;
	}

	mask = Shader_AlphaMaskProgArgs(s);

	s->prog = Shader_FindGeneric(va("%s%s", prog, mask), qrenderer);
	s->numpasses = 0;
	s->passes[s->numpasses++].texgen = T_GEN_DIFFUSE;
	s->flags |= SHADER_HASDIFFUSE;
}

void Shader_Finish (shader_t *s)
{
	int i;
	shaderpass_t *pass;
	
	//FIXME: reorder doom3 stages.
	//put diffuse first. give it a lightmap pass also, if we found a diffuse one with no lightmap.
	//then the ambient stages.
	//and forget about the bump/specular stages as we don't support them and already stripped them.

	if (s->flags & SHADER_SKY)
	{
		/*skies go all black if fastsky is set*/
		if (r_fastsky.ival)
			s->flags = 0;
		/*or if its purely a skybox and has missing textures*/
//		if (!s->numpasses)
//			for (i = 0; i < 6; i++)
//				if (missing_texture.ref == s->skydome->farbox_textures[i].ref)
//					s->flags = 0;
		if (!(s->flags & SHADER_SKY))
		{
			Shader_Reset(s);

			Shader_DefaultScript(s->name, s,
						"{\n"
							"sort sky\n"
							"{\n"
								"map $whiteimage\n"
								"rgbgen const $r_fastskycolour\n"
							"}\n"
							"surfaceparm nodlight\n"
						"}\n"
					);
			return;
		}
	}

	if (s->prog && !s->numpasses)
	{
		pass = &s->passes[s->numpasses++];
		pass->tcgen = TC_GEN_BASE;
		pass->texgen = T_GEN_DIFFUSE;
		pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
		pass->rgbgen = RGB_GEN_IDENTITY;
		pass->alphagen = ALPHA_GEN_IDENTITY;
		pass->numMergedPasses = 1;
		Shader_SetBlendmode(pass);
	}

	if (!s->numpasses && s->sort != SHADER_SORT_PORTAL && !(s->flags & (SHADER_NODRAW|SHADER_SKY)) && !s->fog_dist)
	{
		pass = &s->passes[s->numpasses++];
		pass = &s->passes[0];
		pass->tcgen = TC_GEN_BASE;
		if (TEXVALID(s->defaulttextures.base))
			pass->texgen = T_GEN_DIFFUSE;
		else
		{
			pass->texgen = T_GEN_SINGLEMAP;
			TEXASSIGN(pass->anim_frames[0], R_LoadHiResTexture(s->name, NULL, IF_NOALPHA));
			if (!TEXVALID(pass->anim_frames[0]))
			{
				Con_Printf("Shader %s failed to load default texture\n", s->name);
				pass->anim_frames[0] = missing_texture;
			}
			Con_Printf("Shader %s with no passes and no surfaceparm nodraw, inserting pass\n", s->name);
		}
		pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
		pass->rgbgen = RGB_GEN_VERTEX_LIGHTING;
		pass->alphagen = ALPHA_GEN_IDENTITY;
		pass->numMergedPasses = 1;
		Shader_SetBlendmode(pass);
	}

	if (!Q_stricmp (s->name, "flareShader"))
	{
		s->flags |= SHADER_FLARE;
		s->flags |= SHADER_NODRAW;
	}

	if (!s->numpasses && !s->sort)
	{
		s->sort = SHADER_SORT_ADDITIVE;
		return;
	}

	if (!s->sort && s->passes->texgen == T_GEN_CURRENTRENDER)
		s->sort = SHADER_SORT_NEAREST;


	if ((s->polyoffset.unit < 0) && !s->sort)
	{
		s->sort = SHADER_SORT_DECAL;
	}

	if ((r_vertexlight.value || !(s->usageflags & SUF_LIGHTMAP)) && !s->prog)
	{
		// do we have a lightmap pass?
		pass = s->passes;
		for (i = 0; i < s->numpasses; i++, pass++)
		{
			if (pass->flags & SHADER_PASS_LIGHTMAP)
				break;
		}

		if (i == s->numpasses)
		{
			goto done;
		}

		// try to find pass with rgbgen set to RGB_GEN_VERTEX
		pass = s->passes;
		for (i = 0; i < s->numpasses; i++, pass++)
		{
			if (pass->rgbgen == RGB_GEN_VERTEX_LIGHTING)
				break;
		}

		if (i < s->numpasses)
		{		// we found it
			pass->flags |= SHADER_CULL_FRONT;
			pass->flags &= ~SHADER_PASS_ANIMMAP;
			pass->shaderbits &= ~SBITS_BLEND_BITS;
			pass->blendmode = 0;
			pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
			pass->alphagen = ALPHA_GEN_IDENTITY;
			pass->numMergedPasses = 1;
			s->flags |= SHADER_DEPTHWRITE;
			s->sort = SHADER_SORT_OPAQUE;
			s->numpasses = 1;
			memcpy(&s->passes[0], pass, sizeof(shaderpass_t));
		}
		else
		{	// we didn't find it - simply remove all lightmap passes
			pass = s->passes;
			for(i = 0; i < s->numpasses; i++, pass++)
			{
				if (pass->flags & SHADER_PASS_LIGHTMAP)
					break;
			}

			if ( i == s->numpasses -1 )
			{
				s->numpasses--;
			}
			else if ( i < s->numpasses - 1 )
			{
				for ( ; i < s->numpasses - 1; i++, pass++ )
				{
					memcpy ( pass, &s->passes[i+1], sizeof(shaderpass_t) );
				}
				s->numpasses--;
			}

			if ( s->passes[0].numtcmods )
			{
				pass = s->passes;
				for ( i = 0; i < s->numpasses; i++, pass++ )
				{
					if ( !pass->numtcmods )
						break;
				}

				memcpy ( &s->passes[0], pass, sizeof(shaderpass_t) );
			}

			s->passes[0].rgbgen = RGB_GEN_VERTEX_LIGHTING;
			s->passes[0].alphagen = ALPHA_GEN_IDENTITY;
			s->passes[0].blendmode = 0;
			s->passes[0].flags &= ~(SHADER_PASS_ANIMMAP|SHADER_PASS_NOCOLORARRAY);
			s->passes[0].shaderbits &= ~SBITS_BLEND_BITS;
			s->passes[0].shaderbits |= SBITS_MISC_DEPTHWRITE;
			s->passes[0].numMergedPasses = 1;
			s->numpasses = 1;
			s->flags |= SHADER_DEPTHWRITE;
		}
	}
done:;

	//if we've no specular map, try and find whatever the q3 syntax said. hopefully it'll be compatible...
	if (!TEXVALID(s->defaulttextures.specular))
	{
		for (pass = s->passes, i = 0; i < s->numpasses; i++, pass++)
		{
			if (pass->alphagen == ALPHA_GEN_SPECULAR)
				if (pass->texgen == T_GEN_ANIMMAP || pass->texgen == T_GEN_SINGLEMAP)
					s->defaulttextures.specular = pass->anim_frames[0];
		}
	}

	if (!TEXVALID(s->defaulttextures.base))
	{
		shaderpass_t *best = NULL;
		int bestweight = 9999999;
		int weight;

		for (pass = s->passes, i = 0; i < s->numpasses; i++, pass++)
		{
			weight = 0;
			if (pass->flags & SHADER_PASS_DETAIL)
				weight += 500;	//prefer not to use a detail pass. these are generally useless.
			if (pass->numtcmods || pass->tcgen != TC_GEN_BASE)
				weight += 200;
			if (pass->rgbgen != RGB_GEN_IDENTITY && pass->rgbgen != RGB_GEN_IDENTITY_OVERBRIGHT && pass->rgbgen != RGB_GEN_IDENTITY_LIGHTING)
				weight += 100;

			if (pass->texgen != T_GEN_ANIMMAP && pass->texgen != T_GEN_SINGLEMAP && pass->texgen != T_GEN_VIDEOMAP)
				weight += 1000;

			if ((pass->texgen == T_GEN_ANIMMAP || pass->texgen == T_GEN_SINGLEMAP) && pass->anim_frames[0] && *pass->anim_frames[0]->ident == '$')
				weight += 1500;
			
			if (weight < bestweight)
			{
				bestweight = weight;
				best = pass;
			}
		}

		if (best)
		{
			if (best->texgen == T_GEN_ANIMMAP || best->texgen == T_GEN_SINGLEMAP)
			{
				if (best->anim_frames[0] && *best->anim_frames[0]->ident != '$')
					s->defaulttextures.base = best->anim_frames[0];
			}
#ifndef NOMEDIA
			else if (pass->texgen == T_GEN_VIDEOMAP && pass->cin)
				s->defaulttextures.base = Media_UpdateForShader(best->cin);
#endif
		}
	}

	pass = s->passes;
	for (i = 0; i < s->numpasses; i++, pass++)
	{
		if (!(pass->shaderbits & (SBITS_BLEND_BITS|SBITS_MASK_BITS)))
		{
			break;
		}
	}

	// all passes have blendfuncs
	if (i == s->numpasses)
	{
		int opaque;

		opaque = -1;
		pass = s->passes;
		for (i = 0; i < s->numpasses; i++, pass++ )
		{
			if (pass->shaderbits & SBITS_ATEST_BITS)
			{
				opaque = i;
			}

			if (pass->rgbgen == RGB_GEN_UNKNOWN)
			{
				if (   (pass->shaderbits & SBITS_SRCBLEND_BITS) == 0
					|| (pass->shaderbits & SBITS_SRCBLEND_BITS) == SBITS_SRCBLEND_ONE
					|| (pass->shaderbits & SBITS_SRCBLEND_BITS) == SBITS_SRCBLEND_SRC_ALPHA)
					pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING;
				else
					pass->rgbgen = RGB_GEN_IDENTITY;
			}

			Shader_SetBlendmode (pass);

			if (pass->blendmode == PBM_ADD)
				s->defaulttextures.fullbright = pass->anim_frames[0];
		}

		if (!(s->flags & SHADER_SKY ) && !s->sort)
		{
			if (opaque == -1)
				s->sort = SHADER_SORT_BLEND;
			else
				s->sort = SHADER_SORT_SEETHROUGH;
		}
	}
	else
	{
		int	j;
		shaderpass_t *sp;

		sp = s->passes;
		for (j = 0; j < s->numpasses; j++, sp++)
		{
			if (sp->rgbgen == RGB_GEN_UNKNOWN)
			{
				if (sp->flags & SHADER_PASS_LIGHTMAP)
					sp->rgbgen = RGB_GEN_IDENTITY_LIGHTING;
				else
					sp->rgbgen = RGB_GEN_IDENTITY;
			}

			Shader_SetBlendmode (sp);
		}

		if (!s->sort)
		{
			if (pass->shaderbits & SBITS_ATEST_BITS)
				s->sort = SHADER_SORT_SEETHROUGH;
		}

		if (!( s->flags & SHADER_DEPTHWRITE) &&
			!(s->flags & SHADER_SKY))
		{
			pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
			s->flags |= SHADER_DEPTHWRITE;
		}
	}

	if (s->numpasses >= 2)
	{
		int j;

		pass = s->passes;
		for (i = 0; i < s->numpasses;)
		{
			if (i == s->numpasses - 1)
				break;

			pass = s->passes + i;
			for (j = 1; j < s->numpasses-i && j == i + pass->numMergedPasses && j < be_maxpasses; j++)
				Shader_SetPassFlush (pass, pass + j);

			i += pass->numMergedPasses;
		}
	}

	if (!s->sort)
	{
		s->sort = SHADER_SORT_OPAQUE;
	}

	if ((s->flags & SHADER_SKY) && (s->flags & SHADER_DEPTHWRITE))
	{
		s->flags &= ~SHADER_DEPTHWRITE;
	}

	if (!s->bemoverrides[bemoverride_depthonly])
	{
		const char *mask = Shader_AlphaMaskProgArgs(s);
		if (*mask)
			s->bemoverrides[bemoverride_depthonly] = R_RegisterShader(va("depthonly%s", mask), SUF_NONE, 
				"{\n"
					"program depthonly\n"
					"{\n"
						"map $diffuse\n"
						"depthwrite\n"
						"maskcolor\n"
					"}\n"
				"}\n");
	}

	if (!s->prog && sh_config.progs_required)
		Shader_Programify(s);

	if (s->prog)
	{
		struct
		{
			int gen;
			unsigned int flags;
		} defaulttgen[] =
		{
			{T_GEN_DIFFUSE,			SHADER_HASDIFFUSE},
			{T_GEN_NORMALMAP,		SHADER_HASNORMALMAP},
			{T_GEN_SPECULAR,		SHADER_HASGLOSS},
			{T_GEN_UPPEROVERLAY,	SHADER_HASTOPBOTTOM},
			{T_GEN_LOWEROVERLAY,	SHADER_HASTOPBOTTOM},
			{T_GEN_FULLBRIGHT,		SHADER_HASFULLBRIGHT},
			{T_GEN_PALETTED,		SHADER_HASPALETTED},
			{T_GEN_SHADOWMAP,		0},
			{T_GEN_LIGHTCUBEMAP,	0},
//			{T_GEN_REFLECTION,		SHADER_HASREFLECT},
//			{T_GEN_REFRACTION,		SHADER_HASREFRACT},
//			{T_GEN_REFRACTIONDEPTH,	SHADER_HASREFRACTDEPTH},
//			{T_GEN_RIPPLEMAP,		SHADER_HASRIPPLEMAP},
			{T_GEN_LIGHTMAP,		SHADER_HASLIGHTMAP},
			{T_GEN_DELUXMAP,		0},
		};
		//if the glsl doesn't specify all samplers, just trim them.
		s->numpasses = s->prog->numsamplers;

		//if the glsl has specific textures listed, be sure to provide a pass for them.
		for (i = 0; i < sizeof(defaulttgen)/sizeof(defaulttgen[0]); i++)
		{
			if (s->prog->defaulttextures & (1u<<i))
			{
				s->passes[s->numpasses].flags &= ~SHADER_PASS_DEPTHCMP;
				if (defaulttgen[i].gen == T_GEN_SHADOWMAP)
					s->passes[s->numpasses].flags |= SHADER_PASS_DEPTHCMP;
				s->passes[s->numpasses].texgen = defaulttgen[i].gen;
				s->numpasses++;
				s->flags |= defaulttgen[i].flags;
			}
		}

		//must have at least one texture.
		if (!s->numpasses)
		{
			s->passes[0].texgen = T_GEN_DIFFUSE;
			s->numpasses = 1;
		}
		s->passes->numMergedPasses = s->numpasses;
	}
}
/*
void Shader_UpdateRegistration (void)
{
	int i, j, l;
	shader_t *shader;
	shaderpass_t *pass;

	shader = r_shaders;
	for (i = 0; i < MAX_SHADERS; i++, shader++)
	{
		if (!shader->registration_sequence)
			continue;
		if (shader->registration_sequence != registration_sequence)
		{
			Shader_Free ( shader );
			shader->registration_sequence = 0;
			continue;
		}

		pass = shader->passes;
		for (j = 0; j < shader->numpasses; j++, pass++)
		{
			if (pass->flags & SHADER_PASS_ANIMMAP)
			{
				for (l = 0; l < pass->anim_numframes; l++)
				{
					if (pass->anim_frames[l])
						pass->anim_frames[l]->registration_sequence = registration_sequence;
				}
			}
			else if ( pass->flags & SHADER_PASS_VIDEOMAP )
			{
				// Shader_RunCinematic will do the job
//				pass->cin->frame = -1;
			}
			else if ( !(pass->flags & SHADER_PASS_LIGHTMAP) )
			{
				if ( pass->anim_frames[0] )
					pass->anim_frames[0]->registration_sequence = registration_sequence;
			}
		}
	}
}
*/

/*
	if (*shader_diffusemapname)
	{
		if (!s->defaulttextures.base)
			s->defaulttextures.base = Shader_FindImage (va("%s.tga", shader_diffusemapname), 0);
		if (!s->defaulttextures.bump)
			s->defaulttextures.bump = Shader_FindImage (va("%s_norm.tga", shader_diffusemapname), 0);
		if (!s->defaulttextures.fullbright)
			s->defaulttextures.fullbright = Shader_FindImage (va("%s_glow.tga", shader_diffusemapname), 0);
		if (!s->defaulttextures.specular)
			s->defaulttextures.specular = Shader_FindImage (va("%s_gloss.tga", shader_diffusemapname), 0);
		if (!s->defaulttextures.upperoverlay)
			s->defaulttextures.upperoverlay = Shader_FindImage (va("%s_shirt.tga", shader_diffusemapname), 0);
		if (!s->defaulttextures.loweroverlay)
			s->defaulttextures.loweroverlay = Shader_FindImage (va("%s_pants.tga", shader_diffusemapname), 0);	//stupid yanks...
	}
*/
void Shader_DefaultSkin(const char *shortname, shader_t *s, const void *args);
void QDECL R_BuildDefaultTexnums(texnums_t *tn, shader_t *shader)
{
	char *h;
	char imagename[MAX_QPATH];
	char *subpath = NULL;
	strcpy(imagename, shader->name);
	h = strchr(imagename, '#');
	if (h)
		*h = 0;
	if (*imagename == '/' || strchr(imagename, ':'))
	{	//this is not security. this is anti-spam for the verbose security in the filesystem code.
		Con_Printf("Warning: shader has absolute path: %s\n", shader->name);
		*imagename = 0;
	}

	//skins can use an alternative path in certain cases, to work around dodgy models.
	if (shader->generator == Shader_DefaultSkin)
		subpath = shader->genargs;

	if (!tn)
		tn = &shader->defaulttextures;
	if (!TEXVALID(shader->defaulttextures.base))
	{
		/*dlights/realtime lighting needs some stuff*/
		if (!TEXVALID(tn->base) && *shader->mapname)// && (shader->flags & SHADER_HASDIFFUSE))
			tn->base = R_LoadHiResTexture(shader->mapname, NULL, 0);
		if (!TEXVALID(tn->base))
			tn->base = R_LoadHiResTexture(imagename, subpath, (*imagename=='{')?0:IF_NOALPHA);

		TEXASSIGN(shader->defaulttextures.base, tn->base);
	}

	if (!TEXVALID(shader->defaulttextures.paletted))
	{
		/*dlights/realtime lighting needs some stuff*/
//		if (!TEXVALID(tn->paletted) && *shader->mapname)// && (shader->flags & SHADER_HASDIFFUSE))
//			tn->paletted = R_LoadHiResTexture(shader->mapname, NULL, 0);
//		if (!TEXVALID(tn->paletted))
//			tn->paletted = R_LoadHiResTexture(imagename, subpath, (*imagename=='{')?0:IF_NOALPHA);

		TEXASSIGN(shader->defaulttextures.paletted, tn->paletted);
	}

	COM_StripExtension(imagename, imagename, sizeof(imagename));

	if (!TEXVALID(shader->defaulttextures.bump))
	{
		if (r_loadbumpmapping || (shader->flags & SHADER_HASNORMALMAP))
		{
			if (!TEXVALID(tn->bump) && *shader->mapname && (shader->flags & SHADER_HASNORMALMAP))
				tn->bump = R_LoadHiResTexture(va("%s_norm", shader->mapname), NULL, IF_TRYBUMP);
			if (!TEXVALID(tn->bump))
				tn->bump = R_LoadHiResTexture(va("%s_norm", imagename), subpath, IF_TRYBUMP);
		}
		TEXASSIGN(shader->defaulttextures.bump, tn->bump);
	}

	if (!TEXVALID(shader->defaulttextures.loweroverlay))
	{
		if (shader->flags & SHADER_HASTOPBOTTOM)
		{
			if (!TEXVALID(tn->loweroverlay) && *shader->mapname)
				tn->loweroverlay = R_LoadHiResTexture(va("%s_pants", shader->mapname), NULL, 0);
			if (!TEXVALID(tn->loweroverlay))
				tn->loweroverlay = R_LoadHiResTexture(va("%s_pants", imagename), subpath, 0);	/*how rude*/
		}
		TEXASSIGN(shader->defaulttextures.loweroverlay, tn->loweroverlay);
	}

	if (!TEXVALID(shader->defaulttextures.upperoverlay))
	{
		if (shader->flags & SHADER_HASTOPBOTTOM)
		{
			if (!TEXVALID(tn->upperoverlay) && *shader->mapname)
				tn->upperoverlay = R_LoadHiResTexture(va("%s_shirt", shader->mapname), NULL, 0);
			if (!TEXVALID(tn->upperoverlay))
				tn->upperoverlay = R_LoadHiResTexture(va("%s_shirt", imagename), subpath, 0);
		}
		TEXASSIGN(shader->defaulttextures.upperoverlay, tn->upperoverlay);
	}

	if (!TEXVALID(shader->defaulttextures.specular))
	{
		extern cvar_t gl_specular;
		if ((shader->flags & SHADER_HASGLOSS) && gl_specular.value && gl_load24bit.value)
		{
			if (!TEXVALID(tn->specular) && *shader->mapname)
				tn->specular = R_LoadHiResTexture(va("%s_gloss", shader->mapname), NULL, 0);
			if (!TEXVALID(tn->specular))
				tn->specular = R_LoadHiResTexture(va("%s_gloss", imagename), subpath, 0);
		}
		TEXASSIGN(shader->defaulttextures.specular, tn->specular);
	}

	if (!TEXVALID(shader->defaulttextures.fullbright))
	{
		extern cvar_t r_fb_bmodels;
		if ((shader->flags & SHADER_HASFULLBRIGHT) && r_fb_bmodels.value && gl_load24bit.value)
		{
			if (!TEXVALID(tn->fullbright) && *shader->mapname)
				tn->fullbright = R_LoadHiResTexture(va("%s_luma", shader->mapname), NULL, 0);
			if (!TEXVALID(tn->fullbright))
				tn->fullbright = R_LoadHiResTexture(va("%s_luma", imagename), subpath, 0);
		}
		TEXASSIGN(shader->defaulttextures.fullbright, tn->fullbright);
	}
}

//call this with some fallback textures to directly load some textures
void QDECL R_BuildLegacyTexnums(shader_t *shader, const char *subpath, unsigned int loadflags, uploadfmt_t basefmt, size_t width, size_t height, qbyte *mipdata[4], qbyte *palette)
{
	char *h;
	char imagename[MAX_QPATH];
	texnums_t *tex = &shader->defaulttextures;
	unsigned int imageflags;
	qbyte *dontcrashme[4] = {NULL};
	if (!mipdata)
		mipdata = dontcrashme;
	strcpy(imagename, shader->name);
	h = strchr(imagename, '#');
	if (h)
		*h = 0;
	if (*imagename == '/' || strchr(imagename, ':'))
	{	//this is not security. this is anti-spam for the verbose security in the filesystem code.
		Con_Printf("Warning: shader has absolute path: %s\n", shader->name);
		*imagename = 0;
	}

	loadflags &= shader->flags;

	//skins can use an alternative path in certain cases, to work around dodgy models.
	if (shader->generator == Shader_DefaultSkin)
		subpath = shader->genargs;

	//make sure the noalpha thing is set properly.
	imageflags = (basefmt==TF_SOLID8)?IF_NOALPHA:0;
	imageflags |= IF_MIPCAP;

	COM_StripExtension(imagename, imagename, sizeof(imagename));

	/*dlights/realtime lighting needs some stuff*/
	if (loadflags & SHADER_HASDIFFUSE)
	{
		if (!TEXVALID(tex->base) && *shader->mapname)
			tex->base = R_LoadHiResTexture(shader->mapname, NULL, imageflags);
		if (!TEXVALID(tex->base))
			tex->base = Image_GetTexture(imagename, subpath, imageflags, mipdata[0], palette, width, height, basefmt);
	}

	if (loadflags & SHADER_HASPALETTED)
	{
		if (!TEXVALID(tex->paletted) && *shader->mapname)
			tex->paletted = R_LoadHiResTexture(va("%s_pal", shader->mapname), NULL, imageflags|IF_NEAREST);
		if (!TEXVALID(tex->paletted))
			tex->paletted = Image_GetTexture(va("%s_pal", imagename), subpath, imageflags|IF_NEAREST, mipdata[0], palette, width, height, TF_LUM8);
	}

	//all the rest need/want an alpha channel in some form.
	imageflags &= ~IF_NOALPHA;
	imageflags |= IF_NOGAMMA;

	if (loadflags & SHADER_HASNORMALMAP)
	{
		extern cvar_t r_shadow_bumpscale_basetexture;
		if (!TEXVALID(tex->bump) && *shader->mapname)
			tex->bump = R_LoadHiResTexture(va("%s_norm", shader->mapname), NULL, imageflags|IF_TRYBUMP);
		if (!TEXVALID(tex->bump))
			tex->bump = Image_GetTexture(va("%s_norm", imagename), subpath, imageflags|IF_TRYBUMP, (r_shadow_bumpscale_basetexture.ival||*imagename=='*')?mipdata[0]:NULL, palette, width, height, TF_HEIGHT8PAL);
	}

	if (loadflags & SHADER_HASTOPBOTTOM)
	{
		if (!TEXVALID(tex->loweroverlay) && *shader->mapname)
			tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", shader->mapname), NULL, imageflags);
		if (!TEXVALID(tex->loweroverlay))
			tex->loweroverlay = Image_GetTexture(va("%s_pants", imagename), subpath, imageflags, NULL, palette, width, height, 0);
	}
	if (loadflags & SHADER_HASTOPBOTTOM)
	{
		if (!TEXVALID(tex->upperoverlay) && *shader->mapname)
			tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", shader->mapname), NULL, imageflags);
		if (!TEXVALID(tex->upperoverlay))
			tex->upperoverlay = Image_GetTexture(va("%s_shirt", imagename), subpath, imageflags, NULL, palette, width, height, 0);
	}

	if (loadflags & SHADER_HASGLOSS)
	{
		if (!TEXVALID(tex->specular) && *shader->mapname)
			tex->specular = R_LoadHiResTexture(va("%s_gloss", shader->mapname), NULL, imageflags);
		if (!TEXVALID(tex->specular))
			tex->specular = Image_GetTexture(va("%s_gloss", imagename), subpath, imageflags, NULL, palette, width, height, 0);
	}

	if (loadflags & SHADER_HASFULLBRIGHT)
	{
		if (!TEXVALID(tex->fullbright) && *shader->mapname)
			tex->fullbright = R_LoadHiResTexture(va("%s_luma", shader->mapname), NULL, imageflags);
		if (!TEXVALID(tex->fullbright))
		{
			int s=-1;
			if (mipdata[0])
			for(s = width*height; s-->0; )
			{
				if (mipdata[0][s] >= 256-vid.fullbright)
					break;
			}
			tex->fullbright = Image_GetTexture(va("%s_luma", imagename), subpath, imageflags, (s>=0)?mipdata[0]:NULL, palette, width, height, TF_TRANS8_FULLBRIGHT);
		}
	}
}

void Shader_DefaultScript(const char *shortname, shader_t *s, const void *args)
{
	const char *f = args;
	if (!args)
		return;
	while (*f == ' ' || *f == '\t' || *f == '\n' || *f == '\r')
		f++;
	if (*f == '{')
	{
		f++;
		Shader_ReadShader(s, (void*)f, SPM_DEFAULT);
	}
};

void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args)
{
	char *builtin = NULL;
	if (!builtin && r_drawflat.ival)
		builtin = (
				"{\n"
					"program drawflat_wall\n"
					"{\n"
						"map $lightmap\n"
						"tcgen lightmap\n"
						"rgbgen const $r_floorcolour\n"
					"}\n"
				"}\n"
			);


#ifdef GLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		if (!builtin && r_lightprepass.ival)
		{
				builtin = (
					"{\n"
						"program lpp_wall\n"
						"{\n"
							"map $sourcecolour\n"
						"}\n"
					"}\n"
				);
		}
	}
#endif
	if (!builtin && ((sh_config.progs_supported && qrenderer == QR_OPENGL) || sh_config.progs_required))
	{
			builtin = (
					"{\n"
						"program defaultwall\n"
						//FIXME: these maps are a legacy thing, and could be removed if third-party glsl properly contains s_diffuse
						"{\n"
							"map $diffuse\n"
						"}\n"
						"{\n"
							"map $lightmap\n"
						"}\n"
						"{\n"
							"map $normalmap\n"
						"}\n"
						"{\n"
							"map $deluxmap\n"
						"}\n"
						"{\n"
							"map $fullbright\n"
						"}\n"
						"{\n"
							"map $specular\n"
						"}\n"
					"}\n"
				);
	}
	if (!builtin)
		builtin = (
				"{\n"
/*					"if $deluxmap\n"
						"{\n"
							"map $normalmap\n"
							"tcgen base\n"
							"depthwrite\n"
						"}\n"
						"{\n"
							"map $deluxmap\n"
							"tcgen lightmap\n"
						"}\n"
					"endif\n"
*///				"if !r_fullbright\n"
						"{\n"
							"map $lightmap\n"
//							"if $deluxmap\n"
//								"blendfunc gl_dst_color gl_zero\n"
//							"endif\n"
						"}\n"
//					"endif\n"
					"{\n"
						"map $diffuse\n"
						"tcgen base\n"
//						"if $deluxmap || !r_fullbright\n"
//							"blendfunc gl_dst_color gl_zero\n"
							"blendfunc filter\n"
//						"endif\n"
					"}\n"
					"if gl_fb_bmodels\n"
						"{\n"
							"map $fullbright\n"
							"blendfunc add\n"
							"depthfunc equal\n"
						"}\n"
					"endif\n"
				"}\n"
			);

	Shader_DefaultScript(shortname, s, builtin);
}

void Shader_DefaultCinematic(const char *shortname, shader_t *s, const void *args)
{
	Shader_DefaultScript(shortname, s,
		va(
			"{\n"
				"program default2d\n"
				"{\n"
					"videomap \"%s\"\n"
				"}\n"
			"}\n"
		, (const char*)args)
	);
}

/*shortname should begin with 'skybox_'*/
void Shader_DefaultSkybox(const char *shortname, shader_t *s, const void *args)
{
	Shader_DefaultScript(shortname, s,
		va(
			"{\n"
				"skyparms %s - -\n"
			"}\n"
		, shortname+7)
	);
}

char *Shader_DefaultBSPWater(shader_t *s, const char *shortname)
{
	int wstyle;
	int type;
	float alpha;
	qboolean explicitalpha = false;
	cvar_t *alphavars[] = {	&r_wateralpha, &r_lavaalpha, &r_slimealpha, &r_telealpha};
	cvar_t *stylevars[] = {	&r_waterstyle, &r_lavastyle, &r_slimestyle, &r_telestyle};
	if (!strncmp(shortname, "*lava", 5))
		type = 1;
	else if (!strncmp(shortname, "*slime", 6))
		type = 2;
	else if (!strncmp(shortname, "*tele", 5))
		type = 3;
	else
		type = 0;
	alpha = Shader_FloatArgument(s, "#ALPHA");
	if (alpha)
		explicitalpha = true;
	else
		alpha = *alphavars[type]->string?alphavars[type]->value:alphavars[0]->value;

	if (alpha <= 0)
		wstyle = -1;
	else if (r_fastturb.ival)
		wstyle = 0;
#ifdef GLQUAKE
	else if (qrenderer == QR_OPENGL && sh_config.progs_supported && *stylevars[type]->string)
		wstyle = stylevars[type]->ival;
	else if (qrenderer == QR_OPENGL && sh_config.progs_supported && stylevars[0]->ival > 0)
		wstyle = stylevars[0]->ival;
#endif
	else
		wstyle = 1;

#ifdef GLQUAKE
	if (wstyle > 2 && !gl_config.ext_framebuffer_objects)
		wstyle = 2;
#endif
	switch(wstyle)
	{
	case -1:	//invisible
		return (
			"{\n"
				"surfaceparm nodraw\n"
				"surfaceparm nodlight\n"
			"}\n"
		);
	case -2:	//regular with r_wateralpha forced off.
		return (
			"{\n"
				"program defaultwarp\n"
				"{\n"
					"map $diffuse\n"
					"tcmod turb 0.02 0.1 0.5 0.1\n"
				"}\n"
				"surfaceparm nodlight\n"
			"}\n"
		);
	case 0:	//fastturb
		return (
			"{\n"
//				"program defaultfill\n"
				"{\n"
					"map $whiteimage\n"
					"rgbgen const $r_fastturbcolour\n"
				"}\n"
				"surfaceparm nodlight\n"
			"}\n"
		);
	default:
	case 1:	//vanilla style
		return va(
				"{\n"
					"program defaultwarp%s\n"
					"{\n"
						"map $diffuse\n"
						"tcmod turb 0.02 0.1 0.5 0.1\n"
						"if %g < 1\n"
							"alphagen const %g\n"
							"blendfunc gl_src_alpha gl_one_minus_src_alpha\n"
						"endif\n"
					"}\n"
					"surfaceparm nodlight\n"
				"}\n"
				, explicitalpha?"":va("#ALPHA=%g",alpha), alpha, alpha);
	case 2:	//refraction of the underwater surface, with a fresnel
		return (
			"{\n"
				"surfaceparm nodlight\n"
				"{\n"
					"map $refraction\n"
				"}\n"
				"{\n"
					"map $null\n"//$reflection
				"}\n"
				"{\n"
					"map $null\n"//$ripplemap
				"}\n"
				"{\n"
					"map $null\n"//$refractiondepth
				"}\n"
				"program altwater#FRESNEL=4\n"
			"}\n"
		);
	case 3:	//reflections
		return (
			"{\n"
				"surfaceparm nodlight\n"
				"{\n"
					"map $refraction\n"
				"}\n"
				"{\n"
					"map $reflection\n"
				"}\n"
				"{\n"
					"map $null\n"//$ripplemap
				"}\n"
				"{\n"
					"map $null\n"//$refractiondepth
				"}\n"
				"program altwater#REFLECT#FRESNEL=4\n"
			"}\n"
		);
	case 4:	//ripples
		return (
			"{\n"
				"surfaceparm nodlight\n"
				"{\n"
					"map $refraction\n"
				"}\n"
				"{\n"
					"map $null\n"//$reflection
				"}\n"
				"{\n"
					"map $ripplemap\n"
				"}\n"
				"{\n"
					"map $null\n"//$refractiondepth
				"}\n"
				"program altwater#RIPPLEMAP#FRESNEL=4\n"
			"}\n"
		);
	case 5:	//ripples+reflections
		return (
			"{\n"
				"surfaceparm nodlight\n"
				"{\n"
					"map $refraction\n"
				"}\n"
				"{\n"
					"map $reflection\n"
				"}\n"
				"{\n"
					"map $ripplemap\n"
				"}\n"
				"{\n"
					"map $null\n"//$refractiondepth
				"}\n"
				"program altwater#REFLECT#RIPPLEMAP#FRESNEL=4\n"
			"}\n"
		);
	}
}

void Shader_DefaultWaterShader(const char *shortname, shader_t *s, const void *args)
{
	Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(s, shortname));
}
void Shader_DefaultBSPQ2(const char *shortname, shader_t *s, const void *args)
{
	if (!strncmp(shortname, "sky/", 4))
	{
		Shader_DefaultScript(shortname, s,
				"{\n"
					"surfaceparm nodlight\n"
					"skyparms - - -\n"
				"}\n"
			);
	}
	else if (!strncmp(shortname, "warp/", 5) || !strncmp(shortname, "warp33/", 7) || !strncmp(shortname, "warp66/", 7))
	{
		Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(s, shortname));
	}
	else if (!strncmp(shortname, "trans/", 6))
		Shader_DefaultScript(shortname, s,
				"{\n"
					"{\n"
						"map $diffuse\n"
						"alphagen const $#ALPHA\n"
						"blendfunc blend\n"
					"}\n"
				"}\n"
			);
	else
		Shader_DefaultBSPLM(shortname, s, args);
}

void Shader_DefaultBSPQ1(const char *shortname, shader_t *s, const void *args)
{
	char *builtin = NULL;
	if (r_mirroralpha.value < 1 && !strcmp(shortname, "window02_1"))
	{
		if (r_mirroralpha.value < 0)
		{
			builtin =	"{\n"
							"portal\n"
							"{\n"
								"map $diffuse\n"
								"blendfunc blend\n"
								"alphagen portal 512\n"
								"depthwrite\n"
							"}\n"
						"}\n";
		}
		else
		{
			builtin =	"{\n"
							"portal\n"
							"{\n"
								"map $diffuse\n"
								"blendfunc blend\n"
								"alphagen const $r_mirroralpha\n"
								"depthwrite\n"
							"}\n"
							"surfaceparm nodlight\n"
						"}\n";
		}

	}

	if (!builtin && (*shortname == '*'))
	{
		builtin = Shader_DefaultBSPWater(s, shortname);
	}
	if (!builtin && !strncmp(shortname, "sky", 3))
	{
		//q1 sky
		if (r_fastsky.ival)
		{
			builtin = (
					"{\n"
						"sort sky\n"
						"{\n"
							"map $whiteimage\n"
							"rgbgen const $r_fastskycolour\n"
						"}\n"
						"surfaceparm nodlight\n"
					"}\n"
				);
		}
		else if (*r_skyboxname.string)
		{
			builtin = (
					"{\n"
						"sort sky\n"
						"skyparms $r_skybox - -\n"
						"surfaceparm nodlight\n"
					"}\n"
				);
			Shader_DefaultScript(shortname, s, builtin);
			if (s->flags & SHADER_SKY)
				return;
			builtin = NULL;
			/*if the r_skybox failed to load or whatever, reset and fall through and just use the regular sky*/
			Shader_Reset(s);
		}
		if (!builtin)
			builtin = (
				"{\n"
					"sort sky\n"
					"program defaultsky\n"
					"skyparms - 512 -\n"
					/*WARNING: these values are not authentic quake, only close aproximations*/
					"{\n"
						"map $diffuse\n"
						"tcmod scale 10 10\n"
						"tcmod scroll 0.04 0.04\n"
						"depthwrite\n"
					"}\n"
					"{\n"
						"map $fullbright\n"
						"blendfunc blend\n"
						"tcmod scale 10 10\n"
						"tcmod scroll 0.02 0.02\n"
					"}\n"
				"}\n"
			);
	}
	if (!builtin && *shortname == '{')
	{
		/*alpha test*/
		builtin = (
			"{\n"
		/*		"if $deluxmap\n"
					"{\n"
						"map $normalmap\n"
						"tcgen base\n"
					"}\n"
					"{\n"
						"map $deluxmap\n"
						"tcgen lightmap\n"
					"}\n"
				"endif\n"*/
				"{\n"
					"map $diffuse\n"
					"tcgen base\n"
						"alphamask\n"
				"}\n"
				"if $lightmap\n"
					"{\n"
						"map $lightmap\n"
						"blendfunc gl_dst_color gl_zero\n"
						"depthfunc equal\n"
					"}\n"
				"endif\n"
				"{\n"
					"map $fullbright\n"
					"blendfunc add\n"
					"depthfunc equal\n"
				"}\n"
			"}\n"
		);
	}

	/*Hack: note that halflife would normally expect you to use rendermode/renderampt*/
	if (!builtin && (!strncmp(shortname, "glass", 5)/* || !strncmp(shortname, "window", 6)*/))
	{
		/*alpha bended*/
		builtin = (
			"{\n"
				"{\n"
					"map $diffuse\n"
					"tcgen base\n"
					"blendfunc blend\n"
				"}\n"
			"}\n"
		);
	}

	if (!builtin && r_softwarebanding.ival)
	{
		/*alpha bended*/
		builtin = (
			"{\n"
			"program defaultwall#EIGHTBIT\n"
				"{\n"
					"map $colourmap\n"
				"}\n"
			"}\n"
		);
	}

	if (builtin)
		Shader_DefaultScript(shortname, s, builtin);
	else
		Shader_DefaultBSPLM(shortname, s, args);
}

void Shader_DefaultBSPVertex(const char *shortname, shader_t *s, const void *args)
{
	shaderpass_t *pass;

//	s->defaulttextures.base = R_LoadHiResTexture(va("%s_d.tga", shortname), NULL, 0);

	if (Shader_ParseShader("defaultvertexlit", s))
		return;

	pass = &s->passes[0];
	pass->tcgen = TC_GEN_BASE;
	pass->shaderbits |= SBITS_MISC_DEPTHWRITE;
	pass->rgbgen = RGB_GEN_VERTEX_LIGHTING;
	pass->alphagen = ALPHA_GEN_IDENTITY;
	pass->numMergedPasses = 1;
	Shader_SetBlendmode(pass);

/*	if (TEXVALID(s->defaulttextures.base))
	{
		pass->texgen = T_GEN_DIFFUSE;
	}
	else*/
	{
		s->defaulttextures.base = R_LoadHiResTexture(shortname, NULL, 0);
		pass->texgen = T_GEN_DIFFUSE;
		if (!TEXVALID(s->defaulttextures.base))
			Con_DPrintf (CON_WARNING "Shader %s has a stage with no image: %s.\n", s->name, shortname );
	}

	s->numpasses = 1;
	s->numdeforms = 0;
	s->flags = SHADER_IMAGEPENDING|SHADER_DEPTHWRITE|SHADER_CULL_FRONT;
	s->sort = SHADER_SORT_OPAQUE;
	s->uses = 1;
}
void Shader_DefaultBSPFlare(const char *shortname, shader_t *s, const void *args)
{
	shaderpass_t *pass;
	if (Shader_ParseShader("defaultflare", s))
		return;

	pass = &s->passes[0];
	pass->flags = SHADER_IMAGEPENDING|SHADER_PASS_NOCOLORARRAY;
	pass->shaderbits |= SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ONE;
	pass->anim_frames[0] = R_LoadHiResTexture(shortname, NULL, 0);
	pass->rgbgen = RGB_GEN_VERTEX_LIGHTING;
	pass->alphagen = ALPHA_GEN_IDENTITY;
	pass->numtcmods = 0;
	pass->tcgen = TC_GEN_BASE;
	pass->numMergedPasses = 1;
	Shader_SetBlendmode(pass);

	if (!TEXVALID(pass->anim_frames[0]))
	{
		Con_DPrintf (CON_WARNING "Shader %s has a stage with no image: %s.\n", s->name, shortname );
		pass->anim_frames[0] = missing_texture;
	}

	s->numpasses = 1;
	s->numdeforms = 0;
	s->flags = SHADER_IMAGEPENDING|SHADER_FLARE;
	s->sort = SHADER_SORT_ADDITIVE;
	s->uses = 1;

	s->flags |= SHADER_NODRAW;
}
void Shader_DefaultSkin(const char *shortname, shader_t *s, const void *args)
{
	if (Shader_ParseShader("defaultskin", s))
		return;

	Shader_DefaultScript(shortname, s,
		"{\n"
			"if $lpp\n"
				"program lpp_skin\n"
			"else\n"
				"program defaultskin\n"
			"endif\n"
			"if gl_affinemodels\n"
				"affine\n"
			"endif\n"
			"{\n"
				"map $diffuse\n"
				"rgbgen lightingDiffuse\n"
			"}\n"
			"{\n"
				"map $loweroverlay\n"
				"rgbgen bottomcolor\n"
				"blendfunc gl_src_alpha gl_one\n"
			"}\n"
			"{\n"
				"map $upperoverlay\n"
				"rgbgen topcolor\n"
				"blendfunc gl_src_alpha gl_one\n"
			"}\n"
			"{\n"
				"map $fullbright\n"
				"blendfunc add\n"
			"}\n"
		"}\n"
		);
}
void Shader_DefaultSkinShell(const char *shortname, shader_t *s, const void *args)
{
	if (Shader_ParseShader("defaultskinshell", s))
		return;

	Shader_DefaultScript(shortname, s,
		"{\n"
			"sort seethrough\n"	//before blend, but after other stuff. should fix most issues with shotgun etc effects obscuring it.
//			"deformvertexes normal 1 1\n"
			//draw it with depth but no colours at all
			"{\n"
				"map $whiteimage\n"
				"maskcolor\n"
				"depthwrite\n"
			"}\n"
			//now draw it again, depthfunc = equal should fill only the near-side, avoiding any excess-brightness issues with overlapping triangles
			"{\n"
				"map $whiteimage\n"
				"rgbgen entity\n"
				"alphagen entity\n"
				"blendfunc blend\n"
			"}\n"
		"}\n"
		);
}
void Shader_Default2D(const char *shortname, shader_t *s, const void *genargs)
{
	if (Shader_ParseShader("default2d", s))
		return;
	if (sh_config.progs_supported)
	{
		//hexen2 needs premultiplied alpha to avoid looking ugly
		//but that results in problems where things are drawn with alpha not 0, so scale vertex colour by alpha in the fragment program
		Shader_DefaultScript(shortname, s,
			"{\n"
				"affine\n"
				"nomipmaps\n"
				"program default2d#PREMUL\n"
				"{\n"
				"map $diffuse\n"
				"blend gl_one gl_one_minus_src_alpha\n"
				"}\n"
				"sort additive\n"
			"}\n"
			);
		TEXASSIGN(s->defaulttextures.base, R_LoadHiResTexture(s->name, NULL, IF_PREMULTIPLYALPHA|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_CLAMP));
	}
	else
	{
		Shader_DefaultScript(shortname, s,
			"{\n"
				"affine\n"
				"nomipmaps\n"
				"{\n"
					"clampmap $diffuse\n"
					"rgbgen vertex\n"
					"alphagen vertex\n"
					"blendfunc gl_src_alpha gl_one_minus_src_alpha\n"
				"}\n"
				"sort additive\n"
			"}\n"
			);
		TEXASSIGN(s->defaulttextures.base, R_LoadHiResTexture(s->name, NULL, IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_CLAMP));
	}
}

qboolean Shader_ReadShaderTerms(shader_t *s, char **shadersource, int parsemode, int *conddepth, int maxconddepth, int *cond)
{
	char *token;

#define COND_IGNORE 1
#define COND_IGNOREPARENT 2
#define COND_ALLOWELSE 4

	if (!*shadersource)
		return false;

	token = COM_ParseExt (shadersource, true, true);

	if ( !token[0] )
		return true;
	else if (!Q_stricmp(token, "if"))
	{
		if (*conddepth+1 == maxconddepth)
		{
			Con_Printf("if statements nest too deeply in shader %s\n", s->name);
			return false;
		}
		*conddepth+=1;
		cond[*conddepth] = (!Shader_EvaluateCondition(s, shadersource)?COND_IGNORE:0);
		cond[*conddepth] |= COND_ALLOWELSE;
		if (cond[*conddepth-1] & (COND_IGNORE|COND_IGNOREPARENT))
			cond[*conddepth] |= COND_IGNOREPARENT;
	}
	else if (!Q_stricmp(token, "endif"))
	{
		if (!*conddepth)
		{
			Con_Printf("endif without if in shader %s\n", s->name);
			return false;
		}
		*conddepth-=1;
	}
	else if (!Q_stricmp(token, "else"))
	{
		if (cond[*conddepth] & COND_ALLOWELSE)
		{
			cond[*conddepth] ^= COND_IGNORE;
			cond[*conddepth] &= ~COND_ALLOWELSE;
		}
		else
			Con_Printf("unexpected else statement in shader %s\n", s->name);
	}
	else if (cond[*conddepth] & (COND_IGNORE|COND_IGNOREPARENT))
	{
		//eat it.
		while (**shadersource)
		{
			token = COM_ParseExt(shadersource, false, true);
			if ( !token[0] )
				break;
		}
	}
	else
	{
		int i;
		for (i = 0; shadermacros[i].name; i++)
		{
			if (!Q_stricmp (token, shadermacros[i].name))
			{
#define SHADER_MACRO_ARGS 6
				int argn = 0;
				char *body;
				char arg[SHADER_MACRO_ARGS][256];
				int cond = 0;
				//parse args until the end of the line
				while (*shadersource)
				{
					token = COM_ParseExt(shadersource, false, true);
					if ( !token[0] )
					{
						break;
					}
					if (argn <= SHADER_MACRO_ARGS)
					{
						Q_strncpyz(arg[argn], token, sizeof(arg[argn]));
						argn++;
					}
				}
				body = shadermacros[i].body;
				Shader_ReadShaderTerms(s, &body, parsemode, &cond, 0, &cond);
				return true;
			}
		}
		if (token[0] == '}')
			return false;
		else if (token[0] == '{')
			Shader_Readpass(s, shadersource);
		else if (Shader_Parsetok(s, NULL, shaderkeys, token, shadersource))
			return false;
	}
	return true;
}

//loads a shader string into an existing shader object, and finalises it and stuff
static void Shader_ReadShader(shader_t *s, char *shadersource, int parsemode)
{
	char *shaderstart = shadersource;
	int conddepth = 0;
	int cond[8];
	cond[0] = 0;

	memset(&parsestate, 0, sizeof(parsestate));
	parsestate.mode = parsemode;

// set defaults
	s->flags = SHADER_IMAGEPENDING|SHADER_CULL_FRONT;
	s->uses = 1;

	while (Shader_ReadShaderTerms(s, &shadersource, parsemode, &conddepth, sizeof(cond)/sizeof(cond[0]), cond))
	{
	}

	if (conddepth)
	{
		Con_Printf("if statements without endif in shader %s\n", s->name);
	}

	Shader_Finish ( s );

	//querying the shader body often requires generating the shader, which then gets parsed.
	if (saveshaderbody)
	{
		size_t l = shadersource - shaderstart;
		Z_Free(*saveshaderbody);
		*saveshaderbody = BZ_Malloc(l+1);
		(*saveshaderbody)[l] = 0;
		memcpy(*saveshaderbody, shaderstart, l);
		saveshaderbody = NULL;
	}
}

static qboolean Shader_ParseShader(char *parsename, shader_t *s)
{
	size_t offset = 0, length;
	char *buf = NULL;
	enum shaderparsemode_e parsemode = SPM_DEFAULT;

	if (Shader_LocateSource(parsename, &buf, &length, &offset, &parsemode))
	{
		// the shader is in the shader scripts
		if (buf && offset < length )
		{
			char *file, *token;


			file = buf + offset;
			token = COM_ParseExt (&file, true, true);
			if ( !file || token[0] != '{' )
			{
				FS_FreeFile(buf);
				return false;
			}

			Shader_Reset(s);

			Shader_ReadShader(s, file, parsemode);

			return true;
		}
	}

	return false;
}
void R_UnloadShader(shader_t *shader)
{
	if (shader->uses-- == 1)
		Shader_Free(shader);
}
static shader_t *R_LoadShader (const char *name, unsigned int usageflags, shader_gen_t *defaultgen, const char *genargs)
{
	int i, f = -1;
	char cleanname[MAX_QPATH];
	char shortname[MAX_QPATH];
	char *argsstart;
	shader_t *s;

	if (!*name)
		name = "gfx/unspecified";

	COM_AssertMainThread("R_LoadShader");

	Q_strncpyz(cleanname, name, sizeof(cleanname));
	COM_CleanUpPath(cleanname);

	// check the hash first
	s = Hash_Get(&shader_active_hash, cleanname);
	while (s)
	{
		//make sure the same texture can be used as either a lightmap or vertexlit shader
		//if it has an explicit shader overriding it then that still takes precidence. we might just have multiple copies of it.
		//q3 has a separate (internal) shader for every lightmap.
		if (!((s->usageflags ^ usageflags) & SUF_LIGHTMAP))
		{
			s->uses++;
			return s;
		}
		s = Hash_GetNext(&shader_active_hash, cleanname, s);
	}

	// not loaded, find a free slot
	for (i = 0; i < r_numshaders; i++)
	{
		if (!r_shaders[i] || !r_shaders[i]->uses)
		{
			if ( f == -1 )	// free shader
			{
				f = i;
				break;
			}
		}
	}

	if (f == -1)
	{
		shader_t **n;
		int nm;
		f = r_numshaders;
		if (f == r_maxshaders)
		{
			if (!r_maxshaders)
				Sys_Error( "R_LoadShader: shader system not inited.");

			nm = r_maxshaders * 2;
			n = realloc(r_shaders, nm*sizeof(*n));
			if (!n)
			{
				Sys_Error( "R_LoadShader: Shader limit exceeded.");
				return NULL;
			}
			memset(n+r_maxshaders, 0, (nm - r_maxshaders)*sizeof(*n));
			r_shaders = n;
			r_maxshaders = nm;
		}
	}
	if (strlen(cleanname) >= sizeof(s->name))
	{
		Sys_Error( "R_LoadShader: Shader name too long.");
		return NULL;
	}

	s = r_shaders[f];
	if (!s)
		s = r_shaders[f] = Z_Malloc(sizeof(*s));
	s->id = f;
	if (r_numshaders < f+1)
		r_numshaders = f+1;

	Q_strncpyz(s->name, cleanname, sizeof(s->name));
	s->usageflags = usageflags;
	s->generator = defaultgen;
	s->width = 0;
	s->height = 0;
	if (genargs)
		s->genargs = strdup(genargs);
	else
		s->genargs = NULL;

	//now determine the 'short name'. ie: the shader that is loaded off disk (no args, no extension)
	argsstart = strchr(cleanname, '#');
	if (argsstart)
		*argsstart = 0;
	COM_StripExtension (cleanname, shortname, sizeof(shortname));

	if (ruleset_allow_shaders.ival)
	{
		if (sh_config.shadernamefmt)
		{
			char drivername[MAX_QPATH];
			Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname);
			if (Shader_ParseShader(drivername, s))
				return s;
		}
		if (Shader_ParseShader(cleanname, s))
			return s;
		if (Shader_ParseShader(shortname, s))
			return s;
	}

	// make a default shader

	if (s->generator)
	{
		Shader_Reset(s);

		if (!strcmp(shortname, "textures/common/clip"))
			Shader_DefaultScript(cleanname, s,
				"{\n"
					"surfaceparm nodraw\n"
					"surfaceparm nodlight\n"
				"}\n");
		else
			s->generator(cleanname, s, s->genargs);
		return s;
	}
	else
	{
		Shader_Free(s);
	}
	return NULL;
}

char *Shader_GetShaderBody(shader_t *s)
{
	char *adr;
	char cleanname[MAX_QPATH];
	char shortname[MAX_QPATH];
	int oldsort;
	qboolean resort = false;
	if (!s || !s->uses)
		return NULL;

	adr = Z_StrDup("UNKNOWN BODY");
	saveshaderbody = &adr;

	strcpy(cleanname, s->name);
	COM_StripExtension (cleanname, shortname, sizeof(shortname));
	if (ruleset_allow_shaders.ival)
	{
		if (sh_config.shadernamefmt)
		{
			char drivername[MAX_QPATH];
			Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname);
			if (Shader_ParseShader(drivername, s))
				return adr;
		}
		if (Shader_ParseShader(cleanname, s))
			return adr;
		if (Shader_ParseShader(shortname, s))
			return adr;
	}
	if (s->generator)
	{
		oldsort = s->sort;
		Shader_Reset(s);

		s->generator(shortname, s, s->genargs);

		if (s->sort != oldsort)
			resort = true;
	}

	if (resort)
	{
		Mod_ResortShaders();
	}
	return adr;
}

void Shader_ShowShader_f(void)
{
	char *sourcename = Cmd_Argv(1);
	shader_t *o = R_LoadShader(sourcename, SUF_NONE, NULL, NULL);
	if (!o)
		o = R_LoadShader(sourcename, SUF_LIGHTMAP, NULL, NULL);
	if (!o)
		o = R_LoadShader(sourcename, SUF_2D, NULL, NULL);
	if (o)
	{
		char *body = Shader_GetShaderBody(o);
		if (body)
		{
			Con_Printf("%s\n{%s\n", o->name, body);
			Z_Free(body);
		}
		else
		{
			Con_Printf("Shader \"%s\" is not in use\n", o->name);
		}
	}
	else
		Con_Printf("Shader \"%s\" is not loaded\n", sourcename);
}

void Shader_DoReload(void)
{
	shader_t *s;
	unsigned int i;
	char shortname[MAX_QPATH];
	char cleanname[MAX_QPATH];
	int oldsort;
	qboolean resort = false;

	if (shader_rescan_needed && ruleset_allow_shaders.ival)
	{
		Shader_FlushCache();

		COM_EnumerateFiles("materials/*.mtr", Shader_InitCallback, NULL);
		COM_EnumerateFiles("shaders/*.shader", Shader_InitCallback, NULL);
		COM_EnumerateFiles("scripts/*.shader", Shader_InitCallback, NULL);
		COM_EnumerateFiles("scripts/*.rscript", Shader_InitCallback, NULL);

		shader_reload_needed = true;
		shader_rescan_needed = false;
	}

	if (!shader_reload_needed)
		return;
	shader_reload_needed = false;
	Font_InvalidateColour();
	Shader_ReloadGenerics();

	for (i = 0; i < r_numshaders; i++)
	{
		s = r_shaders[i];
		if (!s || !s->uses)
			continue;

		strcpy(cleanname, s->name);
		COM_StripExtension (cleanname, shortname, sizeof(shortname));
		if (ruleset_allow_shaders.ival)
		{
			if (sh_config.shadernamefmt)
			{
				char drivername[MAX_QPATH];
				Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname);
				if (Shader_ParseShader(drivername, s))
					continue;
			}
			if (Shader_ParseShader(cleanname, s))
				continue;
			if (Shader_ParseShader(shortname, s))
				continue;
		}
		if (s->generator)
		{
			oldsort = s->sort;
			Shader_Reset(s);

			s->generator(shortname, s, s->genargs);

			if (s->sort != oldsort)
				resort = true;
		}
	}

	if (resort)
	{
		Mod_ResortShaders();
	}
}

void Shader_NeedReload(qboolean rescanfs)
{
	if (rescanfs)
		shader_rescan_needed = true;
	shader_reload_needed = true;
}

cin_t *R_ShaderGetCinematic(shader_t *s)
{
#ifndef NOMEDIA
	int j;
	if (!s)
		return NULL;
	for (j = 0; j < s->numpasses; j++)
		if (s->passes[j].cin)
			return s->passes[j].cin;
#endif
	/*no cinematic in this shader!*/
	return NULL;
}

cin_t *R_ShaderFindCinematic(const char *name)
{
#ifdef NOMEDIA
	return NULL;
#else
	int i;
	char shortname[MAX_QPATH];
	shader_t *s;

	if (!r_shaders)
		return NULL;

	COM_StripExtension ( name, shortname, sizeof(shortname));

	COM_CleanUpPath(shortname);

	//try and find it
	for (i = 0; i < r_numshaders; i++)
	{
		s = r_shaders[i];
		if (!s || !s->uses)
			continue;

		if (!Q_stricmp (shortname, s->name) )
			return R_ShaderGetCinematic(s);
	}
	return NULL;
#endif
}

void Shader_ResetRemaps(void)
{
	shader_t *s;
	int i;
	for (i = 0; i < r_numshaders; i++)
	{
		s = r_shaders[i];
		if (!s)
			continue;
		s->remapto = s;
		s->remaptime = 0;
	}
}

void R_RemapShader(const char *sourcename, const char *destname, float timeoffset)
{
	shader_t *o;
	shader_t *n;

	//make sure all types of the shader are remapped properly.
	//if there's a .shader file with it then it should 'just work'.

	o = R_LoadShader (sourcename, SUF_NONE, NULL, NULL);
	n = R_LoadShader (destname, SUF_NONE, NULL, NULL);
	if (o)
	{
		if (!n)
			n = o;
		o->remapto = n;
		o->remaptime = timeoffset;	//this just feels wrong.
	}

	o = R_LoadShader (sourcename, SUF_2D, NULL, NULL);
	n = R_LoadShader (destname, SUF_2D, NULL, NULL);
	if (o)
	{
		if (!n)
			n = o;
		o->remapto = n;
		o->remaptime = timeoffset;
	}

	o = R_LoadShader (sourcename, SUF_LIGHTMAP, NULL, NULL);
	n = R_LoadShader (destname, SUF_LIGHTMAP, NULL, NULL);
	if (o)
	{
		if (!n)
		{
			n = R_LoadShader (destname, SUF_2D, NULL, NULL);
			if (!n)
				n = o;
		}
		o->remapto = n;
		o->remaptime = timeoffset;
	}
}

void Shader_RemapShader_f(void)
{
	char *sourcename = Cmd_Argv(1);
	char *destname = Cmd_Argv(2);
	float timeoffset = atof(Cmd_Argv(3));
	
	if (!Cmd_FromGamecode() && strcmp(Info_ValueForKey(cl.serverinfo, "*cheats"), "ON"))
	{
		Con_Printf("%s may only be used from gamecode, or when cheats are enabled\n", Cmd_Argv(0));
		return;
	}
	if (!*sourcename)
	{
		Con_Printf("%s originalshader remappedshader starttime\n", Cmd_Argv(0));
		return;
	}
	R_RemapShader(sourcename, destname, timeoffset);
}

//blocks
int R_GetShaderSizes(shader_t *shader, int *width, int *height, qboolean blocktillloaded)
{
	if (!shader)
		return false;
	if (shader->flags &	SHADER_IMAGEPENDING)
	{
		int i;
		if (width)
			*width = 0;
		if (height)
			*height = 0;
		for (i = 0; i < shader->numpasses; i++)
		{
			if (shader->passes[i].texgen == T_GEN_SINGLEMAP && shader->passes[i].anim_frames[0] && shader->passes[i].anim_frames[0]->status == TEX_LOADING)
			{
				if (!blocktillloaded)
					return -1;
				COM_WorkerPartialSync(shader->passes[i].anim_frames[0], &shader->passes[i].anim_frames[0]->status, TEX_LOADING);
			}
			if (shader->passes[i].texgen == T_GEN_DIFFUSE && (shader->defaulttextures.base && shader->defaulttextures.base->status == TEX_LOADING))
			{
				if (!blocktillloaded)
					return -1;
				COM_WorkerPartialSync(shader->defaulttextures.base, &shader->defaulttextures.base->status, TEX_LOADING);
			}
		}

		shader->flags &= ~SHADER_IMAGEPENDING;
		for (i = 0; i < shader->numpasses; i++)
		{
			if (shader->passes[i].texgen == T_GEN_SINGLEMAP)
			{
				if (shader->passes[i].anim_frames[0] && shader->passes[i].anim_frames[0]->status == TEX_LOADED)
				{
					shader->width = shader->passes[i].anim_frames[0]->width;
					shader->height = shader->passes[i].anim_frames[0]->height;
				}
				break;
			}
			if (shader->passes[i].texgen == T_GEN_DIFFUSE && shader->defaulttextures.base)
			{
				if (shader->defaulttextures.base->status == TEX_LOADED)
				{
					shader->width = shader->defaulttextures.base->width;
					shader->height = shader->defaulttextures.base->height;
				}
				break;
			}
		}
		if (i == shader->numpasses)
		{	//this shader has no textures from which to source a width and height
			if (!shader->width)
				shader->width = 64;
			if (!shader->height)
				shader->height = 64;
		}
	}
	if (shader->width && shader->height)
	{
		if (width)
			*width = shader->width;
		if (height)
			*height = shader->height;
		return true;	//final size
	}
	else
	{
		//fill with dummy values
		if (width)
			*width = 64;
		if (height)
			*height = 64;
		return false;
	}
}
shader_t *R_RegisterPic (const char *name)
{
	shader_t *shader;
	shader = R_LoadShader (name, SUF_2D, Shader_Default2D, NULL);
	return shader;
}

shader_t *QDECL R_RegisterShader (const char *name, unsigned int usageflags, const char *shaderscript)
{
	return R_LoadShader (name, usageflags, Shader_DefaultScript, shaderscript);
}

shader_t *R_RegisterShader_Lightmap (const char *name)
{
	return R_LoadShader (name, SUF_LIGHTMAP, Shader_DefaultBSPLM, NULL);
}

shader_t *R_RegisterShader_Vertex (const char *name)
{
	return R_LoadShader (name, 0, Shader_DefaultBSPVertex, NULL);
}

shader_t *R_RegisterShader_Flare (const char *name)
{
	return R_LoadShader (name, 0, Shader_DefaultBSPFlare, NULL);
}

shader_t *QDECL R_RegisterSkin (const char *shadername, const char *modname)
{
	char newsname[MAX_QPATH];
	shader_t *shader;
#ifdef _DEBUG
	if (shadername == com_token)
		Con_Printf("R_RegisterSkin was passed com_token. that will bug out.\n");
#endif

	newsname[0] = 0;
	if (modname && !strchr(shadername, '/') && *shadername)
	{
		char *b = COM_SkipPath(modname);
		if (b != modname && b-modname + strlen(shadername)+1 < sizeof(newsname))
		{
			b--;	//no trailing /
			memcpy(newsname, modname, b - modname);
			newsname[b-modname] = 0;
		}
	}
	if (*newsname)
	{
		int l = strlen(newsname);
		Q_strncpyz(newsname+l, ":models", sizeof(newsname)-l);
	}
	else
		Q_strncpyz(newsname, "models", sizeof(newsname));
	shader = R_LoadShader (shadername, 0, Shader_DefaultSkin, newsname);
	return shader;
}
shader_t *R_RegisterCustom (const char *name, unsigned int usageflags, shader_gen_t *defaultgen, const void *args)
{
	return R_LoadShader (name, usageflags, defaultgen, args);
}
#endif //SERVERONLY