#include "DeferredShadowSSTiled.h"

#include "ShadowApp.h"
#include "ShadowScene.h"
#include "DirectionalLight.h"

DeferredShadowSSTiled::DeferredShadowSSTiled(ShadowApp* const pApp) : DeferredShadowTechnique(pApp)
{
	m_FXSSTiled = m_pApp->GetDevice()->GetShaderManager()->CreateFX("Shaders\\SSTiledShadow.fx");

	m_FXConfShadow = m_pApp->GetDevice()->GetShaderManager()->CreateFX("Shaders\\ConfidenceShadow.fx");

	m_FXConfTest = m_pApp->GetDevice()->GetShaderManager()->CreateFX("Shaders\\ConfidenceTest.fx");

	m_RTZmin = m_pApp->GetDevice()->GetTextureManager()->CreateOffscreenRenderTexture(1, 1, FMT_R32F, true, false, true);
	m_RTZmin->GetGPUSurface()->CreateDepthStencil(FMT_D24X8);
	m_RTZmax = m_pApp->GetDevice()->GetTextureManager()->CreateOffscreenRenderTexture(1, 1, FMT_R32F, true, false, true);
	m_RTZmax->GetGPUSurface()->CreateDepthStencil(FMT_D24X8);
}

DeferredShadowSSTiled::~DeferredShadowSSTiled(void)
{
	SAFERELEASE(m_FXSSTiled);
	SAFERELEASE(m_FXConfShadow);
	SAFERELEASE(m_FXConfTest);

	SAFERELEASE(m_RTZmin);
	SAFERELEASE(m_RTZmax);
}

void DeferredShadowSSTiled::RenderShadowMask(void)
{
	if (m_FXSSTiled->IsCompiled() && m_FXConfShadow->IsCompiled() && m_FXConfTest->IsCompiled())
	{
		XString tech = "RenderShadow_Adapted";

		if (m_pApp->GetWorldPosType() == WPT_DEPTH)
			tech.appand("_Depth");
		else if (m_pApp->GetWorldPosType() == WPT_LINEARDEPTH)
			tech.appand("_LinearDepth");
		else if (m_pApp->GetWorldPosType() == WPT_WORLDPOS)
			tech.appand("_World");

		m_FXConfShadow->SetTechnique(tech);

		m_FXConfShadow->SetMatrix("g_matInvProj", DXMatrix::InvertMatrix(m_pApp->GetActiveCam().GetProjMatrix()));
		m_FXConfShadow->SetMatrix("g_matInvViewProj",  DXMatrix::InvertMatrix(m_pApp->GetActiveCam().GetViewProjMatrix()));

		m_FXConfShadow->SetTexture("g_texDepth", m_pApp->m_DSDepth->GetSurface());
		m_FXConfShadow->SetTexture("g_texNormal", m_pApp->m_DSNormal->GetSurface());
		m_FXConfShadow->SetTexture("g_texDiffuse", m_pApp->m_DSDiffuse->GetSurface());
		
		m_FXConfShadow->SetTexture("SM_ShadowMap", m_pApp->GetShadowMapping()->GetShadowMap());
		m_FXConfShadow->SetTexture("SM_IDBuffer", m_pApp->m_SM->GetIDBuffer());
		m_FXConfShadow->SetFloat("SM_ShadowMapSize", m_pApp->GetShadowMapping()->GetShadowMapSize());
		m_FXConfShadow->SetMatrix("SM_matSM", m_pApp->GetShadowMapping()->GetSMMatrix());
		
		m_FXConfShadow->SetFloatArray("g_LightDir", m_pApp->GetActiveScene()->GetLight()->GetDirection(), 3);
		
		m_pApp->m_SMOptConfSize = m_pApp->GetShadowMapping()->GetSMWorldSize();

		RenderShadowDSTile(0, 0, 0);
	}
}

void DeferredShadowSSTiled::RenderShadowDSTile(const unsigned tx, const unsigned ty, const unsigned level)
{
	const int maxlevel = 3;
	if(level > maxlevel)
		return;

	int jitterpasses = 1;
	unsigned ijitter = 0;

	float xl = (m_pApp->GetDevice()->GetViewPort()->GetWidth() >> level) + 1;
	float yl = (m_pApp->GetDevice()->GetViewPort()->GetHeight() >> level) + 1;

	float screenxl = m_pApp->GetDevice()->GetViewPort()->GetWidth();
	float screenyl = m_pApp->GetDevice()->GetViewPort()->GetHeight();

	//find depth bound(zmin/zmax) of area which isnt converged yet
	if(level > 0)
	{
		float xl = (m_pApp->GetDevice()->GetViewPort()->GetWidth() >> level) + 1;
		float yl = (m_pApp->GetDevice()->GetViewPort()->GetHeight() >> level) + 1;

		float startx = tx * xl;
		float starty = ty * yl;

		VertexBuffer* vb = m_pApp->GetDevice()->GetBufferManager()->CreateVB((xl + 1)  * (yl + 1) * sizeof(float) * 2, 0, D3DUSAGE_WRITEONLY, POOL_DEFAULT);
		float* vbdata;
		vb->GetBuffer()->Lock(0, 0, (void**)&vbdata, 0);

		for(int y = 0; y <= yl; y++)
		{
			for(int x = 0; x <= xl; x++)
			{
				//sample at center, therefore + 0.5
				*vbdata++ = (startx + x + 0.5f) / screenxl;
				*vbdata++ = (starty + y + 0.5f) / screenyl;
			}
		}

		vb->GetBuffer()->Unlock();

		m_FXSSTiled->SetTechnique("RenderUnconverged");

		m_FXSSTiled->SetTexture("g_texShadowMap", m_pApp->m_DSShadow[0]->GetSurface());
		m_FXSSTiled->SetTexture("g_texDepth", m_pApp->m_DSDepth->GetSurface());

		m_pApp->GetDevice()->SetFVF(D3DFVF_TEX1);
		m_pApp->GetDevice()->SetStreamSource(0, vb->GetBuffer(), 0, sizeof(float) * 2);
		m_pApp->GetDevice()->SetPointSize(1);
		//outpos of 0, 0 is set in shader

		m_RTZmin->StartRender(0);
		m_pApp->GetDevice()->Clear(D3DCLEAR_ZBUFFER, 0, 1, 0);
		m_pApp->GetDevice()->SetZFunc(CMP_ALWAYS);
		RENDERFX(m_FXSSTiled, 
			m_pApp->GetDevice()->DrawPrimitive(PT_POINTLIST, 0, (xl + 1)  * (yl + 1));
		);
		m_RTZmin->EndRender();

		m_RTZmax->StartRender(0);
		m_pApp->GetDevice()->Clear(D3DCLEAR_ZBUFFER, 0, 0, 0);
		m_pApp->GetDevice()->SetZFunc(CMP_GREATER);
		RENDERFX(m_FXSSTiled, 
			m_pApp->GetDevice()->DrawPrimitive(PT_POINTLIST, 0, (xl + 1)  * (yl + 1));
		);
		m_RTZmax->EndRender();

		float zmin = *(float*)(m_RTZmin->GetLockedData()->pBits);
		float zmax = *(float*)(m_RTZmax->GetLockedData()->pBits);

		vb->Release();

		DXMatrix invViewProj = m_pApp->GetActiveScene()->GetCamera().GetViewProjMatrix();
		invViewProj.Invert();

		float sx = 2 * startx / screenxl - 1;
		float sy = - 2 * starty / screenyl + 1;

		float sxl = 2 * xl / screenxl;
		float syl = - 2 * yl / screenyl;

		DXVector3 Bound[8] = {DXVector3(sx, sy, zmin), DXVector3(sx + sxl, sy, zmin), DXVector3(sx, sy + syl, zmin), DXVector3(sx + sxl, sy + syl, zmin), 
							DXVector3(sx, sy, zmax), DXVector3(sx + sxl, sy, zmax), DXVector3(sx, sy + syl, zmax), DXVector3(sx + sxl, sy + syl, zmax)};
		D3DXVec3TransformCoordArray((D3DXVECTOR3*)Bound, sizeof(DXVector3), (D3DXVECTOR3*)Bound, sizeof(DXVector3), (D3DXMATRIX*)&invViewProj, 8);

		AABB aabb;
		aabb.SetVolume(Bound[0], Bound[0]);
		aabb.AddPoint(Bound[1]); aabb.AddPoint(Bound[2]);
		aabb.AddPoint(Bound[3]); aabb.AddPoint(Bound[4]);
		aabb.AddPoint(Bound[5]); aabb.AddPoint(Bound[6]);
		aabb.AddPoint(Bound[7]);

		m_pApp->GetShadowMapping()->Update(aabb, m_pApp->GetActiveScene()->GetCamera(), m_pApp->GetActiveScene()->GetLight()->GetDirection());
		m_FXConfShadow->SetFloat("g_ConfScale", m_pApp->m_SMOptConfSize / m_pApp->GetShadowMapping()->GetSMWorldSize());

		if(level == maxlevel)
		{
			DXMatrix invProj = m_pApp->GetActiveScene()->GetCamera().GetProjMatrix();
			invProj.Invert();

			DXVector3 vmin = DXVector3(0, 0, zmin);
			DXVector3 vmax = DXVector3(0, 0, zmax);
			D3DXVec3TransformCoord((D3DXVECTOR3*)&vmin, (D3DXVECTOR3*)&vmin, (D3DXMATRIX*)&invProj);
			D3DXVec3TransformCoord((D3DXVECTOR3*)&vmax, (D3DXVECTOR3*)&vmax, (D3DXMATRIX*)&invProj);

			XString strbound = "[";
			strbound.appand((int)vmin.z);
			strbound.appand(",");
			strbound.appand((int)vmax.z);
			strbound.appand("]");
			m_pApp->m_DSDiffuse->StartRender(0);
			//m_Font->Draw(strbound, startx + xl * 0.3f + 1, starty + yl * 0.45f + 1, 0xff000000);
			m_pApp->m_Font->Draw(strbound, startx + xl * 0.3f + 1, starty + yl * 0.45f + 1, 0xffffffff);
			m_pApp->m_DSDiffuse->EndRender();
		}
	}
	else
	{
		m_FXConfShadow->SetFloat("g_ConfScale", 1);
	}

	//if(level == maxlevel)
	//{
	//	m_DynVB->SetVBStream(sizeof(float)*6, 0);
	//	m_Device->SetVertexDeclaration(m_VDeclFSQuad);
	//			
	//	m_DSDiffuse->StartRender(0);

	//	m_Device->SetDiffuseMaterial( D3DXCOLOR((0x1<<tx)<<ty) );

	//	m_Device->SetZEnable(ZB_FALSE);
	//	m_Device->DrawPrimitive(PT_TRIANGLESTRIP, 0, 2);
	//	
	//	m_DSDiffuse->EndRender();
	//}

//	m_DSDepthStencil->StartRender(0);

	while(jitterpasses-- > 0)
	{
		m_pApp->GetShadowMapping()->Jitter(ijitter++);
		
		m_pApp->GetActiveScene()->GetLight()->RenderShadowMap();
				
		m_pApp->m_DSShadow[0]->StartRender(0);
		
		if(level == 0 && ijitter == 1) 
			m_pApp->GetDevice()->Clear(D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 0, 0);
		
		//needs  to be set each iteration, because of jitter
		m_FXConfShadow->SetMatrix("SM_matSM", m_pApp->GetShadowMapping()->GetSMMatrix());
		m_FXConfShadow->SetFloat("g_iPass", m_pApp->m_PassesDone);
				
		//render tile quad
		m_pApp->RenderQuad(tx * xl, ty * yl, xl, yl, m_FXConfShadow, screenxl, screenyl);

		m_pApp->m_DSShadow[0]->EndRender();
	}

	//	m_DSDepthStencil->EndRender();

	//tile current 4 quads and test each if its is converged

	m_pApp->GetDevice()->SetColorWriteEnable(0);

	LPDIRECT3DQUERY9 Query[4];
	for(int i=0; i < 4; i++)
		m_pApp->GetDevice()->GetDevice()->CreateQuery(D3DQUERYTYPE_OCCLUSION, Query + i);
	

	m_FXConfTest->SetTechnique("TestConverged");

	m_pApp->m_DSShadow[0]->GetDepthStencil()->StartRender();

	//fill subtiles
	float subxl = (m_pApp->GetDevice()->GetViewPort()->GetWidth() >> (level + 1)) + 1;
	float subyl = (m_pApp->GetDevice()->GetViewPort()->GetHeight() >> (level + 1)) + 1;

	for(int y=0; y < 2; y++)
	{
		for(int x=0; x < 2; x++)
		{
			float sx = ((tx<<1) + x) * subxl;
			float sy = ((ty<<1) + y) * subyl;
			
			int i = x * 2 + y;
			Query[i]->Issue(D3DISSUE_BEGIN);

			m_pApp->RenderQuad(sx, sy, subxl, subyl, m_FXConfTest, screenxl, screenyl);

			Query[i]->Issue(D3DISSUE_END);
		}
	}

	m_pApp->m_DSShadow[0]->GetDepthStencil()->EndRender();
		
	bool converged[4];
	int drawT = 100;
	for(int i=0; i < 4; i++)
	{
		unsigned pixels = 0;
		while(S_FALSE == Query[i]->GetData(&pixels, sizeof(DWORD), D3DGETDATA_FLUSH ));

		if(pixels <= (100 >> level >> level))
			converged[i] = true;
		else
			converged[i] = false;
	}

	for(int i=0; i < 4; i++)
		Query[i]->Release();

	m_pApp->GetDevice()->SetColorWriteEnable(D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED);

	for(int y=0; y < 2; y++)
	{
		for(int x=0; x < 2; x++)
		{
			if(!converged[x + y * 2])
			{
				RenderShadowDSTile((tx<<1) + x, (ty<<1) + y, level + 1);
			}
		}
	}
}