/******************************************************************************

 @File         PVRTPFXParser.cpp

 @Title        PVRTPFXParser

 @Version      

 @Copyright    Copyright (c) Imagination Technologies Limited.

 @Platform     ANSI compatible

 @Description  PFX file parser.

******************************************************************************/

/*****************************************************************************
** Includes
******************************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "PVRTGlobal.h"
#include "PVRTContext.h"
#include "PVRTMatrix.h"
#include "PVRTFixedPoint.h"
#include "PVRTMisc.h"
#include "PVRTPFXParser.h"
#include "PVRTResourceFile.h"
#include "PVRTString.h"
#include "PVRTMisc.h"		// Used for POT functions

/****************************************************************************
** Constants
****************************************************************************/
const char* c_pszLinear   = "LINEAR";
const char* c_pszNearest  = "NEAREST";
const char* c_pszNone	  = "NONE";
const char* c_pszClamp    = "CLAMP";
const char* c_pszRepeat	  = "REPEAT";
const char* c_pszCurrentView = "PFX_CURRENTVIEW";

const unsigned int CPVRTPFXParser::VIEWPORT_SIZE = 0xAAAA;

const char* c_ppszFilters[eFilter_Size] = 
{ 
	c_pszNearest,		// eFilter_Nearest
	c_pszLinear,		// eFilter_Linear
	c_pszNone,			// eFilter_None
};
const char* c_ppszWraps[eWrap_Size] = 
{ 
	c_pszClamp,			// eWrap_Clamp
	c_pszRepeat			// eWrap_Repeat
};

#define NEWLINE_TOKENS "\r\n"
#define DELIM_TOKENS " \t"

#define DEFAULT_EFFECT_NUM_TEX		100
#define DEFAULT_EFFECT_NUM_UNIFORM	100
#define DEFAULT_EFFECT_NUM_ATTRIB	100

/****************************************************************************
** Data tables
****************************************************************************/

/****************************************************************************
** CPVRTPFXParserReadContext Class
****************************************************************************/
class CPVRTPFXParserReadContext
{
public:
	char			**ppszEffectFile;
	int				*pnFileLineNumber;
	unsigned int	nNumLines, nMaxLines;

public:
	CPVRTPFXParserReadContext();
	~CPVRTPFXParserReadContext();
};

/*!***************************************************************************
 @Function			CPVRTPFXParserReadContext
 @Description		Initialises values.
*****************************************************************************/
CPVRTPFXParserReadContext::CPVRTPFXParserReadContext()
{
	nMaxLines = 5000;
	nNumLines = 0;
	ppszEffectFile		= new char*[nMaxLines];
	pnFileLineNumber	= new int[nMaxLines];
}

/*!***************************************************************************
 @Function			~CPVRTPFXParserReadContext
 @Description		Frees allocated memory
*****************************************************************************/
CPVRTPFXParserReadContext::~CPVRTPFXParserReadContext()
{
	// free effect file
	for(unsigned int i = 0; i < nNumLines; i++)
	{
		FREE(ppszEffectFile[i]);
	}
	delete [] ppszEffectFile;
	delete [] pnFileLineNumber;
}

/*!***************************************************************************
 @Function			IgnoreWhitespace
 @Input				pszString
 @Output			pszString
 @Description		Skips space, tab, new-line and return characters.
*****************************************************************************/
static void IgnoreWhitespace(char **pszString)
{
	while(	*pszString[0] == '\t' ||
			*pszString[0] == '\n' ||
			*pszString[0] == '\r' ||
			*pszString[0] == ' ' )
	{
		(*pszString)++;
	}
}

/*!***************************************************************************
 @Function			ReadEOLToken
 @Input				pToken
 @Output			char*
 @Description		Reads next strings to the end of the line and interperts as
					a token.
*****************************************************************************/
static char* ReadEOLToken(char* pToken)
{
	char* pReturn = NULL;

	char szDelim[2] = {'\n', 0};				// try newline
	pReturn = strtok(pToken, szDelim);			
	if(pReturn == NULL)
	{
		szDelim[0] = '\r';
		pReturn = strtok (pToken, szDelim);		// try linefeed
	}
	return pReturn;
}

/*!***************************************************************************
 @Function			GetSemanticDataFromString
 @Output			pDataItem
 @Modified			pszArgumentString
 @Input				eType
 @Output			pError				error message
 @Return			true if successful
 @Description		Extracts the semantic data from the string and stores it
					in the output SPVRTSemanticDefaultData parameter.
*****************************************************************************/
static bool GetSemanticDataFromString(SPVRTSemanticDefaultData *pDataItem, const char * const pszArgumentString, ESemanticDefaultDataType eType, CPVRTString *pError)
{
	char *pszString = (char *)pszArgumentString;
	char *pszTmp;

	IgnoreWhitespace(&pszString);

	if(pszString[0] != '(')
	{
		*pError = CPVRTString("Missing '(' after ") + c_psSemanticDefaultDataTypeInfo[eType].pszName;
		return false;
	}
	pszString++;

	IgnoreWhitespace(&pszString);

	if(!strlen(pszString))
	{
		*pError = c_psSemanticDefaultDataTypeInfo[eType].pszName + CPVRTString(" missing arguments");
		return false;
	}

	pszTmp = pszString;
	switch(c_psSemanticDefaultDataTypeInfo[eType].eInternalType)
	{
		case eFloating:
			pDataItem->pfData[0] = (float)strtod(pszString, &pszTmp);
			break;
		case eInteger:
			pDataItem->pnData[0] = (int)strtol(pszString, &pszTmp, 10);
			break;
		case eBoolean:
			if(strncmp(pszString, "true", 4) == 0)
			{
				pDataItem->pbData[0] = true;
				pszTmp = &pszString[4];
			}
			else if(strncmp(pszString, "false", 5) == 0)
			{
				pDataItem->pbData[0] = false;
				pszTmp = &pszString[5];
			}
			break;
	}

	if(pszString == pszTmp)
	{
		size_t n = strcspn(pszString, ",\t ");
		char *pszError = (char *)malloc(n + 1);
		strcpy(pszError, "");
		strncat(pszError, pszString, n);
		*pError = CPVRTString("'") + pszError + "' unexpected for " + c_psSemanticDefaultDataTypeInfo[eType].pszName;
		FREE(pszError);
		return false;
	}
	pszString = pszTmp;

	IgnoreWhitespace(&pszString);

	for(unsigned int i = 1; i < c_psSemanticDefaultDataTypeInfo[eType].nNumberDataItems; i++)
	{
		if(!strlen(pszString))
		{
			*pError = c_psSemanticDefaultDataTypeInfo[eType].pszName + CPVRTString(" missing arguments");
			return false;
		}

		if(pszString[0] != ',')
		{
			size_t n = strcspn(pszString, ",\t ");
			char *pszError = (char *)malloc(n + 1);
			strcpy(pszError, "");
			strncat(pszError, pszString, n);
			*pError = CPVRTString("'") + pszError + "' unexpected for " + c_psSemanticDefaultDataTypeInfo[eType].pszName;
			FREE(pszError);
			return false;
		}
		pszString++;

		IgnoreWhitespace(&pszString);

		if(!strlen(pszString))
		{
			*pError = c_psSemanticDefaultDataTypeInfo[eType].pszName + CPVRTString(" missing arguments");
			return false;
		}

		pszTmp = pszString;
		switch(c_psSemanticDefaultDataTypeInfo[eType].eInternalType)
		{
			case eFloating:
				pDataItem->pfData[i] = (float)strtod(pszString, &pszTmp);
				break;
			case eInteger:
				pDataItem->pnData[i] = (int)strtol(pszString, &pszTmp, 10);
				break;
			case eBoolean:
				if(strncmp(pszString, "true", 4) == 0)
				{
					pDataItem->pbData[i] = true;
					pszTmp = &pszString[4];
				}
				else if(strncmp(pszString, "false", 5) == 0)
				{
					pDataItem->pbData[i] = false;
					pszTmp = &pszString[5];
				}
				break;
		}

		if(pszString == pszTmp)
		{
			size_t n = strcspn(pszString, ",\t ");
			char *pszError = (char *)malloc(n + 1);
			strcpy(pszError, "");
			strncat(pszError, pszString, n);
			*pError = CPVRTString("'") + pszError + "' unexpected for " + c_psSemanticDefaultDataTypeInfo[eType].pszName;
			FREE(pszError);
			return false;
		}
		pszString = pszTmp;

		IgnoreWhitespace(&pszString);
	}

	if(pszString[0] != ')')
	{
		size_t n = strcspn(pszString, "\t )");
		char *pszError = (char *)malloc(n + 1);
		strcpy(pszError, "");
		strncat(pszError, pszString, n);
		*pError = CPVRTString("'") + pszError + "' found when expecting ')' for " + c_psSemanticDefaultDataTypeInfo[eType].pszName;
		FREE(pszError);
		return false;
	}
	pszString++;

	IgnoreWhitespace(&pszString);

	if(strlen(pszString))
	{
		*pError = CPVRTString("'") + pszString + "' unexpected after ')'";
		return false;
	}

	return true;
}

/*!***************************************************************************
 @Function			ConcatenateLinesUntil
 @Output			pszOut		output text
 @Output			nLine		end line number
 @Input				nLine		start line number
 @Input				ppszLines	input text - one array element per line
 @Input				nLimit		number of lines input
 @Input				pszEnd		end string
 @Return			true if successful
 @Description		Outputs a block of text starting from nLine and ending
					when the string pszEnd is found.
*****************************************************************************/
static bool ConcatenateLinesUntil(CPVRTString& Out, int &nLine, const char * const * const ppszLines, const unsigned int nLimit, const char * const pszEnd)
{
	unsigned int	i, j;
	size_t			nLen;

	nLen = 0;
	for(i = nLine; i < nLimit; ++i)
	{
		if(strcmp(ppszLines[i], pszEnd) == 0)
			break;
		nLen += strlen(ppszLines[i]) + 1;
	}
	if(i == nLimit)
	{
		return false;
	}

	if(nLen)
	{
		++nLen;

		Out.reserve(nLen);

		for(j = nLine; j < i; ++j)
		{
			Out.append(ppszLines[j]);
			Out.append("\n");
		}
	}

	nLine = i;
	return true;
}

/****************************************************************************
** SPVRTPFXParserEffect Struct
****************************************************************************/
SPVRTPFXParserEffect::SPVRTPFXParserEffect() :  
	Uniforms(DEFAULT_EFFECT_NUM_UNIFORM),
	Attributes(DEFAULT_EFFECT_NUM_ATTRIB),
	Textures(DEFAULT_EFFECT_NUM_TEX)
{
}

/****************************************************************************
** SPVRTPFXRenderPass Class
****************************************************************************/
SPVRTPFXRenderPass::SPVRTPFXRenderPass() :
	eRenderPassType(eNULL_PASS),
	eViewType(eVIEW_NONE),
	uiFormatFlags(0),
	pEffect(NULL),
	pTexture(NULL)
{
}

/****************************************************************************
** SPVRTPFXParserShader Class
****************************************************************************/
SPVRTPFXParserShader::SPVRTPFXParserShader()
	:
	pszGLSLfile(NULL),
	pszGLSLBinaryFile(NULL),
	pszGLSLcode(NULL),
	pbGLSLBinary(NULL)
{
}

SPVRTPFXParserShader::~SPVRTPFXParserShader()
{
	FREE(pszGLSLfile);
	FREE(pszGLSLcode);
	FREE(pszGLSLBinaryFile);
	FREE(pbGLSLBinary);
}

SPVRTPFXParserShader::SPVRTPFXParserShader(const SPVRTPFXParserShader& rhs)
{
	Copy(rhs);
}

SPVRTPFXParserShader& SPVRTPFXParserShader::operator=(const SPVRTPFXParserShader& rhs)
{
	if(&rhs != this)
		Copy(rhs);

	return *this;
}

void SPVRTPFXParserShader::Copy(const SPVRTPFXParserShader& rhs)
{
	Name = rhs.Name;

	PVRTPFXCreateStringCopy(&pszGLSLfile, rhs.pszGLSLfile);
	PVRTPFXCreateStringCopy(&pszGLSLBinaryFile, rhs.pszGLSLBinaryFile);
	PVRTPFXCreateStringCopy(&pszGLSLcode, rhs.pszGLSLcode);
	PVRTPFXCreateStringCopy(&pbGLSLBinary, rhs.pbGLSLBinary);

	bUseFileName	= rhs.bUseFileName;
	nGLSLBinarySize = rhs.nGLSLBinarySize;
	nFirstLineNumber= rhs.nFirstLineNumber;
	nLastLineNumber = rhs.nLastLineNumber;
}

/****************************************************************************
** SPVRTSemanticDefaultData Struct
****************************************************************************/
SPVRTSemanticDefaultData::SPVRTSemanticDefaultData() 
	: 
	eType(eDataTypeNone)
{
}

SPVRTSemanticDefaultData::SPVRTSemanticDefaultData(const SPVRTSemanticDefaultData& rhs)	
{	
	Copy(rhs); 
}

SPVRTSemanticDefaultData& SPVRTSemanticDefaultData::operator=(const SPVRTSemanticDefaultData& rhs)
{
	if(&rhs != this)
		Copy(rhs);
	return *this;
}

void SPVRTSemanticDefaultData::Copy(const SPVRTSemanticDefaultData& rhs)
{
	memcpy(pfData, rhs.pfData, sizeof(pfData));
	memcpy(pnData, rhs.pnData, sizeof(pnData));
	memcpy(pbData, rhs.pbData, sizeof(pbData));
	eType = rhs.eType;
}

/****************************************************************************
** SPVRTPFXParserSemantic Struct
****************************************************************************/
SPVRTPFXParserSemantic::SPVRTPFXParserSemantic() 
	: 
	pszName(NULL), 
	pszValue(NULL)
{
}

SPVRTPFXParserSemantic::~SPVRTPFXParserSemantic()
{
	FREE(pszName);
	FREE(pszValue);
}

SPVRTPFXParserSemantic::SPVRTPFXParserSemantic(const SPVRTPFXParserSemantic& rhs)
{
	Copy(rhs);
}

SPVRTPFXParserSemantic& SPVRTPFXParserSemantic::operator=(const SPVRTPFXParserSemantic& rhs)
{
	if(&rhs != this)
		Copy(rhs);

	return *this;
}

void SPVRTPFXParserSemantic::Copy(const SPVRTPFXParserSemantic& rhs)
{
	PVRTPFXCreateStringCopy(&pszName, rhs.pszName);
	PVRTPFXCreateStringCopy(&pszValue, rhs.pszValue);
	nIdx			= rhs.nIdx;
	sDefaultValue   = rhs.sDefaultValue;
}

/****************************************************************************
** CPVRTPFXParser Class
****************************************************************************/
/*!***************************************************************************
 @Function			CPVRTPFXParser
 @Description		Sets initial values.
*****************************************************************************/
CPVRTPFXParser::CPVRTPFXParser()
{
	m_szFileName.assign("");

	// NOTE: Temp hardcode viewport size
	m_uiViewportWidth = 640;
	m_uiViewportHeight = 480;
}

/*!***************************************************************************
 @Function			~CPVRTPFXParser
 @Description		Frees memory used.
*****************************************************************************/
CPVRTPFXParser::~CPVRTPFXParser()
{
}

/*!***************************************************************************
 @Function			Parse
 @Output			pReturnError	error string
 @Return			bool			true for success parsing file
 @Description		Parses a loaded PFX file.
*****************************************************************************/
bool CPVRTPFXParser::Parse(CPVRTString * const pReturnError)
{
	enum eCmd
	{
		eCmds_Header,
		eCmds_Texture,
		eCmds_Target,
		eCmds_Textures,
		eCmds_VertexShader,
		eCmds_FragmentShader,
		eCmds_Effect,

		eCmds_Size
	};

	const CPVRTHash ParserCommands[] =
	{
		"[HEADER]",				// eCmds_Header
		"[TEXTURE]",			// eCmds_Texture
		"[TARGET]",				// eCmds_Target
		"[TEXTURES]",			// eCmds_Textures
		"[VERTEXSHADER]",		// eCmds_VertexShader
		"[FRAGMENTSHADER]",		// eCmds_FragmentShader
		"[EFFECT]",				// eCmds_Effect
	};
	PVRTCOMPILEASSERT(ParserCommands, sizeof(ParserCommands) / sizeof(ParserCommands[0]) == eCmds_Size);

	int nEndLine = 0;
	int nHeaderCounter = 0, nTexturesCounter = 0;
	unsigned int i,j,k;

	// Loop through the file
	for(unsigned int nLine=0; nLine < m_psContext->nNumLines; nLine++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[nLine])
			continue;

		CPVRTHash Cmd(m_psContext->ppszEffectFile[nLine]);
		if(Cmd == ParserCommands[eCmds_Header])
		{
			if(nHeaderCounter>0)
			{
				*pReturnError = PVRTStringFromFormattedStr("[HEADER] redefined on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			if(GetEndTag("HEADER", nLine, &nEndLine))
			{
				if(ParseHeader(nLine, nEndLine, pReturnError))
					nHeaderCounter++;
				else
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/HEADER] tag after [HEADER] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_Texture])
		{
			if(GetEndTag("TEXTURE", nLine, &nEndLine))
			{
				if(!ParseTexture(nLine, nEndLine, pReturnError))
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/TEXTURE] tag after [TEXTURE] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_Target])
		{
			if(GetEndTag("TARGET", nLine, &nEndLine))
			{
				if(!ParseTarget(nLine, nEndLine, pReturnError))
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/TARGET] tag after [TARGET] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_Textures])
		{
			if(nTexturesCounter>0)
			{
				*pReturnError = PVRTStringFromFormattedStr("[TEXTURES] redefined on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			if(GetEndTag("TEXTURES", nLine, &nEndLine))
			{
				if(ParseTextures(nLine, nEndLine, pReturnError))
					nTexturesCounter++;
				else
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/TEXTURES] tag after [TEXTURES] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_VertexShader])
		{
			if(GetEndTag("VERTEXSHADER", nLine, &nEndLine))
			{
				SPVRTPFXParserShader VertexShader;
				if(ParseShader(nLine, nEndLine, pReturnError, VertexShader, "VERTEXSHADER"))
					m_psVertexShader.Append(VertexShader);
				else
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/VERTEXSHADER] tag after [VERTEXSHADER] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_FragmentShader])
		{
			if(GetEndTag("FRAGMENTSHADER", nLine, &nEndLine))
			{
				SPVRTPFXParserShader FragShader;
				if(ParseShader(nLine, nEndLine, pReturnError, FragShader, "FRAGMENTSHADER"))
					m_psFragmentShader.Append(FragShader);
				else
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/FRAGMENTSHADER] tag after [FRAGMENTSHADER] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else if(Cmd == ParserCommands[eCmds_Effect])
		{
			if(GetEndTag("EFFECT", nLine, &nEndLine))
			{
				SPVRTPFXParserEffect Effect;
				if(ParseEffect(Effect, nLine, nEndLine, pReturnError))
					m_psEffect.Append(Effect);
				else
					return false;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing [/EFFECT] tag after [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nLine]);
				return false;
			}
			nLine = nEndLine;
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("'%s' unexpected on line %d\n", m_psContext->ppszEffectFile[nLine], m_psContext->pnFileLineNumber[nLine]);
			return false;
		}
	}

	if(m_psEffect.GetSize() < 1)
	{
		*pReturnError = CPVRTString("No [EFFECT] found. PFX file must have at least one defined.\n");
		return false;
	}

	if(m_psFragmentShader.GetSize() < 1)
	{
		*pReturnError = CPVRTString("No [FRAGMENTSHADER] found. PFX file must have at least one defined.\n");;
		return false;
	}

	if(m_psVertexShader.GetSize() < 1)
	{
		*pReturnError = CPVRTString("No [VERTEXSHADER] found. PFX file must have at least one defined.\n");
		return false;
	}

	// Loop Effects
	for(i = 0; i < m_psEffect.GetSize(); ++i)
	{
		// Loop Textures in Effects
		for(j = 0; j < m_psEffect[i].Textures.GetSize(); ++j)
		{
			// Loop Textures in whole PFX
			unsigned int uiTexSize = m_psTexture.GetSize();
			for(k = 0; k < uiTexSize; ++k)
			{
				if(m_psTexture[k]->Name == m_psEffect[i].Textures[j].Name)
					break;
			}

			// Texture mismatch. Report error.
			if(!uiTexSize || k == uiTexSize)
			{
				*pReturnError = "Error: TEXTURE '" + m_psEffect[i].Textures[j].Name.String() + "' is not defined in [TEXTURES].\n";
				return false;
			}
		}
	}

	DetermineRenderPassDependencies(pReturnError);
	if(pReturnError->compare(""))
	{
		return false;
	}

	return true;
}

/*!***************************************************************************
 @Function			ParseFromMemory
 @Input				pszScript		PFX script
 @Output			pReturnError	error string
 @Return			EPVRTError		PVR_SUCCESS for success parsing file
									PVR_FAIL if file doesn't exist or is invalid
 @Description		Parses a PFX script from memory.
*****************************************************************************/
EPVRTError CPVRTPFXParser::ParseFromMemory(const char * const pszScript, CPVRTString * const pReturnError)
{
	CPVRTPFXParserReadContext	context;
	char			pszLine[512];
	const char		*pszEnd, *pszCurr;
	int				nLineCounter;
	unsigned int	nLen;
	unsigned int	nReduce;
	bool			bDone;

	if(!pszScript)
		return PVR_FAIL;

	m_psContext = &context;

	// Find & process each line
	nLineCounter	= 0;
	bDone			= false;
	pszCurr			= pszScript;
	while(!bDone)
	{
		nLineCounter++;

		while(*pszCurr == '\r')
			++pszCurr;

		// Find length of line
		pszEnd = strchr(pszCurr, '\n');
		if(pszEnd)
		{
			nLen = (unsigned int)(pszEnd - pszCurr);
		}
		else
		{
			nLen = (unsigned int)strlen(pszCurr);
			bDone = true;
		}

		nReduce = 0; // Tells how far to go back because of '\r'.
		while(nLen - nReduce > 0 && pszCurr[nLen - 1 - nReduce] == '\r')
			nReduce++;

		// Ensure pszLine will not be not overrun
		if(nLen+1-nReduce > sizeof(pszLine) / sizeof(*pszLine))
			nLen = sizeof(pszLine) / sizeof(*pszLine) - 1 + nReduce;

		// Copy line into pszLine
		strncpy(pszLine, pszCurr, nLen - nReduce);
		pszLine[nLen - nReduce] = 0;
		pszCurr += nLen + 1;

		_ASSERT(strchr(pszLine, '\r') == 0);
		_ASSERT(strchr(pszLine, '\n') == 0);

		// Ignore comments
		char *tmp = strstr(pszLine, "//");
		if(tmp != NULL)	*tmp = '\0';

		// Reduce whitespace to one character.
		ReduceWhitespace(pszLine);

		// Store the line, even if blank lines (to get correct errors from GLSL compiler).
		if(m_psContext->nNumLines < m_psContext->nMaxLines)
		{
			m_psContext->pnFileLineNumber[m_psContext->nNumLines] = nLineCounter;
			m_psContext->ppszEffectFile[m_psContext->nNumLines] = (char *)malloc((strlen(pszLine) + 1) * sizeof(char));
			strcpy(m_psContext->ppszEffectFile[m_psContext->nNumLines], pszLine);
			m_psContext->nNumLines++;
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Too many lines of text in file (maximum is %d)\n", m_psContext->nMaxLines);
			return PVR_FAIL;
		}
	}

	return Parse(pReturnError) ? PVR_SUCCESS : PVR_FAIL;
}

/*!***************************************************************************
 @Function			ParseFromFile
 @Input				pszFileName		PFX file name
 @Output			pReturnError	error string
 @Return			EPVRTError		PVR_SUCCESS for success parsing file
									PVR_FAIL if file doesn't exist or is invalid
 @Description		Reads the PFX file and calls the parser.
*****************************************************************************/
EPVRTError CPVRTPFXParser::ParseFromFile(const char * const pszFileName, CPVRTString * const pReturnError)
{
	CPVRTResourceFile PfxFile(pszFileName);
	if (!PfxFile.IsOpen())
	{
		*pReturnError = CPVRTString("Unable to open file ") + pszFileName;
		return PVR_FAIL;
	}

	CPVRTString PfxFileString;
	const char* pPfxData = (const char*) PfxFile.DataPtr();

	// Is our shader resource file data null terminated?
	if(pPfxData[PfxFile.Size()-1] != '\0')
	{
		// If not create a temporary null-terminated string
		PfxFileString.assign(pPfxData, PfxFile.Size());
		pPfxData = PfxFileString.c_str();
	}

	m_szFileName.assign(pszFileName);

	return ParseFromMemory(pPfxData, pReturnError);
}

/*!***************************************************************************
 @Function			SetViewportSize
 @Input				uiWidth				New viewport width
 @Input				uiHeight			New viewport height
 @Return			bool				True on success
 @Description		Allows the current viewport size to be set. This value
					is used for calculating relative texture resolutions
*****************************************************************************/
bool CPVRTPFXParser::SetViewportSize(unsigned int uiWidth, unsigned int uiHeight)
{
	if(uiWidth > 0 && uiHeight > 0)
	{
		m_uiViewportWidth = uiWidth;
		m_uiViewportHeight = uiHeight;
		return true;
	}
	else
	{
		return false;
	}
}
/*!***************************************************************************
@Function		RetrieveRenderPassDependencies
@Output			aRequiredRenderPasses
@Output			aszActiveEffectStrings
@Return			bool	
@Description	Returns a list of dependencies associated with the pass.
*****************************************************************************/
bool CPVRTPFXParser::RetrieveRenderPassDependencies(CPVRTArray<SPVRTPFXRenderPass*> &aRequiredRenderPasses, CPVRTArray<CPVRTStringHash> &aszActiveEffectStrings)
{
	unsigned int ui(0), uj(0), uk(0), ul(0);
	const SPVRTPFXParserEffect* pTempEffect(NULL);
	
	if(aRequiredRenderPasses.GetSize() > 0)
	{
		/* aRequiredRenderPasses should be empty when it is passed in */
		return false;
	}

	for(ui = 0; ui < (unsigned int)aszActiveEffectStrings.GetSize(); ++ui)
	{
		if(aszActiveEffectStrings[ui].String().empty())
		{
			// Empty strings are not valid
			return false;
		}

		// Find the specified effect
		for(uj = 0, pTempEffect = NULL; uj < (unsigned int)m_psEffect.GetSize(); ++uj)
		{
			if(aszActiveEffectStrings[ui] == m_psEffect[uj].Name)
			{
				// Effect found
				pTempEffect = &m_psEffect[uj];
				break;
			}
		}

		if(pTempEffect == NULL)
		{
			// Effect not found
			return false;
		}
		
		for(uj = 0; uj < m_renderPassSkipGraph.GetNumNodes(); ++uj)
		{
			if(m_renderPassSkipGraph[uj]->pEffect == pTempEffect)
			{
				m_renderPassSkipGraph.RetreiveSortedDependencyList(aRequiredRenderPasses, uj);
				return true;
			}
		}

		/*
			The effect wasn't a post-process. Check to see if it has any non-post-process dependencies,
			e.g. RENDER CAMERA textures.
		*/
		// Loop Effects
		for(uj = 0; uj < (unsigned int)m_psEffect.GetSize(); ++uj)
		{
			if(aszActiveEffectStrings[ui] != m_psEffect[uj].Name)
				continue;

			// Loop Textures in Effect
			for(uk = 0; uk < m_psEffect[uj].Textures.GetSize();++uk)
			{
				// Loop Render Passes for whole PFX
				for(ul = 0; ul < m_RenderPasses.GetSize(); ++ul)
				{
					// Check that the name of this render pass output texture matches a provided texture in an Effect
					if(m_RenderPasses[ul].pTexture->Name == m_psEffect[uj].Textures[uk].Name)
						aRequiredRenderPasses.Append(&m_RenderPasses[ul]);
				}
			}
			
			return true;
		}
	}

	return false;
}
/*!***************************************************************************
 @Function			GetEndTag
 @Input				pszTagName		tag name
 @Input				nStartLine		start line
 @Output			pnEndLine		line end tag found
 @Return			true if tag found
 @Description		Searches for end tag pszTagName from line nStartLine.
					Returns true and outputs the line number of the end tag if
					found, otherwise returning false.
*****************************************************************************/
bool CPVRTPFXParser::GetEndTag(const char* pszTagName, int nStartLine, int *pnEndLine)
{
	char pszEndTag[100];
	strcpy(pszEndTag, "[/");
	strcat(pszEndTag, pszTagName);
	strcat(pszEndTag, "]");

	for(unsigned int i = nStartLine; i < m_psContext->nNumLines; i++)
	{
		if(strcmp(pszEndTag, m_psContext->ppszEffectFile[i]) == 0)
		{
			*pnEndLine = i;
			return true;
		}
	}

	return false;
}

/*!***************************************************************************
 @Function			ReduceWhitespace
 @Output			line		output text
 @Input				line		input text
 @Description		Reduces all white space characters in the string to one
					blank space.
*****************************************************************************/
void CPVRTPFXParser::ReduceWhitespace(char *line)
{
	// convert tabs and newlines to ' '
	char *tmp = strpbrk (line, "\t\n");
	while(tmp != NULL)
	{
		*tmp = ' ';
		tmp = strpbrk (line, "\t\n");
	}

	// remove all whitespace at start
	while(line[0] == ' ')
	{
		// move chars along to omit whitespace
		int counter = 0;
		do{
			line[counter] = line[counter+1];
			counter++;
		}while(line[counter] != '\0');
	}

	// step through chars of line remove multiple whitespace
	for(int i=0; i < (int)strlen(line); i++)
	{
		// whitespace found
		if(line[i] == ' ')
		{
			// count number of whitespace chars
			int numWhiteChars = 0;
			while(line[i+1+numWhiteChars] == ' ')
			{
				numWhiteChars++;
			}

			// multiple whitespace chars found
			if(numWhiteChars>0)
			{
				// move chars along to omit whitespace
				int counter=1;
				while(line[i+counter] != '\0')
				{
					line[i+counter] = line[i+numWhiteChars+counter];
					counter++;
				}
			}
		}
	}

	// If there is no string then do not remove terminating white symbols
	if(!strlen(line))
	    return;

	// remove all whitespace from end
	while(line[strlen(line)-1] == ' ')
	{
		// move chars along to omit whitespace
		line[strlen(line)-1] = '\0';
	}
}

/*!***************************************************************************
 @Function			FindParameter
 @Output
 @Input
 @Description		Finds the parameter after the specified delimiting character and
					returns the parameter as a string. An empty string is returned
					if a parameter cannot be found

*****************************************************************************/
CPVRTString CPVRTPFXParser::FindParameter(char *aszSourceString, const CPVRTString &parameterTag, const CPVRTString &delimiter)
{
	CPVRTString returnString("");
	char* aszTagStart = strstr(aszSourceString, parameterTag.c_str());

	// Tag was found, so search for parameter
	if(aszTagStart)
	{
		char* aszDelimiterStart = strstr(aszTagStart, delimiter.c_str());
		char* aszSpaceStart = strstr(aszTagStart, " ");

		// Delimiter found
		if(aszDelimiterStart && (!aszSpaceStart ||(aszDelimiterStart < aszSpaceStart)))
		{
			// Create a string from the delimiter to the next space
			size_t strCount(strcspn(aszDelimiterStart, " "));
			aszDelimiterStart++;	// Skip =
			returnString.assign(aszDelimiterStart, strCount-1);
		}
	}

	return returnString;
}

/*!***************************************************************************
@Function		ReadStringToken
@Input			pszSource			Parameter string to process
@Output			output				Processed string
@Output			ErrorStr			String containing errors
@Return								Returns true on success
@Description	Processes the null terminated char array as if it's a
				formatted string array. Quote marks are determined to be
				start and end of strings. If no quote marks are found the
				string is delimited by whitespace.
*****************************************************************************/
bool CPVRTPFXParser::ReadStringToken(char* pszSource, CPVRTString& output, CPVRTString &ErrorStr, int i, const char* pCaller)
{
	if(*pszSource == '\"')		// Quote marks. Continue parsing until end mark or NULL
	{	
		pszSource++;		// Skip past first quote
		while(*pszSource != '\"')
		{
			if(*pszSource == '\0')
			{
				ErrorStr = PVRTStringFromFormattedStr("Incomplete argument in [%s] on line %d: %s\n", pCaller,m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				return false;
			}

			output.push_back(*pszSource);
			pszSource++;
		}

		pszSource++;		// Skip past final quote.
	}
	else		// No quotes. Read until space
	{
		pszSource = strtok(pszSource, DELIM_TOKENS NEWLINE_TOKENS);
		output = pszSource;

		pszSource += strlen(pszSource);
	}

	// Check that there's nothing left on this line
	pszSource = strtok(pszSource, NEWLINE_TOKENS);
	if(pszSource)
	{
		ErrorStr = PVRTStringFromFormattedStr("Unknown keyword '%s' in [%s] on line %d: %s\n", pszSource, pCaller, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
		return false;
	}

	return true;
}

/*!***************************************************************************
 @Function			ParseHeader
 @Input				nStartLine		start line number
 @Input				nEndLine		end line number
 @Output			pReturnError	error string
 @Return			bool			true if parse is successful
 @Description		Parses the HEADER section of the PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseHeader(int nStartLine, int nEndLine, CPVRTString * const pReturnError)
{
	enum eCmd
	{
		eCmds_Version,
		eCmds_Description,
		eCmds_Copyright,

		eCmds_Size
	};

	const CPVRTHash HeaderCommands[] =
	{
		"VERSION",			// eCmds_Version
		"DESCRIPTION",		// eCmds_Description
		"COPYRIGHT",		// eCmds_Copyright
	};
	PVRTCOMPILEASSERT(HeaderCommands, sizeof(HeaderCommands) / sizeof(HeaderCommands[0]) == eCmds_Size);

	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i]," ");
		if(str != NULL)
		{
			CPVRTHash Cmd(str);
			if(Cmd == HeaderCommands[eCmds_Version])
			{
				str += (strlen(str)+1);
				m_sHeader.Version = str;
			}
			else if(Cmd == HeaderCommands[eCmds_Description])
			{
				str += (strlen(str)+1);
				m_sHeader.Description = str;
			}
			else if(Cmd == HeaderCommands[eCmds_Copyright])
			{
				str += (strlen(str)+1);
				m_sHeader.Copyright = str;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [HEADER] on line %d\n", str, m_psContext->pnFileLineNumber[i]);
				return false;
			}
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [HEADER] on line %d : %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
			return false;
		}
	}

	return true;
}

/*!***************************************************************************
@Function			ParseGenericSurface
@Input				nStartLine		start line number
@Input				nEndLine		end line number
@Output				uiWrapS			
@Output				uiWrapT			
@Output				uiWrapR			
@Output				uiMin			
@Output				uiMag			
@Output				uiMip			
@Output				pReturnError	error string
@Return				bool			true if parse is successful
@Description		Parses generic data from TARGET and TEXTURE blocks. Namely
					wrapping and filter commands.
*****************************************************************************/
bool CPVRTPFXParser::ParseGenericSurface(int nStartLine, int nEndLine, SPVRTPFXParserTexture& Params, CPVRTArray<CPVRTHash>& KnownCmds, 
										 const char* pCaller, CPVRTString * const pReturnError)
{
	const unsigned int INVALID_TYPE = 0xAC1DBEEF;
	
	enum eCmd
	{
		eCmds_Min,
		eCmds_Mag,
		eCmds_Mip,
		eCmds_WrapS,
		eCmds_WrapT,
		eCmds_WrapR,
		eCmds_Filter,
		eCmds_Wrap,
		eCmds_Resolution,
		eCmds_Surface,

		eCmds_Size
	};

	const CPVRTHash GenericSurfCommands[] =
	{
		"MINIFICATION",			// eCmds_Min
		"MAGNIFICATION",		// eCmds_Mag
		"MIPMAP",				// eCmds_Mip
		"WRAP_S",				// eCmds_WrapS
		"WRAP_T",				// eCmds_WrapT
		"WRAP_R",				// eCmds_WrapR
		"FILTER",				// eCmds_Filter
		"WRAP",					// eCmds_Wrap
		"RESOLUTION",			// eCmds_Resolution
		"SURFACETYPE",			// eCmds_Surface
	};
	PVRTCOMPILEASSERT(GenericSurfCommands, sizeof(GenericSurfCommands) / sizeof(GenericSurfCommands[0]) == eCmds_Size);

	struct SSurfacePair
	{
		CPVRTHash Name;
		PVRTPixelType eType;
		unsigned int BufferType;
	};

	const SSurfacePair SurfacePairs[] = 
	{
		{ "RGBA8888",	OGL_RGBA_8888,	PVRPFXTEX_COLOUR },
		{ "RGBA4444",	OGL_RGBA_4444,	PVRPFXTEX_COLOUR },
		{ "RGB888",		OGL_RGB_888,	PVRPFXTEX_COLOUR },
		{ "RGB565",		OGL_RGB_565,	PVRPFXTEX_COLOUR },		
		{ "INTENSITY8",	OGL_I_8,		PVRPFXTEX_COLOUR },
		{ "DEPTH24",	OGL_RGB_888,	PVRPFXTEX_DEPTH },
		{ "DEPTH16",	OGL_RGB_565,	PVRPFXTEX_DEPTH },
		{ "DEPTH8",		OGL_I_8,		PVRPFXTEX_DEPTH },
	};
	const unsigned int uiNumSurfTypes = sizeof(SurfacePairs) / sizeof(SurfacePairs[0]);

	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		// Need to make a copy so we can use strtok and not affect subsequent parsing
		size_t lineLen = strlen(m_psContext->ppszEffectFile[i]);
		char* pBlockCopy = new char[lineLen+1];
		strcpy(pBlockCopy, m_psContext->ppszEffectFile[i]);

		char *str = strtok (pBlockCopy, NEWLINE_TOKENS DELIM_TOKENS);
		if(!str)
		{
			delete[] pBlockCopy;
			return false;		
		}

		CPVRTHash Cmd(str);
		const char** ppFilters  = NULL;
		bool bKnown = false;

		// --- Verbose filtering flags
		if(Cmd == GenericSurfCommands[eCmds_Min] || Cmd == GenericSurfCommands[eCmds_Mag] || Cmd == GenericSurfCommands[eCmds_Mip])
		{
			ppFilters = c_ppszFilters;
			bKnown     = true;
		}
		// --- Verbose wrapping flags
		else if(Cmd == GenericSurfCommands[eCmds_WrapS] || Cmd == GenericSurfCommands[eCmds_WrapT] || Cmd == GenericSurfCommands[eCmds_WrapR])
		{
			ppFilters = c_ppszWraps;
			bKnown     = true;
		}
		// --- Inline filtering flags
		else if(Cmd == GenericSurfCommands[eCmds_Filter])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing FILTER arguments in [%s] on line %d: %s\n", pCaller, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				delete[] pBlockCopy;
				return false;
			}

			unsigned int* pFlags[3] =
			{
				&Params.nMin,
				&Params.nMag,
				&Params.nMIP,
			};

			if(!ParseTextureFlags(pszRemaining, pFlags, 3, c_ppszFilters, eFilter_Size, pReturnError, i))
			{
				delete[] pBlockCopy;
				return false;
			}

			bKnown     = true;
		}
		// --- Inline wrapping flags
		else if(Cmd == GenericSurfCommands[eCmds_Wrap])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing WRAP arguments in [%s] on line %d: %s\n", pCaller, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				delete[] pBlockCopy;
				return false;
			}

			unsigned int* pFlags[3] =
			{
				&Params.nWrapS,
				&Params.nWrapT,
				&Params.nWrapR,
			};

			if(!ParseTextureFlags(pszRemaining, pFlags, 3, c_ppszWraps, eWrap_Size, pReturnError, i))
			{
				delete[] pBlockCopy;
				return false;
			}

			bKnown     = true;
		}
		// --- Resolution
		else if(Cmd == GenericSurfCommands[eCmds_Resolution])
		{
			char* pszRemaining;

			unsigned int* uiVals[2] = { &Params.uiWidth, &Params.uiHeight };

			// There should be precisely TWO arguments for resolution (width and height)
			for(unsigned int uiIndex = 0; uiIndex < 2; ++uiIndex)
			{
				pszRemaining = strtok(NULL, DELIM_TOKENS NEWLINE_TOKENS);
				if(!pszRemaining)
				{
					*pReturnError = PVRTStringFromFormattedStr("Missing RESOLUTION argument(s) (requires width AND height) in [TARGET] on line %d\n", m_psContext->pnFileLineNumber[i]);
					delete[] pBlockCopy;
					return false;
				}

				int val = atoi(pszRemaining);

				if( (val == 0 && *pszRemaining != '0')			// Make sure they haven't explicitly set the value to be 0 as this might be a valid use-case.
					||  (val < 0))
				{
					*pReturnError = PVRTStringFromFormattedStr("Invalid RESOLUTION argument \"%s\" in [TEXTURE] on line %d\n", pszRemaining, m_psContext->pnFileLineNumber[i]);
					delete[] pBlockCopy;
					return false;
				}

				*(uiVals[uiIndex]) = (unsigned int)val;
			}

			bKnown     = true;
		}
		// --- Surface type
		else if(Cmd == GenericSurfCommands[eCmds_Surface])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing SURFACETYPE arguments in [TARGET] on line %d\n", m_psContext->pnFileLineNumber[i]);
				delete[] pBlockCopy;
				return false;
			}

			CPVRTHash hashType(pszRemaining);
			for(unsigned int uiIndex = 0; uiIndex < uiNumSurfTypes; ++uiIndex)
			{
				if(hashType == SurfacePairs[uiIndex].Name)
				{
					Params.uiFlags =  SurfacePairs[uiIndex].eType | SurfacePairs[uiIndex].BufferType;
					break;
				}
			}

			bKnown     = true;
		}

		// Valid Verbose command
		if(ppFilters)
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [%s] on line %d: %s\n", pCaller, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				delete[] pBlockCopy;
				return false;
			}

			unsigned int Type = INVALID_TYPE;
			for(unsigned int uiIndex = 0; uiIndex < 3; ++uiIndex)
			{
				if(strcmp(pszRemaining, ppFilters[uiIndex]) == 0)	
				{
					Type = uiIndex;			// Yup, it's valid.
					break;
				}
			}

			// Tell the user it's invalid.
			if(Type == INVALID_TYPE)
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [%s] on line %d: %s\n", pszRemaining, pCaller, m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
				delete[] pBlockCopy;
				return false;
			}

			if(Cmd == GenericSurfCommands[eCmds_Min])			Params.nMin = Type;		
			else if(Cmd == GenericSurfCommands[eCmds_Mag])		Params.nMag = Type;	
			else if(Cmd == GenericSurfCommands[eCmds_Mip])		Params.nMIP = Type;	
			else if(Cmd == GenericSurfCommands[eCmds_WrapR])	Params.nWrapR = Type;	
			else if(Cmd == GenericSurfCommands[eCmds_WrapS])	Params.nWrapS = Type;	
			else if(Cmd == GenericSurfCommands[eCmds_WrapT])	Params.nWrapT = Type;
		}

		if(bKnown)
		{
			KnownCmds.Append(Cmd);

			// Make sure nothing else exists on the line that hasn't been parsed.
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS);
			if(pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Unexpected keyword '%s' in [%s] on line %d: %s\n", pszRemaining, pCaller, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				delete[] pBlockCopy;
				return false;
			}
		}	

		delete [] pBlockCopy;
	}

	return true;
}

/*!***************************************************************************
@Function			ParseTexture
@Input				nStartLine		start line number
@Input				nEndLine		end line number
@Output				pReturnError	error string
@Return				bool			true if parse is successful
@Description		Parses the TEXTURE section of the PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseTexture(int nStartLine, int nEndLine, CPVRTString * const pReturnError)
{
	enum eCmd
	{
		eCmds_Name,
		eCmds_Path,
		eCmds_View,
		eCmds_Camera,

		eCmds_Size
	};

	const CPVRTHash TextureCmds[] =
	{
		"NAME",				// eTextureCmds_Name
		"PATH",				// eTextureCmds_Path
		"VIEW",				// eTextureCmds_View
		"CAMERA",			// eTextureCmds_Camera
	};
	PVRTCOMPILEASSERT(TextureCmds, sizeof(TextureCmds) / sizeof(TextureCmds[0]) == eCmds_Size);

	SPVRTPFXParserTexture TexDesc;
	TexDesc.nMin = eFilter_Default;
	TexDesc.nMag = eFilter_Default;
	TexDesc.nMIP = eFilter_MipDefault;
	TexDesc.nWrapS = eWrap_Default;
	TexDesc.nWrapT = eWrap_Default;
	TexDesc.nWrapR = eWrap_Default;
	TexDesc.uiWidth  = VIEWPORT_SIZE;
	TexDesc.uiHeight = VIEWPORT_SIZE;
	TexDesc.uiFlags  = OGL_RGBA_8888 | PVRPFXTEX_COLOUR;

	CPVRTArray<CPVRTHash> KnownCmds;
	if(!ParseGenericSurface(nStartLine, nEndLine, TexDesc, KnownCmds, "TEXTURE", pReturnError))
		return false;

	CPVRTString texName, filePath, viewName;
	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i], NEWLINE_TOKENS DELIM_TOKENS);
		if(!str)
		{
			*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [TEXTURE] on line %d: %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
			return false;
		}

		CPVRTHash texCmd(str);
		// --- Texture Name
		if(texCmd == TextureCmds[eCmds_Name])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing NAME arguments in [TEXTURE] on line %d: %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				return false;
			}

			texName = pszRemaining;
		}
		// --- Texture Path
		else if(texCmd == TextureCmds[eCmds_Path])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing PATH arguments in [TEXTURE] on line %d: %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				return false;
			}

			if(!ReadStringToken(pszRemaining, filePath, *pReturnError, i, "TEXTURE"))
			{
				return false;
			}
		}
		// --- View/Camera Name
		else if(texCmd == TextureCmds[eCmds_View] || texCmd == TextureCmds[eCmds_Camera])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS);		// String component. Get the rest of the line.
			if(!pszRemaining || strlen(pszRemaining) == 0)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing VIEW argument in [TEXTURE] on line %d: %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
				return false;
			}

			if(!ReadStringToken(pszRemaining, viewName, *pReturnError, i, "TEXTURE"))
			{
				return false;
			}
		}
		else if(KnownCmds.Contains(texCmd))
		{
			// Remove from 'unknown' list.
			for(unsigned int uiIndex = 0; uiIndex < KnownCmds.GetSize(); ++uiIndex)
			{
				if(KnownCmds[uiIndex] == texCmd)
				{
					KnownCmds.Remove(uiIndex);
					break;
				}
			}

			continue;		// This line has already been processed.
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TEXTURE] on line %d: %s\n", str, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
			return false;
		}

		char* pszRemaining = strtok(NULL, NEWLINE_TOKENS);
		if(pszRemaining)
		{
			*pReturnError = PVRTStringFromFormattedStr("Unexpected keyword '%s' in [TEXTURE] on line %d: %s\n", pszRemaining, m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
			return false;
		}
	}

	if(texName.empty())
	{
		*pReturnError = PVRTStringFromFormattedStr("No NAME tag specified in [TEXTURE] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}
	if(!filePath.empty() && !viewName.empty())
	{
		*pReturnError = PVRTStringFromFormattedStr("Both PATH and VIEW tags specified in [TEXTURE] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}
	if(filePath.empty() && viewName.empty())
	{
		*pReturnError = PVRTStringFromFormattedStr("No PATH or VIEW tag specified in [TEXTURE] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}

	bool bRTT = (viewName.empty() ? false : true);
	if(bRTT)
	{
		filePath = texName;									// RTT doesn't have a physical file.
	}

	// Create a new texture and copy over the vals.
	SPVRTPFXParserTexture* pTex = new SPVRTPFXParserTexture();
	pTex->Name				= CPVRTStringHash(texName);
	pTex->FileName			= CPVRTStringHash(filePath);
	pTex->bRenderToTexture	= bRTT;
	pTex->nMin				= TexDesc.nMin;
	pTex->nMag				= TexDesc.nMag;
	pTex->nMIP				= TexDesc.nMIP;
	pTex->nWrapS			= TexDesc.nWrapS;
	pTex->nWrapT			= TexDesc.nWrapT;
	pTex->nWrapR			= TexDesc.nWrapR;
	pTex->uiWidth			= TexDesc.uiWidth;
	pTex->uiHeight			= TexDesc.uiHeight;
	pTex->uiFlags			= TexDesc.uiFlags;
	m_psTexture.Append(pTex);

	if(bRTT)
	{
		unsigned int uiPassIdx = m_RenderPasses.Append();
		m_RenderPasses[uiPassIdx].SemanticName = texName;

		if(viewName == c_pszCurrentView)
		{
			m_RenderPasses[uiPassIdx].eViewType	 = eVIEW_CURRENT;
		}
		else
		{
			m_RenderPasses[uiPassIdx].eViewType	 = eVIEW_POD_CAMERA;
			m_RenderPasses[uiPassIdx].NodeName	 = viewName;
		}

		m_RenderPasses[uiPassIdx].eRenderPassType = eCAMERA_PASS;			// Textures are always 'camera' passes

		// Set render pass texture to the newly created texture.
		m_RenderPasses[uiPassIdx].pTexture		 = pTex;
		m_RenderPasses[uiPassIdx].uiFormatFlags  = TexDesc.uiFlags;
	}
	
	return true;
}

/*!***************************************************************************
@Function			ParseTarget
@Input				nStartLine		start line number
@Input				nEndLine		end line number
@Output				pReturnError	error string
@Return				bool			true if parse is successful
@Description		Parses the TARGET section of the PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseTarget(int nStartLine, int nEndLine, CPVRTString * const pReturnError)
{
	enum eCmd
	{
		eCmds_Name,

		eCmds_Size
	};

	const CPVRTHash TargetCommands[] =
	{
		"NAME",				// eCmds_Name
	};
	PVRTCOMPILEASSERT(TargetCommands, sizeof(TargetCommands) / sizeof(TargetCommands[0]) == eCmds_Size);
	
	CPVRTString targetName;
	SPVRTPFXParserTexture TexDesc;
	TexDesc.nMin = eFilter_Default;
	TexDesc.nMag = eFilter_Default;
	TexDesc.nMIP = eFilter_MipDefault;
	TexDesc.nWrapS = eWrap_Default;
	TexDesc.nWrapT = eWrap_Default;
	TexDesc.nWrapR = eWrap_Default;
	TexDesc.uiWidth  = VIEWPORT_SIZE;
	TexDesc.uiHeight = VIEWPORT_SIZE;
	TexDesc.uiFlags  = OGL_RGBA_8888 | PVRPFXTEX_COLOUR;

	CPVRTArray<CPVRTHash> KnownCmds;
	if(!ParseGenericSurface(nStartLine, nEndLine, TexDesc, KnownCmds, "TARGET", pReturnError))
		return false;
	
	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i], NEWLINE_TOKENS DELIM_TOKENS);
		if(!str)
		{
			*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [TARGET] on line %d\n", m_psContext->pnFileLineNumber[i]);
			return false;
		}

		CPVRTHash texCmd(str);
		// --- Target Name
		if(texCmd == TargetCommands[eCmds_Name])
		{
			char* pszRemaining = strtok(NULL, NEWLINE_TOKENS DELIM_TOKENS);
			if(!pszRemaining)
			{
				*pReturnError = PVRTStringFromFormattedStr("Missing NAME arguments in [TARGET] on line %d\n", m_psContext->pnFileLineNumber[i]);
				return false;
			}

			targetName = pszRemaining;
		}
		else if(KnownCmds.Contains(texCmd))
		{
			// Remove from 'unknown' list.
			for(unsigned int uiIndex = 0; uiIndex < KnownCmds.GetSize(); ++uiIndex)
			{
				if(KnownCmds[uiIndex] == texCmd)
				{
					KnownCmds.Remove(uiIndex);
					break;
				}
			}

			continue;		// This line has already been processed.
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TARGET] on line %d\n", str, m_psContext->pnFileLineNumber[i]);
			return false;
		}

		char* pszRemaining = strtok(NULL, NEWLINE_TOKENS);
		if(pszRemaining)
		{
			*pReturnError = PVRTStringFromFormattedStr("Unexpected keyword '%s' in [TARGET] on line %d\n", pszRemaining, m_psContext->pnFileLineNumber[i]);
			return false;
		}
	}

	// Create a new texture and copy over the vals.
	SPVRTPFXParserTexture* pTex = new SPVRTPFXParserTexture();
	pTex->Name				= CPVRTStringHash(targetName);
	pTex->FileName			= CPVRTStringHash(targetName);
	pTex->bRenderToTexture	= true;
	pTex->nMin				= TexDesc.nMin;
	pTex->nMag				= TexDesc.nMag;
	pTex->nMIP				= TexDesc.nMIP;
	pTex->nWrapS			= TexDesc.nWrapS;
	pTex->nWrapT			= TexDesc.nWrapT;
	pTex->nWrapR			= TexDesc.nWrapR;
	pTex->uiWidth			= TexDesc.uiWidth;
	pTex->uiHeight			= TexDesc.uiHeight;
	pTex->uiFlags			= TexDesc.uiFlags;
	m_psTexture.Append(pTex);

	// Copy to render pass struct
	unsigned int uiPassIdx = m_RenderPasses.Append();
	m_RenderPasses[uiPassIdx].SemanticName		= targetName;
	m_RenderPasses[uiPassIdx].eViewType			= eVIEW_NONE;
	m_RenderPasses[uiPassIdx].eRenderPassType	= ePOSTPROCESS_PASS;			// Targets are always post-process passes.
	m_RenderPasses[uiPassIdx].pTexture			= pTex;
	m_RenderPasses[uiPassIdx].uiFormatFlags		= TexDesc.uiFlags;

	return true;
}

/*!***************************************************************************
 @Function			ParseTextures		** DEPRECATED **
 @Input				nStartLine		start line number
 @Input				nEndLine		end line number
 @Output			pReturnError	error string
 @Return			bool			true if parse is successful
 @Description		Parses the TEXTURE section of the PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseTextures(int nStartLine, int nEndLine, CPVRTString * const pReturnError)
{
	char *pszName(NULL), *pszFile(NULL), *pszKeyword(NULL);
	char *pszRemaining(NULL), *pszTemp(NULL);
	bool bReturnVal(false);

	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i]," ");
		if(str != NULL)
		{
			// Set defaults
			unsigned int	uiMin(eFilter_Default), uiMag(eFilter_Default), uiMip(eFilter_MipDefault);
			unsigned int	uiWrapS(eWrap_Default), uiWrapT(eWrap_Default), uiWrapR(eWrap_Default);
			unsigned int	uiFlags = 0;

			unsigned int uiWidth	= CPVRTPFXParser::VIEWPORT_SIZE;
			unsigned int uiHeight	= CPVRTPFXParser::VIEWPORT_SIZE;

			// Reset variables
			FREE(pszName)		pszName = NULL;
			FREE(pszFile)		pszFile = NULL;
			FREE(pszKeyword)	pszKeyword = NULL;
			FREE(pszTemp)		pszTemp = NULL;
			pszRemaining		= NULL;

			// Compare against all valid keywords
			if((strcmp(str, "FILE") != 0) && (strcmp(str, "RENDER") != 0))
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TEXTURES] on line %d\n", str, m_psContext->pnFileLineNumber[i]);
				goto fail_release_return;
			}

#if 1
			if((strcmp(str, "RENDER") == 0))
			{
				*pReturnError = PVRTStringFromFormattedStr("RENDER tag no longer supported in [TEXTURES] block. Use new [TARGET] block instead\n");
				goto fail_release_return;
			}
#endif

			pszKeyword = (char *)malloc( ((int)strlen(str)+1) * sizeof(char));
			strcpy(pszKeyword, str);

			str = strtok (NULL, " ");
			if(str != NULL)
			{
				pszName = (char *)malloc( ((int)strlen(str)+1) * sizeof(char));
				strcpy(pszName, str);
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Texture name missing in [TEXTURES] on line %d: %s\n", m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
				goto fail_release_return;
			}

			/*
				The pszRemaining string is used to look for remaining flags.
				This has the advantage of allowing flags to be order independent
				and makes it easier to ommit some flags, but still pick up others
				(the previous method made it diffifult to retrieve filtering info
				if flags before it were missing)
			*/
			pszRemaining  = strtok(NULL, "\n");

			if(pszRemaining == NULL)
			{
				*pReturnError = PVRTStringFromFormattedStr("Incomplete definition in [TEXTURES] on line %d: %s\n", m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
				goto fail_release_return;
			}
			else if(strcmp(pszKeyword, "FILE") == 0)
			{
				pszTemp = (char *)malloc( ((int)strlen(pszRemaining)+1) * sizeof(char));
				strcpy(pszTemp, pszRemaining);
				str = strtok (pszTemp, " ");

				if(str != NULL)
				{
					pszFile = (char *)malloc( ((int)strlen(str)+1) * sizeof(char));
					strcpy(pszFile, str);
				}
				else
				{
					*pReturnError = PVRTStringFromFormattedStr("Texture name missing in [TEXTURES] on line %d: %s\n", m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
					goto fail_release_return;
				}
			}

			if(strcmp(pszKeyword, "FILE") == 0)
			{
				// --- Filter flags
				{
					unsigned int* pFlags[3] =
					{
						&uiMin,
						&uiMag,
						&uiMip,
					};

					if(!ParseTextureFlags(pszRemaining, pFlags, 3, c_ppszFilters, eFilter_Size, pReturnError, i))
						goto fail_release_return;
				}

				// --- Wrap flags
				{
					unsigned int* pFlags[3] =
					{
						&uiWrapS,
						&uiWrapT,
						&uiWrapR,
					};

					if(!ParseTextureFlags(pszRemaining, pFlags, 3, c_ppszWraps, eWrap_Size, pReturnError, i))
						goto fail_release_return;
				}
	
				SPVRTPFXParserTexture* pTex = new SPVRTPFXParserTexture();
				pTex->Name				= CPVRTStringHash(pszName);
				pTex->FileName			= CPVRTStringHash(pszFile);
				pTex->bRenderToTexture	= false;
				pTex->nMin				= uiMin;
				pTex->nMag				= uiMag;
				pTex->nMIP				= uiMip;
				pTex->nWrapS			= uiWrapS;
				pTex->nWrapT			= uiWrapT;
				pTex->nWrapR			= uiWrapR;
				pTex->uiWidth			= uiWidth;
				pTex->uiHeight			= uiHeight;
				pTex->uiFlags			= uiFlags;
				m_psTexture.Append(pTex);
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TEXTURES] on line %d\n", str, m_psContext->pnFileLineNumber[i]);;
				goto fail_release_return;
			}
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [TEXTURES] on line %d: %s\n", m_psContext->pnFileLineNumber[i],  m_psContext->ppszEffectFile[i]);
			goto fail_release_return;
		}
	}

	/*
		Should only reach here if there have been no issues
	*/
	bReturnVal = true;
	goto release_return;

fail_release_return:
	bReturnVal = false;
release_return:
	FREE(pszKeyword);
	FREE(pszName);
	FREE(pszFile);
	FREE(pszTemp);
	return bReturnVal;
}

/*!***************************************************************************
@Function		ParseTextureFlags
@Input			c_pszCursor
@Output			pFlagsOut
@Input			uiNumFlags
@Input			ppszFlagNames
@Input			uiNumFlagNames
@Input			pReturnError
@Input			iLineNum
@Return			bool	
@Description	Parses the texture flag sections.
*****************************************************************************/
bool CPVRTPFXParser::ParseTextureFlags(	const char* c_pszRemainingLine, unsigned int** ppFlagsOut, unsigned int uiNumFlags, const char** c_ppszFlagNames, unsigned int uiNumFlagNames, 
										CPVRTString * const pReturnError, int iLineNum)
{
	const unsigned int INVALID_TYPE = 0xAC1DBEEF;
	unsigned int uiIndex;
	const char* c_pszCursor;
	const char* c_pszResult;

	// --- Find the first flag
	uiIndex = 0;
	c_pszCursor = strstr(c_pszRemainingLine, c_ppszFlagNames[uiIndex++]);
	while(uiIndex < uiNumFlagNames)
	{
		c_pszResult = strstr(c_pszRemainingLine, c_ppszFlagNames[uiIndex++]);
		if(((c_pszResult < c_pszCursor) || !c_pszCursor) && c_pszResult)
			c_pszCursor = c_pszResult;
	}

	if(!c_pszCursor)
		return true;		// No error, but just return as no flags specified.

	// Quick error check - make sure that the first flag found is valid.
	if(c_pszCursor != c_pszRemainingLine)
	{
		if(*(c_pszCursor-1) == '-')		// Yeah this shouldn't be there. Must be invalid first tag.
		{
			char szBuffer[128];		// Find out the tag.
			memset(szBuffer, 0, sizeof(szBuffer));
			const char* pszStart = c_pszCursor-1;
			while(pszStart != c_pszRemainingLine && *pszStart != ' ')		pszStart--;
			pszStart++;	// Escape the space.
			unsigned int uiNumChars = (unsigned int) ((c_pszCursor-1) - pszStart);
			strncpy(szBuffer, pszStart, uiNumChars);

			*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TEXTURES] on line %d: %s\n", szBuffer, m_psContext->pnFileLineNumber[iLineNum], m_psContext->ppszEffectFile[iLineNum]);
			return false;
		}
	}

	unsigned int uiFlagsFound = 0;
	unsigned int uiBufferIdx;
	char szBuffer[128];		// Buffer to hold the token

	while(*c_pszCursor != ' ' && *c_pszCursor != 0 && uiFlagsFound < uiNumFlags)
	{
		memset(szBuffer, 0, sizeof(szBuffer));		// Clear the buffer
		uiBufferIdx = 0;

		while(*c_pszCursor != '-' && *c_pszCursor != 0 && *c_pszCursor != ' ' && uiBufferIdx < 128)		// - = delim. token
			szBuffer[uiBufferIdx++] = *c_pszCursor++;

		// Check if the buffer content is a valid flag name.
		unsigned int Type = INVALID_TYPE;
		for(unsigned int uiIndex = 0; uiIndex < uiNumFlagNames; ++uiIndex)
		{
			if(strcmp(szBuffer, c_ppszFlagNames[uiIndex]) == 0)	
			{
				Type = uiIndex;			// Yup, it's valid. uiIndex here would translate to one of the enums that matches the string array of flag names passed in.
				break;
			}
		}

		// Tell the user it's invalid.
		if(Type == INVALID_TYPE)
		{
			*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [TEXTURES] on line %d: %s\n", szBuffer, m_psContext->pnFileLineNumber[iLineNum], m_psContext->ppszEffectFile[iLineNum]);
			return false;
		}

		// Set the flag to the enum type.
		*ppFlagsOut[uiFlagsFound++] = Type;

		if(*c_pszCursor == '-')	c_pszCursor++;
	}

	return true;
}

/*!***************************************************************************
 @Function			ParseShader
 @Input				nStartLine		start line number
 @Input				nEndLine		end line number
 @Output			pReturnError	error string
 @Output			shader			shader data object
 @Input				pszBlockName	name of block in PFX file
 @Return			bool			true if parse is successful
 @Description		Parses the VERTEXSHADER or FRAGMENTSHADER section of the
					PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseShader(int nStartLine, int nEndLine, CPVRTString * const pReturnError, SPVRTPFXParserShader &shader, const char * const pszBlockName)
{
	enum eCmd
	{
		eCmds_GLSLCode,
		eCmds_Name,
		eCmds_File,
		eCmds_BinaryFile,

		eCmds_Size
	};

	const CPVRTHash ShaderCommands[] = 
	{
		"[GLSL_CODE]",
		"NAME",
		"FILE",
		"BINARYFILE",
	};
	PVRTCOMPILEASSERT(ShaderCommands, sizeof(ShaderCommands) / sizeof(ShaderCommands[0]) == eCmds_Size);

	bool glslcode=0, glslfile=0, bName=0;

	shader.bUseFileName		= false;
	shader.pszGLSLfile		= NULL;
	shader.pszGLSLcode		= NULL;
	shader.pszGLSLBinaryFile= NULL;
	shader.pbGLSLBinary		= NULL;
	shader.nFirstLineNumber	= 0;
	shader.nLastLineNumber  = 0;

	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i]," ");
		if(str != NULL)
		{
			CPVRTHash Cmd(str);

			// Check for [GLSL_CODE] tags first and remove those lines from loop.
			if(Cmd == ShaderCommands[eCmds_GLSLCode])
			{
				if(glslcode)
				{
					*pReturnError = PVRTStringFromFormattedStr("[GLSL_CODE] redefined in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}
				if(glslfile && shader.pbGLSLBinary==NULL )
				{
					*pReturnError = PVRTStringFromFormattedStr("[GLSL_CODE] not allowed with FILE in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				shader.nFirstLineNumber = m_psContext->pnFileLineNumber[i];

				// Skip the block-start
				i++;

				CPVRTString GLSLCode;
				if(!ConcatenateLinesUntil(
					GLSLCode,
					i,
					m_psContext->ppszEffectFile,
					m_psContext->nNumLines,
					"[/GLSL_CODE]"))
				{
					return false;
				}
				
				shader.nLastLineNumber = m_psContext->pnFileLineNumber[i];

				shader.pszGLSLcode = (char*)malloc((GLSLCode.size()+1) * sizeof(char));
				strcpy(shader.pszGLSLcode, GLSLCode.c_str());

				shader.bUseFileName = false;
				glslcode = 1;
			}
			else if(Cmd == ShaderCommands[eCmds_Name])
			{
				if(bName)
				{
					*pReturnError = PVRTStringFromFormattedStr("NAME redefined in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				str = ReadEOLToken(NULL);

				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("NAME missing value in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				shader.Name.assign(str);
				bName = true;
			}
			else if(Cmd == ShaderCommands[eCmds_File])
			{
				if(glslfile)
				{
					*pReturnError = PVRTStringFromFormattedStr("FILE redefined in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}
				if(glslcode)
				{
					*pReturnError = PVRTStringFromFormattedStr("FILE not allowed with [GLSL_CODE] in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				str = ReadEOLToken(NULL);

				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("FILE missing value in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				shader.pszGLSLfile = (char*)malloc((strlen(str)+1) * sizeof(char));
				strcpy(shader.pszGLSLfile, str);

				CPVRTResourceFile GLSLFile(str);

				if(!GLSLFile.IsOpen())
				{
					*pReturnError = PVRTStringFromFormattedStr("Error loading file '%s' in [%s] on line %d\n", str, pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}
				shader.pszGLSLcode = (char*)malloc((GLSLFile.Size()+1) * sizeof(char));
				memcpy(shader.pszGLSLcode, (const char*) GLSLFile.DataPtr(), GLSLFile.Size());
				shader.pszGLSLcode[GLSLFile.Size()] = '\0';

				shader.nFirstLineNumber = m_psContext->pnFileLineNumber[i];		// Mark position where GLSL file is defined.

				shader.bUseFileName = true;
				glslfile = 1;
			}
			else if(Cmd == ShaderCommands[eCmds_BinaryFile])
			{
				str = ReadEOLToken(NULL);

				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("BINARYFILE missing value in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}

				shader.pszGLSLBinaryFile = (char*)malloc((strlen(str)+1) * sizeof(char));
				strcpy(shader.pszGLSLBinaryFile, str);

				CPVRTResourceFile GLSLFile(str);

				if(!GLSLFile.IsOpen())
				{
					*pReturnError = PVRTStringFromFormattedStr("Error loading file '%s' in [%s] on line %d\n", str, pszBlockName, m_psContext->pnFileLineNumber[i]);
					return false;
				}
				shader.pbGLSLBinary = new char[GLSLFile.Size()];
				shader.nGLSLBinarySize = (unsigned int)GLSLFile.Size();
				memcpy(shader.pbGLSLBinary, GLSLFile.DataPtr(), GLSLFile.Size());

				shader.bUseFileName = true;
				glslfile = 1;
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [%s] on line %d\n", str, pszBlockName, m_psContext->pnFileLineNumber[i]);
				return false;
			}

			str = strtok (NULL, " ");
			if(str != NULL)
			{
				*pReturnError = PVRTStringFromFormattedStr("Unexpected data in [%s] on line %d: '%s'\n", pszBlockName, m_psContext->pnFileLineNumber[i], str);
				return false;
			}
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr("Missing arguments in [%s] on line %d: %s\n", pszBlockName, m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
			return false;
		}
	}

	if(!bName)
	{
		*pReturnError = PVRTStringFromFormattedStr("NAME not found in [%s] on line %d.\n", pszBlockName, m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}

	if(!glslfile && !glslcode)
	{
		*pReturnError = PVRTStringFromFormattedStr("No Shader File or Shader Code specified in [%s] on line %d\n", pszBlockName, m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}

	return true;
}

/*!***************************************************************************
 @Function			ParseSemantic
 @Output			semantic		semantic data object
 @Input				nStartLine		start line number
 @Output			pReturnError	error string
 @Return			bool			true if parse is successful
 @Description		Parses a semantic.
*****************************************************************************/
bool CPVRTPFXParser::ParseSemantic(SPVRTPFXParserSemantic &semantic, const int nStartLine, CPVRTString * const pReturnError)
{
	char *str;

	semantic.pszName = 0;
	semantic.pszValue = 0;
	semantic.sDefaultValue.eType = eDataTypeNone;
	semantic.nIdx = 0;

	str = strtok (NULL, " ");
	if(str == NULL)
	{
		*pReturnError = PVRTStringFromFormattedStr("UNIFORM missing name in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}
	semantic.pszName = (char*)malloc((strlen(str)+1) * sizeof(char));
	strcpy(semantic.pszName, str);

	str = strtok (NULL, " ");
	if(str == NULL)
	{
		*pReturnError = PVRTStringFromFormattedStr("UNIFORM missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);

		FREE(semantic.pszName);
		return false;
	}

	/*
		If the final digits of the semantic are a number they are
		stripped off and used as the index, with the remainder
		used as the semantic.
	*/
	{
		size_t idx, len;
		len = strlen(str);

		idx = len;
		while(idx)
		{
			--idx;
			if(strcspn(&str[idx], "0123456789") != 0)
			{
				break;
			}
		}
		if(idx == 0)
		{
			*pReturnError = PVRTStringFromFormattedStr("Semantic contains only numbers in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);

			FREE(semantic.pszName);
			return false;
		}

		++idx;
		// Store the semantic index
		if(len == idx)
		{
			semantic.nIdx = 0;
		}
		else
		{
			semantic.nIdx = atoi(&str[idx]);
		}

		// Chop off the index from the string containing the semantic
		str[idx] = 0;
	}

	// Store a copy of the semantic name
	semantic.pszValue = (char*)malloc((strlen(str)+1) * sizeof(char));
	strcpy(semantic.pszValue, str);

	/*
		Optional default semantic value
	*/
	char pszString[2048];
	strcpy(pszString,"");
	str = strtok (NULL, " ");
	if(str != NULL)
	{
		// Get all ramainning arguments
		while(str != NULL)
		{
			strcat(pszString, str);
			strcat(pszString, " ");
			str = strtok (NULL, " ");
		}

		// default value
		int i;
		for(i = 0; i < eNumDefaultDataTypes; i++)
		{
			if(strncmp(pszString, c_psSemanticDefaultDataTypeInfo[i].pszName, strlen(c_psSemanticDefaultDataTypeInfo[i].pszName)) == 0)
			{
				if(!GetSemanticDataFromString(	&semantic.sDefaultValue,
												&pszString[strlen(c_psSemanticDefaultDataTypeInfo[i].pszName)],
												c_psSemanticDefaultDataTypeInfo[i].eType,
												pReturnError
												))
				{
					*pReturnError = PVRTStringFromFormattedStr(" on line %d.\n", m_psContext->pnFileLineNumber[nStartLine]);

					FREE(semantic.pszValue);
					FREE(semantic.pszName);
					return false;
				}

				semantic.sDefaultValue.eType = c_psSemanticDefaultDataTypeInfo[i].eType;
				break;
			}
		}

		// invalid data type
		if(i == eNumDefaultDataTypes)
		{
			*pReturnError = PVRTStringFromFormattedStr("'%s' unknown on line %d.\n", pszString, m_psContext->pnFileLineNumber[nStartLine]);

			FREE(semantic.pszValue);
			FREE(semantic.pszName);
			return false;
		}

	}

	return true;
}

/*!***************************************************************************
 @Function			ParseEffect
 @Output			effect			effect data object
 @Input				nStartLine		start line number
 @Input				nEndLine		end line number
 @Output			pReturnError	error string
 @Return			bool			true if parse is successful
 @Description		Parses the EFFECT section of the PFX file.
*****************************************************************************/
bool CPVRTPFXParser::ParseEffect(SPVRTPFXParserEffect &effect, const int nStartLine, const int nEndLine, CPVRTString * const pReturnError)
{
	enum eCmds
	{
		eCmds_Annotation,
		eCmds_VertexShader,
		eCmds_FragmentShader,
		eCmds_Texture,
		eCmds_Uniform,
		eCmds_Attribute,
		eCmds_Name,
		eCmds_Target,

		eCmds_Size
	};

	const CPVRTHash EffectCommands[] = 
	{
		"[ANNOTATION]",
		"VERTEXSHADER",
		"FRAGMENTSHADER",
		"TEXTURE",
		"UNIFORM",
		"ATTRIBUTE",
		"NAME",
		"TARGET",
	};
	PVRTCOMPILEASSERT(EffectCommands, sizeof(EffectCommands) / sizeof(EffectCommands[0]) == eCmds_Size);

	bool bName = false;
	bool bVertShader = false;
	bool bFragShader = false;

	for(int i = nStartLine+1; i < nEndLine; i++)
	{
		// Skip blank lines
		if(!*m_psContext->ppszEffectFile[i])
			continue;

		char *str = strtok (m_psContext->ppszEffectFile[i]," ");
		if(str != NULL)
		{
			CPVRTHash Cmd(str);

			if(Cmd == EffectCommands[eCmds_Annotation])
			{
				if(!effect.Annotation.empty())
				{
					*pReturnError = PVRTStringFromFormattedStr("ANNOTATION redefined in [EFFECT] on line %d: \n", m_psContext->pnFileLineNumber[i]);
					return false;
				}

				i++;		// Skip the block-start
				if(!ConcatenateLinesUntil(
					effect.Annotation,
					i,
					m_psContext->ppszEffectFile,
					m_psContext->nNumLines,
					"[/ANNOTATION]"))
				{
					return false;
				}
			}
			else if(Cmd == EffectCommands[eCmds_VertexShader])
			{
				if(bVertShader)
				{
					*pReturnError = PVRTStringFromFormattedStr("VERTEXSHADER redefined in [EFFECT] on line %d: \n", m_psContext->pnFileLineNumber[i]);
					return false;
				}

				str = ReadEOLToken(NULL);

				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("VERTEXSHADER missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[i]);
					return false;
				}
				effect.VertexShaderName.assign(str);

				bVertShader = true;
			}
			else if(Cmd == EffectCommands[eCmds_FragmentShader])
			{
				if(bFragShader)
				{
					*pReturnError = PVRTStringFromFormattedStr("FRAGMENTSHADER redefined in [EFFECT] on line %d: \n", m_psContext->pnFileLineNumber[i]);
					return false;
				}

				str = ReadEOLToken(NULL);

				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("FRAGMENTSHADER missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[i]);
					return false;
				}
				effect.FragmentShaderName.assign(str);

				bFragShader = true;
			}
			else if(Cmd == EffectCommands[eCmds_Texture])
			{
				unsigned int uiTexIdx = effect.Textures.Append();
				// texture number
				str = strtok(NULL, " ");
				if(str != NULL)
					effect.Textures[uiTexIdx].nNumber = atoi(str);
				else
				{
					*pReturnError = PVRTStringFromFormattedStr("TEXTURE missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[i]);
					return false;
				}

				// texture name
				str = strtok(NULL, " ");
				if(str != NULL)
				{
					effect.Textures[uiTexIdx].Name = CPVRTStringHash(str);
				}
				else
				{
					*pReturnError = PVRTStringFromFormattedStr("TEXTURE missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[i]);
					return false;
				}
			}
			else if(Cmd == EffectCommands[eCmds_Uniform])
			{
				unsigned int uiUniformIdx = effect.Uniforms.Append();
				if(!ParseSemantic(effect.Uniforms[uiUniformIdx], i, pReturnError))
					return false;

			}
			else if(Cmd == EffectCommands[eCmds_Attribute])
			{
				unsigned int uiAttribIdx = effect.Attributes.Append();
				if(!ParseSemantic(effect.Attributes[uiAttribIdx], i, pReturnError))
					return false;
			}
			else if(Cmd == EffectCommands[eCmds_Name])
			{
				if(bName)
				{
					*pReturnError = PVRTStringFromFormattedStr("NAME redefined in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
					return false;
				}

				str = strtok (NULL, " ");
				if(str == NULL)
				{
					*pReturnError = PVRTStringFromFormattedStr("NAME missing value in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
					return false;
				}

				effect.Name.assign(str);
				bName = true;
			}
			else if(Cmd == EffectCommands[eCmds_Target])
			{
				unsigned int uiIndex = effect.Targets.Append();

				// Target requires 2 components
				CPVRTString* pVals[] = { &effect.Targets[uiIndex].BufferType, &effect.Targets[uiIndex].TargetName };

				for(unsigned int uiVal = 0; uiVal < 2; ++uiVal)
				{
					str = strtok (NULL, " ");
					if(str == NULL)
					{
						*pReturnError = PVRTStringFromFormattedStr("TARGET missing value(s) in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
						return false;
					}
					
					*(pVals[uiVal]) = str;
				}
			}
			else
			{
				*pReturnError = PVRTStringFromFormattedStr("Unknown keyword '%s' in [EFFECT] on line %d\n", str, m_psContext->pnFileLineNumber[i]);
				return false;
			}
		}
		else
		{
			*pReturnError = PVRTStringFromFormattedStr( "Missing arguments in [EFFECT] on line %d: %s\n", m_psContext->pnFileLineNumber[i], m_psContext->ppszEffectFile[i]);
			return false;
		}
	}

	// Check that every TEXTURE has a matching UNIFORM
	for(unsigned int uiTex = 0; uiTex < effect.Textures.GetSize(); ++uiTex)
	{
		unsigned int uiTexUnit			= effect.Textures[uiTex].nNumber;
		const CPVRTStringHash& texName  = effect.Textures[uiTex].Name;
		// Find UNIFORM associated with the TexUnit (e.g TEXTURE0).
		bool bFound = false;
		for(unsigned int uiUniform = 0; uiUniform < effect.Uniforms.GetSize(); ++uiUniform)
		{
			const SPVRTPFXParserSemantic& Sem = effect.Uniforms[uiUniform];
			if(strcmp(Sem.pszValue, "TEXTURE") == 0 && Sem.nIdx == uiTexUnit)
			{
				bFound = true;
				break;
			}
		}

		if(!bFound)
		{
			*pReturnError = PVRTStringFromFormattedStr("TEXTURE %s missing matching UNIFORM in [EFFECT] on line %d\n", texName.c_str(), m_psContext->pnFileLineNumber[nStartLine]);
			return false;
		}
	}


	if(!bName)
	{
		*pReturnError = PVRTStringFromFormattedStr("No 'NAME' found in [EFFECT] on line %d\n", m_psContext->pnFileLineNumber[nStartLine]);
		return false;
	}
	if(!bVertShader)
	{
		*pReturnError = PVRTStringFromFormattedStr("No 'VERTEXSHADER' defined in [EFFECT] starting on line %d: \n", m_psContext->pnFileLineNumber[nStartLine-1]);
		return false;
	}
	if(!bFragShader)
	{
		*pReturnError = PVRTStringFromFormattedStr("No 'FRAGMENTSHADER' defined in [EFFECT] starting on line %d: \n", m_psContext->pnFileLineNumber[nStartLine-1]);
		return false;
	}

	return true;
}

/*!***************************************************************************
 @Function			DetermineRenderPassDependencies
 @Return			True if dependency tree is valid. False if there are errors
					in the dependency tree (e.g. recursion)
 @Description		Looks through all of the effects in the .pfx and determines
					the order of render passes that have been declared with
					the RENDER tag (found in [TEXTURES]
*****************************************************************************/
bool CPVRTPFXParser::DetermineRenderPassDependencies(CPVRTString * const pReturnError)
{
	unsigned int	ui(0), uj(0), uk(0);

	if(m_RenderPasses.GetSize() == 0)
		return true;

	// --- Add all render pass nodes to the skip graph.
	for(ui = 0; ui < m_RenderPasses.GetSize(); ++ui)
	{
		SPVRTPFXRenderPass& Pass = m_RenderPasses[ui];
		bool bFound = false;

		// Search all EFFECT blocks for matching TARGET. This is for post-processes behavior.
		for(unsigned int uiEffect = 0; uiEffect < m_psEffect.GetSize(); ++uiEffect)
		{
			SPVRTPFXParserEffect& Effect = m_psEffect[uiEffect];

			// Search all TARGETs in this effect
			for(unsigned int uiTargets = 0; uiTargets < Effect.Targets.GetSize(); ++uiTargets)
			{
				const SPVRTTargetPair& Target = Effect.Targets[uiTargets];
				if(Target.TargetName == Pass.SemanticName)
				{
					// Match. This EFFECT block matches the pass name.
					Pass.pEffect = &Effect;
					bFound = true;

					// This is now a post-process pass. Set relevant values.
					Pass.eRenderPassType = ePOSTPROCESS_PASS;
					m_aszPostProcessNames.Append(Pass.SemanticName);

					// Check that the surface type and output match are relevant (i.e DEPTH != RGBA8888).
					if( (Target.BufferType.find_first_of("DEPTH") != CPVRTString::npos && !(Pass.uiFormatFlags & PVRPFXTEX_DEPTH))
					||	(Target.BufferType.find_first_of("COLOR") != CPVRTString::npos && !(Pass.uiFormatFlags & PVRPFXTEX_COLOUR)) )
					{
						*pReturnError = PVRTStringFromFormattedStr("Surface type mismatch in [EFFECT]. \"%s\" has different type than \"%s\"\n", Target.TargetName.c_str(), Pass.SemanticName.c_str());
						return false;
					}
					
					break;
				}
			}

			if(bFound)
				break;
		}

		// Add a pointer to the post process
		m_renderPassSkipGraph.AddNode(&Pass);
	}


	// --- Loop through all created render passes in the skip graph and determine their dependencies
	for(ui = 0; ui < m_renderPassSkipGraph.GetNumNodes(); ++ui)
	{
		//	Loop through all other nodes in the skip graph 
		SPVRTPFXRenderPass* pPass			= m_renderPassSkipGraph[ui];
		SPVRTPFXRenderPass* pTestPass       = NULL;

		for(uj = 0; uj < m_RenderPasses.GetSize(); ++uj)
		{
			pTestPass = m_renderPassSkipGraph[uj];
				
			// No self compare
			if(pPass == pTestPass)
				continue;

			// No effect associated.
			if(!pPass->pEffect)			
				continue;

			// Is the node a render pass I rely on?
			for(uk = 0; uk < pPass->pEffect->Textures.GetSize(); ++uk)
			{
				/*
					If the texture names match, add a new node
				*/
				if(pTestPass->pTexture->Name == pPass->pEffect->Textures[uk].Name)
				{
					m_renderPassSkipGraph.AddNodeDependency(pPass, pTestPass);
					break;
				}
			}
		}
	}
	
	return true;
}

/*!***************************************************************************
@Function		FindTextureIndex
@Input			TextureName
@Return			unsigned int	Index in to the effect.Texture array.
@Description	Returns the index in to the texture array within the effect 
				block where the given texture resides.
*****************************************************************************/
unsigned int CPVRTPFXParser::FindTextureIndex( const CPVRTStringHash& TextureName, unsigned int uiEffect ) const
{
	for(unsigned int uiIndex = 0; uiIndex < m_psEffect[uiEffect].Textures.GetSize(); ++uiIndex)
	{
		const SPVRTPFXParserEffectTexture& Tex = m_psEffect[uiEffect].Textures[uiIndex];
		if(Tex.Name == TextureName)
		{
			return uiIndex;
		}
	}

	return 0xFFFFFFFF;
}

/*!***************************************************************************
@Function		GetNumberRenderPasses
@Return			unsigned int
@Description	Returns the number of render passes within this PFX.
*****************************************************************************/
unsigned int CPVRTPFXParser::GetNumberRenderPasses() const
{
	return m_RenderPasses.GetSize();
}

/*!***************************************************************************
@Function		GetNumberRenderPasses
@Input			unsigned int		The render pass index.
@Return			SPVRTPFXRenderPass*
@Description	Returns the given render pass.
*****************************************************************************/
const SPVRTPFXRenderPass& CPVRTPFXParser::GetRenderPass( unsigned int uiIndex ) const
{
	_ASSERT(uiIndex >= 0 && uiIndex < GetNumberRenderPasses());
	return m_RenderPasses[uiIndex];
}

/*!***************************************************************************
@Function		GetPFXFileName
@Return			const CPVRTString &	
@Description	Returns the PFX file name associated with this object.
*****************************************************************************/
const CPVRTString& CPVRTPFXParser::GetPFXFileName() const
{
	return m_szFileName;
}

/*!***************************************************************************
@Function		GetPostProcessNames
@Return			const CPVRTArray<CPVRTString>&	
@Description	Returns a list of prost process effect names.
*****************************************************************************/
const CPVRTArray<CPVRTString>& CPVRTPFXParser::GetPostProcessNames() const
{
	return m_aszPostProcessNames;
}

/*!***************************************************************************
@Function		GetNumberFragmentShaders
@Return			unsigned int	Number of fragment shaders.
@Description	Returns the number of fragment shaders referenced in the PFX.
*****************************************************************************/
unsigned int CPVRTPFXParser::GetNumberFragmentShaders() const
{
	return m_psFragmentShader.GetSize();
}


/*!***************************************************************************
@Function		GetFragmentShader
@Input			unsigned int		The index of this shader.
@Return			const SPVRTPFXParserShader&		The PFX fragment shader.
@Description	Returns a given fragment shader.
*****************************************************************************/
SPVRTPFXParserShader& CPVRTPFXParser::GetFragmentShader( unsigned int uiIndex )
{
	_ASSERT(uiIndex < GetNumberFragmentShaders());
	return m_psFragmentShader[uiIndex];
}

/*!***************************************************************************
@Function		GetNumberVertexShaders
@Return			unsigned int	Number of vertex shaders.
@Description	Returns the number of vertex shaders referenced in the PFX.
*****************************************************************************/
unsigned int CPVRTPFXParser::GetNumberVertexShaders() const
{
	return m_psVertexShader.GetSize();
}

/*!***************************************************************************
@Function		GetVertexShader
@Input			unsigned int		The index of this shader.
@Return			const SPVRTPFXParserShader&		The PFX vertex shader.
@Description	Returns a given vertex shader.
*****************************************************************************/
SPVRTPFXParserShader& CPVRTPFXParser::GetVertexShader( unsigned int uiIndex )
{
	_ASSERT(uiIndex < GetNumberVertexShaders());
	return m_psVertexShader[uiIndex];
}

/*!***************************************************************************
@Function		GetNumberEffects
@Return			unsigned int	Number of effects.
@Description	Returns the number of effects referenced in the PFX.
*****************************************************************************/
unsigned int CPVRTPFXParser::GetNumberEffects() const
{
	return m_psEffect.GetSize();
}

/*!***************************************************************************
@Function		GetEffect
@Input			uiIndex		The index of this effect.
@Return			The PFX effect.
@Description	Returns a given effect.
*****************************************************************************/
const SPVRTPFXParserEffect& CPVRTPFXParser::GetEffect(unsigned int uiIndex) const
{
	_ASSERT(uiIndex < GetNumberEffects());
	return m_psEffect[uiIndex];
}

/*!***************************************************************************
@Function		GetNumberTextures
@Return			unsigned int	Number of effects.
@Description	Returns the number of textures referenced in the PFX.
*****************************************************************************/
unsigned int CPVRTPFXParser::GetNumberTextures() const
{
	return m_psTexture.GetSize();
}

/*!***************************************************************************
@Function		GetTexture
@Input			unsigned int		The index of this texture
@Return			const SPVRTPFXParserEffect&		The PFX texture.
@Description	Returns a given texture.
*****************************************************************************/
const SPVRTPFXParserTexture* CPVRTPFXParser::GetTexture( unsigned int uiIndex ) const
{
	_ASSERT(uiIndex < GetNumberTextures());
	return m_psTexture[uiIndex];
}

/*!***************************************************************************
@Function		FindEffectByName
@Input			Name
@Return			int	
@Description	Returns the index of the given string. Returns -1 on failure.
*****************************************************************************/
int CPVRTPFXParser::FindEffectByName(const CPVRTStringHash& Name) const
{
	if(Name.Hash() == 0)
		return -1;

	for(unsigned int uiIndex = 0; uiIndex < GetNumberEffects(); ++uiIndex)
	{
		if(GetEffect(uiIndex).Name == Name)
		{
			return (int)uiIndex;
		}
	}
	
	return -1;
}

/*!***************************************************************************
@Function		FindTextureByName
@Input			Name		Name of the texture.
@Return			int	
@Description	Returns the index of the given texture. Returns -1 on failure.
*****************************************************************************/
int CPVRTPFXParser::FindTextureByName(const CPVRTStringHash& Name) const
{
	if(Name.Hash() == 0)
		return -1;

	for(unsigned int uiIndex = 0; uiIndex < GetNumberTextures(); ++uiIndex)
	{
		if(GetTexture(uiIndex)->Name == Name)
		{
			return (int)uiIndex;
		}
	}

	return -1;
}

/*!***************************************************************************
@Function		PVRTPFXCreateStringCopy
@Return			void
@Description	Safely copies a C string.
*****************************************************************************/
void PVRTPFXCreateStringCopy(char** ppDst, const char* pSrc)
{
	if(pSrc)
	{
		FREE(*ppDst);
		*ppDst = (char*)malloc((strlen(pSrc)+1) * sizeof(char));
		strcpy(*ppDst, pSrc);
	}
}

/*****************************************************************************
 End of file (PVRTPFXParser.cpp)
*****************************************************************************/