/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

#include "CD3DCG.h"
#include "wsnes9x.h"
#include "win32_display.h"
#include "snes9x.h"
#include "dxerr.h"
#include <png.h>
#include "CDirect3D.h"
#include "image_functions.h"

#ifndef max
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif

static float npot(float desired)
{
	float out=512.0;
	while(out<desired)
		out*=2;
	return out;
}

CD3DCG::CD3DCG(CGcontext cgContext,LPDIRECT3DDEVICE9 pDevice)
{
	this->cgContext = cgContext;
	this->pDevice = pDevice;
	ClearPasses();
	frameCnt=0;
}

CD3DCG::~CD3DCG(void)
{
	LoadShader(NULL);
}

void CD3DCG::ClearPasses()
{
	/* clean up cg programs, vertex buffers/declarations and textures
	   from all regular passes. pass 0 is the orignal texture, so ignore that
	*/
	if(shaderPasses.size()>1) {
		for(std::vector<shaderPass>::iterator it=(shaderPasses.begin()+1);it!=shaderPasses.end();it++) {
			if(it->cgFragmentProgram)
				cgDestroyProgram(it->cgFragmentProgram);
			if(it->cgVertexProgram)
				cgDestroyProgram(it->cgVertexProgram);
			if(it->tex)
				it->tex->Release();
			if(it->vertexBuffer)
				it->vertexBuffer->Release();
			if(it->vertexDeclaration)
				it->vertexDeclaration->Release();
		}
	}
	for(std::vector<lookupTexture>::iterator it=lookupTextures.begin();it!=lookupTextures.end();it++) {
		if(it->tex)
			it->tex->Release();
	}
	for(std::deque<prevPass>::iterator it=prevPasses.begin();it!=prevPasses.end();it++) {
		if(it->tex)
			it->tex->Release();
		if(it->vertexBuffer)
			it->vertexBuffer->Release();
	}
	prevPasses.clear();
	shaderPasses.clear();
	lookupTextures.clear();
	// prevPasses deque is always filled with PREV + PREV1-6 elements
	prevPasses.resize(7);
	shaderLoaded = false;
}

void CD3DCG::OnLostDevice()
{
	/* release all non-managed textures so the device can reset - 
	   will be recreated when required
	*/
	if(shaderPasses.size()>1) {
		for(std::vector<shaderPass>::iterator it=(shaderPasses.begin()+1);it!=shaderPasses.end();it++) {			
			if(it->tex) {
				it->tex->Release();
				it->tex = NULL;
			}
		}
	}
}

void CD3DCG::OnResetDevice()
{

}

void CD3DCG::checkForCgError(const char *situation)
{
	char buffer[4096];
	CGerror error = cgGetError();
	const char *string = cgGetErrorString(error);

	if (error != CG_NO_ERROR) {
		sprintf(buffer,
			  "Situation: %s\n"
			  "Error: %s\n\n"
			  "Cg compiler output...\n", situation, string);
		MessageBoxA(0, buffer,
				  "Cg error", MB_OK|MB_ICONEXCLAMATION);
		if (error == CG_COMPILER_ERROR) {
			MessageBoxA(0, cgGetLastListing(cgContext),
					  "Cg compilation error", MB_OK|MB_ICONEXCLAMATION);
		}
	}
}

bool CD3DCG::LoadShader(const TCHAR *shaderFile)
{
	CCGShader cgShader;
	TCHAR shaderPath[MAX_PATH];
	TCHAR tempPath[MAX_PATH];
	HRESULT hr;
	GLenum error;

	ClearPasses();

	if (shaderFile == NULL || *shaderFile==TEXT('\0'))
		return true;

	lstrcpy(shaderPath, shaderFile);
    ReduceToPath(shaderPath);

	SetCurrentDirectory(shaderPath);
	if(!cgShader.LoadShader(_tToChar(shaderFile)))
		return false;

	CGprofile vertexProfile = cgD3D9GetLatestVertexProfile();
	CGprofile pixelProfile = cgD3D9GetLatestPixelProfile();

	const char** vertexOptions = cgD3D9GetOptimalOptions(vertexProfile);
	const char** pixelOptions = cgD3D9GetOptimalOptions(pixelProfile);

	shaderPasses.push_back(shaderPass());

	for(CCGShader::passVector::iterator it=cgShader.shaderPasses.begin();
			it!=cgShader.shaderPasses.end();it++) {
		shaderPass pass;

		pass.scaleParams = it->scaleParams;
		/* if this is the last pass (the only one that can have CG_SCALE_NONE)
		   and no filter has been set use the GUI setting
		*/
		if(pass.scaleParams.scaleTypeX==CG_SCALE_NONE && !it->filterSet) {
			pass.linearFilter = Settings.BilinearFilter;
		} else {
			pass.linearFilter = it->linearFilter;
		}

        pass.frameCounterMod = it->frameCounterMod;

        pass.useFloatTex = it->floatFbo;

		// paths in the meta file can be relative
		_tfullpath(tempPath,_tFromChar(it->cgShaderFile),MAX_PATH);
		char *fileContents = ReadShaderFileContents(tempPath);
		if(!fileContents)
			return false;

        // individual shader might include files, these should be relative to shader
        ReduceToPath(tempPath);
        SetCurrentDirectory(tempPath);

		pass.cgVertexProgram = cgCreateProgram( cgContext, CG_SOURCE, fileContents,
						vertexProfile, "main_vertex", vertexOptions);

		checkForCgError("Compiling vertex program");

		pass.cgFragmentProgram = cgCreateProgram( cgContext, CG_SOURCE, fileContents,
						pixelProfile, "main_fragment", pixelOptions);

		checkForCgError("Compiling fragment program");

        // set path back for next pass
        SetCurrentDirectory(shaderPath);

		delete [] fileContents;
		if(!pass.cgVertexProgram || !pass.cgFragmentProgram) {
			return false;
		}
		if(pass.cgVertexProgram) {
			hr = cgD3D9LoadProgram(pass.cgVertexProgram,false,0);
		}
		checkForCgError("Loading vertex program");
		if(pass.cgFragmentProgram) {
			hr = cgD3D9LoadProgram(pass.cgFragmentProgram,false,0);
		}
		checkForCgError("Loading fragment program");

		/* generate vertex buffer
		*/
		
		hr = pDevice->CreateVertexBuffer(sizeof(VERTEX)*4,D3DUSAGE_WRITEONLY,0,D3DPOOL_MANAGED,&pass.vertexBuffer,NULL);
		if(FAILED(hr)) {
			pass.vertexBuffer = NULL;
			DXTRACE_ERR_MSGBOX(TEXT("Error creating vertex buffer"), hr);
			return false;
		}

		/* set up vertex declarations for the pass,
		   this also creates the vertex declaration
		*/
		setupVertexDeclaration(pass);

		shaderPasses.push_back(pass);
	}

	for(std::vector<CCGShader::lookupTexture>::iterator it=cgShader.lookupTextures.begin();it!=cgShader.lookupTextures.end();it++) {		
		lookupTexture tex;
		strcpy(tex.id,it->id);
		tex.linearFilter = it->linearfilter;

		_tfullpath(tempPath,_tFromChar(it->texturePath),MAX_PATH);

		if(!d3d_create_texture_from_file(pDevice, tempPath, &tex.tex)){
			tex.tex = NULL;
		}
		lookupTextures.push_back(tex);
	}

	shaderLoaded = true;

	return true;
}

void CD3DCG::ensureTextureSize(LPDIRECT3DTEXTURE9 &tex, float2 &texSize,
                               float2 wantedSize,bool renderTarget,bool useFloat)
{
	HRESULT hr;

	if(!tex || texSize.x != wantedSize.x || texSize.y != wantedSize.y) {
		if(tex)
			tex->Release();

		hr = pDevice->CreateTexture(
			(UINT)wantedSize.x, (UINT)wantedSize.y,
			1, // 1 level, no mipmaps
			renderTarget?D3DUSAGE_RENDERTARGET:0,
            renderTarget?(useFloat?D3DFMT_A32B32G32R32F:D3DFMT_A8R8G8B8):D3DFMT_R5G6B5,
			renderTarget?D3DPOOL_DEFAULT:D3DPOOL_MANAGED, // render targets cannot be managed
			&tex,
			NULL );

		texSize = wantedSize;

		if(FAILED(hr)) {
			DXTRACE_ERR_MSGBOX(TEXT("Error while creating texture"), hr);
			return;
		}
	}
}

void CD3DCG::setVertexStream(IDirect3DVertexBuffer9 *vertexBuffer,
                             float2 inputSize, float2 textureSize, float2 outputSize)
{
	float tX = inputSize.x / textureSize.x;
	float tY = inputSize.y / textureSize.y;
	VERTEX vertexStream[4];

	/* vertex is POSITION, TEXCOORD, LUT TEXCOORD
	*/

	vertexStream[0] = VERTEX(0.0f,0.0f,0.0f,
								0.0f,tY,
								0.0f,1.0f);
	vertexStream[1] = VERTEX(0.0f,1.0f,0.0f,
								0.0f,0.0f,
								0.0f,0.0f);
	vertexStream[2] = VERTEX(1.0f,0.0f,0.0f,
								tX,tY,
								1.0f,1.0f);
	vertexStream[3] = VERTEX(1.0f,1.0f,0.0f,
								tX,0.0f,
								1.0f,0.0f);
	// offset coordinates to correct pixel/texture alignment
	for(int i=0;i<4;i++) {
		vertexStream[i].x -= 0.5f / outputSize.x;
		vertexStream[i].y += 0.5f / outputSize.y;
	}

	void *pLockedVertexBuffer;
	HRESULT hr = vertexBuffer->Lock(0,0,&pLockedVertexBuffer,NULL);
	memcpy(pLockedVertexBuffer,vertexStream,sizeof(vertexStream));
	vertexBuffer->Unlock();

	/* set vertex to stream 0-3, for POSITION,TEXCOORD0,TEXCOORD1,COLOR
	   if stream is used in parameter, this will be set in setShaderVars
	*/
	pDevice->SetStreamSource(0,vertexBuffer,0,sizeof(VERTEX));
	pDevice->SetStreamSource(1,vertexBuffer,0,sizeof(VERTEX));
	pDevice->SetStreamSource(2,vertexBuffer,0,sizeof(VERTEX));
	pDevice->SetStreamSource(3,vertexBuffer,0,sizeof(VERTEX));
}

void CD3DCG::Render(LPDIRECT3DTEXTURE9 &origTex, float2 textureSize,
                    float2 inputSize, float2 viewportSize, float2 windowSize)
{
	LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
	frameCnt++;

	if(!shaderLoaded)
		return;

	/* save back buffer render target
	*/
	pDevice->GetRenderTarget(0,&pBackBuffer);	

	/* pass 0 represents the original texture
	*/
	shaderPasses[0].tex = origTex;
	shaderPasses[0].outputSize = inputSize;
	shaderPasses[0].textureSize = textureSize;

	calculateMatrix();	

	for(int i=1;i<shaderPasses.size();i++) {
		switch(shaderPasses[i].scaleParams.scaleTypeX) {
			case CG_SCALE_ABSOLUTE:
				shaderPasses[i].outputSize.x = (double)shaderPasses[i].scaleParams.absX;
				break;
			case CG_SCALE_SOURCE:
				shaderPasses[i].outputSize.x = shaderPasses[i-1].outputSize.x * shaderPasses[i].scaleParams.scaleX;
				break;
			case CG_SCALE_VIEWPORT:
				shaderPasses[i].outputSize.x = viewportSize.x * shaderPasses[i].scaleParams.scaleX;
				break;
			default:
				shaderPasses[i].outputSize.x = viewportSize.x;
		}
		switch(shaderPasses[i].scaleParams.scaleTypeY) {
			case CG_SCALE_ABSOLUTE:
				shaderPasses[i].outputSize.y = (double)shaderPasses[i].scaleParams.absY;
				break;
			case CG_SCALE_SOURCE:
				shaderPasses[i].outputSize.y = shaderPasses[i-1].outputSize.y * shaderPasses[i].scaleParams.scaleY;
				break;
			case CG_SCALE_VIEWPORT:
				shaderPasses[i].outputSize.y = viewportSize.y * shaderPasses[i].scaleParams.scaleY;
				break;
			default:
				shaderPasses[i].outputSize.y = viewportSize.y;
		}
		float texSize = npot(max(shaderPasses[i].outputSize.x,shaderPasses[i].outputSize.y));
		
		/* make sure the render target exists and has an appropriate size,
		   then set as current render target with last pass as source
		*/
		ensureTextureSize(shaderPasses[i].tex, shaderPasses[i].textureSize, float2{ texSize,texSize }, true, shaderPasses[i].useFloatTex);
		shaderPasses[i].tex->GetSurfaceLevel(0,&pRenderSurface);
		pDevice->SetTexture(0, shaderPasses[i-1].tex);
		pDevice->SetRenderTarget(0,pRenderSurface);
		pRenderSurface->Release();

		/* set vertex declaration of current pass, update vertex
		   buffer and set base streams
		*/
		pDevice->SetVertexDeclaration(shaderPasses[i].vertexDeclaration);
		setVertexStream(shaderPasses[i].vertexBuffer,
			shaderPasses[i-1].outputSize,shaderPasses[i-1].textureSize,shaderPasses[i].outputSize);
		pDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
		pDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
		pDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, shaderPasses[i].linearFilter?D3DTEXF_LINEAR:D3DTEXF_POINT);
		pDevice->SetSamplerState(0, D3DSAMP_MINFILTER, shaderPasses[i].linearFilter?D3DTEXF_LINEAR:D3DTEXF_POINT);

		/* shader vars need to be set after the base vertex streams
		   have been set so they can override them
		*/
		setShaderVars(i);

		cgD3D9BindProgram(shaderPasses[i].cgVertexProgram);
		checkForCgError("Binding vertex program");
		cgD3D9BindProgram(shaderPasses[i].cgFragmentProgram);
		checkForCgError("Binding fragment program");
		
		/* viewport defines output size
		*/
		setViewport(0,0,(DWORD)shaderPasses[i].outputSize.x, (DWORD)shaderPasses[i].outputSize.y);

        pDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

		pDevice->BeginScene();
		pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
		pDevice->EndScene();
	}

	/* take oldes PREV out of deque, make sure it has
	   the same size as the current texture and
	   then set it up as new original texture
	*/
	prevPass &oldestPrev = prevPasses.back();
	ensureTextureSize(oldestPrev.tex,oldestPrev.textureSize,textureSize,false);
	if(oldestPrev.vertexBuffer)
		oldestPrev.vertexBuffer->Release();
	origTex = oldestPrev.tex;
	prevPasses.pop_back();
	
	/* push current original with corresponding vertex
	   buffer to front of PREV deque
	*/
	shaderPasses[0].vertexBuffer = shaderPasses[1].vertexBuffer;
	prevPasses.push_front(prevPass(shaderPasses[0]));
	
	/* create new vertex buffer so that next render call
	   will not overwrite the PREV
	*/
	pDevice->CreateVertexBuffer(sizeof(VERTEX)*4,D3DUSAGE_WRITEONLY,0,D3DPOOL_MANAGED,&shaderPasses[1].vertexBuffer,NULL);
	
	/* set up last pass texture, backbuffer and viewport
	   for final display pass without shaders
	*/
	pDevice->SetTexture(0, shaderPasses.back().tex);
	pDevice->SetRenderTarget(0,pBackBuffer);
	pBackBuffer->Release();
	RECT displayRect=CalculateDisplayRect((unsigned int)shaderPasses.back().outputSize.x, (unsigned int)shaderPasses.back().outputSize.y, (unsigned int)windowSize.x, (unsigned int)windowSize.y);
	setViewport(displayRect.left,displayRect.top,displayRect.right - displayRect.left,displayRect.bottom - displayRect.top);
	setVertexStream(shaderPasses.back().vertexBuffer,
		shaderPasses.back().outputSize,shaderPasses.back().textureSize,
		float2{ (float)(displayRect.right - displayRect.left),(float)(displayRect.bottom - displayRect.top) });
	pDevice->SetVertexShader(NULL);
	pDevice->SetPixelShader(NULL);
}

static D3DMATRIX matrix_mul(D3DMATRIX mat1, D3DMATRIX mat2)
{
	D3DMATRIX out;
	for (auto y = 0; y < 4; y++)
	{
		for (auto x = 0; x < 4; x++)
		{
			out.m[y][x] =
				mat1.m[y][0] * mat2.m[0][x] +
				mat1.m[y][1] * mat2.m[1][x] +
				mat1.m[y][2] * mat2.m[2][x] +
				mat1.m[y][3] * mat2.m[3][x];
		}
	}

	return out;
}

static D3DMATRIX matrix_transpose(D3DMATRIX mat)
{
	D3DMATRIX out;
	for (auto y = 0; y < 4; y++)
		for (auto x = 0; x < 4; x++)
			out.m[y][x] = mat.m[x][y];

	return out;
}

void CD3DCG::calculateMatrix()
{
	D3DMATRIX matWorld;
	D3DMATRIX matView;
	D3DMATRIX matProj;

	pDevice->GetTransform(D3DTS_WORLD, (D3DMATRIX*)&matWorld);
	pDevice->GetTransform(D3DTS_VIEW, (D3DMATRIX*)&matView);
	pDevice->GetTransform(D3DTS_PROJECTION, (D3DMATRIX*)&matProj);

	mvp = matrix_transpose(matrix_mul(matProj, matrix_mul(matWorld, matView)));
}

void CD3DCG::setViewport(DWORD x, DWORD y, DWORD width, DWORD height)
{
	D3DVIEWPORT9 viewport;
	viewport.X = x;
	viewport.Y = y;
	viewport.Height = height;
	viewport.Width = width;
	viewport.MinZ = 0.0f;
	viewport.MaxZ = 1.0f;
	HRESULT hr = pDevice->SetViewport(&viewport);
}

void CD3DCG::setShaderVars(int pass)
{
	float2 inputSize = shaderPasses[pass-1].outputSize;
	float2 textureSize = shaderPasses[pass-1].textureSize;
	float2 outputSize = shaderPasses[pass].outputSize;

	/* mvp paramater
	*/
	CGparameter cgpModelViewProj = cgGetNamedParameter(shaderPasses[pass].cgVertexProgram, "modelViewProj");

	if(cgpModelViewProj)
		cgD3D9SetUniformMatrix(cgpModelViewProj,(D3DMATRIX*)&mvp);

#define setProgramUniform(pass,varname,floats)\
{\
	CGparameter cgpf = cgGetNamedParameter(shaderPasses[pass].cgFragmentProgram, varname);\
	CGparameter cgpv = cgGetNamedParameter(shaderPasses[pass].cgVertexProgram, varname);\
	if(cgpf)\
		cgD3D9SetUniform(cgpf,floats);\
	if(cgpv)\
		cgD3D9SetUniform(cgpv,floats);\
}\

#define setTextureParameter(pass,varname,val,linear)\
{\
	CGparameter cgpf = cgGetNamedParameter(shaderPasses[pass].cgFragmentProgram, varname);\
    CGparameter cgpv = cgGetNamedParameter(shaderPasses[pass].cgVertexProgram, varname);\
	if(cgpf) {\
		cgD3D9SetTexture(cgpf,val);\
		cgD3D9SetSamplerState(cgpf, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);\
		cgD3D9SetSamplerState(cgpf, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);\
		cgD3D9SetSamplerState(cgpf, D3DSAMP_MINFILTER, linear?D3DTEXF_LINEAR:D3DTEXF_POINT);\
		cgD3D9SetSamplerState(cgpf, D3DSAMP_MAGFILTER, linear?D3DTEXF_LINEAR:D3DTEXF_POINT);\
	}\
    if(cgpv) {\
		cgD3D9SetTexture(cgpv,val);\
		/*cgD3D9SetSamplerState(cgpv, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);\
		cgD3D9SetSamplerState(cgpv, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);\
		cgD3D9SetSamplerState(cgpv, D3DSAMP_MINFILTER, linear?D3DTEXF_LINEAR:D3DTEXF_POINT);\
		cgD3D9SetSamplerState(cgpv, D3DSAMP_MAGFILTER, linear?D3DTEXF_LINEAR:D3DTEXF_POINT);*/\
	}\
}\

#define setTexCoordsParameter(pass,varname,val)\
{\
	CGparameter cgpv = cgGetNamedParameter(shaderPasses[pass].cgVertexProgram, varname);\
	if(cgpv) {\
		unsigned long index = cgGetParameterResourceIndex(cgpv);\
		pDevice->SetStreamSource(shaderPasses[pass].parameterMap[index].streamNumber,val,0,sizeof(VERTEX));\
	}\
}\

	/* IN paramater
	*/
	setProgramUniform(pass,"IN.video_size",&inputSize);
	setProgramUniform(pass,"IN.texture_size",&textureSize);
	setProgramUniform(pass,"IN.output_size",&outputSize);
    float shaderFrameCnt = (float)frameCnt;
    if(shaderPasses[pass].frameCounterMod)
        shaderFrameCnt = (float)(frameCnt % shaderPasses[pass].frameCounterMod);
	setProgramUniform(pass,"IN.frame_count",&shaderFrameCnt);
    float frameDirection = Settings.Rewinding?-1.0f:1.0f;
    setProgramUniform(pass,"IN.frame_direction",&frameDirection);

	/* ORIG parameter
	*/
	setProgramUniform(pass,"ORIG.video_size",&shaderPasses[0].outputSize);
	setProgramUniform(pass,"ORIG.texture_size",&shaderPasses[0].textureSize);
	setTextureParameter(pass,"ORIG.texture",shaderPasses[0].tex,shaderPasses[1].linearFilter);
	setTexCoordsParameter(pass,"ORIG.tex_coord",shaderPasses[1].vertexBuffer);

	/* PREV parameter
	*/
	if(prevPasses[0].tex) {
		setProgramUniform(pass,"PREV.video_size",&prevPasses[0].imageSize);
		setProgramUniform(pass,"PREV.texture_size",&prevPasses[0].textureSize);
		setTextureParameter(pass,"PREV.texture",prevPasses[0].tex,shaderPasses[1].linearFilter);
		setTexCoordsParameter(pass,"PREV.tex_coord",prevPasses[0].vertexBuffer);
	}

	/* PREV1-6 parameters
	*/
	for(int i=1;i<prevPasses.size();i++) {
		if(!prevPasses[i].tex)
			break;
		char varname[100];
		sprintf(varname,"PREV%d.video_size",i);
		setProgramUniform(pass,varname,&prevPasses[i].imageSize);
		sprintf(varname,"PREV%d.texture_size",i);
		setProgramUniform(pass,varname,&prevPasses[i].textureSize);
		sprintf(varname,"PREV%d.texture",i);
		setTextureParameter(pass,varname,prevPasses[i].tex,shaderPasses[1].linearFilter);
		sprintf(varname,"PREV%d.tex_coord",i);
		setTexCoordsParameter(pass,varname,prevPasses[i].vertexBuffer);
	}

	/* LUT parameters
	*/
	for(int i=0;i<lookupTextures.size();i++) {
		setTextureParameter(pass,lookupTextures[i].id,lookupTextures[i].tex,lookupTextures[i].linearFilter);
	}

	/* PASSX parameters, only for third pass and up
	*/
	if(pass>2) {
		for(int i=1;i<pass-1;i++) {
			char varname[100];
			sprintf(varname,"PASS%d.video_size",i);
			setProgramUniform(pass,varname,&shaderPasses[i].outputSize);
			sprintf(varname,"PASS%d.texture_size",i);
			setProgramUniform(pass,varname,&shaderPasses[i].textureSize);
			sprintf(varname,"PASS%d.texture",i);
			setTextureParameter(pass,varname,shaderPasses[i].tex,shaderPasses[i+1].linearFilter);
			sprintf(varname,"PASS%d.tex_coord",i);
			setTexCoordsParameter(pass,varname,shaderPasses[i+1].vertexBuffer);
		}
	}
}

/* checks for known cg parameters, to make sure they
   get stream offset 12
*/
static bool isKnownParameter(const char *paramName)
{
	int len = strlen(paramName);
	if(len<3)
		return false;
	if(!strncmp(paramName,"IN.",3))
		return true;
	if(len<4)
		return false;
	if(!strncmp(paramName,"PREV",4) ||
		!strncmp(paramName,"PASS",4) ||
		!strncmp(paramName,"ORIG",4))
		return true;
	return false;
}

/* iterate all parameters and record input variyings
   with their semantic and resource index
*/
void CD3DCG::fillParameterMap(std::vector<parameterEntry> &map, CGparameter param)
{
	parameterEntry mapEntry;
	while (param) {
		if(cgGetParameterType(param)==CG_STRUCT)
			fillParameterMap(map,cgGetFirstStructParameter(param));
		else
			if (cgGetParameterDirection(param) == CG_IN && cgGetParameterVariability(param) == CG_VARYING) {
				mapEntry.rIndex = cgGetParameterResourceIndex(param);
				mapEntry.semantic = cgGetParameterSemantic(param);
				mapEntry.isKnownParam = isKnownParameter(cgGetParameterName(param));
				if(map.size()<mapEntry.rIndex+1)
					map.resize(mapEntry.rIndex+1);
				map[mapEntry.rIndex] = mapEntry;
			}
		param = cgGetNextParameter(param);
	}
}

void CD3DCG::setupVertexDeclaration(shaderPass &pass)
{
	static const D3DVERTEXELEMENT9 end = D3DDECL_END();
	CGparameter param = cgGetFirstParameter(pass.cgVertexProgram, CG_PROGRAM);
	fillParameterMap(pass.parameterMap,param);

	/* set up vertex elements according to parameter map.
	   all streams use the same vertex buffer layout, with
	   different offsets.
	   default (unknown name) parameters with semantics
	   POSITION0, TEXCOORD0, TEXCOORD1 and COLOR0 get stream
	   0,1,2 and 3 respectively.
	   the rest (all texcoords) get stream 4 and up
	*/
	D3DVERTEXELEMENT9 *vElems = new D3DVERTEXELEMENT9[pass.parameterMap.size()+1];
	int streamNum = 4;
	for(int i=0;i<pass.parameterMap.size();i++) {
		const char* sem = pass.parameterMap[i].semantic;
		pass.parameterMap[i].streamNumber = 0;
		if(pass.parameterMap[i].isKnownParam==false) {
			if(!strcmp(sem,"TEXCOORD0")) {
				D3DVERTEXELEMENT9 elem = {1, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0};
				vElems[i] = elem;
			} else if(!strcmp(sem,"TEXCOORD1")) {
				D3DVERTEXELEMENT9 elem = {2, 20, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1};
				vElems[i] = elem;
			} else if(!strcmp(sem,"COLOR0")) {
				D3DVERTEXELEMENT9 elem = {3, 12, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0};
				vElems[i] = elem;
			} else if(!strcmp(sem,"POSITION0")) {
				D3DVERTEXELEMENT9 elem = {0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0};
				vElems[i] = elem;
			}
		} else {
			int resIndex = atoi(sem + strlen(sem) - 1);
			D3DVERTEXELEMENT9 elem = {(WORD)streamNum, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, (BYTE)resIndex};
			vElems[i] = elem;
			pass.parameterMap[i].streamNumber = streamNum;
			streamNum++;
		}
	}	
	vElems[pass.parameterMap.size()] = end;

	/* create vertex declaration for vertex elements, then
	   get rid of the elements
	*/
	LPDIRECT3DVERTEXDECLARATION9 vertexDeclaration;
	HRESULT hr = pDevice->CreateVertexDeclaration(vElems,&vertexDeclaration);
	if(FAILED(hr)) {
		DXTRACE_ERR_MSGBOX(TEXT("Error creating vertex declaration"), hr);
	}
	if(pass.vertexDeclaration)
		pass.vertexDeclaration->Release();
	pass.vertexDeclaration = vertexDeclaration;
	delete [] vElems;
}
