// Copyright (C) 2003-2008 Dolphin Project.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/

#include "Globals.h"
#include "PixelShader.h"
#include "BPStructs.h"
#include "XFStructs.h"

/*
   old tev->pixelshader notes

   color for this stage (alpha, color) is given by bpmem.tevorders[0].colorchan0
   konstant for this stage (alpha, color) is given by bpmem.tevksel   
   inputs are given by bpmem.combiners[0].colorC.a/b/c/d     << could be current chan color
   according to GXTevColorArg table above
   output is given by .outreg
   tevtemp is set according to swapmodetables and 
*/

const float epsilon = 1.0f/255.0f;

const char *tevKSelTableC[] =
{
	"1,1,1",            //KCSEL_1   = 0x00
	"0.875,0.875,0.875",//KCSEL_7_8 = 0x01
	"0.75,0.75,0.75",	//KCSEL_3_4 = 0x02
	"0.625,0.625,0.625",//KCSEL_5_8 = 0x03
	"0.5,0.5,0.5",      //KCSEL_1_2 = 0x04
	"0.375,0.375,0.375",//KCSEL_3_8 = 0x05
	"0.25,0.25,0.25",   //KCSEL_1_4 = 0x06
	"0.125,0.125,0.125",//KCSEL_1_8 = 0x07
	"ERROR", //0x08
	"ERROR", //0x09
	"ERROR", //0x0a
	"ERROR", //0x0b
	"k0.rgb",//KCSEL_K0 = 0x0C
	"k1.rgb",//KCSEL_K1 = 0x0D
	"k2.rgb",//KCSEL_K2 = 0x0E
	"k3.rgb",//KCSEL_K3 = 0x0F
	"k0.rrr",//KCSEL_K0_R = 0x10
	"k1.rrr",//KCSEL_K1_R = 0x11
	"k2.rrr",//KCSEL_K2_R = 0x12
	"k3.rrr",//KCSEL_K3_R = 0x13
	"k0.ggg",//KCSEL_K0_G = 0x14 
	"k1.ggg",//KCSEL_K1_G = 0x15
	"k2.ggg",//KCSEL_K2_G = 0x16
	"k3.ggg",//KCSEL_K3_G = 0x17
	"k0.bbb",//KCSEL_K0_B = 0x18
	"k1.bbb",//KCSEL_K1_B = 0x19
	"k2.bbb",//KCSEL_K2_B = 0x1A
	"k3.bbb",//KCSEL_K3_B = 0x1B
	"k0.aaa",//KCSEL_K0_A = 0x1C
	"k1.aaa",//KCSEL_K1_A = 0x1D
	"k2.aaa",//KCSEL_K2_A = 0x1E
	"k3.aaa",//KCSEL_K3_A = 0x1F
};
const char *tevKSelTableA[] =
{
	"1",    //KASEL_1   = 0x00
	"0.875",//KASEL_7_8 = 0x01
	"0.75",	//KASEL_3_4 = 0x02
	"0.625",//KASEL_5_8 = 0x03
	"0.5",  //KASEL_1_2 = 0x04
	"0.375",//KASEL_3_8 = 0x05
	"0.25", //KASEL_1_4 = 0x06
	"0.125",//KASEL_1_8 = 0x07
	"ERROR",//0x08
	"ERROR",//0x09
	"ERROR",//0x0a
	"ERROR",//0x0b
	"ERROR",//0x0c
	"ERROR",//0x0d
	"ERROR",//0x0e
	"ERROR",//0x0f
	"k0.r", //KASEL_K0_R = 0x10
	"k1.r", //KASEL_K1_R = 0x11
	"k2.r", //KASEL_K2_R = 0x12
	"k3.r", //KASEL_K3_R = 0x13
	"k0.g", //KASEL_K0_G = 0x14
	"k1.g", //KASEL_K1_G = 0x15
	"k2.g", //KASEL_K2_G = 0x16
	"k3.g", //KASEL_K3_G = 0x17
	"k0.b", //KASEL_K0_B = 0x18
	"k1.b", //KASEL_K1_B = 0x19
	"k2.b", //KASEL_K2_B = 0x1A
	"k3.b", //KASEL_K3_B = 0x1B
	"k0.a", //KASEL_K0_A = 0x1C
	"k1.a", //KASEL_K1_A = 0x1D
	"k2.a", //KASEL_K2_A = 0x1E
	"k3.a", //KASEL_K3_A = 0x1F
};

const char *tevScaleTable[] = 
{
	"1",   //SCALE_1
	"2",   //SCALE_2
	"4",   //SCALE_4
	"0.5", //DIVIDE_2
};

const char *tevBiasTable[] = 
{
	"",      //ZERO,
    "+0.5",  //ADD_HALF,
    "-0.5",  //SUB_HALF,
	"",      //WTF? seen in shadow2
};

const char *tevOpTable[] = 
{
	"+",      //ADD = 0,
    "-",      //SUB = 1,
};

const char *tevCompOpTable[] = 
{
	">",
	"==",
};


#define TEV_COMP_R8    0
#define TEV_COMP_GR16  1
#define TEV_COMP_BGR24 2
#define TEV_COMP_RGB8  3

const char *tevCInputTable[] = 
{
	"prev.rgb",           //CPREV,
    "prev.aaa",           //APREV,
    "c0.rgb",             //C0,
    "c0.aaa",             //A0,
    "c1.rgb",             //C1,
    "c1.aaa",             //A1,
    "c2.rgb",             //C2,
    "c2.aaa",             //A2,
    "textemp.rgb",        //TEXC,
    "textemp.aaa",        //TEXA,
    "rastemp.rgb",        //RASC,
    "rastemp.aaa",        //RASA,
    "float3(1,1,1)",      //ONE,
    "float3(.5,.5,.5)",   //HALF,
    "konsttemp.rgb",      //KONST,
    "float3(0,0,0)",      //ZERO
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
};
const char *tevCInputTable2[] = 
{
	"prev",                 //CPREV,
	"(prev.aaa)",           //APREV,
	"c0",                   //C0,
	"(c0.aaa)",             //A0,
	"c1",                   //C1,
	"(c1.aaa)",             //A1,
	"c2",                   //C2,
	"(c2.aaa)",             //A2,
	"textemp",              //TEXC,
	"(textemp.aaa)",        //TEXA,
	"rastemp",              //RASC,
	"(rastemp.aaa)",        //RASA,
	"float3(1,1,1)",        //ONE,
	"float3(.5,.5,.5)",     //HALF,
	"konsttemp",            //KONST,
	"float3(0,0,0)",        //ZERO
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
};

const char *tevAInputTable[] = 
{
    "prev.a",           //APREV,
    "c0.a",             //A0,
    "c1.a",             //A1,
    "c2.a",             //A2,
    "textemp.a",        //TEXA,
    "rastemp.a",        //RASA,
    "konsttemp.a",      //KONST,  (hw1 had quarter)
    "0.0",				//ZERO
	"PADERROR", "PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR", "PADERROR",
};

const char *tevAInputTable1[] = 
{
	"prev.r",           //APREV,
	"c0.r",             //A0,
	"c1.r",             //A1,
	"c2.r",             //A2,
	"textemp.r",        //TEXA,
	"rastemp.r",        //RASA,
	"konsttemp.r",      //KONST,  (hw1 had quarter)
	"0.0",				//ZERO
	"PADERROR", "PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR", "PADERROR",
};	

const char *tevAInputTable2[] = 
{
    "prev",           //APREV,
    "c0",             //A0,
    "c1",             //A1,
    "c2",             //A2,
    "textemp",        //TEXA,
    "rastemp",        //RASA,
    "konsttemp",      //KONST,  (hw1 had quarter)
    "float4(0,0,0,0)",//ZERO
	"PADERROR", "PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR",	"PADERROR",
	"PADERROR",	"PADERROR",	"PADERROR", "PADERROR",
};	

const char *tevRasTable[] = 
{
	"colors[0]",//RAS1_CC_0	0x00000000 /* color channel 0 */
	"colors[1]",//RAS1_CC_1	0x00000001 /* color channel 1 */
	"ERROR", //2
	"ERROR", //3
	"ERROR", //4
	"alphabump", //RAS1_CC_B	0x00000005 /* indirect texture bump alpha */ //green cuz unsupported
	"(alphabump*(255.0f/248.0f))", //RAS1_CC_BN	0x00000006 /* ind tex bump alpha, normalized 0-255 *///green cuz unsupported
	"float4(0,0,0,0)", //RAS1_CC_Z	0x00000007 /* set color value to zero */
};

const char *tevCOutputTable[]  = { "prev.rgb", "c0.rgb", "c1.rgb", "c2.rgb" };
const char *tevAOutputTable[]  = { "prev.a", "c0.a", "c1.a", "c2.a" };
const char *tevIndAlphaSel[]   = {"", "x", "y", "z"};
const char *tevIndAlphaScale[] = {"", "*32","*16","*8"};
const char *tevIndBiasField[]  = {"", "x", "y", "xy", "z", "xz", "yz", "xyz"}; // indexed by bias
const char *tevIndBiasAdd[]    = {"-128.0f", "1.0f", "1.0f", "1.0f" }; // indexed by fmt
const char *tevIndWrapStart[]  = {"0", "256", "128", "64", "32", "16", "0.001" };
const char *tevIndFmtScale[]   = {"255.0f", "31.0f", "15.0f", "8.0f" };

const char *tevTexFuncs[] = 
{
	"tex2D",
	"tex2Dproj"
};

const char *alphaRef[2] = 
{
	"alphaRef.x",
	"alphaRef.y"
};



char text[65536];
#define WRITE p+=sprintf


void WriteStage(char *&p, int n);
void WriteAlphaTest(char *&p);


char *swapColors = "rgba";
char swapModeTable[4][5];

void BuildSwapModeTable()
{
	//bpmem.tevregs[0].
	for (int i = 0; i < 4; i++)
	{
		swapModeTable[i][0]=swapColors[bpmem.tevksel[i*2].swap1];
		swapModeTable[i][1]=swapColors[bpmem.tevksel[i*2].swap2];
		swapModeTable[i][2]=swapColors[bpmem.tevksel[i*2+1].swap1];
		swapModeTable[i][3]=swapColors[bpmem.tevksel[i*2+1].swap2];
		swapModeTable[i][4]=0;
	}
}

const char *GeneratePixelShader()
{
	BuildSwapModeTable();
	int numStages = bpmem.genMode.numtevstages + 1;
	int numTexgen = bpmem.genMode.numtexgens;
	int numSamplers = 8;

	char *p = text;
	WRITE(p,"//Pixel Shader for TEV stages\n\
//%i TEV stages, %i texgens, %i IND stages, %i COL channels\n",
		bpmem.genMode.numtevstages,bpmem.genMode.numtexgens,bpmem.genMode.numindstages,bpmem.genMode.numcolchans);

	//write kcolor declarations
	for (int i = 0; i < 4; i++) {
		if(i < 3) {
			WRITE(p,"float4 k%i : register(c%i);\n\
                     float4 color%i : register(c%i);\n",i,PS_CONST_KCOLORS+i, i,PS_CONST_COLORS+i+1);
		} else {
			WRITE(p,"float4 k%i : register(c%i);\n",i,PS_CONST_KCOLORS+i);
		}
	}

	WRITE(p,"float constalpha : register(c%i);\n\
float2 alphaRef : register(c%i);\n\n\
sampler samp[%i] : register(s0);\n\n\
float4 main(in float4 colors[2] : COLOR0",PS_CONST_CONSTALPHA,PS_CONST_ALPHAREF,numSamplers);

	if (numTexgen)
		WRITE(p,", float4 uv[%i] : TEXCOORD0",numTexgen);
	else
		WRITE(p,", float4 uv[1] : TEXCOORD0"); //HACK
	WRITE(p,") : COLOR\n\
{\n\
float4 c0=color0,c1=color1,c2=color2,prev=float4(0.0f,0.0f,0.0f,0.0f),textemp,rastemp,konsttemp;\n\
float3 comp16 = float3(1,255,0), comp24 = float3(1,255,255*255);\n\
\n");

	for (int i = 0; i < numStages; i++)
		WriteStage(p,i); //build the equation for this stage

	WriteAlphaTest(p);
	
	if (bpmem.dstalpha.enable)
		WRITE(p,"  return float4(prev.rgb,constalpha.x);\n");
	else
		WRITE(p,"  return prev;\n");

	WRITE(p,"}\n\0");
	
	return text;
}

void WriteStage(char *&p, int n)
{
	char *rasswap = swapModeTable[bpmem.combiners[n].alphaC.rswap];
	char *texswap = swapModeTable[bpmem.combiners[n].alphaC.tswap];

	int texfun = xfregs.texcoords[n].texmtxinfo.projection;

	WRITE(p,"rastemp=%s.%s;\n",tevRasTable[bpmem.tevorders[n/2].getColorChan(n&1)],rasswap);
	if (bpmem.tevorders[n/2].getEnable(n&1))
		WRITE(p,"textemp=%s(samp[%i],uv[%i]).%s;\n",
			tevTexFuncs[texfun],
			bpmem.tevorders[n/2].getTexMap(n&1),
			bpmem.tevorders[n/2].getTexCoord(n&1),texswap);
	else
		WRITE(p,"textemp=float4(1,1,1,1);\n");

	int kc = bpmem.tevksel[n/2].getKC(n&1);
	int ka = bpmem.tevksel[n/2].getKA(n&1);

	WRITE(p,"konsttemp=float4(%s,%s);\n",tevKSelTableC[kc],tevKSelTableA[ka]);

	TevStageCombiner::ColorCombiner &cc = bpmem.combiners[n].colorC;
	TevStageCombiner::AlphaCombiner &ac = bpmem.combiners[n].alphaC;

	WRITE(p,"float4(%s,%s)=", tevCOutputTable[cc.dest], tevAOutputTable[ac.dest]); 

	//////////////////////////////////////////////////////////////////////////
	//start of color
	//////////////////////////////////////////////////////////////////////////
	WRITE(p,"float4(\n"); 

	if (cc.bias != TB_COMPARE)
	{
		//normal color combiner goes here
		WRITE(p,"   %s*(%s%s",tevScaleTable[cc.shift],tevCInputTable[cc.d],tevOpTable[cc.op]);
		WRITE(p,"(lerp(%s,%s,%s)%s)),\n",
			tevCInputTable[cc.a],tevCInputTable[cc.b],
			tevCInputTable[cc.c],tevBiasTable[cc.bias]);
	}
	else
	{
		//compare color combiner goes here
		switch(cc.shift)  // yep comparemode stored here :P
		{
		case TEV_COMP_R8:
			if (cc.op == 0)  //equality check needs tolerance, fp in gpu has drawbacks :(
				WRITE(p,"   %s + ((%s.r > %s.r) ? %s : float3(0,0,0)),\n",
				tevCInputTable[cc.d],tevCInputTable2[cc.a],
				tevCInputTable2[cc.b],tevCInputTable[cc.c]);
			else
				WRITE(p,"   %s + (abs(%s.r - %s.r)<%f ? %s : float3(0,0,0)),\n",
				tevCInputTable[cc.d],tevCInputTable2[cc.a],
				tevCInputTable2[cc.b],epsilon,tevCInputTable[cc.c]);
			break;
		default:
			WRITE(p,"float3(0,0,0),\n");
			break;
		}
	}
	//end of color
	
	//////////////////////////////////////////////////////////////////////////
	//start of alpha
	//////////////////////////////////////////////////////////////////////////
	if (ac.bias != TB_COMPARE)
	{
		//normal alpha combiner goes here
		WRITE(p,"   %s*(%s%s",tevScaleTable[ac.shift],tevAInputTable[ac.d],tevOpTable[ac.op]);
		WRITE(p,"lerp(%s,%s,%s) %s)\n",
			tevAInputTable[ac.a],tevAInputTable[ac.b],
			tevAInputTable[ac.c],tevBiasTable[ac.bias]);
	}
	else
	{
        int cmp = (ac.shift<<1)|ac.op|8; // comparemode stored here
		const char **inputTable = NULL;
		inputTable = (cmp == TEVCMP_R8_GT || cmp == TEVCMP_R8_EQ) ? tevAInputTable1 : tevAInputTable;
		//compare alpha combiner goes here
        switch(cmp) {
        case TEVCMP_R8_GT:
        case TEVCMP_A8_GT:
			WRITE(p,"   %s + ((%s > %s) ? %s : 0)\n",
				tevAInputTable[ac.d], inputTable[ac.a], inputTable[ac.b], tevAInputTable[ac.c]);
            break;
        case TEVCMP_R8_EQ:
        case TEVCMP_A8_EQ:
            WRITE(p,"   %s + (abs(%s - %s)<%f ? %s : 0)\n",
                tevAInputTable[ac.d], inputTable[ac.a], inputTable[ac.b],epsilon,tevAInputTable[ac.c]);
            break;
        
        case TEVCMP_GR16_GT: // 16 bit compares: 255*g+r (probably used for ztextures, so make sure in ztextures, g is the most significant byte)
        case TEVCMP_BGR24_GT: // 24 bit compares: 255*255*b+255*g+r
            WRITE(p,"   %s + (( dot(%s.rgb-%s.rgb, comp%s) > 0) ? %s : 0)\n",
                tevAInputTable[ac.d],tevAInputTable2[ac.a], tevAInputTable2[ac.b], cmp==TEVCMP_GR16_GT?"16":"24", tevAInputTable[ac.c]);
            break;
        case TEVCMP_GR16_EQ:
        case TEVCMP_BGR24_EQ:
            WRITE(p,"   %s + (abs(dot(%s.rgb - %s.rgb, comp%s))<%f ? %s : 0)\n",
                tevAInputTable[ac.d],tevAInputTable2[ac.a], tevAInputTable2[ac.b],cmp==TEVCMP_GR16_GT?"16":"24",epsilon,tevAInputTable[ac.c]);
            break;
        default:
            WRITE(p,"0)\n");
            break;
        }
	}
	WRITE(p, ");");
    if (ac.clamp)
        WRITE(p, "%s = clamp(%s, 0.0f, 1.0f);\n", tevAOutputTable[ac.dest], tevAOutputTable[ac.dest]);
	WRITE(p, "\n");
}

void WriteAlphaCompare(char *&p, int num, int comp)
{
	WRITE(p,"  res%i = ",num);
	switch(comp) {
	case ALPHACMP_ALWAYS:  WRITE(p,"0;\n");	break;
	case ALPHACMP_NEVER:   WRITE(p,"1;\n");	break;
	case ALPHACMP_LEQUAL:  WRITE(p,"prev.a - %s.x;\n",alphaRef[num]);	break;
	case ALPHACMP_LESS:    WRITE(p,"prev.a - %s.x + %f;\n",alphaRef[num],epsilon*2);break;
	case ALPHACMP_GEQUAL:  WRITE(p,"%s - prev.a;\n",alphaRef[num]);	break;
	case ALPHACMP_GREATER: WRITE(p,"%s - prev.a + %f;\n",alphaRef[num],epsilon*2);break;
	case ALPHACMP_EQUAL:   WRITE(p,"abs(%s-prev.a)-%f;\n",alphaRef[num],epsilon*2); break;
	case ALPHACMP_NEQUAL:  WRITE(p,"%f-abs(%s-prev.a);\n",epsilon*2,alphaRef[num]); break;
	}
}

void WriteAlphaTest(char *&p)
{
	AlphaOp op = (AlphaOp)bpmem.alphaFunc.logic;
	Compare comp[2] = {(Compare)bpmem.alphaFunc.comp0,(Compare)bpmem.alphaFunc.comp1};

	//first kill all the simple cases
	if (op == ALPHAOP_AND && (comp[0] == COMPARE_ALWAYS && comp[1] == COMPARE_ALWAYS)) return;
	if (op == ALPHAOP_OR  && (comp[0] == COMPARE_ALWAYS || comp[1] == COMPARE_ALWAYS)) return;
	for (int i = 0; i < 2; i++)
	{
		int one = i;
		int other = 1-i;
		switch(op) {
		case ALPHAOP_XOR:
			if (comp[one] == COMPARE_ALWAYS && comp[other] == COMPARE_NEVER) return;
			break;
		case ALPHAOP_XNOR:
			if (comp[one] == COMPARE_ALWAYS && comp[other] == COMPARE_ALWAYS) return;
			if (comp[one] == COMPARE_ALWAYS && comp[other] == COMPARE_NEVER) return;
			break;
		}
	}

	//Ok, didn't get to do the easy way out :P
	// do the general way
	WRITE(p,"float res0, res1;\n");
	WriteAlphaCompare(p, 0, bpmem.alphaFunc.comp0);
	WriteAlphaCompare(p, 1, bpmem.alphaFunc.comp1);
	WRITE(p,"res0 = max(res0, 0);\n");
	WRITE(p,"res1 = max(res1, 0);\n");
	//probably should use lookup textures for some of these :P
	switch(bpmem.alphaFunc.logic) {
	case ALPHAOP_AND: // if both are 0
		WRITE(p,"clip(-(res0+res1)+%f);\n",epsilon);
		break;
	case ALPHAOP_OR: //if either is 0
		WRITE(p,"clip(-res0*res1+%f);\n",epsilon*epsilon);
		break;
	case ALPHAOP_XOR:
		//hmm, this might work:
		WRITE(p,"res0=(res0>0?1:0)-.5;\n");
		WRITE(p,"res1=(res1>0?1:0)-.5;\n");
		WRITE(p,"clip(-res0*res1);\n",epsilon);
		break;
	case ALPHAOP_XNOR:
		WRITE(p,"res0=(res0>0?1:0)-.5;\n");
		WRITE(p,"res1=(res1>0?1:0)-.5;\n");
		WRITE(p,"clip(res0*res1);\n",epsilon);
		break;
	}
}
