#include "stdafx.h"														// for precompiled headers (has to be in first place)
#include <assert.h>														// assert()
#include "3dRenderer.h"												// C3DRenderer
#include "Error.h"														// Error,Message
#include "3DShaderManager.h"									// C3DShaderManager
#include "3dMaterial.h"												// polybumpvertexdecl


// constructor
C3DPixelShaderManager::C3DPixelShaderManager( void )
{
	m_dwPS_PolyBumpDiffusePoint=0;
	m_dwPS_PolyBumpSpecDir=0;
	m_dwPS_PolyBumpSpecPoint=0;
	m_dwPS_PolyBumpSpecDirAtt=0;
	m_dwPS_PolyBumpSpecPointAtt=0;
	m_dwPS_PolyBumpSpecDir_CM=0;
#ifdef EXTENDED_FOR_DEBUGGING_PURPOSE
	m_dwPS_TDebug=0;
	m_dwVS_TDebug=0;
#endif
	m_dwPS_PolyBumpDiffuseDir_CM=0;
	m_dwVS_PolyBumpDir=0;
	m_dwVS_PolyBumpPoint=0;
	m_dwVS_Standard=0;
	m_pCubeNormalize=0;
	m_pWhiteTexture=0;
	m_pDarkGreenTexture=0;

	m_pVSPTexture=0;
	m_pIrradianceTexture=0;
}

// destructor
C3DPixelShaderManager::~C3DPixelShaderManager( void )
{
}



// initialize the shaders (load, build)
bool C3DPixelShaderManager::Init( C3DRenderer &inRenderer )
{
	IDirect3DDevice8 *pDev=inRenderer.GetDirectXDevice();				assert(pDev);
	HRESULT hRes;

	// load shaders/textures
	if(!LoadPixelShader(pDev,"PolyBumpSpecDir.psh",m_dwPS_PolyBumpSpecDir))return(false);
	if(!LoadPixelShader(pDev,"PolyBumpDiffuseDirCM.psh",m_dwPS_PolyBumpDiffuseDir_CM))return(false);
	if(!LoadPixelShader(pDev,"PolyBumpSpecDirCM.psh",m_dwPS_PolyBumpSpecDir_CM))return(false);
#ifdef EXTENDED_FOR_DEBUGGING_PURPOSE
	if(!LoadPixelShader(pDev,"PolyBumpTDebug.psh",m_dwPS_TDebug))return(false);
#endif

	// load vertex shaders
	if(!LoadVertexShader(pDev,"PolyBumpDir.vsh",m_dwVS_PolyBumpDir))return(false);
	if(!LoadVertexShader(pDev,"PolyBumpPoint.vsh",m_dwVS_PolyBumpPoint))return(false);
	if(!LoadVertexShader(pDev,"StandardSpec.vsh",m_dwVS_Standard))return(false);
#ifdef EXTENDED_FOR_DEBUGGING_PURPOSE
	if(!LoadVertexShader(pDev,"PolyBumpTDebug.vsh",m_dwVS_TDebug))return(false);
#endif


	// ATI GD2002 paper (variable specular power = per pixel gloss)
	assert(!m_pVSPTexture);
	hRes=D3DXCreateTextureFromFile( pDev, "VariableSpecularPower.bmp",(LPDIRECT3DTEXTURE8 *)&m_pVSPTexture );
	if(FAILED(hRes))
	{
		Error.AddDirectX("D3DXCreateTextureFromFile(VariableSpecularPower.bmp) failed: ",hRes);
		return(false);
	}


	{
		hRes=D3DXCreateTexture( pDev,											// d3d device
																1,1,									// width,height
																0,										// mipmaps
																0,										// usage
																D3DFMT_X8R8G8B8,			// format
																D3DPOOL_MANAGED,			// pool
																&m_pWhiteTexture);		// output

		if(FAILED(hRes))
		{
			Error.AddDirectX("D3DXCreateTexture (white 1x1) failed: ",hRes);
			return(false); 
		}

		D3DLOCKED_RECT rRect;

		hRes=m_pWhiteTexture->LockRect(0,&rRect,0,0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("C3DPixelShaderManager::Init LockRect failed: ",hRes);
		}
		else
		{
			DWORD *pMem=(DWORD *)(((char *)rRect.pBits));

			*pMem=0xffffff;	// white
		}

		hRes=m_pWhiteTexture->UnlockRect(0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("C3DPixelShaderManager::Init UnLockRect failed: ",hRes);
			return(false);
		}
	}

	{
		hRes=D3DXCreateTexture( pDev,													// d3d device
																1,1,											// width,height
																0,												// mipmaps
																0,												// usage
																D3DFMT_X8R8G8B8,					// format
																D3DPOOL_MANAGED,					// pool
																&m_pDarkGreenTexture);		// output

		if(FAILED(hRes))
		{
			Error.AddDirectX("D3DXCreateTexture (dark green 1x1) failed: ",hRes);
			return(false); 
		}

		D3DLOCKED_RECT rRect;

		hRes=m_pDarkGreenTexture->LockRect(0,&rRect,0,0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("C3DPixelShaderManager::Init LockRect failed: ",hRes);
		}
		else
		{
			DWORD *pMem=(DWORD *)(((char *)rRect.pBits));

			*pMem=0x000a00;	// dark green
		}

		hRes=m_pDarkGreenTexture->UnlockRect(0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("C3DPixelShaderManager::Init UnLockRect failed: ",hRes);
			return(false);
		}
	}


	assert(!m_pIrradianceTexture);
	hRes=D3DXCreateTextureFromFileEx( pDev, "Irradiance.tga",
		D3DX_DEFAULT,
		D3DX_DEFAULT,
		0,
		0,
		D3DFMT_A8R8G8B8,
		D3DPOOL_MANAGED,
		D3DX_FILTER_LINEAR | D3DX_FILTER_MIRROR,	// cannot wrap when creating mip levels
		D3DX_FILTER_LINEAR | D3DX_FILTER_MIRROR,
		0,
		NULL,
		NULL,
		(LPDIRECT3DTEXTURE8 *)&m_pIrradianceTexture );
	if(FAILED(hRes))
	{
		Error.AddDirectX("D3DXCreateTextureFromFile failed: ",hRes);
		return(false);
	}

	BuildCubeNormalize(pDev,256);

	return(true);
}



// free the shaders
void C3DPixelShaderManager::DeInit( C3DRenderer &inRenderer )
{
	IDirect3DDevice8 *pDev=inRenderer.GetDirectXDevice();				
	if(!pDev)return;

	// pixel shader
	if(m_dwPS_PolyBumpDiffusePoint)pDev->DeletePixelShader(m_dwPS_PolyBumpDiffusePoint);m_dwPS_PolyBumpDiffusePoint=0;
	if(m_dwPS_PolyBumpSpecDir)pDev->DeletePixelShader(m_dwPS_PolyBumpSpecDir);m_dwPS_PolyBumpSpecDir=0;
	if(m_dwPS_PolyBumpSpecPoint)pDev->DeletePixelShader(m_dwPS_PolyBumpSpecPoint);m_dwPS_PolyBumpSpecPoint=0;
	if(m_dwPS_PolyBumpSpecDirAtt)pDev->DeletePixelShader(m_dwPS_PolyBumpSpecDirAtt);m_dwPS_PolyBumpSpecDirAtt=0;
	if(m_dwPS_PolyBumpSpecPointAtt)pDev->DeletePixelShader(m_dwPS_PolyBumpSpecPointAtt);m_dwPS_PolyBumpSpecPointAtt=0;
	if(m_dwPS_PolyBumpSpecDir_CM)pDev->DeletePixelShader(m_dwPS_PolyBumpSpecDir_CM);m_dwPS_PolyBumpSpecDir_CM=0;
	if(m_dwPS_PolyBumpDiffuseDir_CM)pDev->DeletePixelShader(m_dwPS_PolyBumpDiffuseDir_CM);m_dwPS_PolyBumpDiffuseDir_CM=0;
#ifdef EXTENDED_FOR_DEBUGGING_PURPOSE
	if(m_dwPS_TDebug)pDev->DeletePixelShader(m_dwPS_TDebug);m_dwPS_TDebug=0;
#endif

	// vertex shader
	if(m_dwVS_PolyBumpDir)pDev->DeleteVertexShader(m_dwVS_PolyBumpDir);m_dwVS_PolyBumpDir=0;
	if(m_dwVS_PolyBumpPoint)pDev->DeleteVertexShader(m_dwVS_PolyBumpPoint);m_dwVS_PolyBumpPoint=0;
	if(m_dwVS_Standard)pDev->DeleteVertexShader(m_dwVS_Standard);m_dwVS_Standard=0;
#ifdef EXTENDED_FOR_DEBUGGING_PURPOSE
	if(m_dwVS_TDebug)pDev->DeleteVertexShader(m_dwVS_TDebug);m_dwVS_TDebug=0;
#endif

	// textures
	if(m_pVSPTexture)m_pVSPTexture->Release();m_pVSPTexture=0;
	if(m_pIrradianceTexture)m_pIrradianceTexture->Release();m_pIrradianceTexture=0;
	if(m_pCubeNormalize)m_pCubeNormalize->Release();m_pCubeNormalize=0;
	if(m_pWhiteTexture)m_pWhiteTexture->Release();m_pWhiteTexture=0;
	if(m_pDarkGreenTexture)m_pDarkGreenTexture->Release();m_pDarkGreenTexture=0;
}



// load pixel shader
bool C3DPixelShaderManager::LoadPixelShader( IDirect3DDevice8 *inpDevice, const char *inszFilename, 
																						 DWORD &outdwHandle )
{
	assert(inpDevice);

	HRESULT hRes;
	ID3DXBuffer *pCode,*pError;

	hRes=D3DXAssembleShaderFromFile(inszFilename,0,NULL,&pCode,&pError);
	if(FAILED(hRes))
	{
		char *pText="";		if(pError)pText=(char *)pError->GetBufferPointer();

		Error.Add("D3DXAssembleShaderFromFile(P1) %s",inszFilename);
		Error.Add("D3DXAssembleShaderFromFile(P1) failed: %s",pText);
	}
	else
	{
		hRes=inpDevice->CreatePixelShader((DWORD*)pCode->GetBufferPointer(),&outdwHandle);
		if(FAILED(hRes))
		{
			Error.Add("CreatePixelShader1(1) %s",inszFilename);
			Error.AddDirectX("CreatePixelShader(1) failed: ",hRes);
		}
		pCode->Release();
	}

	if(pError)pError->Release();

	return(!FAILED(hRes));
}



// load vertex shader
bool C3DPixelShaderManager::LoadVertexShader( IDirect3DDevice8 *inpDevice, const char *inszFilename, 
																							DWORD &outdwHandle )
{
	assert(inpDevice);

	HRESULT hRes;
	ID3DXBuffer *pCode=0,*pError=0;
	DWORD dwShaderUsage = 0;

	// Try to load and assemble the vertex shader 
	hRes=D3DXAssembleShaderFromFile(inszFilename,0,NULL,&pCode,&pError);		// could crash if file is empty
	if(FAILED(hRes))
	{
		Error.Add("D3DXAssembleShaderFromFile(V1) %s",inszFilename);
		Error.AddDirectX("D3DXAssembleShaderFromFile(V1) failed:",hRes);
	}

	if(pCode)
	{
		DWORD *ptr=(DWORD*)pCode->GetBufferPointer();

		// Create vertex shader from assembled file
		hRes=inpDevice->CreateVertexShader(PolyBumpVertexDecl,ptr,&outdwHandle,dwShaderUsage);
		if(FAILED(hRes))
		{
			Error.Add("CreateVertexShader %s",inszFilename);
			Error.AddDirectX("CreateVertexShader failed: ",hRes);
		}

		// Free up code buffer
		pCode->Release();
	}

	if(pError)
	{
		char *pText="";	if(pError)pText=(char *)pError->GetBufferPointer();

		Error.Add("CreateVertexShader %s",inszFilename);
		Error.Add("D3DXAssembleShaderFromFile(V1) failed: %s",pText);

		pError->Release();
	}

	return(!FAILED(hRes));
}



void C3DPixelShaderManager::BuildCubeNormalize( IDirect3DDevice8 *inpDevice, const DWORD dwSize )
{
	assert(inpDevice);

	HRESULT hRes;
	
	hRes=D3DXCreateCubeTexture( inpDevice,						// d3d device
															dwSize,								// dwSizexdwSize x 6
															D3DX_DEFAULT,					// mipmaps
															0,										// usage
															D3DFMT_X8R8G8B8,			// format
															D3DPOOL_MANAGED,			// pool
															&m_pCubeNormalize);		// output

	if(FAILED(hRes))
	{
		Error.AddDirectX("D3DXCreateCubeTexture failed: ",hRes);
		return; 
	}

	for(DWORD dwSide=0;dwSide<6;dwSide++)
	{
		D3DLOCKED_RECT rRect;

		hRes=m_pCubeNormalize->LockRect((D3DCUBEMAP_FACES)dwSide,0,&rRect,0,0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("BuildCubeNormalize:LockRect failed: ",hRes);
			continue; 
		}

		for(DWORD dwY=0;dwY<dwSize;dwY++)
		{
			DWORD dwPitch=rRect.Pitch;
			DWORD *pMem=(DWORD *)(((char *)rRect.pBits)+dwPitch*dwY);

			for(DWORD dwX=0;dwX<dwSize;dwX++)
			{
				float fX=((float)dwX+0.5f)/(float)(dwSize)*2.0f-1.0f;		// according to Game Programming Gems 3 page 447
				float fY=((float)dwY+0.5f)/(float)(dwSize)*2.0f-1.0f;

				D3DXVECTOR3 vDir;

				switch(dwSide)
				{
					case 0:		vDir=D3DXVECTOR3( 1.0f,   -fY,   -fX); break;
					case 1:		vDir=D3DXVECTOR3(-1.0f,   -fY,    fX); break;
					case 2:		vDir=D3DXVECTOR3(   fX,  1.0f,    fY); break;
					case 3:		vDir=D3DXVECTOR3(   fX, -1.0f,   -fY); break;
					case 4:		vDir=D3DXVECTOR3(   fX,   -fY,  1.0f); break;
					case 5:		vDir=D3DXVECTOR3(  -fX,   -fY, -1.0f); break;

					default:	assert(0);break;
				}

				D3DXVec3Normalize(&vDir,&vDir);

				vDir=(vDir)*127.5f+D3DXVECTOR3(127.5f,127.5f,127.5f);		// values in range [0..255] + 0.5 for rounding

				assert((int)(vDir.x)>=0);
				assert((int)(vDir.x)<=255);
				assert((int)(vDir.y)>=0);
				assert((int)(vDir.y)<=255);
				assert((int)(vDir.z)>=0);
				assert((int)(vDir.z)<=255);

				*pMem++=((DWORD)(vDir.x)<<16) | ((DWORD)(vDir.y)<<8) | (DWORD)(vDir.z);
			}
		}

		hRes=m_pCubeNormalize->UnlockRect((D3DCUBEMAP_FACES)dwSide,0);
		if(FAILED(hRes))
		{
			Error.AddDirectX("BuildCubeNormalize:UnLockRect failed: ",hRes);
			continue; 
		}
	}
}


