//////////////////////////////////////////////////////////////////////
//
//                             Pixie
//
// Copyright © 1999 - 2003, Okan Arikan
//
// Contact: okan@cs.berkeley.edu
//
// This library 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 library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//  File				:	shader.cpp
//  Classes				:	CShader
//							CShaderInstance
//  Description			:	Implementation
//
////////////////////////////////////////////////////////////////////////
#include	<math.h>
#include	<stdio.h>
#include	<string.h>

#include	"renderer.h"
#include	"error.h"
#include	"shader.h"
#include	"stats.h"
#include	"shading.h"
#include	"cache.h"
#include	"bundles.h"

///////////////////////////////////////////////////////////////////////
// Class				:	CShaderLookup
// Method				:	CShaderLookup
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShaderLookup::CShaderLookup() {
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShaderLookup
// Method				:	~CShaderLookup
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShaderLookup::~CShaderLookup() {
}

///////////////////////////////////////////////////////////////////////
// Class				:	CFilterLookup
// Method				:	compute
// Description			:	Lookup filter values at discrete intelvals
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
void	CFilterLookup::compute() {
	int		i;
	float	val;

	valStep		=	2*width / (NUMFILTERSTEPS-1);
	val			=	width;
	normalizer	=	0;

	for (i=0;i<NUMFILTERSTEPS;i++,val-=valStep) {
		vals[i]		=	filter(val,0,width,1);
		normalizer	+=	vals[i]*valStep;
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CShaderVectorVariable
// Method				:	record
// Description			:
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CShaderVectorVariable::record(int nr,CGatherRay **r,float **varying) {
	int		i;
	float	*src	=	varying[entry];

	for (i=nr;i>0;i--,src+=3) {
		CGatherRay	*ray	=	(CGatherRay *) (*r++);

		movvv((float *) dest + ray->index*3,src);
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CShaderVectorVariable
// Method				:	record
// Description			:
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CShaderFloatVariable::record(int nr,CGatherRay **r,float **varying) {
	int		i;
	float	*src	=	varying[entry];

	for (i=nr;i>0;i--) {
		CGatherRay	*ray	=	(CGatherRay *) (*r++);

		dest[ray->index].real	=	*src++;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShaderVectorVariable
// Method				:	record
// Description			:
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CRayOriginVariable::record(int nr,CGatherRay **r,float **varying) {
	int		i;

	for (i=nr;i>0;i--) {
		CGatherRay	*ray	=	(CGatherRay *) (*r++);

		movvv((float *) dest + ray->index*3,ray->from);
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShaderVectorVariable
// Method				:	record
// Description			:
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CRayDirVariable::record(int nr,CGatherRay **r,float **varying) {
	int		i;

	for (i=nr;i>0;i--) {
		CGatherRay	*ray	=	(CGatherRay *) (*r++);

		movvv((float *) dest + ray->index*3,ray->dir);
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShaderVectorVariable
// Method				:	record
// Description			:
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CRayLengthVariable::record(int nr,CGatherRay **r,float **varying) {
	int		i;

	for (i=nr;i>0;i--) {
		CGatherRay	*ray	=	(CGatherRay *) (*r++);

		dest[ray->index].real	=	ray->t;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGatherLookup
// Method				:	CGatherLookup
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
CGatherLookup::CGatherLookup() {
	outputs			=	NULL;
	nonShadeOutputs	=	NULL;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CGatherLookup
// Method				:	~CGatherLookup
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
CGatherLookup::~CGatherLookup() {
	CGatherVariable	*cVar;

	while((cVar=outputs) != NULL) {
		outputs	=	cVar->next;
		if (cVar->destForEachLevel != NULL) delete [] cVar->destForEachLevel;
		delete cVar;
	}

	while((cVar=nonShadeOutputs) != NULL) {
		nonShadeOutputs	=	cVar->next;
		if (cVar->destForEachLevel != NULL) delete [] cVar->destForEachLevel;
		delete cVar;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGatherLookup
// Method				:	addOutput
// Description			:	Adds an output
// Return Value			:	-
// Comments				:
// Date last edited		:	3/23/2003
void	CGatherLookup::addOutput(const char *output,int destIndex) {
	CGatherVariable	*nVar	=	NULL;

	if (strncmp(output,"surface:",8) == 0) {
		CVariable				*var	=	currentRenderer->retrieveVariable(output+8);
		if (var == NULL)	error(CODE_BADTOKEN,"Variable %s not found\n",output);
		else {
			if (	(var->type == TYPE_VECTOR)	||
					(var->type == TYPE_POINT)	||
					(var->type == TYPE_COLOR)	||
					(var->type == TYPE_NORMAL)) {
				CShaderVectorVariable	*outVar	=	new CShaderVectorVariable;
				outVar->shade		=	TRUE;
				outVar->entry		=	var->entry;
				outVar->destIndex	=	destIndex;
				outVar->next		=	outputs;
				outputs				=	outVar;
				nVar				=	outVar;
			} else if (var->type == TYPE_FLOAT) {
				CShaderFloatVariable	*outVar	=	new CShaderFloatVariable;
				outVar->shade		=	TRUE;
				outVar->entry		=	var->entry;
				outVar->destIndex	=	destIndex;
				outVar->next		=	outputs;
				outputs				=	outVar;
				nVar				=	outVar;
			} else {
				error(CODE_BADTOKEN,"Unknown output variable type for gather\n");
			}
		}
	} else if (strcmp(output,"ray:origin") == 0) {
		CRayOriginVariable	*outVar	=	new CRayOriginVariable;
		outVar->destIndex	=	destIndex;
		outVar->next		=	nonShadeOutputs;
		nonShadeOutputs		=	outVar;
		nVar				=	outVar;
	} else if (strcmp(output,"ray:direction") == 0) {
		CRayDirVariable	*outVar	=	new CRayDirVariable;
		outVar->destIndex	=	destIndex;
		outVar->next		=	nonShadeOutputs;
		nonShadeOutputs		=	outVar;
		nVar				=	outVar;
	} else if (strcmp(output,"ray:length") == 0) {
		CRayLengthVariable	*outVar	=	new CRayLengthVariable;
		outVar->destIndex	=	destIndex;
		outVar->next		=	nonShadeOutputs;
		nonShadeOutputs		=	outVar;
		nVar				=	outVar;
	} else {
		error(CODE_BADTOKEN,"Unknown output variable for gather\n");
	}

	if (nVar != NULL) {
		nVar->destForEachLevel	=	new TCode*[maxRayDepth+1];
		nVar->cDepth			=	nVar->destForEachLevel;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGlobalIllumLookup
// Method				:	CGlobalIllumLookup
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CGlobalIllumLookup::CGlobalIllumLookup() {
}

///////////////////////////////////////////////////////////////////////
// Class				:	CGlobalIllumLookup
// Method				:	~CGlobalIllumLookup
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CGlobalIllumLookup::~CGlobalIllumLookup() {
	if (cache != NULL) cache->finalize(this);
}

///////////////////////////////////////////////////////////////////////	
// Class				:	CShader
// Method				:	CShader
// Description			:	Constructor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShader::CShader(const char *name) : CFileResource(name) {
	stats.numShaders++;

	name					=	NULL;
	memory					=	NULL;
	codeArea				=	NULL;
	parametersArea			=	NULL;
	constantsArea			=	NULL;
	parameterEntries		=	NULL;
	parameterDisplacements	=	NULL;
	constantEntries			=	NULL;
	varyingSizes			=	NULL;
	uniformSizes			=	NULL;
	strings					=	NULL;
	parameters				=	NULL;
	cache					=	NULL;
	dirty					=	FALSE;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CShader
// Method				:	~CShader
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShader::~CShader() {
	int	i;

	stats.numShaders--;

	for (i=0;i<numParameters;i++) {
		free(parameters[i].name);
	}

	for (i=0;i<numStrings;i++) {
		free(strings[i]);
	}

	if (memory != NULL)					delete [] memory;

	if (parameters != NULL) {
		delete [] parameters;
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CShader
// Method				:	nullify
// Description			:	This function must make sure the shader can never execute again
// Return Value			:
// Comments				:	
// Date last edited		:	3/10/2001
void	CShader::nullify() {
	codeEntryPoint	=	-1;
	initEntryPoint	=	-1;
}



///////////////////////////////////////////////////////////////////////
// Class				:	CShaderInstance
// Method				:	CShaderInstance
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShaderInstance::CShaderInstance(CAttributes *a,CXform *x) {
	stats.numShaderInstances++;

	refCount	=	1;
	xform		=	x;
	xform->attach();
}

///////////////////////////////////////////////////////////////////////
// Class				:	CShaderInstance
// Method				:	CShaderInstance
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CShaderInstance::~CShaderInstance() {
	stats.numShaderInstances--;

	xform->detach();
}












///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	CProgrammableShaderInstance
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CProgrammableShaderInstance::CProgrammableShaderInstance(CShader *p,CAttributes *a,CXform *x) : CShaderInstance(a,x) {
	int	i;

	parent		=	p;

	// Create a new place to store the parameters
	parametersArea		=	new TCode[parent->totalParameterSize];
	parameterEntries	=	new TCode*[parent->numParameters];

	// Copy the default values from parent
	memcpy(parametersArea,parent->parametersArea,parent->totalParameterSize*sizeof(TCode));

	// Set the parameter pointers
	for (i=0;i<parent->numParameters;i++) {
		parameterEntries[i]	=	&parametersArea[parent->parameterDisplacements[i]];
	}

	numStrings			=	0;
	strings				=	NULL;
	
	if (parent->numPLs > 0) {
		parameterLists	=	new CShaderLookup*[parent->numPLs];

		for (i=0;i<parent->numPLs;i++)
			parameterLists[i]	=	NULL;
	} else {
		parameterLists	=	NULL;
	}

	dirty				=	FALSE;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	~CProgrammableShaderInstance
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CProgrammableShaderInstance::~CProgrammableShaderInstance() {
	if (parametersArea != parent->parametersArea)		delete [] parametersArea;
	if (parameterEntries != parent->parameterEntries)	delete [] parameterEntries;
	if (strings != NULL) {
		int	i;

		for (i=0;i<numStrings;i++) {
			free(strings[i]);
		}

		delete [] strings;
	}

	if (parameterLists != NULL) {
#ifdef _DEBUG
		int	i;

		for (i=0;i<parent->numPLs;i++) {
			assert(parameterLists[i] == NULL);
		}

		assert(dirty == FALSE);
#endif

		delete [] parameterLists;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	setParameters
// Description			:	Set the values of the parameters
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
int	CProgrammableShaderInstance::setParameter(char *param,void *val) {
	int	j;

	for (j=0;j<parent->numParameters;j++) {
		if (strcmp(param,parent->parameters[j].name) == 0) {
			switch(parent->parameters[j].type) {
			case TYPE_FLOAT:
				{
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;
					memcpy(dest,src,parent->parameters[j].numItems*sizeof(float));
				}
				break;
			case TYPE_COLOR:
				{
					int			p;
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;

					for (p=0;p<parent->parameters[j].numItems;p++) {
						movvv(dest,src);
						dest	+=	3;
						src		+=	3;
					}
				}
				break;
			case TYPE_VECTOR:
				{
					int			p;
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;

					for (p=0;p<parent->parameters[j].numItems;p++) {
						mulmv(dest,xform->from,src);
						dest	+=	3;
						src		+=	3;
					}
				}
				break;
			case TYPE_NORMAL:
				{
					int			p;
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;

					for (p=0;p<parent->parameters[j].numItems;p++) {
						mulmn(dest,xform->to,src);
						dest	+=	3;
						src		+=	3;
					}
				}
				break;
			case TYPE_POINT:
				{
					int			p;
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;

					for (p=0;p<parent->parameters[j].numItems;p++) {
						mulmp(dest,xform->from,src);
						dest	+=	3;
						src		+=	3;
					}
				}
				break;
			case TYPE_MATRIX:
				{
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;
					memcpy(dest,src,parent->parameters[j].numItems*sizeof(matrix));
				}
				break;
			case TYPE_QUAD:
				{
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;
					memcpy(dest,src,parent->parameters[j].numItems*sizeof(float)*4);
				}
				break;
			case TYPE_DOUBLE:
				{
					const float	*src	=	(const float *) val;
					float		*dest	=	&parameterEntries[j]->real;
					memcpy(dest,src,parent->parameters[j].numItems*sizeof(float)*2);
				}
				break;
			case TYPE_STRING:
				{
					const char	**src	=	(const char **) val;
					char		**dest	=	&parameterEntries[j]->string;
					int			t;

					for (t=0;t<parent->parameters[j].numItems;t++) {
						strings[numStrings++]	=	*dest++	=	strdup(*src++);
					}
				}
				break;
			case TYPE_INTEGER:
			case TYPE_BOOLEAN:
				{
					const int	*src	=	(int *) val;
					int			*dest	=	&parameterEntries[j]->integer;
					memcpy(dest,src,parent->parameters[j].numItems*sizeof(int));
				}
				break;
			default:
				break;
			}

			break;
		}
	}

	return j != parent->numParameters;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	setParameters
// Description			:	Set the values of the parameters
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
void	CProgrammableShaderInstance::setParameters(int np,char **params,void **vals) {
	int	i;

	// Count the number of new strings that need allocation
	for (i=0;i<np;i++) {
		int	j;

		for (j=0;j<parent->numParameters;j++) {
			if (parent->parameters[j].type == TYPE_STRING) {
				numStrings	+=	parent->parameters[j].numItems;
			}
		}
	}


	strings			=	new char*[numStrings];
	numStrings		=	0;

	// Set the parameter defaults
	for (i=0;i<np;i++) {
		if (setParameter(params[i],vals[i]) == FALSE) {
			CVariable	var;

			if (parseVariable(&var,NULL,params[i]) == TRUE) {
				if (setParameter(var.name,vals[i]) == FALSE) {
					error(CODE_BADTOKEN,"Parameter \"%s\" not found in the shader\n",var.name);
				}
			} else {
				error(CODE_BADTOKEN,"Parameter \"%s\" not found in the shader\n",params[i]);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	getParameter
// Description			:	Get the current value of a parameter
// Return Value			:	TRUE if successful, FALSE othervise
// Comments				:	The second void * must be float *
//							if the type of the parameter is float/vector/matrix
//							and char ** if the type of the parameter is string
// Date last edited		:	3/10/2001
int		CProgrammableShaderInstance::getParameter(const char *name,void *dest) {
	int			i,j;
	float		*destFloat;
	float		*srcFloat;
	char		**destString;
	char		**srcString;
	int			*destInt;
	int			*srcInt;

	for (i=0;i<parent->numParameters;i++) {
		if (strcmp(name,parent->parameters[i].name) == 0) {
			switch(parent->parameters[i].type) {
			case TYPE_FLOAT:
				destFloat	=	(float *) dest;
				srcFloat	=	&parameterEntries[i]->real;
				for (j=0;j<parent->parameters[i].numItems;j++)
					*destFloat++	=	*srcFloat++;
				break;
			case TYPE_COLOR:
			case TYPE_VECTOR:
			case TYPE_NORMAL:
			case TYPE_POINT:
				destFloat	=	(float *) dest;
				srcFloat	=	&parameterEntries[i]->real;
				for (j=0;j<parent->parameters[i].numItems*3;j++)
					*destFloat++	=	*srcFloat++;
				break;
			case TYPE_MATRIX:
				destFloat	=	(float *) dest;
				srcFloat	=	&parameterEntries[i]->real;
				for (j=0;j<parent->parameters[i].numItems*16;j++)
					*destFloat++	=	*srcFloat++;
				break;
			case TYPE_QUAD:
				destFloat	=	(float *) dest;
				srcFloat	=	&parameterEntries[i]->real;
				for (j=0;j<parent->parameters[i].numItems*4;j++)
					*destFloat++	=	*srcFloat++;
				break;
			case TYPE_DOUBLE:
				destFloat	=	(float *) dest;
				srcFloat	=	&parameterEntries[i]->real;
				for (j=0;j<parent->parameters[i].numItems*2;j++)
					*destFloat++	=	*srcFloat++;
				break;
			case TYPE_STRING:
				destString	=	(char **) dest;
				srcString	=	&parameterEntries[i]->string;
				for (j=0;j<parent->parameters[i].numItems;j++)
					*destString++	=	*srcString++;
				break;
			case TYPE_INTEGER:
			case TYPE_BOOLEAN:
				destInt	=	(int *) dest;
				srcInt	=	&parameterEntries[i]->integer;
				for (j=0;j<parent->parameters[i].numItems;j++)
					*destInt++	=	*srcInt++;
				break;
			default:
				break;
			}

			return	TRUE;
		}
	}

	return FALSE;
}


///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	execute
// Description			:	Actually execute the shader
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
void			CProgrammableShaderInstance::execute(CShadingContext *context) {
	context->execute(this);
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	requiredParameters
// Description			:	Return the required parameters
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
unsigned int	CProgrammableShaderInstance::requiredParameters() {
	return parent->usedParameters;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	getName
// Description			:	Get the name of the shader
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
const char		*CProgrammableShaderInstance::getName() {
	return parent->name;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CProgrammableShaderInstance
// Method				:	illuminate
// Description			:	Illuminate the shading state is a lightsource shader
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
void			CProgrammableShaderInstance::illuminate(CShadingContext *context) {
	// This function should never be called
	fatal(CODE_BUG,"illuminate in a non-light source shader\n");
	assert(FALSE);
}


///////////////////////////////////////////////////////////////////////
// Class				:	CShaderInstance
// Method				:	destroy
// Description			:	Destroy this instance
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CLightInstance::CLightInstance(CShader *s,CAttributes *a,CXform *x) : CProgrammableShaderInstance(s,a,x) {
	char	*tmp;

	group				=	NULL;

	if (getParameter("__category",&tmp) == TRUE) {
		group	=	strdup(tmp);
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CLightInstance
// Method				:	destroy
// Description			:	Destroy this instance
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
CLightInstance::~CLightInstance() { 
	if (group != NULL)	free(group);
}

///////////////////////////////////////////////////////////////////////
// Class				:	CLightInstance
// Method				:	illuminate
// Description			:	Eilluminate the current shading state
// Return Value			:	-
// Comments				:
// Date last edited		:	3/10/2001
void			CLightInstance::illuminate(CShadingContext *context) {
	context->execute(this);
}


