//
// Asura_GX.cpp
//
// Created by KOR - 10 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_GX.h"

// Asura headers
#include "Core/Asura_Core.h"	// for IsMainThread()
#include "Animation/Asura_TextureAnimation.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_Multithread.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_CPUProfile.h"
#include "Debugging/Asura_DebugText.h"
#include "Debugging/Asura_Logfile.h"
#include "Debugging/Asura_Screenshot_System.h"
#include "Input/Asura_Rumble_System.h"
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Light/Asura_GI_Solution.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

// Platform headers
#include "Core/Asura_Wii_MEM2.h"
#include "Debugging/Asura_Wii_Profiling.h"
#include "HomeButton/Asura_Wii_HomeButtonMenu.h"
#include "LoadAndSave/Asura_Wii_NAND.h"
#include "Rendering/Asura_GX_SmoothSkin.h"
#include "Rendering/Asura_GX_TextureManagement.h"
#include "Rendering/Asura_PlatformRenderWater.h"
#include "Rendering/Asura_Wii_StrapReminder.h"

// Library headers
#include <revolution/gx.h>
#include <revolution/sc.h>

#ifdef ASURA_WII_SUPPORT_SCREENSHOTS
#include "Rendering/Asura_Wii_Screenshot.h"
#endif

// Enabling this #define allows the WII CPU Profiler to be used.  Must make sure that the relevant 
// library is also being linked in. noa_profiler_v0.27_1100KB.a is available with the 
// profiler sent to Kim.
//#define ASURA_WII_NINTENDO_PROFILER

#ifdef ASURA_WII_NINTENDO_PROFILER
#include <revolution/sprofiler.h>
#endif

// Enabling this will spam out every frame how much data was placed into the FIFO as well as
// the largest frame's worth of data.
//#define ASURA_WII_REPORT_DOUBLE_FIFO_SIZES

static const u_int uASURA_GX_FIFO_SIZE = ((1 * 1024) + 512) * 1024;

//#define ASURA_WII_SUPPORT_SPECIAL_ENV_TEXTURES (1)

#ifdef ASURA_WII_SUPPORT_SPECIAL_ENV_TEXTURES
static int g_iRainSplashTexture = ASURA_TEXTURE_NONE;
static int g_iDetailMap = ASURA_TEXTURE_NONE;
static int g_iReflectanceMap = ASURA_TEXTURE_NONE;
static float g_afRainOffset[2];
#endif

bool			Asura_GX::s_bFirstFrame = true;
bool			Asura_GX::s_bWireFrame  = false;
bool			Asura_GX::s_bThreeD		= false;
bool			Asura_GX::s_bResetHasBeenPressed = false;
bool			Asura_GX::s_bPowerHasBeenPressed = false;
bool			Asura_GX::s_bFatalError = false;
bool			Asura_GX::s_bDisableTranslucency = false;
#ifdef ASURA_ASSERT
bool			Asura_GX::s_bRenderStatesValid = true;
#endif
void*			Asura_GX::s_pvFrameBuffer[2];
void*			Asura_GX::s_pvDefaultFifo;
GXRenderModeObj Asura_GX::s_xRenderModeObj;
Mtx				Asura_GX::s_xViewMatrix;
Mtx44			Asura_GX::s_xProjMatrix;
float			Asura_GX::s_fCurrentZOffset;
int				Asura_GX::s_iWhiteTextureIndex = ASURA_TEXTURE_NONE;
Asura_RGBA		Asura_GX::s_rgbaClearcol;

static Asura_Mutex g_xRenderingMutex;

#define DEFAULT_FIFO_SIZE (256 * 1024)
#define ASURA_GX_OVERSCAN_Y_ADJUST 0
#define ASURA_GX_OVERSCAN_X_ADJUST 0
//#define ASURA_GX_OVERSCAN_Y_ADJUST 16
//#define ASURA_GX_OVERSCAN_X_ADJUST 16

const float ASURA_GX_ZNEAR = 0.f;	// near plane Z in screen coordinates
const float ASURA_GX_ZFAR = 1.f;	// far  plane Z in screen coordinates


/*
static void DEMOEnableGPHangWorkaround ( u32 timeoutFrames );
static void __NoHangRetraceCallback ( u32 count );
static void __NoHangDoneRender      ( void );

static void  __DEMODiagnoseHang     ( void );
static void DEMOSetGPHangMetric( GXBool enable );
static void DEMOReInit( GXRenderModeObj *mode );
*/
/*--------------------------------------------------------------------------*/

//#define ASURA_GX_OVERSCAN_Y_ADJUST 16
//#define ASURA_GX_OVERSCAN_X_ADJUST 16

static u_int s_uDimmingCount = 0;
static bool s_bSyncEndOfFrame = FALSE;
static volatile u_int s_uFramesShown;
static volatile u_int s_uFramesShown1;
static volatile u_int s_uFramesRendered;
static volatile u_int s_uFramesQueued;
DVSVAR bool dbg_bUsePostVSync = true;
DVSVAR u_int dbg_u32FrameRateLock = 1;
DVSVAR bool dbg_bEnableTrapDV = 1;

// Draw sync statics
static u16 s_uDrawSyncTokenCPUCnt = 0;
static u16 s_uDrawSyncTokenGPUCnt = 0;
static const u_int s_uDrawSyncTokenBufferMAX = 4096*4;
static u_int* s_puDrawSyncTokenBuffer = NULL;

#ifdef _DEBUG
static bool g_abResetToggle[100];
#endif

#ifdef ASURA_WII_PROFILING
static u_int s_uFrameStartTime;
#endif

#ifndef ASURA_SUBMISSION_BUILD
#define FRAME_LOG
#endif

#ifdef FRAME_LOG

static const u_int NUM_LOGGED_FRAMES = 16;

DEBUGVAR float dbg_fProfileBarsX = 48.0f;
DEBUGVAR float dbg_fProfileBarsY = 374.0f;
DEBUGVAR float dbg_fProfileBarsW = 544.0f;
DEBUGVAR float dbg_fProfileBarsH = 4.0f;
DVSVAR bool dbg_bShowFrameBars = false;
static bool dbg_bEnableTrapInternal = 1;


struct TLogFrameData {
	static bool bDisable;
	void *pFifoPoint;
	u_int uTime_FrameQueued;
	u_int uTime_FrameStart;
	u_int uTime_FrameRendered;
	u_int uTime_FrameShow;
	u_int uTime_FrameGPUPaused;
	u_int uTime_FrameCPUPaused;
	bool bFrameQueued;
	bool bFrameStarted;
	bool bFrameRendered;
	bool bFrameShown;
	bool bFrameGPUPaused;
	bool bFrameCPUPaused;
	bool bBreak;
	bool bBreakQueued;
	u_int uFrameShown;
	u_int uFrameRendered;
	u_int uFrameQueued;
	void *pBreakFifo;

	void Init()
	{
		bFrameQueued = 0;
		bFrameRendered = 0;
		bFrameShown = 0;
		bFrameGPUPaused = 0;
		bFrameCPUPaused = 0;
		bBreak = 0;
		bBreakQueued = 0;
		uFrameShown = s_uFramesShown;
		uFrameQueued = s_uFramesQueued;
		uFrameRendered = s_uFramesRendered;
	}
	void SetQueued()
	{
		if (bDisable)
			return;
		uTime_FrameQueued = OSGetTick();
		bFrameQueued = 1;
	}
	void SetRendered()
	{
		if (bDisable)
			return;
		uTime_FrameRendered = OSGetTick();
		bFrameRendered = 1;
		//Asura_Assert(!bFrameGPUPaused || uTime_FrameGPUPaused > uTime_FrameRendered, "");

	}
	void SetStarted()
	{
		if (bDisable)
			return;
		uTime_FrameStart = OSGetTick();
		bFrameStarted = 1;
	}
	void SetShown()
	{
		if (bDisable)
			return;
		uTime_FrameShow = OSGetTick();
		bFrameShown = 1;
	}
	void SetGPUPaused()
	{
		if (bDisable)
			return;
		uTime_FrameGPUPaused = OSGetTick();
		bFrameGPUPaused = 1;
		Asura_Assert(!bFrameRendered, "");
	}
	void SetCPUPaused()
	{
		if (bDisable)
			return;
		uTime_FrameCPUPaused = OSGetTick();
		bFrameCPUPaused = 1;
	}
	void SetBreak(void*pBreak)
	{
		if (bDisable)
			return;
		bBreak = 1;
		pBreakFifo = pBreak;
	}
	void SetBreakQueued(void*pBreak)
	{
		if (bDisable)
			return;
		bBreakQueued = 1;
		pBreakFifo = pBreak;
	}
	static float CalcTime(u_int uTime, u_int uRefTime)
	{
		return Asura_Timers::TicksToSeconds(OSDiffTick(uTime, uRefTime)) * 60.0f;
	}
	void Dump(u_int &uRefTime)
	{
		if (bFrameQueued)
		{
			if (uRefTime == 0)
			{
				uRefTime = uTime_FrameQueued;
				if (uRefTime == 0)
					uRefTime = 1;
			}
			Asura_OutputToDebugger_Retail(("********** xFrame[%d] %d %d %d\n", uFrameQueued, uFrameQueued, uFrameRendered, uFrameShown));
			Asura_OutputToDebugger_Retail(("xFrame[%d].uFrameQueued = %f\n", uFrameQueued, CalcTime(uTime_FrameQueued, uRefTime)));
			Asura_OutputToDebugger_Retail(("xFrame[%d].pfifo = %x\n", uFrameQueued, pFifoPoint));
			if (bBreak)
				Asura_OutputToDebugger_Retail(("GPU Break set %x\n", pBreakFifo));
			if (bBreakQueued)
				Asura_OutputToDebugger_Retail(("GPU Queued Break set %x\n", pBreakFifo));

		}
		if (bFrameStarted)
		{
			Asura_OutputToDebugger_Retail(("xFrame[%d].uStarted = %f\n", uFrameQueued, CalcTime(uTime_FrameStart, uRefTime)));
		}
		if (bFrameCPUPaused)
		{
			Asura_OutputToDebugger_Retail(("xFrame[%d].uCPUPaused = %f\n", uFrameQueued, CalcTime(uTime_FrameCPUPaused, uRefTime)));
		}
		if (bFrameGPUPaused)
		{
			Asura_OutputToDebugger_Retail(("xFrame[%d].uGPUPaused = %f\n", uFrameQueued, CalcTime(uTime_FrameGPUPaused, uRefTime)));
		}
		if (bFrameRendered)
		{
			Asura_OutputToDebugger_Retail(("xFrame[%d].uRendered = %f\n", uFrameQueued, CalcTime(uTime_FrameRendered, uRefTime)));
		}
		if (bFrameShown)
		{
			Asura_OutputToDebugger_Retail(("xFrame[%d].uShown = %f\n", uFrameQueued, CalcTime(uTime_FrameShow, uRefTime)));
		}

	}

	static u_int RenderBar(u_int uStartTime, u_int uEndTime, u_int uBarStartTime, u_int uBarEndTime, u_int uRow)
	{
		if (uStartTime - uBarEndTime < uBarEndTime - uStartTime)
		{
			return -1;
		} 
		if (uBarStartTime - uEndTime < uEndTime - uBarStartTime)
		{
			return 1;
		}

		if (uStartTime - uBarStartTime < uBarStartTime - uStartTime)
		{
			uBarStartTime = uStartTime;
		} 
		if (uBarEndTime - uEndTime < uEndTime - uBarEndTime)
		{
			uBarEndTime = uEndTime;
		} 
		float fPos = ((float)(uBarStartTime - uStartTime)) / ((float)(uEndTime - uStartTime));
		float fWidth = ((float)(uBarEndTime - uBarStartTime)) / ((float)(uEndTime - uStartTime));

		if (fPos + fWidth > 1.0f)
			fWidth = 1.0f - fPos;


		float fX = dbg_fProfileBarsX + dbg_fProfileBarsW * fPos;
		float fW = dbg_fProfileBarsW * fWidth;
		Asura_Render::Overlay.SetRectXY(fX, dbg_fProfileBarsY + uRow * 6, fW, 4);
		Asura_Render::Overlay.Render();

		return 0;
	}

	static void Render(TLogFrameData *pxFrameLog)
	{
		static u_int uFrameDropCnt = 60;

		// Use material to set colour of sprites.
		Asura_Render::CurrentStates.RequestTextureIndex( 0, ASURA_TEXTURE_NONE );
		Asura_Render::CurrentStates.RequestZBufferEnabled(false);
		Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF ); 

		static u_int s_uFrameColours[] =
		{
			0xffFF0000,
			0xff00FF00,
			0xff0000FF,
			0xffFFFF00,
			0xffFF00FF,
			0xff00FFFF,

			0xff770000,
			0xff007700,
			0xff000077,
			0xff777700,
			0xff770077,
			0xff007777,
		};
		if (pxFrameLog)
		{
			u_int uSelectedFrame = 0xFFFFFFFF;
			for (int iFrame = NUM_LOGGED_FRAMES - 4; iFrame > 0 && uSelectedFrame == 0xFFFFFFFF; --iFrame)
			{
				TLogFrameData *pxFrameLogRunner = pxFrameLog + ((s_uFramesShown + iFrame) % NUM_LOGGED_FRAMES);

				if (pxFrameLogRunner->bFrameQueued && pxFrameLogRunner->bFrameShown && (pxFrameLogRunner-1)->bFrameQueued)
				{
					uSelectedFrame = iFrame;
				}
			}
			if (uSelectedFrame == 0xFFFFFFFF)
				return;

			TLogFrameData *pxFrameLogSelected = pxFrameLog + ((s_uFramesShown + uSelectedFrame) % NUM_LOGGED_FRAMES);
			TLogFrameData *pxFrameLogPrevSelected = pxFrameLog + ((s_uFramesShown + uSelectedFrame - 1) % NUM_LOGGED_FRAMES);

			u_int uStartTime = pxFrameLogPrevSelected->uTime_FrameQueued;
			u_int uDuration = pxFrameLogSelected->uTime_FrameShow - uStartTime;
			const float fMultiplier = 60.0f / ((float)OS_TIMER_CLOCK);
			static u_int uNumSegments =  1;
			u_int uThisNumSegs = (uDuration * 60) / OS_TIMER_CLOCK + 1;

			if (uThisNumSegs > uNumSegments)
			{
				uNumSegments = uThisNumSegs;
				uFrameDropCnt = 60;
			}
			else if (uThisNumSegs == uNumSegments){
				uFrameDropCnt = 60;
			} else {
				if (uFrameDropCnt == 0)
				{
					uFrameDropCnt = 60;
					--uNumSegments;
				} else {
					--uFrameDropCnt;
				}
			}

			if (uNumSegments > 8)
				uNumSegments = 8;

			uDuration = (uNumSegments * OS_TIMER_CLOCK) / 60;

			const float fOneOverDivisions = 1.0f / static_cast<float>(uNumSegments);
			const float fDivisionWidth = dbg_fProfileBarsW * fOneOverDivisions;

			// Use material to set colour of sprites.
			Asura_Render::CurrentStates.RequestTextureIndex( 0, ASURA_TEXTURE_NONE );
			Asura_Render::CurrentStates.RequestZBufferEnabled(false);
			Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF ); 

			// render background divisions
			{
				for (int i=0; i<uNumSegments; i++)
				{
					const float fX = dbg_fProfileBarsX + (fDivisionWidth * static_cast<float>(i));
					Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
					Asura_Render::Overlay.Colour = (i & 1) ? 0xff333333 : 0xff666666;
					Asura_Render::Overlay.SetRectXY(fX, dbg_fProfileBarsY, fDivisionWidth, 20);
					Asura_Render::Overlay.Render();
				}
			}


			for (int iFrame = NUM_LOGGED_FRAMES - 1; iFrame > 1; --iFrame)
			{
				TLogFrameData *pxFrameLogRunner = pxFrameLog + ((s_uFramesShown + iFrame) % NUM_LOGGED_FRAMES);
				TLogFrameData *pxFrameLogPrev = pxFrameLog + ((s_uFramesShown + iFrame - 1) % NUM_LOGGED_FRAMES);

				if (pxFrameLogRunner->bFrameQueued && pxFrameLogPrev->bFrameQueued)
				{
					u_int uQueueStartTime = pxFrameLogPrev->uTime_FrameQueued;
					u_int uQueueEndTime = (pxFrameLogRunner->bFrameCPUPaused ? pxFrameLogRunner->uTime_FrameCPUPaused : pxFrameLogRunner->uTime_FrameQueued);

					Asura_Render::Overlay.Colour = s_uFrameColours[iFrame % 6];
					RenderBar(uStartTime, uStartTime + uDuration, uQueueStartTime, uQueueEndTime, 0);

					if (pxFrameLogPrev->bFrameCPUPaused)
					{
						Asura_Render::Overlay.Colour = s_uFrameColours[iFrame % 6 + 6];
						RenderBar(uStartTime, uStartTime + uDuration, pxFrameLogRunner->uTime_FrameCPUPaused, pxFrameLogRunner->uTime_FrameQueued, 0);
					}
				}
				if (pxFrameLogRunner->bFrameRendered && pxFrameLogPrev->bFrameRendered)
				{
					u_int uQueueEndTime = pxFrameLogRunner->bFrameGPUPaused ? pxFrameLogRunner->uTime_FrameGPUPaused : pxFrameLogRunner->uTime_FrameRendered;

					Asura_Render::Overlay.Colour = s_uFrameColours[iFrame % 6];
					RenderBar(uStartTime, uStartTime + uDuration, pxFrameLogRunner->uTime_FrameStart, uQueueEndTime, 1);
					if (pxFrameLogPrev->bFrameGPUPaused)
					{
						Asura_Render::Overlay.Colour = s_uFrameColours[iFrame % 6 + 6];
						RenderBar(uStartTime, uStartTime + uDuration, pxFrameLogRunner->uTime_FrameGPUPaused, pxFrameLogRunner->uTime_FrameRendered, 1);

					}
				}
				if (pxFrameLogRunner->bFrameShown && pxFrameLogRunner->bFrameRendered)
				{
					Asura_Render::Overlay.Colour = s_uFrameColours[iFrame % 6 + 6];
					if (RenderBar(uStartTime, uStartTime + uDuration, pxFrameLogRunner->uTime_FrameRendered, pxFrameLogRunner->uTime_FrameShow, 2) < 0)
					{
						break;
					}
				} 
			}
		}
	}

};

TLogFrameData s_xFrameLog[NUM_LOGGED_FRAMES];
bool TLogFrameData::bDisable = false;

static bool bDumpFrameData;
#endif// FRAME_LOG

static void DrawDoneCallback(void)
{
}
static void BreakCallback(void)
{
#ifdef FRAME_LOG
	s_xFrameLog[s_uFramesRendered % NUM_LOGGED_FRAMES].SetGPUPaused();
#endif
}

void Asura_GX::AddDrawSyncCallback(GPU_CALLBACK_TOKEN uToken)
{
	s_puDrawSyncTokenBuffer[s_uDrawSyncTokenCPUCnt] = (u32)uToken;
	s_uDrawSyncTokenCPUCnt = ( s_uDrawSyncTokenCPUCnt + 1) % s_uDrawSyncTokenBufferMAX;
	GXSetDrawSync(s_uDrawSyncTokenCPUCnt);
}

// This function gets called every time a Draw Sync token is received.  A special token
// is dropped onto the end of a frame's worth of data.
static void DrawSyncCallback( u16 uTokenBuf )
{
	if (uTokenBuf >= 0x4444)
	{
		// Bink!
		return;
	}
	for (;s_uDrawSyncTokenGPUCnt != uTokenBuf; s_uDrawSyncTokenGPUCnt = ( s_uDrawSyncTokenGPUCnt + 1) % s_uDrawSyncTokenBufferMAX)
	{
		u32 uToken = s_puDrawSyncTokenBuffer[s_uDrawSyncTokenGPUCnt];
		switch (uToken)
		{
		case Asura_GX::GPU_CALLBACK_SYNC:
			return;
			break;
		case Asura_GX::GPU_CALLBACK_STARTFRAME:
#ifdef FRAME_LOG
			s_xFrameLog[s_uFramesRendered % NUM_LOGGED_FRAMES].SetStarted();
#endif
			break;
		case Asura_GX::GPU_CALLBACK_ENDFRAME:
#ifdef FRAME_LOG
			s_xFrameLog[s_uFramesRendered % NUM_LOGGED_FRAMES].SetRendered();
#endif
			++s_uFramesRendered;
			break;
		case Asura_GX::GPU_CALLBACK_SMOOTHSKIN_SYNC:
			Asura_GX_SmoothSkin_RenderBuffer::IncrementGPUPtr();
			break;
		default:
#ifdef ASURA_WII_PROFILING
			if (uToken >= Asura_GX::GPU_CALLBACK_PROFILE_START_FIRST && uToken < Asura_GX::GPU_CALLBACK_PROFILE_START_LAST)
				Asura_Wii_Profiling::StartGPUProfile(uToken - Asura_GX::GPU_CALLBACK_PROFILE_START_FIRST);
			else if (uToken >= Asura_GX::GPU_CALLBACK_PROFILE_STOP_FIRST && uToken < Asura_GX::GPU_CALLBACK_PROFILE_STOP_LAST)
				Asura_Wii_Profiling::StopGPUProfile(uToken - Asura_GX::GPU_CALLBACK_PROFILE_STOP_FIRST);
#endif
			break;

		}

	}
}

static volatile s32 s_iDVDCheckResult = 0;
static volatile bool s_bDVDCheckComplete = false;
static void DVDCheckCallbackFunction( s32 iResult, DVDCommandBlock* pxBlock)
{
	s_iDVDCheckResult = iResult;
	s_bDVDCheckComplete = true;
}

template <typename T, unsigned int Size> class LocalFifo
{
	T m_Storage[Size];
	volatile u_int m_uReadPtr;
	volatile u_int m_uWritePtr;
public:
	LocalFifo() : m_uReadPtr(0), m_uWritePtr(0)
	{

	}
	void Push(T t)
	{
		while ((m_uWritePtr + 1) % Size == m_uReadPtr)
			OSYieldThread();
		m_Storage[m_uWritePtr] = t;
		m_uWritePtr = (m_uWritePtr + 1) % Size;
	}
	T Pop()
	{
		T t = m_Storage[m_uReadPtr];
		m_uReadPtr = (m_uReadPtr + 1) % Size;
		return t;
	}
	u_int Available()
	{
		return (m_uWritePtr - m_uReadPtr) % Size;
	}
};

LocalFifo<void*, 1024> m_FramePoints;

// This function tracks when a retrace comes back.  When a frame end token has been 
// received AND a retrace has begun then we can initiate the next frame.  The 
// VISetNextFrameBuffer could easily be modified to give support for triple buffering.

static bool s_bIncrementShown = false;
void Asura_GX::RetraceCallback(u32 retraceCount)
{
	if ( s_uFramesRendered != s_uFramesShown1 && (dbg_u32FrameRateLock < 2|| (retraceCount % dbg_u32FrameRateLock == 0)))
	{
		VISetNextFrameBuffer( s_pvFrameBuffer[s_uFramesShown1 & 1] );
		VIFlush();
		s_uFramesShown1++;

		if (!dbg_bUsePostVSync)
		{
			RetraceCallback2(retraceCount);
		}

	}
}

void Asura_GX::RetraceCallback2(u32 retraceCount)
{
	if (s_uFramesShown1 != s_uFramesShown)
	{
#ifdef FRAME_LOG
		s_xFrameLog[s_uFramesShown % NUM_LOGGED_FRAMES].SetShown();
#endif
		s_uFramesShown++;

		if (m_FramePoints.Available())
		{
			GXEnableBreakPt(m_FramePoints.Pop());
		} else {
			GXDisableBreakPt();
		} 
	}
}


Asura_RGBA Asura_GX::GetClearColour()
{
	return s_rgbaClearcol;
}
void Asura_GX::SetClearColour(Asura_RGBA rgbaNewClear)
{
	s_rgbaClearcol = rgbaNewClear;
	GXColor gxClearCol = 
	{
		static_cast<u_char>(s_rgbaClearcol.m_fR * 255.0f), 
		static_cast<u_char>(s_rgbaClearcol.m_fG * 255.0f), 
		static_cast<u_char>(s_rgbaClearcol.m_fB * 255.0f), 
		static_cast<u_char>(s_rgbaClearcol.m_fA * 255.0f)
	};
	GXSetCopyClear(gxClearCol, GX_MAX_Z24);
}



void VerifyCallback( GXWarningLevel xWarningLevel, u_long uId, const char* pszMsg )
{
	OSReport( "Level %d, Warning %03d: %s\n", xWarningLevel, uId, pszMsg );
}

static GXFifoObj*   pxDefaultFifoObj;

Asura_GX::VertexFormatSetupCB Asura_GX::s_pfnVertexFormatSetupCB = NULL;

void Asura_GX::InitVertexFormats(void)
{
	//	GXSetVtxAttrFmt( VAT index, attribute,	n elements,		format,		fractional bits);

	// Low fat env vertices
	GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS,	GX_POS_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM,	GX_NRM_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGB,		GX_RGBA6,	0);
	GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST,		GX_S16,		10);
	GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX1, GX_TEX_ST,		GX_S16,		10);

	// Anorexic hierarchy vertices
	GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS,	GX_POS_XYZ,		GX_S16,		10);
	GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM,	GX_NRM_XYZ,		GX_S16,		14);
	GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST,		GX_S16,		10);

	// 2D vertices for overlay quads
	GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_POS,	GX_POS_XY,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_CLR0,	GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_TEX0,	GX_TEX_ST,		GX_F32,		0);
	// Secondary texcoords used for depth of field
	GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_TEX1, GX_TEX_ST,		GX_F32,		0);

	// 3D vertices for everything else
	GXSetVtxAttrFmt(GX_VTXFMT3, GX_VA_POS,	GX_POS_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT3, GX_VA_NRM,	GX_NRM_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT3, GX_VA_CLR0, GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT3, GX_VA_TEX0, GX_TEX_ST,		GX_F32,		0);

	// Cut down texture co-ords for characters, but still float position/normal
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_POS,	GX_POS_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_NRM,	GX_NRM_XYZ,		GX_F32,		0);
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_CLR0, GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_CLR1, GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_TEX0, GX_TEX_ST,		GX_S16,		10);
	// Second set of texture co-ordinates for graphic novel renderer
	GXSetVtxAttrFmt(GX_VTXFMT4, GX_VA_TEX1, GX_TEX_ST,		GX_F32,		0);

	GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_POS,	GX_POS_XYZ,		GX_S16,		10);
	GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_CLR0, GX_CLR_RGBA,	GX_RGBA8,	0);
	GXSetVtxAttrFmt(GX_VTXFMT6, GX_VA_TEX0, GX_TEX_ST,		GX_S16,		10);


	if (s_pfnVertexFormatSetupCB != NULL)
	{
		s_pfnVertexFormatSetupCB();
	}

}

/* NTSC/EURGB60 */
#define NTSC_VI_WIDTH_WIDE          (686)
#define NTSC_VI_WIDTH_STD           (670)
#define NTSC_VI_HEIGHT              (456)
#define NTSC_VI_X_ORIGIN_WIDE       ((u16)((VI_MAX_WIDTH_NTSC - NTSC_VI_WIDTH_WIDE)/2))
#define NTSC_VI_X_ORIGIN_STD        ((u16)((VI_MAX_WIDTH_NTSC - NTSC_VI_WIDTH_STD)/2))
#define NTSC_VI_Y_ORIGIN            ((u16)((VI_MAX_HEIGHT_NTSC - NTSC_VI_HEIGHT)/2))

/* PAL */
#define PAL_VI_WIDTH_WIDE           (682)
#define PAL_VI_WIDTH_STD            (666)
#define PAL_VI_HEIGHT               (542)
#define PAL_VI_X_ORIGIN_WIDE        ((u16)((VI_MAX_WIDTH_PAL - PAL_VI_WIDTH_WIDE)/2))
#define PAL_VI_X_ORIGIN_STD         ((u16)((VI_MAX_WIDTH_PAL - PAL_VI_WIDTH_STD)/2))
#define PAL_VI_Y_ORIGIN             ((u16)((VI_MAX_HEIGHT_PAL - PAL_VI_HEIGHT)/2))

static bool s_bInited = false;

DEBUGVAR int dbg_iVerifyLevel = GX_WARN_NONE;
void Asura_GX::Initialise(void)
{
	/// Kim Randell - 03/02/2003 14:58:50 - Shamelessly copied from DEMOInit.c provided by Nintendo...
	/// Should get us going quickly though and can be tweaked later
    u32 ufFrameBufferSize;
    u16     uFrameBufferHeight;
    f32     fYScale;
	// Broken
	const bool bFSAA = false;

	bool bUseNtscScreenSize = false; // Used by NTSC and EURGB60
	u32 viTVFormat = VIGetTvFormat();
	// PAL
	// The following code queries the Console system settings and then makes a choice of what modes to 
	// use based off of that.  For progressive mode it also checks that the cable is in.  If the settings
	// say use progressive but the progressive cable is not present then it drops back to a standard
	// interlaced mode.  This is a LOT requirement.
#if ASURA_GX_PAL
#ifdef ASURA_ASSERT
	// Assert doesnt work here!
//	if (viTVFormat  != VI_PAL && viTVFormat != VI_EURGB60 && viTVFormat != VI_MPAL)
//	{
//		OSReport("PAL Build running on NTSC Hardware");
//		Asura_Wii_Debug_Break();
//	}
#endif

		if ( SCGetProgressiveMode() == SC_PROGRESSIVE_MODE_ON && VIGetDTVStatus() )
		{
 
 			// Non-AA 480p
 			GXAdjustForOverscan( &GXEurgb60Hz480Prog, &s_xRenderModeObj, ASURA_GX_OVERSCAN_X_ADJUST, ASURA_GX_OVERSCAN_Y_ADJUST );
 		}
 		else
 		{
 			if ( SCGetEuRgb60Mode() == SC_EURGB60_MODE_ON )
 			{
 				// EURGB60
 				GXAdjustForOverscan( &GXEurgb60Hz480IntDf, &s_xRenderModeObj, ASURA_GX_OVERSCAN_X_ADJUST, ASURA_GX_OVERSCAN_Y_ADJUST );
			bUseNtscScreenSize = true;
 			}
 			else
 			{
 				// PAL
 				GXAdjustForOverscan( &GXPal528IntDf, &s_xRenderModeObj, ASURA_GX_OVERSCAN_X_ADJUST, ASURA_GX_OVERSCAN_Y_ADJUST );
 			}
		}

#else
#ifdef ASURA_ASSERT
	// Assert doesnt work here!
	if (viTVFormat != VI_NTSC && viTVFormat != VI_MPAL)
	{
		OSReport("NTSC Build running on PAL Hardware");
		Asura_Wii_Debug_Break();
	}
#endif

		if ( SCGetProgressiveMode() == SC_PROGRESSIVE_MODE_ON && VIGetDTVStatus() )
		{
			// 480p
			GXAdjustForOverscan( &GXNtsc480Prog, &s_xRenderModeObj, ASURA_GX_OVERSCAN_X_ADJUST, ASURA_GX_OVERSCAN_Y_ADJUST );
		}
		else
		{
			// NTSC
			GXAdjustForOverscan( &GXNtsc480IntDf, &s_xRenderModeObj, ASURA_GX_OVERSCAN_X_ADJUST, ASURA_GX_OVERSCAN_Y_ADJUST );
		}

	bUseNtscScreenSize = true;

#endif
	VIInit();
#ifdef FRAME_LOG
	VISetTrapFilter((dbg_bEnableTrapInternal = dbg_bEnableTrapDV) == true ? VI_ENABLE : VI_DISABLE);
#else
	VISetTrapFilter((dbg_bEnableTrapDV) == true ? VI_ENABLE : VI_DISABLE);
#endif
    ///////////// Init framebuffer

	//ufFrameBufferSize = VIPadFrameBufferWidth(s_xRenderModeObj.fbWidth) * s_xRenderModeObj.xfbHeight * (u32)VI_DISPLAY_PIX_SZ;
	// Always allocate a PAL size framebuffer, so that we know when we've gone over the limit.
	ufFrameBufferSize = VIPadFrameBufferWidth(GXPal528IntDf.fbWidth) * GXPal528IntDf.xfbHeight * (u32)VI_DISPLAY_PIX_SZ;
	s_pvFrameBuffer[0] = ASURA_WII_MEM2_ALLOC(ufFrameBufferSize);
    s_pvFrameBuffer[1] = ASURA_WII_MEM2_ALLOC(ufFrameBufferSize);

	const bool bWidescreen = (SCGetAspectRatio() == SC_ASPECT_RATIO_16x9);

	if ( bUseNtscScreenSize )
	{
		if ( bWidescreen )
		{
			s_xRenderModeObj.viWidth = NTSC_VI_WIDTH_WIDE;
			s_xRenderModeObj.viXOrigin = NTSC_VI_X_ORIGIN_WIDE;
		}
		else
		{
			s_xRenderModeObj.viWidth = NTSC_VI_WIDTH_STD;
			s_xRenderModeObj.viXOrigin = NTSC_VI_X_ORIGIN_STD;
		}
	}
	else
	{
		if ( bWidescreen )
		{
			s_xRenderModeObj.viWidth = PAL_VI_WIDTH_WIDE;
			s_xRenderModeObj.viXOrigin = PAL_VI_X_ORIGIN_WIDE;
		}
		else
		{
			s_xRenderModeObj.viWidth = PAL_VI_WIDTH_STD;
			s_xRenderModeObj.viXOrigin = PAL_VI_X_ORIGIN_STD;
		}
	}

	Asura_Viewport::Set(s_xRenderModeObj.fbWidth,s_xRenderModeObj.xfbHeight);

#ifdef ASURA_WII_SUPPORT_SCREENSHOTS
	SCREENSHOTSetRenderModeObj(&s_xRenderModeObj);
#endif

	VIConfigure(&s_xRenderModeObj);
    ///////////// Init FIFO

	// Initialise double buffered fifo.  
    s_pvDefaultFifo = Asura_AllocateAlign( uASURA_GX_FIFO_SIZE, ASURA_ALIGN_32_BYTE);
	DCInvalidateRange( s_pvDefaultFifo, uASURA_GX_FIFO_SIZE );

	// Initialise a temporary FIFO with GXInit which will, actually, get freed up as soon as i have moved the above
	// FIFO over the top of it.
	void* pFifoTemp	= Asura_AllocateAlign( GX_FIFO_MINSIZE, ASURA_ALIGN_32_BYTE );
	GXInit( pFifoTemp, GX_FIFO_MINSIZE );

	// Set up the FIFO Object.
	pxDefaultFifoObj	= new GXFifoObj;
	GXInitFifoBase( pxDefaultFifoObj, s_pvDefaultFifo, uASURA_GX_FIFO_SIZE );

	// Set up GX to point the CPU write and GP read pointers to be the same.
	GXSetCPUFifo( pxDefaultFifoObj );
	GXSetGPFifo( pxDefaultFifoObj );

	// Grab the read and write pointers.  
	void* pxReadPtr;
	void* pxWritePtr;
	GXGetFifoPtrs( pxDefaultFifoObj, &pxReadPtr, &pxWritePtr );

	// Deallocate the temporary FIFO buffer.
	Asura_DeallocateAlign( pFifoTemp );

	// Set up a handler for Draw Sync commands.  Allows me to know exactly when a frame ends.
	GXSetDrawSyncCallback( DrawSyncCallback );
	GXSetDrawDoneCallback( DrawDoneCallback );
	GXSetBreakPtCallback( BreakCallback );

    ///////////// Init GX

    /*----------------------------------------------------------------*
     *  GX configuration by a render mode obj                         *
     *----------------------------------------------------------------*/

    // These are all necessary function calls to take a render mode
    // object and set up the relevant GX configuration.

    GXSetViewport(0.0F, 0.0F, (f32)s_xRenderModeObj.fbWidth, (f32)s_xRenderModeObj.efbHeight, ASURA_GX_ZNEAR, ASURA_GX_ZFAR);
    GXSetScissor(0, 0, (u32)s_xRenderModeObj.fbWidth, (u32)s_xRenderModeObj.efbHeight);

    fYScale = GXGetYScaleFactor(s_xRenderModeObj.efbHeight, s_xRenderModeObj.xfbHeight);
    uFrameBufferHeight = (u16)GXSetDispCopyYScale(fYScale);
    GXSetDispCopySrc(0, 0, s_xRenderModeObj.fbWidth, s_xRenderModeObj.efbHeight);
    GXSetDispCopyDst(s_xRenderModeObj.fbWidth, uFrameBufferHeight);
    GXSetCopyFilter(s_xRenderModeObj.aa, s_xRenderModeObj.sample_pattern, GX_TRUE, s_xRenderModeObj.vfilter);

    // Note that following is an appropriate setting for full-frame AA mode.
    // You should use "xfbHeight" instead of "efbHeight" to specify actual
    // view size. Since this library doesn't support such special case, please
    // call these in each application to override the normal setting.
	if(bFSAA)
	{
		GXSetViewport(0.0F, 0.0F, (f32)s_xRenderModeObj.fbWidth, (f32)s_xRenderModeObj.xfbHeight, ASURA_GX_ZNEAR, ASURA_GX_ZFAR);
		GXSetDispCopyYScale(1.0F);
	}

    if (s_xRenderModeObj.aa)
        GXSetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);
    else
        GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
        //GXSetPixelFmt(GX_PF_RGBA6_Z24, GX_ZC_LINEAR);

    /*----------------------------------------------------------------*
     *  Miscellaneous GX initialization for demos                     *
     *  (Since these are not actually necessary for all applications, *
     *  you may remove them if you feel they are unnecessary.)        *
     *----------------------------------------------------------------*/

    // Clear embedded framebuffer for the first frame
    GXCopyDisp(s_pvFrameBuffer[0], GX_TRUE);
	GXCopyDisp(s_pvFrameBuffer[1], GX_TRUE);

    // Verify (warning) messages are turned off by default
#ifdef _DEBUG
    GXSetVerifyLevel(static_cast<GXWarningLevel>(dbg_iVerifyLevel));
	GXSetVerifyCallback( VerifyCallback );
#endif

    // Gamma correction
    //
    // You may want to use 1.7 or 2.2 if all artwork made on PCs
    // become darker. However, recommended setting is 1.0.
    GXSetDispCopyGamma(GX_GM_1_0);

    ///////////// Init VI
	
	// Double-buffering initialization
    VISetNextFrameBuffer(s_pvFrameBuffer[1]);

    // Tell VI device driver to write the current VI settings so far
    VIFlush();
    
    // Wait for retrace to start first frame
    VIWaitForRetrace();

    // Because of hardware restriction, we need to wait one more 
    // field to make sure mode is safely changed when we change
    // INT->DS or DS->INT. (VIInit() sets INT mode as a default)
    if ((u32)s_xRenderModeObj.viTVmode & 1)
        VIWaitForRetrace();

	VISetBlack(true);

	GXColor black = {0, 0, 0, 0};
	GXSetCopyClear(black, GX_MAX_Z24);


	InitVertexFormats();

    VIFlush();

	// Set the callback to activate off the leading edge of the VSYNC pulse.  The VSYNC is a, comparitively, long event.
	VISetPreRetraceCallback(&RetraceCallback);
	VISetPostRetraceCallback(&RetraceCallback2);

	s_puDrawSyncTokenBuffer = new u_int[s_uDrawSyncTokenBufferMAX];

	Asura_Render::CurrentStates.ResetAll();

	Asura_Wii_Profiling::Initialise();

	// [OF] 20/03/07 - See the note at the start of Asura_GX::CopyBuffer.
	//DEMOEnableGPHangWorkaround(4);

	VIEnableDimming(true);
#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
	Asura_CommandConsole::AddVar(L"Wii.SyncEOF", &s_bSyncEndOfFrame, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddVar(L"Wii.DimmingCount", &s_uDimmingCount, 0, UINT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
#ifdef FRAME_LOG
	Asura_CommandConsole::AddVar(L"Wii.ShowFrameBars", &dbg_bShowFrameBars , Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddVar(L"Wii.DumpFrameData", &bDumpFrameData, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddVar(L"Wii.VSyncsPerFrame", &dbg_u32FrameRateLock, 1, 3, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddVar(L"Wii.UsePostVSync", &dbg_bUsePostVSync, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddVar(L"Wii.UseTrapFilter", &dbg_bEnableTrapDV, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
#endif
#endif
	s_bInited = true;

	Asura_Wii_StrapReminder::Initialise();

	Asura_Wii_StrapReminder::SetInsideStrapReminder(true);
	// Initialise the home menu.
	Asura_Wii_HomeButtonMenu::Initialise();
	Asura_Wii_StrapReminder::SetInsideStrapReminder(false);
#ifdef _DEBUG
	Asura_Mem::Set(g_abResetToggle, 1, sizeof(g_abResetToggle));
#endif
}

void Asura_GX::WaitForRenderDone()
{
	GXPixModeSync();
	GXFlush();
	while (s_uFramesQueued != s_uFramesShown)
		OSYieldThread();
	GXDrawDone();
	VIWaitForRetrace();
	VIWaitForRetrace();
	VIWaitForRetrace();
	VIWaitForRetrace();
}
void *Asura_GX::GetFrameBuffer(u_int uSwap)
{
	void *pBuf = s_pvFrameBuffer[(s_uFramesShown+1+uSwap) & 1];

	DCInvalidateRange(pBuf, GetScreenHeight() * GetScreenStride());

	return pBuf;
}


u_int Asura_GX::GetScreenHeight()
{
	Asura_Assert(s_bInited, "");
	return s_xRenderModeObj.efbHeight;
}

u_int Asura_GX::GetScreenWidth()
{
	Asura_Assert(s_bInited, "");
	return s_xRenderModeObj.fbWidth;
}
u_int Asura_GX::GetScreenStride()
{
	Asura_Assert(s_bInited, "");
	return VIPadFrameBufferWidth(s_xRenderModeObj.fbWidth) * VI_DISPLAY_PIX_SZ;
}


void Asura_GX::FlushFrame()
{
	if (!ReadyToDraw())
		return;
	GXFlush();
}

void Asura_GX::ClearZBuffer()
{
	/// Kim Randell - 18/03/2003 11:25:50 - There doesn't appear to be a GX function to do this
	s_fCurrentZOffset -= 0.5f;
	GXSetZScaleOffset(1.0f, s_fCurrentZOffset);
}

void Asura_GX::SetViewingArea()
{
	static bool bReEntrant = false;
	const Asura_ViewingArea* area = Asura_Camera::GetViewingArea();

	if (bReEntrant)
		return;

	bReEntrant = true;

	if (area->m_fWidth < 0.1f || area->m_fHeight < 0.1f)
	{
		static Asura_ViewingArea xDefault(0.0f, 0.0f, 1.0f, 1.0f);
		area = &xDefault;
	}

	const Asura_Vector_2&	xSubPixelOffset		= Asura_Render::GetSubPixelOffset();

	GXSetViewport(
		Asura_Maths::FloatToInt(Asura_Viewport::GetWidth() * area->m_fXOffset) + xSubPixelOffset.x,
		Asura_Maths::FloatToInt(Asura_Viewport::GetHeight() * area->m_fYOffset) + xSubPixelOffset.y,
		Asura_Maths::FloatToInt(Asura_Viewport::GetWidth() * area->m_fWidth),
		Asura_Maths::FloatToInt(Asura_Viewport::GetHeight() * area->m_fHeight),
		ASURA_GX_ZNEAR, ASURA_GX_ZFAR);
	GXSetScissor(
		Asura_Maths::FloatToInt(Asura_Viewport::GetWidth() * area->m_fXOffset),
		Asura_Maths::FloatToInt(Asura_Viewport::GetHeight() * area->m_fYOffset),
		Asura_Maths::FloatToInt(Asura_Viewport::GetWidth() * area->m_fWidth),
		Asura_Maths::FloatToInt(Asura_Viewport::GetHeight() * area->m_fHeight)
		);
	Set3DProjection(true, true);

	bReEntrant = false;

}

void Asura_GX::Set3DProjection(bool bThreeDee, bool bForceUpdate /* = false */)
{
	if(s_bThreeD == bThreeDee && !bForceUpdate) return;

	const Asura_ViewingArea* pxArea = Asura_Camera::GetViewingArea();

	s_bThreeD = bThreeDee;


	if(s_bThreeD)
	{
		// TODO - cache per camera per frame
		const Asura_Vector_3* xPosition = Asura_Camera::GetPosition();
		const Asura_Matrix_3x3* xOrientation= Asura_Camera::GetOrientation();
		Mtx xRot, xTrans;

		if(!Asura_Camera::GetXOverYAspectRatio() || Asura_Camera::GetVerticalFieldOfView() <= 0.0f || Asura_Camera::GetVerticalFieldOfView() > 3.141592654f)
		{
			// Default
			MTXPerspective(s_xProjMatrix, 75.0f, 4.0f/3.0f, 0.1, 2000);
		}
		else
		{
			s_xProjMatrix[0][0] = Asura_Camera::GetOneOverTanHalfFovX();
			s_xProjMatrix[0][1] = 0.f;
			s_xProjMatrix[0][2] = 0.f;
			s_xProjMatrix[0][3] = 0.f;
			s_xProjMatrix[1][0] = 0.f;
			s_xProjMatrix[1][1] = Asura_Camera::GetOneOverTanHalfFovX() * Asura_Camera::GetPhysicalScreenAspectRatio() * pxArea->GetAspectRatio();
			s_xProjMatrix[1][2] = 0.f;
			s_xProjMatrix[1][3] = 0.f;
			s_xProjMatrix[2][0] = 0.f;
			s_xProjMatrix[2][1] = 0.f;
			s_xProjMatrix[2][2] = -fASURA_GX_NEAR_CLIP / (fASURA_GX_FAR_CLIP - fASURA_GX_NEAR_CLIP);
			s_xProjMatrix[2][3] = -fASURA_GX_NEAR_CLIP;
			s_xProjMatrix[3][0] = 0.f;
			s_xProjMatrix[3][1] = 0.f;
			s_xProjMatrix[3][2] = -1.f;
			s_xProjMatrix[3][3] = 0.f;
		}
		if(Asura_Render::ShouldRenderMirrored())
		{
			s_xProjMatrix[0][0] = -s_xProjMatrix[0][0];
		}

		GXSetProjection(s_xProjMatrix, GX_PERSPECTIVE);

		// Transposed
		MTXRowCol(xRot, 0, 0) = xOrientation->e11;
		MTXRowCol(xRot, 1, 0) = -xOrientation->e12;
		MTXRowCol(xRot, 2, 0) = -xOrientation->e13;
		MTXRowCol(xRot, 0, 1) = xOrientation->e21;
		MTXRowCol(xRot, 1, 1) = -xOrientation->e22;
		MTXRowCol(xRot, 2, 1) = -xOrientation->e23;
		MTXRowCol(xRot, 0, 2) = xOrientation->e31;
		MTXRowCol(xRot, 1, 2) = -xOrientation->e32;
		MTXRowCol(xRot, 2, 2) = -xOrientation->e33;
		MTXRowCol(xRot, 0, 3) = 0.0f;
		MTXRowCol(xRot, 1, 3) = 0.0f;
		MTXRowCol(xRot, 2, 3) = 0.0f;

		MTXTrans(xTrans, -xPosition->x, -xPosition->y, -xPosition->z);
		MTXConcat(xRot, xTrans, s_xViewMatrix);

	
		GXLoadPosMtxImm(s_xViewMatrix, GX_PNMTX0);

		// Calculate
		Mtx xViewMatrixInvTranspose;
		MTXInverse(s_xViewMatrix, xViewMatrixInvTranspose); 
		MTXTranspose(xViewMatrixInvTranspose, xViewMatrixInvTranspose); 
		GXLoadNrmMtxImm(xViewMatrixInvTranspose, GX_PNMTX0);

		SetViewingArea();
	}
	else
	{
		Mtx44 p;
		const Vec camPt = {0.0F, 0.0F, 800.0F};
		const Vec at = {0.0F, 0.0F, -100.0F};
		const Vec up = {0.0F, 1.0F, 0.0F};
		
		MTXOrtho(p, 0, Asura_Viewport::GetHeight() , 0, Asura_Viewport::GetWidth(), 500, 1000);
		GXSetProjection(p, GX_ORTHOGRAPHIC);

		MTXLookAt(s_xViewMatrix, &camPt, &up, &at);
		GXLoadPosMtxImm(s_xViewMatrix, GX_PNMTX0);
		const Asura_Vector_2&	xSubPixelOffset		= Asura_Render::GetSubPixelOffset();
		GXSetViewport(xSubPixelOffset.x, xSubPixelOffset.y, (float)s_xRenderModeObj.fbWidth, (float)s_xRenderModeObj.efbHeight, ASURA_GX_ZNEAR, ASURA_GX_ZFAR);
//	    GXSetScissor(0, 0, (u32)s_xRenderModeObj.fbWidth, (u32)s_xRenderModeObj.efbHeight);
	}
}

void Asura_GX::Shutdown(void)
{
	// Kill the home button menu.
	Asura_Wii_HomeButtonMenu::Shutdown();

	Asura_Wii_Profiling::Shutdown();
	Asura_Deallocate(s_pvDefaultFifo);
	delete [] s_puDrawSyncTokenBuffer;
}


static bool s_bCompletelyDoneStrap = false;

bool Asura_GX::ReadyToDraw()
{
	return s_bCompletelyDoneStrap || Asura_Wii_StrapReminder::IsInsideStrapReminder();
}

void Asura_GX::BeginFrame(void)
{
	if (!Asura_Wii_StrapReminder::IsInsideStrapReminder())
	{
		if (!s_bCompletelyDoneStrap)
		{
			if (!Asura_Wii_StrapReminder::HasShownStrapReminder())
			{
				Asura_Wii_StrapReminder::CheckForFinish();
				return;
			}
			else
			{
				s_bCompletelyDoneStrap = true;
			}
		}
	}
#ifdef FRAME_LOG
	if (dbg_bEnableTrapDV != dbg_bEnableTrapInternal)
	{
		VISetBlack(VI_ENABLE);
		VISetTrapFilter((dbg_bEnableTrapInternal = dbg_bEnableTrapDV) == true ? VI_ENABLE : VI_DISABLE);
		VIFlush();
		for (int i = 0; i < 10; ++i)
			VIWaitForRetrace();
		VISetBlack(VI_DISABLE);
		VIFlush();
	}
#endif
	s_uDimmingCount = VIGetDimmingCount() / 60;


	LockMutex();
	s_fCurrentZOffset = 0.0f;
	GXSetZScaleOffset(1.0f, 0.0f);
	// Set up viewport (This is inappropriate for full-frame AA.)
    if (s_xRenderModeObj.field_rendering)
    {
        GXSetViewportJitter(
          0.0F, 0.0F, (float)s_xRenderModeObj.fbWidth, (float)s_xRenderModeObj.efbHeight, 
         ASURA_GX_ZNEAR, ASURA_GX_ZFAR, VIGetNextField());
    }
    else
    {
		const Asura_Vector_2&	xSubPixelOffset		= Asura_Render::GetSubPixelOffset();
		GXSetViewport(xSubPixelOffset.x, xSubPixelOffset.y, (float)s_xRenderModeObj.fbWidth, (float)s_xRenderModeObj.efbHeight, ASURA_GX_ZNEAR, ASURA_GX_ZFAR);
    }
    
    // Invalidate vertex cache in GP
    GXInvalidateVtxCache();

	GXSetNumChans(1); // Enable light channel; by default = vertex color
	Set3DProjection(false, true);

	VIFlush();
	GXFlush();
#ifdef ASURA_WII_SUPPORT_SPECIAL_ENV_TEXTURES
	g_afRainOffset[0] = (Asura_Timers::GetGameTimeScale() == 0.0f) ? 0.0f : Asura_Random::GetFloat() * 5.0f;
	g_afRainOffset[1] = (Asura_Timers::GetGameTimeScale() == 0.0f) ? 0.0f : Asura_Random::GetFloat() * 5.0f;
#endif

	if (s_bCompletelyDoneStrap)
	{
		// Allow the home button to process the controller.
		Asura_Wii_HomeButtonMenu::ProcessController();
	}
	Asura_GX_Water::RenderReflectionTexture();
}

void Asura_GX::EndFrame(void)
{
	if (!ReadyToDraw()) { return; }
	// Perform rendering of the home button (at the end of the frame to prevent anything
	// getting overwritten!)
	Asura_Wii_HomeButtonMenu::Render();

	// [OF] 20/03/07 - I have swapped these 2 calls as i am getting ready on the PRE-VSYNC interrupt.  
	// This allows the screen to be set up for the GXCopyDisp at the start of the frame.  The copy is issued
	// before the HSYNCs start and so it continues running.  This allows me to eliminate the extra frame delay
	// associated with triple buffering the display buffers and also allows a bit less latency when using 
	// double buffered display chains.
	SwapBuffers();
	CopyBuffer();
	UnlockMutex();
}

#ifdef ASURA_WII_SUPPORT_SCREENSHOTS
static char g_axScreenshotWorkingBuffer[SCRNSHOT_SHARE_MEMORY_SIZE] __attribute__ ((aligned (32)));
#endif

u_int Asura_GX::DiffFifoPtr(const void *pFifo1, const void *pFifo2)
{
	if (pFifo1 == pFifo2)
		return 0;
	else if (pFifo1 > pFifo2)
		return ((const char*)pFifo1) - ((const char*)pFifo2);
	else
		return ((const char*)pFifo1) - ((const char*)s_pvDefaultFifo) + uASURA_GX_FIFO_SIZE - (((const char*)pFifo2) - ((const char*)s_pvDefaultFifo));
}

void Asura_GX::CopyBuffer()
{

#ifdef FRAME_LOG

	if(s_uFramesRendered - s_uFramesShown > 10 || bDumpFrameData)
	{
		TLogFrameData::bDisable = true;
		u_int uTick = OSGetTick();
		u_int uRefTime = 0;
		int i = s_uFramesQueued % NUM_LOGGED_FRAMES;
		Asura_OutputToDebugger_Retail(("***********************************************************************\n***********************************************************************\n***********************************************************************"));
		Asura_OutputToDebugger_Retail(("Beginning Frame history dump\n"));
		do 
		{
			i = ((i + 1) % NUM_LOGGED_FRAMES);

			TLogFrameData *xFrame = &s_xFrameLog[i];
			xFrame->Dump(uRefTime);
			xFrame->Init();

		} while (i != (s_uFramesQueued% NUM_LOGGED_FRAMES));
		Asura_OutputToDebugger_Retail(("%f) %d %d %d\n", TLogFrameData::CalcTime(uTick, uRefTime), s_uFramesQueued, s_uFramesRendered, s_uFramesShown));

		while(s_uFramesQueued != s_uFramesShown)
		{

		}
		s_uFramesQueued = 0;
		s_uFramesRendered = 0;
		s_uFramesShown = 0;
		s_uFramesShown1 = 0;
		TLogFrameData::bDisable = false;
		s_xFrameLog[0].Init();
		if (!bDumpFrameData)
		{
			Asura_OutputToDebugger_Retail(("***********************************************************************\n***********************************************************************\n***********************************************************************"));
			Asura_OutputToDebugger_Retail(("A bad bad BAD thing just happened, please copy the above dump and send \n to Andy Southgate with a report of what you were doing and \nwhere it occured\n"));
		}
		bDumpFrameData = false;
	}

#endif


#ifdef FRAME_LOG
	s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].Init();
#endif

	ASURA_CPU_PROFILE__STOP(ASURA_CPU_PROFILE__TOTALFRAME);

	Asura_Wii_Profiling::Render();
#ifdef FRAME_LOG
	if (dbg_bShowFrameBars)
		TLogFrameData::Render(s_xFrameLog);
#endif

	Asura_Wii_Profiling::ClearValues();
	ASURA_CPU_PROFILE__START_LABEL(ASURA_CPU_PROFILE__TOTALFRAME, "Total frame");


#ifndef ASURA_WII_NINTENDO_PROFILER
#ifdef ASURA_WII_SUPPORT_SCREENSHOTS
	SCREENSHOTService( s_pvFrameBuffer[s_uFramesQueued & 1], g_axScreenshotWorkingBuffer, SCRNSHOT_SHARE_MEMORY_SIZE );
#endif
#endif

	// Set Z/Color update to make sure eFB will be cleared at GXCopyDisp.
    // (If you want to control these modes by yourself in your application,
    //  please comment out this part.)
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
	GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
    GXSetColorUpdate(GX_TRUE);


	GXFlush();
	void *pxReadPtr, *pxWritePtr;
	void *pxReadPtr2, *pxWritePtr2;
	GXFifoObj fifo;
	GXFifoObj fifo2;

	GXGetCPUFifo(&fifo);
	GXGetGPFifo(&fifo2);
	GXGetFifoPtrs( &fifo, &pxReadPtr, &pxWritePtr );		
	GXGetFifoPtrs( &fifo2, &pxReadPtr2, &pxWritePtr2 );		

//	Asura_Assert()

#ifdef FRAME_LOG
	s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].pFifoPoint = pxWritePtr;
#endif

	ASURA_CPU_PROFILE__START_LABEL(ASURA_CPU_PROFILE__PROJECT_PALE_CYAN, "Present frame");
	if ((DiffFifoPtr(pxWritePtr, pxReadPtr2) < 0x1000 || !Asura_Core::IsMainThread()) && s_uFramesQueued != s_uFramesShown)
	{
#ifdef FRAME_LOG
		s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].SetCPUPaused();
#endif
		while (s_uFramesQueued != s_uFramesShown)
			OSYieldThread();
	} else {
		OSDisableInterrupts();
		switch (s_uFramesQueued - s_uFramesShown)
		{
		case 0:
			break;
		case 1:
			GXEnableBreakPt((void*)(((u_int)pxWritePtr)));
#ifdef FRAME_LOG
			s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].SetBreak(pxReadPtr2);
#endif
			break;
		default:
			m_FramePoints.Push(pxWritePtr);
#ifdef FRAME_LOG
			s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].SetBreakQueued(pxReadPtr2);
#endif
			break;
		}	
		OSEnableInterrupts();

		if (s_uFramesQueued - s_uFramesRendered > 0)
		{
#ifdef FRAME_LOG
			s_xFrameLog[s_uFramesQueued % NUM_LOGGED_FRAMES].SetCPUPaused();
#endif
			// allow a frame being rendered + the one we have just queued up
			while (s_uFramesQueued - s_uFramesRendered > 0)
			{
				OSYieldThread();
			}
		}


	}

	if (Asura_ScreenShot_System::IsScreenShotPending())
	{
		GXSetCopyFilter(GX_FALSE, Asura_GX::GetRenderModeObject().sample_pattern, GX_FALSE, Asura_GX::GetRenderModeObject().vfilter);
	} else {
		GXSetCopyFilter(GX_FALSE, Asura_GX::GetRenderModeObject().sample_pattern, GX_TRUE, Asura_GX::GetRenderModeObject().vfilter);
	}

	if (Asura_ScreenShot_System::IsScreenShotPending())
	{
		Asura_ScreenShot_System::DrawBufferIsReady();
	}

    // Issue display copy command
	if(Asura_Render::GetEnableScreenClear() && !Asura_ScreenShot_System::IsScreenShotPending())
	{
		GXCopyDisp(s_pvFrameBuffer[s_uFramesQueued & 1], GX_TRUE);
	}
	else
	{
		GXCopyDisp(s_pvFrameBuffer[s_uFramesQueued & 1], GX_FALSE);
	}



	ASURA_CPU_PROFILE__STOP(ASURA_CPU_PROFILE__PROJECT_PALE_CYAN);

	s_uFramesQueued++;


	AddDrawSyncCallback( GPU_CALLBACK_ENDFRAME );



	if (s_bSyncEndOfFrame || Asura_ScreenShot_System::IsScreenShotPending())
	{
		GXPixModeSync();
		GXFlush();
		while (s_uFramesQueued != s_uFramesShown)
			OSYieldThread();

	}
#ifdef FRAME_LOG
	if (dbg_bShowFrameBars)
		Asura_DebugTextPrint(( 45, 25, 0xffffffff, 0, "%d %d", s_uFramesQueued - s_uFramesRendered, s_uFramesRendered - s_uFramesShown));
	s_xFrameLog[(s_uFramesQueued - 1) % NUM_LOGGED_FRAMES].SetQueued();
#endif


	AddDrawSyncCallback( GPU_CALLBACK_STARTFRAME );
	if(Asura_Render::GetEnableScreenClear() && Asura_ScreenShot_System::IsScreenShotPending())
	{
		GXCopyDisp(s_pvFrameBuffer[s_uFramesQueued & 1], GX_TRUE);
	}

}

void Asura_GX::ReturnToMenu()
{
	VISetBlack(true);
	VIFlush();
	VIWaitForRetrace();
	OSReturnToMenu();
}

void Asura_GX::RestartSystem()
{
	VISetBlack(true);
	VIFlush();
	VIWaitForRetrace();
	OSRestart(0);
}

void Asura_GX::CheckForRestart()
{
	// Don't reset or power down during NAND operation.
	if(!Asura_Wii_NAND::IsBusy())
	{
		// Check for reset
		// Should we check for NAND busy here?
		if(s_bResetHasBeenPressed && !s_bFatalError)
		{
			DVDCommandBlock xCommandBlock;
			DVDCheckDiskAsync(&xCommandBlock, &DVDCheckCallbackFunction);

			if(Asura_Rumble_System::IsInitialised())
			{
				Asura_Rumble_System::StopAllRumbling();
			}

			// Apparently outstanding callbacks (audio, GX) may cause crash on reset.
			// If this happens, will have to do a more comprehensive shutdown.
			VISetBlack(true);
			VIFlush();
			VIWaitForRetrace();

			while(!s_bDVDCheckComplete) {}

			if(s_iDVDCheckResult == 1)
			{
				OSRestart(0);
			}
			else
			{
				// Should use this instead on fatal error or disc removed
				OSReturnToMenu();
			}
		}

		if(s_bPowerHasBeenPressed)
		{
			// KOR - there are a bunch of recommendations for shutdown,
			// including audio shutdown, waiting for NAND, etc.
			// However, they aren't required, so this will do.
			VISetBlack(true);
			VIFlush();
			VIWaitForRetrace();
			OSShutdownSystem();
		}
	}

}

#ifdef ASURA_WII_REPORT_DOUBLE_FIFO_SIZES
void*	g_pPrevBrkPt	= NULL;
u32		g_uLargest		= 0;
#endif

void Asura_GX::SwapBuffers()
{
#ifdef ASURA_WII_NINTENDO_PROFILER 
	SPROFILER_MarkFrameBegin();
#endif

    // If this is the first frame, turn off VIBlack
    if(s_bFirstFrame)
    {
        VISetBlack(FALSE);
        s_bFirstFrame = GX_FALSE;
    }
	CheckForRestart();
    
}

void Asura_GX::SetZBufferMode(bool Enabled, enum ASURA_ZBUFFERCMP_ID CompareMode, bool WriteEnabled)
{
	switch(CompareMode)
	{
		case ASURA_ZBUFFERCMP_LESSEQUAL:
		{
			GXSetZMode(Enabled, GX_LEQUAL, WriteEnabled);
			break;		
		}
		case ASURA_ZBUFFERCMP_ALWAYS:
		{
			GXSetZMode(Enabled, GX_ALWAYS, WriteEnabled);
			break;		
		}
		case ASURA_ZBUFFERCMP_EQUALS:
		{
			GXSetZMode(Enabled, GX_EQUAL, WriteEnabled);
			break;
		}
		default:
		{
			Asura_Assertf(0, ("Unhandled Compare Mode (%i) in SetZBufferMode", CompareMode));
			break;
		}
	}
}

void Asura_GX::ARGBToGXColor(const u_int uARGB, GXColor& xColour)
{
	xColour.a = (uARGB & 0xff000000) >> 24;
	xColour.r = (uARGB & 0x00ff0000) >> 16;
	xColour.g = (uARGB & 0x0000ff00) >>  8;
	xColour.b = (uARGB & 0x000000ff) >>  0;
}

static inline u_char FloatToUChar(const float f)
{
	return static_cast<u_char>(Asura_Maths::ClampToRange(f, 0.f, 1.f) * 0xff);
}

DEBUGVAR float dbg_fLightShininess = 8.f;
static void SetupLight(u_int uLightIndex, const Asura_Vector_3& xCentre, const Asura_Vector_3& xLightDir, const Asura_RGBA& xColour, bool bSpecular)
{
	Asura_Assert(uLightIndex < 8, "Light index out of range");
	_GXLightID axLightEnums[8] = { GX_LIGHT0, GX_LIGHT1, GX_LIGHT2, GX_LIGHT3, GX_LIGHT4, GX_LIGHT5, GX_LIGHT6, GX_LIGHT7};
	const float fLARGEDISTANCE = 1000.f;
	GXLightObj xLight;
	GXColor xLightColour = { FloatToUChar(xColour.m_fR), FloatToUChar(xColour.m_fG), FloatToUChar(xColour.m_fB), 0xff };
	GXInitLightColor(&xLight, xLightColour);
	if(bSpecular)
	{
		Asura_Vector_3 xViewspaceLightDir;
		const Mtx& xViewMatrix = Asura_GX::GetViewMatrix();
		MTXMultVecSR(xViewMatrix, (VecPtr)&xLightDir, (VecPtr)&xViewspaceLightDir);
		Asura_Vector_3 xHalfAngle = (xViewspaceLightDir + Asura_Vector_3(0.f, 0.f, 1.f)) * 0.5f;
		xHalfAngle.Normalise();
		// This is a nasty hack, becase for some reason Wii hardware clamps off the specular highlight based on N.L
		// Therefore, set the light direction to be the opposite of the half angle.
		const Asura_Vector_3 xMinusHalfAngle = -xHalfAngle;
		GXInitSpecularDirHAv(&xLight, (VecPtr)&xMinusHalfAngle, (VecPtr)&xHalfAngle);
		GXInitLightShininess(&xLight, dbg_fLightShininess);
	}
	else
	{
		Asura_Vector_3 xLightPos = (xCentre + xLightDir * fLARGEDISTANCE);
		// Rotate light position into viewspace.
		MTXMultVec(Asura_GX::GetViewMatrix(), (VecPtr)&xLightPos, (VecPtr)&xLightPos);
		GXInitLightPos(&xLight, xLightPos.x, xLightPos.y, xLightPos.z);
		GXInitLightDir(&xLight, 1.0f, 0.0f, 0.0f);
		GXInitLightAttnA(&xLight, 1.0f, 0.0f, 0.0f);
		GXInitLightDistAttn(&xLight, 1.f, 1.f, GX_DA_OFF);
	}
	GXLoadLightObjImm(&xLight, axLightEnums[uLightIndex]);
}

void Asura_GX::SetUpEntityLights(const Asura_Vector_3& xPos, bool bVertexAlpha /* = false */)
{
	// New SH stuff
	const Asura_SphericalHarmonics_Order2_RGB& xSH = Asura_GI_Solution::s_xSHForCurrentRender;
	Asura_Vector_3 axLightDirs[3];
	axLightDirs[0] = Asura_Vector_3(xSH.m_afRedCoefficients[1], xSH.m_afRedCoefficients[2], xSH.m_afRedCoefficients[3]);
	axLightDirs[1] = Asura_Vector_3(xSH.m_afGreenCoefficients[1], xSH.m_afGreenCoefficients[2], xSH.m_afGreenCoefficients[3]);
	axLightDirs[2] = Asura_Vector_3(xSH.m_afBlueCoefficients[1], xSH.m_afBlueCoefficients[2], xSH.m_afBlueCoefficients[3]);

	for(u_int uChannel = 0; uChannel < 3; ++uChannel)
	{
		const float fMag = axLightDirs[uChannel].Normalise();
		Asura_RGBA xColour;
		xColour.Zero();
		switch(uChannel)
		{
		case 0: xColour.m_fR = fMag; break;
		case 1: xColour.m_fG = fMag; break;
		case 2: xColour.m_fB = fMag; break;
			default: Asura_Assert(0, "Shouldn't get here");
		}
		SetupLight(uChannel, xPos, axLightDirs[uChannel], xColour, false);
	}

	GXColor xAmbientColour;
	xAmbientColour.r = static_cast<u_char>(Asura_Maths::ClampToRange(xSH.m_afRedCoefficients[0], 0.f, 1.f) * 0xff);
	xAmbientColour.g = static_cast<u_char>(Asura_Maths::ClampToRange(xSH.m_afGreenCoefficients[0], 0.f, 1.f) * 0xff);
	xAmbientColour.b = static_cast<u_char>(Asura_Maths::ClampToRange(xSH.m_afBlueCoefficients[0], 0.f, 1.f) * 0xff);
	xAmbientColour.a = 0xff;

	GXColor xWhite = {0xff, 0xff, 0xff, 0xff};
	GXSetChanAmbColor(GX_COLOR0A0, xAmbientColour);
	GXSetChanMatColor(GX_COLOR0A0, xWhite);	

	// Primary colour from lighting, white register material.
	GXSetChanCtrl(GX_COLOR0, TRUE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT0 | GX_LIGHT1 | GX_LIGHT2, GX_DF_SIGN, GX_AF_NONE);
	// Alpha from vertex (for hierarchies)
	GXSetChanCtrl(GX_ALPHA0,FALSE, GX_SRC_REG, bVertexAlpha ? GX_SRC_VTX : GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
}

DEBUGVAR float dbg_fReflectiveMultiplier = 2.5f;
void Asura_GX::SetUpEntitySpecular(const Asura_Vector_3& xPos, int iMaterialID)
{
	const Asura_Material* pxMaterial = Asura_Material_System::GetMaterial(iMaterialID);
	const u_int uMaterialFlags = pxMaterial ? pxMaterial->m_uMaterialFlags : 0;
	const bool bSpecular = (uMaterialFlags & (AsuraMaterialFlag_Specular_Soft|AsuraMaterialFlag_Specular_Sharp)) != 0;
	const bool bReflective = (uMaterialFlags & (AsuraMaterialFlag_Reflective)) != 0;
	const u_int uSpecularColour = (bSpecular || bReflective) ? pxMaterial->m_uSpecularColour : 0;
	GXSetNumChans(uSpecularColour ? 2 : 1);
	const Asura_SphericalHarmonics_Order2_RGB& xSH = Asura_GI_Solution::s_xSHForCurrentRender;
	if(uSpecularColour)
	{
		Asura_RGBA xSpecularColour;
		Asura_Vector_3 xSpecularDirection;
		xSH.GetBrightestDirection(xSpecularDirection);
		xSH.EvaluateInDirection(xSpecularDirection, xSpecularColour);
		if(bReflective)
		{
			xSpecularColour *= dbg_fReflectiveMultiplier;
		}
		SetupLight(3, xPos, xSpecularDirection, xSpecularColour, true);

		GXColor xSpecularGXColour;
		ARGBToGXColor(uSpecularColour, xSpecularGXColour);
		GXColor xBlack = {0, 0, 0, 0};
		GXSetChanAmbColor(GX_COLOR1A1, xBlack);
		GXSetChanMatColor(GX_COLOR1A1, xSpecularGXColour);	
		GXSetChanCtrl(GX_COLOR1, TRUE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT3, GX_DF_CLAMP, GX_AF_SPEC);
	}
	else
	{
		// Second colour unused.
	GXSetChanCtrl(GX_COLOR1,FALSE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
	}
	// Second alpha always unused.
	GXSetChanCtrl(GX_ALPHA1,FALSE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
}

DEBUGVAR bool dbg_bUntextured = false;
DEBUGVAR bool dbg_bUseAlphaForSpecular = true;

inline void SetAlphaTevStageDefault(GXTevStageID eStage)
{
	GXSetTevAlphaIn(eStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
	GXSetTevAlphaOp(eStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
}

void Asura_GX::SetupMaterial(int iMaterialID, u_int uPrevTevStages /* = 0 */, u_int uPrevTexGens /* = 0 */, const int iTexIDOverride /* = ASURA_TEXTURE_NONE */, float fAlpha /* = 1.f */)
{
#ifdef ASURA_WII_SUPPORT_SPECIAL_ENV_TEXTURES 
	if(g_iReflectanceMap == ASURA_TEXTURE_NONE)
	{
		g_iReflectanceMap		= Asura_Texture_Management::AddTextureWithResetGroup("\\specialfx\\spheremap1.bmp", ASURA_TEXTURE_LOADED_PERMANENT );
		g_iRainSplashTexture	= Asura_Texture_Management::AddTextureWithResetGroup("\\specialfx\\splash.tpl", ASURA_TEXTURE_LOADED_PERMANENT );
		g_iDetailMap			= Asura_Texture_Management::AddTextureWithResetGroup("\\graphicnovel\\detail.dds", ASURA_TEXTURE_LOADED_PERMANENT );
		//Asura_Texture_Management::LoadTextures();
	}
#endif
	const Asura_Material* pxMaterial = Asura_Material_System::GetMaterial(iMaterialID);
	// Enum setup
	const GXTevStageID eInitialStage = (GXTevStageID)(GX_TEVSTAGE0 + uPrevTevStages);
	const GXTexCoordID eInitialTexGen= (GXTexCoordID)(GX_TEXCOORD0 + uPrevTexGens);
	const GXTevStageID eStage0 = (GXTevStageID)(eInitialStage + 0);
	const GXTevStageID eStage1 = (GXTevStageID)(eInitialStage + 1);
	const GXTevStageID eStage2 = (GXTevStageID)(eInitialStage + 2);
	const GXTexCoordID eTexGen0 = (GXTexCoordID)(eInitialTexGen + 0);
	const GXTexCoordID eTexGen1 = (GXTexCoordID)(eInitialTexGen + 1);

	// Load primary texture
	const int iTextureID = pxMaterial ? pxMaterial->m_iTextureID[0] : 0;
	// Texture must be loaded before tev stage setup (so we know whether we have an alpha channel)
	Asura_Render::CurrentStates.RequestTextureIndex(0, (iTexIDOverride != ASURA_TEXTURE_NONE) ? iTexIDOverride : iTextureID);

	const bool bAlphaResDiffers = Asura_GX_Texture_Management::IsAlphaTextureLoaded() && Asura_GX_Texture_Management::IsAlphaTextureDifferentRes();
	const GXTexCoordID eAlphaTexGen = bAlphaResDiffers ? eTexGen1 : eTexGen0;

	// Secondary texture?
	const int iSecondaryTexture = pxMaterial ? pxMaterial->m_iTextureID[1] : ASURA_TEXTURE_NONE;
	const bool bDualTexture = (iSecondaryTexture != ASURA_TEXTURE_NONE);
	if(bDualTexture)
	{
		GXSetNumTexGens(uPrevTexGens + 2);
		// Load up the second texture
		Asura_Render::CurrentStates.RequestTextureIndex(1, iSecondaryTexture);
		GXSetTexCoordGen(eTexGen1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY);
	}
	else
	{
		GXSetNumTexGens(uPrevTexGens + (bAlphaResDiffers ? 2 : 1));
	}

	Asura_TextureAnimationOutput xTextureAnim;
	if(Asura_Material_System::GetTextureAnimationState(iMaterialID,0,&xTextureAnim))
	{
		Mtx xTexMtx;
		MTXTrans(xTexMtx, xTextureAnim.m_fUOffset, xTextureAnim.m_fVOffset, 0.f);
		GXLoadTexMtxImm(xTexMtx, GX_TEXMTX0, GX_MTX2x4);
		GXSetTexCoordGen(eTexGen0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
		if(bAlphaResDiffers && !bDualTexture)
		{
			GXSetTexCoordGen(eTexGen1, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
		}
	}
	else
	{
		GXSetTexCoordGen(eTexGen0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
		if(bAlphaResDiffers && !bDualTexture)
		{
			GXSetTexCoordGen(eTexGen1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
	}
	}

	// Make sure swap mode is reset.
	GXSetTevSwapMode(eStage0, GX_TEV_SWAP0, GX_TEV_SWAP0);
	GXSetTevSwapMode(eStage1, GX_TEV_SWAP0, GX_TEV_SWAP0);
	GXSetTevSwapMode(eStage2, GX_TEV_SWAP0, GX_TEV_SWAP0);

	bool bTranslucent = false;
	bool bAlphaTest = false;
	bool bAlphaKonst = false;
	if(fAlpha < 1.f)
	{
		GXColor xAlpha;
		xAlpha.r = xAlpha.g = xAlpha.b = 0xff;
		xAlpha.a = static_cast<u_char>(fAlpha * 0xff);
		GXSetTevKColor(GX_KCOLOR0, xAlpha);
		GXSetTevKAlphaSel(eStage0, GX_TEV_KASEL_K0_A);
		bAlphaKonst = true;
		bAlphaTest = true;
		bTranslucent = true;
		GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST);
		GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevAlphaOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		GXSetTevAlphaIn(eStage2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevAlphaOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
	}
	else
	{
	SetAlphaTevStageDefault(eStage0);
	SetAlphaTevStageDefault(eStage1);
	SetAlphaTevStageDefault(eStage2);
	}

	const u_int uMaterialFlags = pxMaterial ? pxMaterial->m_uMaterialFlags : 0;
	if(dbg_bUntextured)
	{
		if(uMaterialFlags & (AsuraMaterialFlag_Transparent | AsuraMaterialFlag_PS2AdditiveAlpha | AsuraMaterialFlag_AlphaDecal) || bAlphaKonst)
		{
			Asura_Render::CurrentStates.RequestAlphaTest(true);
			if(uMaterialFlags & (AsuraMaterialFlag_PS2AdditiveAlpha))
			{
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);
			}
			else
			{
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
			}
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
		}
		else
		{
			Asura_Render::CurrentStates.RequestAlphaTest(false);
			Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
		}
		GXSetNumTevStages(uPrevTevStages + 1);

		GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
		if(uPrevTevStages)
		{
			GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_ONE, GX_CC_CPREV, GX_CC_ZERO);
		}
		else
		{
			GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_ONE, GX_CC_RASC, GX_CC_ZERO);
		}
		GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		if(bAlphaKonst)
		{
			GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_KONST, GX_CA_RASA, GX_CA_ZERO);
		}
		else
		{
		GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA);
		}
		GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		return;
	}

	if(Asura_GX_Texture_Management::IsColourTextureLoaded())
	{
		bool bCustomTexturing = false;
		// Initialize lighting, texgen, and tev parameters		
		if(uMaterialFlags & AsuraMaterialFlag_SelfIlluminate)
		{
			if(Asura_GX_Texture_Management::IsAlphaTextureLoaded())
			{
				GXSetNumTevStages(uPrevTevStages + 2);

				GXSetTevOrder(eStage0, eAlphaTexGen, Asura_GX_Texture_Management::GetAlphaTexMapID(), GX_COLOR0A0);
				GXSetTevOrder(eStage1, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);

				// Add diffuse lighting colour to alpha
				GXSetTevSwapMode(eStage0, GX_TEV_SWAP0, GX_TEV_SWAP1);
				if(uPrevTevStages)
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_HALF, GX_CC_TEXA, GX_CC_CPREV);
				}
				else
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_HALF, GX_CC_TEXA, GX_CC_RASC);
				}
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

				// Multiply by texture colour
				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_TEXC, GX_CC_ZERO);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
			}
			else
			{
				GXSetNumTevStages(uPrevTevStages + 2);
				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				GXSetTevOrder(eStage1, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);

				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);

				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_CPREV);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
			bCustomTexturing = true;
		}
		else if(uMaterialFlags & (AsuraMaterialFlag_Transparent | AsuraMaterialFlag_PS2AdditiveAlpha | AsuraMaterialFlag_AlphaDecal))
		{
			bAlphaTest = true;
			if(uMaterialFlags & (AsuraMaterialFlag_PS2AdditiveAlpha))
			{
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);
				bTranslucent = true;
			}
			else if(!s_bDisableTranslucency || (uMaterialFlags & AsuraMaterialFlag_AlphaDecal))
			{
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
				bTranslucent = true;
			}
			else
			{
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
			}

			if(Asura_GX_Texture_Management::IsAlphaTextureLoaded())
			{
				GXSetNumTevStages(uPrevTevStages + 2);

				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				GXSetTevOrder(eStage1, eAlphaTexGen, Asura_GX_Texture_Management::GetAlphaTexMapID(), GX_COLOR0A0);

				if(uPrevTevStages)
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_ZERO);
				}
				else
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
				}
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				if(bAlphaKonst)
				{
					GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_KONST, GX_CA_RASA, GX_CA_ZERO);
					GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				}

				GXSetTevSwapMode(eStage1, GX_TEV_SWAP0, GX_TEV_SWAP1);
				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				if(bAlphaKonst)
				{
					GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
				}
				else
				{
				// Multiply texture alpha by raster alpha
				GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO);
				}
				GXSetTevAlphaOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
			else
			{
				GXSetNumTevStages(uPrevTevStages + 1);

				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);

				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST, GX_CA_TEXA);
				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
			bCustomTexturing = true;
		}
		else if(uMaterialFlags & (AsuraMaterialFlag_Specular_Soft|AsuraMaterialFlag_Specular_Sharp|AsuraMaterialFlag_Reflective))
		{
			// Test code
#if 0
			GXSetNumTevStages(1);
			GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR1A1);
			if(s_bDisableTranslucency)
			{
				GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASA);
			}
			else
			{
				GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
			}
			GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
#endif
			GXSetNumTevStages(uPrevTevStages + 2);

			if(uPrevTevStages)
			{
				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR_NULL);
				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_ZERO);
			}
			else
			{
				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
			}
			GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
			if(Asura_GX_Texture_Management::IsAlphaTextureLoaded() && dbg_bUseAlphaForSpecular)
			{
				GXSetTevOrder(eStage1, eAlphaTexGen, Asura_GX_Texture_Management::GetAlphaTexMapID(), GX_COLOR1A1);
				// HACK - disable translucency means it's environment.
				if(s_bDisableTranslucency)
				{
					GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_TEXA, GX_CC_RASA, GX_CC_CPREV);
				}
				else
				{
					GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_TEXA, GX_CC_RASC, GX_CC_CPREV);
				}
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
			else
			{
				GXSetTevOrder(eStage1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR1A1);
				// HACK - disable translucency means it's environment.
				if(s_bDisableTranslucency)
				{
					GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ONE, GX_CC_RASA, GX_CC_CPREV);
				}
				else
				{
					GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ONE, GX_CC_RASC, GX_CC_CPREV);
				}
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
			bCustomTexturing = true;
		}
//		else if(uMaterialFlags & AsuraMaterialFlag_Reflective)
//		{
//			Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
//			Asura_Render::CurrentStates.RequestTextureIndex(1, g_iReflectanceMap);
//			if(Asura_GX::IsColourTextureLoaded())
//			{
//				GXSetNumTevStages(uPrevTevStages + 2);
//				GXSetNumTexGens(2);
//				//GXLoadTexMtxImm(g_xSphereMapMatrix, GX_TEXMTX0, GX_MTX2x4);
//				//GXSetTexCoordGen(eTexGen1, GX_TG_MTX2x4, GX_TG_NRM, GX_TEXMTX0);
//				Mtx nMtx;
//				MTXIdentity(nMtx);
//				//nMtx = Asura_GX::s_xViewMatrix;
//				//MTXInverse(Asura_GX::s_xViewMatrix, nMtx);
//				//MTXTranspose(nMtx, nMtx);
//				//MTXTranspose(Asura_GX::s_xViewMatrix, nMtx);
//				//MTXInverse(nMtx, nMtx);
//				//MTXConcat(Asura_GX::s_xViewMatrix, nMtx, nMtx);
//				Mtx  m0, m1, m2;
//				MTXScale(m1, 0.50F, -0.50F, 0.0F);
//				MTXTrans(m0, 0.50F, 0.50F, 1.0F);
//				MTXConcat(m0, m1, m2);
//				GXLoadTexMtxImm(nMtx, GX_TEXMTX0, GX_MTX3x4);
//				GXLoadTexMtxImm(m2, GX_PTTEXMTX0, GX_MTX3x4);
//				GXSetTexCoordGen2(eTexGen1, GX_TG_MTX3x4, GX_TG_NRM, GX_TEXMTX0, GX_TRUE, GX_PTTEXMTX0);
//
//				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
//				GXSetTevOrder(eStage1, eTexGen1, GX_TEXMAP1, GX_COLOR0A0);
//
//				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
//				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
//				GXSetTevAlphaIn(eStage0, GX_CA_ONE, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
//				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
//
//				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_TEXC, GX_CC_ZERO);
//				//GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ONE, GX_CC_TEXC, GX_CC_ZERO);
//				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
//				GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
//				GXSetTevAlphaOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
//			}
//			else
//			{
//				Asura_Assert(0, "Could not find environment sphere map texture.");
//				GXSetNumTevStages(1);
//
//				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
//
//				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
//				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
//				GXSetTevAlphaIn(eStage0, GX_CA_ONE, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
//				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
//			}
//			bCustomTexturing = true;
//		}
#ifdef ASURA_WII_SUPPORT_SPECIAL_ENV_TEXTURES
		else if(/* Asura_Weather_System::IsRaining() && */(uMaterialFlags & AsuraMaterialFlag_RainSplashes))
		{
			Asura_Render::CurrentStates.RequestTextureIndex(1, g_iRainSplashTexture);
			if(Asura_GX_Texture_Management::IsColourTextureLoaded())
			{
				GXSetNumTevStages(uPrevTevStages + 3);
				GXSetNumTexGens(uPrevTexGens + 2);
				float afTexMatrix[2][4] = {{ 2, 0, 0, g_afRainOffset[0]}, { 0, 0, 2, g_afRainOffset[1]} };
				GXLoadTexMtxImm(afTexMatrix, GX_TEXMTX0, GX_MTX2x4);
				GXSetTexCoordGen(eTexGen0, GX_TG_MTX2x4, GX_TG_POS, GX_TEXMTX0);

				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				GXSetTevOrder(eStage1, eTexGen0, GX_TEXMAP1, GX_COLOR0A0);
				GXSetTevOrder(eStage2, eTexGen0, GX_TEXMAP1, GX_COLOR0A0);

				if(uPrevTevStages)
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_CPREV, GX_CC_TEXC, GX_CC_ZERO);
				}
				else
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
				}
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
				GXSetTevAlphaOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV);

				GXSetTevColorIn(eStage2, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);
				GXSetTevColorOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				// Alpha is irrelevant
				GXSetTevAlphaIn(eStage2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
				GXSetTevAlphaOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				bCustomTexturing = true;
			}
			else
			{
				Asura_Assert(0, "Could not load rain splash texture");
			}

		}
		else if (uMaterialFlags & AsuraMaterialFlag_UseDetailMap)
		{
			Asura_Render::CurrentStates.RequestTextureIndex(1, g_iDetailMap);
			if(Asura_GX_Texture_Management::IsColourTextureLoaded())
			{
				GXSetNumTevStages(uPrevTevStages + 3);
				GXSetNumTexGens(uPrevTexGens + 2);
				float afTexMatrix[2][4] = {{ 16.0f, 0.0f, 0.0f, 0.0f}, { 0.0f, 16.0f, 0.0f, 0.0f} };
				GXLoadTexMtxImm(afTexMatrix, GX_TEXMTX0, GX_MTX2x4);
				GXSetTexCoordGen(eTexGen0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);

				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				GXSetTevOrder(eStage1, eTexGen0, GX_TEXMAP1, GX_COLOR0A0);
				GXSetTevOrder(eStage2, eTexGen0, GX_TEXMAP1, GX_COLOR0A0);

				if(uPrevTevStages)
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_CPREV, GX_CC_TEXC, GX_CC_ZERO);
				}
				else
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
				}
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

				GXSetTevColorIn(eStage1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
				GXSetTevAlphaOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV);

				GXSetTevColorIn(eStage2, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO);
				GXSetTevColorOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
				GXSetTevAlphaIn(eStage2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
				GXSetTevAlphaOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
				bCustomTexturing = true;
			}
			else
			{
				Asura_Assert(0, "Could not load detail map texture");
			}
		}
#endif
		if(!bCustomTexturing)
		{
			if(bDualTexture)
			{
				GXSetNumTevStages(uPrevTevStages + 3);
				// Primary texture
				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR_NULL);
				GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1);
				// Secondary texture lerped using alpha
				GXSetTevOrder(eStage1, eTexGen1, GX_TEXMAP1, GX_COLOR0A0);
				GXSetTevColorIn(eStage1, GX_CC_TEXC, GX_CC_C1, GX_CC_RASA, GX_CC_ZERO);
				//GXSetTevColorIn(eStage1, GX_CC_C1, GX_CC_TEXC, GX_CC_HALF, GX_CC_ZERO);
				GXSetTevColorOp(eStage1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1);

				// Modulate with lighting
				if(uPrevTevStages)
				{
					GXSetTevOrder(eStage2, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
					GXSetTevColorIn(eStage2, GX_CC_ZERO, GX_CC_C1, GX_CC_CPREV, GX_CC_ZERO);
				}
				else
				{
					GXSetTevOrder(eStage2, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
					GXSetTevColorIn(eStage2, GX_CC_ZERO, GX_CC_C1, GX_CC_RASC, GX_CC_ZERO);
				}
				GXSetTevColorOp(eStage2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
			}
			else
			{
				GXSetNumTevStages(uPrevTevStages + 1);
				GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
				if(uPrevTevStages)
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_ZERO);
				}
				else
				{
					GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO);
				}
				GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);

				GXSetTevAlphaIn(eStage0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST);
				GXSetTevAlphaOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
			}
		}
	}
	else
	{
		GXSetNumTevStages(uPrevTevStages + 1);

		GXSetTevOrder(eStage0, eTexGen0, GX_TEXMAP0, GX_COLOR0A0);
		GXSetTevColorIn(eStage0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
		GXSetTevColorOp(eStage0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV);
	}

	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(!bTranslucent);
	Asura_Render::CurrentStates.RequestAlphaTest(bAlphaTest);
	if(!bTranslucent)
	{
		Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	}
}

void Asura_GX::SetupDepthOnly()
{
	GXSetNumTexGens(0);
	GXSetNumTevStages(1);
	GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
	GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);
	GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
	GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
	GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
	Asura_Render::CurrentStates.RequestTextureIndex(0, ASURA_TEXTURE_NONE);
	Asura_Render::CurrentStates.RequestAlphaTest(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);

	// Make sure lighting is off.
	GXSetChanCtrl(GX_COLOR0,FALSE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
	GXSetChanCtrl(GX_ALPHA0,FALSE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
}

void Asura_GX::LockMutex()
{
	g_xRenderingMutex.Lock();
}
void Asura_GX::UnlockMutex()
{
	g_xRenderingMutex.Unlock();
}

void Asura_GX::LoadRequiredTextures()
{
	Asura_Assert(s_iWhiteTextureIndex == ASURA_TEXTURE_NONE, "White texture already loaded?")
	s_iWhiteTextureIndex = Asura_Texture_Management::AddTextureWithResetGroup("white_pixel.tpl", ASURA_TEXTURE_LOADED_PERMANENT);
	Asura_Texture_Management::AddTextureWithResetGroup("\\sphereshade.tpl", ASURA_TEXTURE_LOADED_PERMANENT );
	Asura_Texture_Management::LoadTextures();
}

#ifdef _DEBUG
void Asura_GX::ResetGX()
{
	// KOR - this is taken directly from the GXInit manual page.
	// Use it to help track down render state bugs; simply call it
	// before the problem render call, and it *should* fix things.
	// Once you've confirmed that, use the toggles to narrow down the cause.
	GXColor GX_DEFAULT_BG = {64, 64, 64, 255};
	GXColor BLACK  = {0, 0, 0, 0};
	GXColor WHITE = {255, 255, 255, 255};

	//
	// Render Mode
	//
	// (set 'rmode' based upon VIGetTvFormat(); code not shown)

	//
	// Geometry and Vertex
	//
	u_int uToggleCount = 0;
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX3, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX4, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX5, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX6, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD7, GX_TG_MTX2x4, GX_TG_TEX7, GX_IDENTITY);
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetNumTexGens(1);
		GXClearVtxDesc();
		GXInvalidateVtxCache();
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetLineWidth(6, GX_TO_ZERO);
		GXSetPointSize(6, GX_TO_ZERO);
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXEnableTexOffsets( GX_TEXCOORD0, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD1, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD2, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD3, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD4, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD5, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD6, GX_DISABLE, GX_DISABLE );
		GXEnableTexOffsets( GX_TEXCOORD7, GX_DISABLE, GX_DISABLE );
	}

	//
	// Transformation and Matrix
	//
	Mtx xIdentity;
	MTXIdentity(xIdentity);
	if(g_abResetToggle[uToggleCount++])
	{
		// Note: projection matrix is not initialized!
		GXLoadPosMtxImm(xIdentity, GX_PNMTX0);
		GXLoadNrmMtxImm(xIdentity, GX_PNMTX0);
		GXSetCurrentMtx(GX_PNMTX0);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXLoadTexMtxImm(xIdentity, GX_IDENTITY, GX_MTX3x4);
		GXLoadTexMtxImm(xIdentity, GX_PTIDENTITY, GX_MTX3x4);
	}
#if 0
	GXSetViewport(0.0F, // left
		0.0F, // top
		(float)rmode->fbWidth, // width
		(float)rmode->xfbHeight, // height
		0.0F, // nearz
		1.0F); // farz
#endif

	//
	// Clipping and Culling
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetCoPlanar(GX_DISABLE);
		GXSetCullMode(GX_CULL_BACK);
		GXSetClipMode(GX_CLIP_ENABLE);
	}
#if 0
	GXSetScissor(0, 0, (u32)rmode->fbWidth, (u32)rmode->efbHeight);
	GXSetScissorBoxOffset(0, 0);
#endif
	//
	// Lighting - pass vertex color through
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetNumChans(0); // no colors by default
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetChanCtrl(
			GX_COLOR0A0,
			GX_DISABLE,
			GX_SRC_REG,
			GX_SRC_VTX,
			GX_LIGHT_NULL,
			GX_DF_NONE,
			GX_AF_NONE );
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetChanAmbColor(GX_COLOR0A0, BLACK);
		GXSetChanMatColor(GX_COLOR0A0, WHITE);
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetChanCtrl(
			GX_COLOR1A1,
			GX_DISABLE,
			GX_SRC_REG,
			GX_SRC_VTX,
			GX_LIGHT_NULL,
			GX_DF_NONE,
			GX_AF_NONE );
	}

	if(g_abResetToggle[uToggleCount++])
	{
		GXSetChanAmbColor(GX_COLOR1A1, BLACK);
		GXSetChanMatColor(GX_COLOR1A1, WHITE);
	}
	//
	// Texture
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXInvalidateTexAll();
	}
	// Allocate 8 32k caches for RGBA texture mipmaps.
	// Equal size caches to support 32b RGBA textures.
	//
	// (code not shown)

	// Allocate color index caches in low bank of TMEM.
	// Each cache is 32kB.
	// Even and odd regions should be allocated on different address.
	//
	// (code not shown)

	// Allocate TLUTs, 16 256-entry TLUTs and 4 1K-entry TLUTs.
	// 256-entry TLUTs are 8kB, 1k-entry TLUTs are 32kB.
	//
	// (code not shown)

#if 0
	//
	// Set texture region and tlut region Callbacks
	//
	GXSetTexRegionCallback(__GXDefaultTexRegionCallback);
	GXSetTlutRegionCallback(__GXDefaultTlutRegionCallback);
#endif
	//
	// Texture Environment
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD3, GX_TEXMAP3, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD4, GX_TEXMAP4, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD5, GX_TEXMAP5, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE6, GX_TEXCOORD6, GX_TEXMAP6, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE7, GX_TEXCOORD7, GX_TEXMAP7, GX_COLOR0A0);
		GXSetTevOrder(GX_TEVSTAGE8, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE9, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE10,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE11,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE12,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE13,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE14,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
		GXSetTevOrder(GX_TEVSTAGE15,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetNumTevStages(1);
		GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
		GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
		GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);
	}

	int i;
	if(g_abResetToggle[uToggleCount++])
	{
		for (i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++)
		{
			GXSetTevKColorSel((GXTevStageID) i, GX_TEV_KCSEL_1_4 );
			GXSetTevKAlphaSel((GXTevStageID) i, GX_TEV_KASEL_1 );
			GXSetTevSwapMode ((GXTevStageID) i, GX_TEV_SWAP0, GX_TEV_SWAP0 );
		}
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetTevSwapModeTable(GX_TEV_SWAP0,
			GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
		GXSetTevSwapModeTable(GX_TEV_SWAP1,
			GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_ALPHA);
		GXSetTevSwapModeTable(GX_TEV_SWAP2,
			GX_CH_GREEN, GX_CH_GREEN, GX_CH_GREEN, GX_CH_ALPHA);
		GXSetTevSwapModeTable(GX_TEV_SWAP3,
			GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE, GX_CH_ALPHA);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		// Indirect Textures.
		for (i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++)
		{
			GXSetTevDirect((GXTevStageID) i);
		}
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetNumIndStages(0);
		GXSetIndTexCoordScale( GX_INDTEXSTAGE0, GX_ITS_1, GX_ITS_1 );
		GXSetIndTexCoordScale( GX_INDTEXSTAGE1, GX_ITS_1, GX_ITS_1 );
		GXSetIndTexCoordScale( GX_INDTEXSTAGE2, GX_ITS_1, GX_ITS_1 );
		GXSetIndTexCoordScale( GX_INDTEXSTAGE3, GX_ITS_1, GX_ITS_1 );
	}

	//
	// Pixel Processing
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetFog(GX_FOG_NONE, 0.0F, 1.0F, 0.1F, 1.0F, BLACK);
		GXSetFogRangeAdj( GX_DISABLE, 0, 0 );
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetBlendMode(GX_BM_NONE,
			GX_BL_SRCALPHA, // src factor
			GX_BL_INVSRCALPHA, // dst factor
			GX_LO_CLEAR);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetColorUpdate(GX_ENABLE);
		GXSetAlphaUpdate(GX_ENABLE);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
		GXSetZCompLoc(GX_TRUE); // before texture
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetDither(GX_ENABLE);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetDstAlpha(GX_DISABLE, 0);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
	}
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetFieldMask( GX_ENABLE, GX_ENABLE );
	}
#if 0
	GXSetFieldMode((GXBool)(rmode->field_rendering),
		((rmode->viHeight == 2*rmode->xfbHeight) ?
GX_ENABLE : GX_DISABLE));
#endif

#if 0
	//
	// Frame buffer
	//
	GXSetCopyClear(GX_DEFAULT_BG, GX_MAX_Z24);
	GXSetDispCopySrc(0, 0, rmode->fbWidth, rmode->efbHeight);
	GXSetDispCopyDst(rmode->fbWidth, rmode->efbHeight);
	GXSetDispCopyYScale((f32)(rmode->xfbHeight) / (f32)(rmode->efbHeight));
	GXSetCopyClamp((GXFBClamp)(GX_CLAMP_TOP | GX_CLAMP_BOTTOM));
	GXSetCopyFilter(rmode->aa, rmode->sample_pattern, GX_TRUE, rmode->vfilter);
	GXSetDispCopyGamma( GX_GM_1_0 );
	GXSetDispCopyFrame2Field(GX_COPY_PROGRESSIVE);
	GXClearBoundingBox();
#endif
	//
	// CPU direct EFB access
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXPokeColorUpdate(GX_TRUE);
		GXPokeAlphaUpdate(GX_TRUE);
		GXPokeDither(GX_FALSE);
		GXPokeBlendMode(GX_BM_NONE, GX_BL_ZERO, GX_BL_ONE, GX_LO_SET);
		GXPokeAlphaMode(GX_ALWAYS, 0);
		GXPokeAlphaRead(GX_READ_FF);
		GXPokeDstAlpha(GX_DISABLE, 0);
		GXPokeZMode(GX_TRUE, GX_ALWAYS, GX_TRUE);
	}
	//
	// Performance Counters
	//
	if(g_abResetToggle[uToggleCount++])
	{
		GXSetGPMetric(GX_PERF0_NONE, GX_PERF1_NONE);
		GXClearGPMetric();
	}
}
#endif
